Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Rework enums and bitflags handling

They are now primarily trwated as string or sets of strings, instead of numbers.
  • Loading branch information...
commit d19244b4e403f9386d7f9576e89086ea4c1cce3b 1 parent 7d40c1c
@pavouk authored
View
7 README.md
@@ -43,7 +43,14 @@ markdown processor if you want to read it in HTML.
### 0.4 (unreleased)
+- Changed handling of enums and bitflags, switched from marshaling
+ them as numbers to prefering strings for enums and tables (sets or
+ lists) for bitflags. Numeric values still work for Lua->C
+ marshalling, but backward compatibility is broken in C->Lua enum and
+ bitflags marshalling.
- Compatible with Lua 5.2 and LuaJIT
+- Added standardized way for overrides to handle constructor argument
+ table array part.
- Existing Gtk overrides reworked and improved, there is now a way to
describe and create widget hierarchies in Lua-friendly way. See
`docs/gtk.lua`, chapter about `Gtk.Container` for overview and
View
61 docs/guide.md
@@ -61,6 +61,11 @@ mapping between GLib types and Lua types is established.
* `gboolean` is mapped to Lua's `boolean` type, with `true` and
`false` values
* All numeric types are mapped to Lua's `number` type
+* Enumerations are primarily handled as strings with uppercased GType
+ nicks, optionally the direct numeric values are also accepted.
+* Bitflags are primarily handled as lists or sets of strings with
+ uppercased GType nicks, optionally the direct numeric values are
+ also accepted.
* `gchar*` string is mapped to Lua as `string` type, UTF-8 encoded
* C array types and `GArray` is mapped to Lua tables, using array part
of the table. Note that although in C the arrays are 0-based, when
@@ -440,11 +445,22 @@ Fields are accessed using `.` operator on structure instance, for example
## 5. Enums and bitflags, constants
-Enum instances are represented as plain numbers in LGI. So in any
-place where enum or bitflags instance is needed, a number can be used
-directly instead.
+LGI primarily maps enumerations to strings containing uppercased nicks
+of enumeration constant names. Optionally, a direct enumeration value
+is also accepted. Similarly, bitflags are primarily handled as sets
+containing uppercased flag nicks, but also lists of these nicks or
+direct numeric value is accepted. When a numeric value cannot be
+mapped cleanly to the known set of bitflags, the remaining number is
+stored in the first array slot of the returned set.
-### 5.1. Accessing values
+Note that this behavior changed in lgi 0.4; up to that alpha release,
+lgi handled enums and bitmaps exclusively as numbers only. The change
+is compatible in Lua->C direction, where numbers still can be used,
+but incompatible in C->Lua direction, where lgi used to return
+numbers, while now it returns either string with enum value or table
+with flags.
+
+### 5.1. Accessing numeric values
In order to retrieve real enum values from symbolic names, enum and
bitflags are loaded into repository as tables mapping symbolic names
@@ -459,7 +475,8 @@ yields following output:
};
so constants can be referenced using `Gtk.WindowType.TOPLEVEL`
-construct.
+construct, or directly using string `'TOPLEVEL'` when a
+`Gtk.WindowType` is expected.
### 5.2. Backward mapping, getting names from numeric values
@@ -493,6 +510,40 @@ all symbolic names which make up the requested value:
SORTED = 32;
ODD = 2;
};
+
+This way, it is possible to check for presence of specified flag very
+easily:
+
+ if Gtk.RegionFlags[flags].ODD then
+ -- Code handling region-odd case
+ endif
+
+If the value cannot be cleanly decomposed to known flags, remaining
+bits are accumulated into number stored at index 1:
+
+> dump(Gtk.RegionFlags[51])
+
+ ["table: 0x242fb20"] = { -- table: 0x242fb20
+ EVEN = 1;
+ SORTED = 32;
+ [1] = 16;
+ ODD = 2;
+ };
+
+To construct numeric value which can be passed to a function expecting
+an enum, it is possible to simply add requested flags. However, there
+is a danger if some definition contains multiple flags , in which case
+numeric adding produces incorrect results. Therefore, it is possible
+to use bitflags pseudoconstructor', which accepts table containing
+requested flags:
+
+> =Gtk.RegionFlags { 'FIRST', 'SORTED' }
+
+ 36
+
+> =Gtk.RegionFlags { Gtk.RegionFlags.ODD, 16, 'EVEN' }
+
+ 19
## 6. Threading and synchronization
View
89 lgi/enum.lua
@@ -13,6 +13,37 @@ local core = require 'lgi.core'
local gi = core.gi
local component = require 'lgi.component'
+-- Prepare needed bit operations. Prefer bit32 C module if available,
+-- but if it is not, use poor-man Lua-only variants.
+local bor, has_bit
+local ok, bitlib = pcall(require, 'bit32')
+if ok then
+ -- Lua 5.2 style bit operations.
+ bor, has_bit = bitlib.bor, bitlib.btest
+else
+ ok, bitlib = pcall(require, 'bit')
+ if ok then
+ -- LuaBitOp package.
+ bor, has_bit = bitlib.bor, bitlib.band
+ else
+ -- Poor-man's Lua-only implementation, slow but out-of-the-box
+ -- for any kind of Lua.
+ function has_bit(value, bitmask)
+ return value % (2 * bitmask) >= bitmask
+ end
+ local function bor(o1, o2)
+ local res, bit = 0, 1
+ while bit <= o1 or bit <= o2 do
+ if has_bit(o1, bit) or has_bit(o2, bit) then
+ res = res + bit
+ end
+ bit = bit * 2
+ end
+ return res
+ end
+ end
+end
+
local enum = {
enum_mt = component.mt:clone { '_method' },
bitflags_mt = component.mt:clone { '_method' }
@@ -29,8 +60,9 @@ function enum.load(info, meta)
local prefix = info.name:gsub('%u+[^%u]+', '%1_'):lower()
local namespace = core.repo[info.namespace]
enum_type._method = setmetatable(
- {},
- { __index = function(_, name) return namespace[prefix .. name] end })
+ {}, { __index = function(_, name)
+ return namespace[prefix .. name]
+ end })
end
-- Load all enum values.
@@ -47,26 +79,55 @@ end
-- Enum reverse mapping, value->name.
function enum.enum_mt:_element(instance, value)
- local element, category = component.mt._element(self, instance, value)
- if element then return element, category end
- for name, val in pairs(self) do
- if val == value then return name end
+ if type(value) == 'number' then
+ for name, val in pairs(self) do
+ if val == value then return name end
+ end
+ return value
+ else
+ return component.mt._element(self, instance, value)
end
end
+-- Constructs enum number from specified string.
+function enum.enum_mt:_new(param)
+ if type(param) == 'string' then param = self[param] end
+ return param
+end
+
-- Resolving arbitrary number to the table containing symbolic names
-- of contained bits.
function enum.bitflags_mt:_element(instance, value)
- local element, category = component.mt._element(self, instance, value)
- if element then return element, category end
- if type(value) ~= 'number' then return end
- local result = {}
- for name, flag in pairs(self) do
- if type(flag) == 'number' and core.has_bit(value, flag) then
- result[name] = flag
+ if type(value) == 'number' then
+ local result, remainder = {}, value
+ for name, flag in pairs(self) do
+ if type(flag) == 'number' and has_bit(value, flag) then
+ result[name] = true
+ remainder = remainder - flag
+ end
+ end
+ if remainder > 0 then result[1] = remainder end
+ return result
+ else
+ return component.mt._element(self, instance, value)
+ end
+end
+
+-- 'Constructs' number from specified flags (or accepts just number).
+function enum.bitflags_mt:_new(param)
+ if type(param) == 'string' then
+ return self[param]
+ elseif type(param) == 'number' then
+ return param
+ else
+ local num = 0
+ for key, value in pairs(param) do
+ if type(key) == 'string' then value = key end
+ if type(value) == 'string' then value = self[value] end
+ num = bor(num, value)
end
+ return num
end
- return result
end
return enum
View
6 lgi/init.lua
@@ -19,12 +19,6 @@ local core = require 'lgi.core'
-- Create lgi table, containing the module.
local lgi = { _NAME = 'lgi', _VERSION = require 'lgi.version' }
--- Add simple flag-checking function, avoid compatibility hassle with
--- importing bitlib just because of this simple operation.
-function core.has_bit(value, flag)
- return value % (2 * flag) >= flag
-end
-
-- Forward 'yield' functionality into external interface.
lgi.yield = core.yield
View
21 lgi/marshal.c
@@ -826,6 +826,16 @@ lgi_marshal_2c (lua_State *L, GITypeInfo *ti, GIArgInfo *ai,
{
case GI_INFO_TYPE_ENUM:
case GI_INFO_TYPE_FLAGS:
+ /* If the argument is not numeric, convert to number
+ first. Use enum/flags 'constructor' to do this. */
+ if (lua_type (L, narg) != LUA_TNUMBER)
+ {
+ lgi_type_get_repotype (L, G_TYPE_INVALID, info);
+ lua_pushvalue (L, narg);
+ lua_call (L, 1, 1);
+ narg = -1;
+ }
+
/* Directly store underlying value. */
marshal_2c_int (L, g_enum_info_get_storage_type (info), arg, narg,
optional, FALSE);
@@ -1094,9 +1104,18 @@ lgi_marshal_2lua (lua_State *L, GITypeInfo *ti, GITransfer transfer,
{
case GI_INFO_TYPE_ENUM:
case GI_INFO_TYPE_FLAGS:
- /* Directly store underlying value. */
+ /* Prepare repotable of enum/flags on the stack. */
+ lgi_type_get_repotype (L, G_TYPE_INVALID, info);
+
+ /* Unmarshal the numeric value. */
marshal_2lua_int (L, g_enum_info_get_storage_type (info),
arg, FALSE);
+
+ /* Get symbolic value from the table. */
+ lua_gettable (L, -2);
+
+ /* Remove the table from the stack. */
+ lua_remove (L, -2);
break;
case GI_INFO_TYPE_STRUCT:
View
5 lgi/override/Clutter.lua
@@ -40,7 +40,6 @@ Clutter.threads_init()
-- Automatically initialize clutter, avoid continuing if
-- initialization fails.
local status = Clutter.init()
-if status ~= Clutter.InitError.SUCCESS then
- error(("Clutter initialization failed: %s"):format(
- Clutter.InitError(status)))
+if status ~= 'SUCCESS' then
+ error(("Clutter initialization failed: %s"):format(status))
end
View
5 lgi/override/GObject-Object.lua
@@ -128,7 +128,7 @@ end
local function marshal_property(obj, name, flags, gtype, marshaller, ...)
-- Check access rights of the property.
local mode = select('#', ...) > 0 and 'WRITABLE' or 'READABLE'
- if not core.has_bit(flags, repo.GObject.ParamFlags[mode]) then
+ if not flags[mode] then
error(("%s: `%s' not %s"):format(core.object.query(obj, 'repo')._name,
name, mode:lower()))
end
@@ -147,7 +147,8 @@ function Object:_access_property(object, property, ...)
local typeinfo = property.typeinfo
local gtype = Type.from_typeinfo(typeinfo)
local marshaller = Value.find_marshaller(gtype, typeinfo, property.transfer)
- return marshal_property(object, property.name, property.flags,
+ return marshal_property(object, property.name,
+ repo.GObject.ParamFlags[property.flags],
gtype, marshaller, ...)
end
View
9 samples/console.lua
@@ -283,11 +283,10 @@ local function Console()
function entry:on_key_press_event(event)
-- Lookup action to be activated for specified key combination.
local action = keytable[event.keyval]
- local mask = Gdk.ModifierType[event.state]
- local wants_control = actions.multiline.active
- and Gdk.ModifierType.CONTROL_MASK or nil
- if not action or mask.SHIFT_MASK
- or mask.CONTROL_MASK ~= wants_control then
+ local state = event.state
+ local without_control = not state.CONTROL_MASK
+ if not action or state.SHIFT_MASK
+ or actions.multiline.active == without_control then
return false
end
View
60 tests/gireg.lua
@@ -755,7 +755,7 @@ function gireg.enum()
check(R.TestEnum[0] == 'VALUE1')
check(R.TestEnum[1] == 'VALUE2')
check(R.TestEnum[-1] == 'VALUE3')
- check(R.TestEnum[43] == nil)
+ check(R.TestEnum[43] == 43)
check(R.test_enum_param(0) == 'value1')
check(R.test_enum_param(1) == 'value2')
check(R.test_enum_param(-1) == 'value3')
@@ -764,7 +764,7 @@ function gireg.enum()
check(R.TestEnumUnsigned.VALUE2 == 0x80000000)
check(R.TestEnumUnsigned[1] == 'VALUE1')
check(R.TestEnumUnsigned[0x80000000] == 'VALUE2')
- check(R.TestEnumUnsigned[-1] == nil)
+ check(R.TestEnumUnsigned[-1] == -1)
end
function gireg.flags()
@@ -772,12 +772,28 @@ function gireg.flags()
check(R.TestFlags.FLAG1 == 1)
check(R.TestFlags.FLAG2 == 2)
check(R.TestFlags.FLAG3 == 4)
- check(R.TestFlags[7].FLAG1 == 1)
- check(R.TestFlags[7].FLAG2 == 2)
- check(R.TestFlags[7].FLAG3 == 4)
- check(R.TestFlags[3].FLAG1 == 1)
- check(R.TestFlags[3].FLAG2 == 2)
+ check(R.TestFlags[7].FLAG1 == true)
+ check(R.TestFlags[7].FLAG2 == true)
+ check(R.TestFlags[7].FLAG3 == true)
+ check(R.TestFlags[3].FLAG1 == true)
+ check(R.TestFlags[3].FLAG2 == true)
check(R.TestFlags[3].FLAG3 == nil)
+ check(R.TestFlags[10].FLAG2 == true)
+ check(R.TestFlags[10][1] == 8)
+ checkv(R.TestFlags { 'FLAG1', 'FLAG2' }, 3, 'number')
+ checkv(R.TestFlags { 1, 2, 'FLAG1', R.TestFlags.FLAG2 }, 3, 'number')
+ checkv(R.TestFlags { 10, FLAG2 = 2 }, 10, 'number')
+ checkv(R.TestFlags { 2, 'FLAG2' }, 2, 'number')
+end
+
+function gireg.flags_out()
+ local R = lgi.Regress
+ local out = R.global_get_flags_out()
+ check(type(out) == 'table')
+ check(out.FLAG1 == true)
+ check(out.FLAG2 == nil)
+ check(out.FLAG3 == true)
+ check(#out == 0)
end
function gireg.const()
@@ -800,25 +816,25 @@ function gireg.struct_a()
a.some_double = 3.14
check(a.some_double == 3.14)
a.some_enum = R.TestEnum.VALUE2
- check(a.some_enum == R.TestEnum.VALUE2)
+ check(a.some_enum == 'VALUE2')
a = R.TestStructA { some_int = 42, some_int8 = 12,
- some_double = 3.14, some_enum = R.TestEnum.VALUE2 }
+ some_double = 3.14, some_enum = 'VALUE2' }
a.some_int = 43
a.some_int8 = 13
check(a.some_int == 43)
check(a.some_int8 == 13)
check(a.some_double == 3.14)
- check(a.some_enum == R.TestEnum.VALUE2)
+ check(a.some_enum == 'VALUE2')
a.some_double = 3.15
check(a.some_int == 43)
check(a.some_int8 == 13)
check(a.some_double == 3.15)
- check(a.some_enum == R.TestEnum.VALUE2)
+ check(a.some_enum == 'VALUE2')
a.some_enum = R.TestEnum.VALUE3
check(a.some_int == 43)
check(a.some_int8 == 13)
check(a.some_double == 3.15)
- check(a.some_enum == R.TestEnum.VALUE3)
+ check(a.some_enum == 'VALUE3')
check(not pcall(function() return a.foo end))
check(not pcall(function() a.foo = 1 end))
check(select('#', (function() a.some_int = 0 end)()) == 0)
@@ -839,11 +855,11 @@ function gireg.struct_a_clone()
check(b.some_int == 42)
check(b.some_int8 == 12)
check(b.some_double == 3.14)
- check(b.some_enum == R.TestEnum.VALUE2)
+ check(b.some_enum == 'VALUE2')
check(a.some_int == 42)
check(a.some_int8 == 12)
check(a.some_double == 3.14)
- check(a.some_enum == R.TestEnum.VALUE2)
+ check(a.some_enum == 'VALUE2')
end
function gireg.struct_b()
@@ -867,7 +883,7 @@ function gireg.struct_b()
check(b.nested_a.some_int == 42)
check(b.nested_a.some_int8 == 12)
check(b.nested_a.some_double == 3.14)
- check(b.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(b.nested_a.some_enum == 'VALUE2')
-- Nested structure construction.
b = R.TestStructB { some_int8 = 21, nested_a =
@@ -877,7 +893,7 @@ function gireg.struct_b()
check(b.nested_a.some_int == 42)
check(b.nested_a.some_int8 == 12)
check(b.nested_a.some_double == 3.14)
- check(b.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(b.nested_a.some_enum == 'VALUE2')
end
function gireg.struct_b_clone()
@@ -896,21 +912,21 @@ function gireg.struct_b_clone()
check(bc.nested_a.some_int == 42)
check(bc.nested_a.some_int8 == 12)
check(bc.nested_a.some_double == 3.14)
- check(bc.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(bc.nested_a.some_enum == 'VALUE2')
check(bc.nested_a.some_int == 42)
check(bc.nested_a.some_int8 == 12)
check(bc.nested_a.some_double == 3.14)
- check(bc.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(bc.nested_a.some_enum == 'VALUE2')
check(b.some_int8 == 21)
check(b.nested_a.some_int == 42)
check(b.nested_a.some_int8 == 12)
check(b.nested_a.some_double == 3.14)
- check(b.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(b.nested_a.some_enum == 'VALUE2')
check(b.nested_a.some_int == 42)
check(b.nested_a.some_int8 == 12)
check(b.nested_a.some_double == 3.14)
- check(b.nested_a.some_enum == R.TestEnum.VALUE2)
+ check(b.nested_a.some_enum == 'VALUE2')
end
function gireg.boxed_a_equals()
@@ -1058,14 +1074,14 @@ function gireg.gvalue_date()
local v = R.test_date_in_gvalue()
check(v.gtype == 'GDate')
check(v.value:get_day() == 5)
- check(v.value:get_month() == 12)
+ check(v.value:get_month() == 'DECEMBER')
check(v.value:get_year() == 1984)
local d = GLib.Date()
d:set_dmy(25, 1, 1975)
v = GObject.Value(GLib.Date, d)
check(v.gtype == 'GDate')
check(v.value:get_day() == 25)
- check(v.value:get_month() == 1)
+ check(v.value:get_month() == 'JANUARY')
check(v.value:get_year() == 1975)
end
Please sign in to comment.
Something went wrong with that request. Please try again.