raze/polymer/eduke32/source/lunatic/bcarray.lua
2013-07-19 12:49:02 +00:00

117 lines
3.8 KiB
Lua

-- Implementation of a bound-checked array type factory for LuaJIT 2.0 or later.
--
-- Usage example:
--
-- > bcarray.new("int8_t", 3, "test", "three_pigs")
-- > a = ffi.new("struct { int32_t a; three_pigs p; int16_t b; }")
-- > =ffi.sizeof(a) --> 12
-- > b = ffi.new("__attribute__((packed)) struct { int32_t a; three_pigs p; int16_t b; }")
-- > =ffi.sizeof(b) --> 9
local ffi = require("ffi")
local string = require("string")
local table = require("table")
local assert = assert
local error = error
local pairs = pairs
local type = type
module(...)
-- Generate C decl for a sequence of <nelts> const struct members.
-- For example, for 4 elements,
-- "const $ _r1, _f2, _u3, _n4;"
local function flatten_array(nelts, rng)
local strtab = { "$ " }
if (rng and rng.getu32==nil) then
assert(type(rng)=="table")
for i=1,#rng do
strtab[i+1] = rng[i]..((i<#rng) and "," or ";")
end
else
for i=1,nelts do
local ch = 97 + (rng and (rng:getu32() % 25) or 0) -- 'a'..'z'
strtab[i+1] = string.format("_%c%x%s", ch, i, (i<nelts) and "," or ";")
end
end
return table.concat(strtab)
end
-- ctype = bcarray.new(basetype, numelts, showname [, typename] [, rng] [, mtadd])
-- (optional fields may be nil)
--
-- <numelts>: Number of elements in array (small number)
-- <showname>: The name to be shown for the derived type in error messages
-- <typename>: If non-nil, the name under which the derived type is typedef'd
-- <rng>: Random generator state + method :getu32(). If nil, then members are
-- named _a1, _a2, ...
-- It also may be a table containing member names at numeric indices 1..#rng.
-- <mtadd>: A table containing functions __index and/or __newindex. They are
-- called first and the bound-checking ones are tail-called then.
function new(basetype, numelts, showname, typename, rng, mtadd)
local eltptr_t = ffi.typeof("$ *", ffi.typeof(basetype))
local mt = {
__index = function(ar, idx)
if (not (idx >= 0 and idx < numelts)) then
error("out-of-bounds "..showname.." read access", 2)
end
return ffi.cast(eltptr_t, ar)[idx]
end,
-- NOTE: this function will be dead code if the prefixed __newindex
-- errors out unconditionally or the bcarray is declared 'const'.
__newindex = function(ar, idx, val)
if (not (idx >= 0 and idx < numelts)) then
error("out-of-bounds "..showname.." write access", 2)
end
ffi.cast(eltptr_t, ar)[idx] = val
end,
}
if (mtadd ~= nil) then
local curindexf, curnewindexf = mt.__index, mt.__newindex
local addindexf, addnewindexf = mtadd.__index, mtadd.__newindex
if (addindexf) then
-- Additional __index metamethod given.
mt.__index = function(ar, idx)
addindexf(ar, idx)
return curindexf(ar, idx)
end
end
if (addnewindexf) then
-- Additional __newindex metamethod given.
mt.__newindex = function(ar, idx, val)
addnewindexf(ar, idx, val)
return curnewindexf(ar, idx, val)
end
end
end
local cdeclstr = "struct {"..flatten_array(numelts, rng).."}"
local bcarray_t = ffi.typeof(cdeclstr, ffi.typeof(basetype));
bcarray_t = ffi.metatype(bcarray_t, mt)
if (not (rng and rng.getu32==nil)) then
-- When passing a member name table, it is allowed to have a different
-- number of named members than array elements.
assert(ffi.sizeof(bcarray_t) == ffi.sizeof(basetype)*numelts)
end
if (typename ~= nil) then
-- Register the type name in the global namespace.
assert(type(typename)=="string")
ffi.cdef("typedef $ $;", bcarray_t, typename)
end
return bcarray_t
end