Skip to content

Commit

Permalink
Rework enums and bitflags handling
Browse files Browse the repository at this point in the history
They are now primarily trwated as string or sets of strings, instead of numbers.
  • Loading branch information
pavouk committed Dec 27, 2011
1 parent 7d40c1c commit d19244b
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 58 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 56 additions & 5 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
89 changes: 75 additions & 14 deletions lgi/enum.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
Expand All @@ -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.
Expand All @@ -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
6 changes: 0 additions & 6 deletions lgi/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
21 changes: 20 additions & 1 deletion lgi/marshal.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand Down
5 changes: 2 additions & 3 deletions lgi/override/Clutter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 3 additions & 2 deletions lgi/override/GObject-Object.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
9 changes: 4 additions & 5 deletions samples/console.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit d19244b

Please sign in to comment.