mirror of
https://github.com/zap-proto/lua.git
synced 2026-10-30 15:04:38 +00:00
Rename the Cap'n Proto Lua port to ZAP, brand-neutral throughout: - capnp.lua -> zap.lua, capnp/ -> zap/, *.capnp -> *.zap, compiler bins capnpc-* -> zapc-*, schema/handwritten modules -> *_zap.lua. Zero capnp/Cap'n Proto leftovers in zap-native source/build (upstream-fork Kenton Varda copyright headers in proto/c++.zap and bin/schema.zap stay). Generated-code header and doc links point at github.com/zap-proto/lua and zap-proto.io. Tests now run with no extra install: - Vendor the lunitx test framework (Michael Roth / Doug Currie, MIT) under vendor/ (lunit, lunit.console, lunitx, lunitx.atexit). Its __gc atexit hook drives each test file under LuaJIT and exits nonzero on any failure. - tests/run_tests.sh and test.sh are self-contained (set LUA_PATH incl vendor/), generate proto/example_zap.lua via the `zap` compiler when present, and skip 01/10 with a clear note (never a false pass) when it is absent. 02-util and 03-compile are pure-Lua and always run. - Verified under LuaJIT 2.1: 01(20) + 02(7) + 03(1) + 10(31) = 59 tests pass, 0 failed; runner returns nonzero on an injected failure. Decouple optional cjson so the runtime/compiler load under a bare LuaJIT: - zap/compile.lua loads cjson lazily and only for debug logging (guarded by the debug flag); bin/zapc-llua makes the schema JSON dump optional. Rockspec: bump lua-zap to 0.1.3-4, declare lua-cjson >= 2.1.0, and install the vendored lunitx modules. .gitignore: scope the rockspec-pack ignore to repo root so rockspec/ specs are tracked; ignore the `dump` test artifact. TODO trimmed to the genuinely remaining items (AnyPointer set/decode, packed serialization, schema_zap regeneration, multi-file compile, perf).
1325 lines
36 KiB
Lua
1325 lines
36 KiB
Lua
-----------------------------------------------------------------
|
|
-- lua-zap compiler
|
|
-- @copyright 2013-2016 Jiale Zhi ([email protected])
|
|
-----------------------------------------------------------------
|
|
|
|
-- cjson is only used for optional debug logging (see dbgf below); load it
|
|
-- lazily so the compiler is usable under a bare LuaJIT without the C module.
|
|
local function cjson()
|
|
return require("cjson")
|
|
end
|
|
local util = require "zap.util"
|
|
|
|
local insert = table.insert
|
|
local concat = table.concat
|
|
local format = string.format
|
|
local lower = string.lower
|
|
local gsub = string.gsub
|
|
local sub = string.sub
|
|
|
|
local debug = false
|
|
local NOT_UNION = 65535
|
|
|
|
local _M = {}
|
|
|
|
local missing_enums = {}
|
|
|
|
local config = {
|
|
default_naming_func = util.lower_underscore_naming,
|
|
default_enum_naming_func = util.upper_underscore_naming,
|
|
}
|
|
|
|
|
|
function _M.set_debug(_debug)
|
|
debug = _debug
|
|
end
|
|
|
|
function dbg(...)
|
|
if not debug then
|
|
return
|
|
end
|
|
print(...)
|
|
end
|
|
|
|
function dbgf(...)
|
|
if not debug then
|
|
return
|
|
end
|
|
print(format(...))
|
|
end
|
|
|
|
function get_schema_text(file)
|
|
local f = io.open(file)
|
|
if not f then
|
|
return nil, "Can't open file: " .. tostring(file)
|
|
end
|
|
|
|
local s = f:read("*a")
|
|
f:close()
|
|
|
|
s = string.gsub(s, "%(", "{")
|
|
s = string.gsub(s, "%)", "}")
|
|
s = string.gsub(s, "%[", "{")
|
|
s = string.gsub(s, "%]", "}")
|
|
s = string.gsub(s, "%<", "\"")
|
|
s = string.gsub(s, "%>", "\"")
|
|
s = string.gsub(s, "id = (%d+)", "id = \"%1\"")
|
|
s = string.gsub(s, "typeId = (%d+)", "typeId = \"%1\"")
|
|
s = string.gsub(s, "void", "\"void\"")
|
|
return "return " .. s
|
|
end
|
|
|
|
function comp_header(res, nodes)
|
|
dbg("compile_headers")
|
|
insert(res, format([[
|
|
-- Generated by lua-zap %s on %s
|
|
-- https://github.com/zap-proto/lua
|
|
|
|
|
|
]], config.version, os.date()))
|
|
insert(res, format([[
|
|
local ffi = require "ffi"
|
|
local zap = require "zap"
|
|
local bit = require "bit"
|
|
|
|
local ceil = math.ceil
|
|
local write_struct_field= zap.write_struct_field
|
|
local read_struct_field = zap.read_struct_field
|
|
local read_text = zap.read_text
|
|
local write_text = zap.write_text
|
|
local get_enum_val = zap.get_enum_val
|
|
local get_enum_name = zap.get_enum_name
|
|
local get_data_off = zap.get_data_off
|
|
local write_listp_buf = zap.write_listp_buf
|
|
local write_structp_buf = zap.write_structp_buf
|
|
local write_structp = zap.write_structp
|
|
local read_struct_buf = zap.read_struct_buf
|
|
local read_listp_struct = zap.read_listp_struct
|
|
local read_list_data = zap.read_list_data
|
|
local write_list = zap.write_list
|
|
local write_list_data = zap.write_list_data
|
|
local ffi_new = ffi.new
|
|
local ffi_string = ffi.string
|
|
local ffi_cast = ffi.cast
|
|
local ffi_copy = ffi.copy
|
|
local ffi_fill = ffi.fill
|
|
local ffi_typeof = ffi.typeof
|
|
local band, bor, bxor = bit.band, bit.bor, bit.bxor
|
|
|
|
local pint8 = ffi_typeof("int8_t *")
|
|
local pint16 = ffi_typeof("int16_t *")
|
|
local pint32 = ffi_typeof("int32_t *")
|
|
local pint64 = ffi_typeof("int64_t *")
|
|
local puint8 = ffi_typeof("uint8_t *")
|
|
local puint16 = ffi_typeof("uint16_t *")
|
|
local puint32 = ffi_typeof("uint32_t *")
|
|
local puint64 = ffi_typeof("uint64_t *")
|
|
local pbool = ffi_typeof("uint8_t *")
|
|
local pfloat32 = ffi_typeof("float *")
|
|
local pfloat64 = ffi_typeof("double *")
|
|
|
|
|
|
local ok, new_tab = pcall(require, "table.new")
|
|
|
|
if not ok then
|
|
new_tab = function (narr, nrec) return {} end
|
|
end
|
|
|
|
local round8 = function(size)
|
|
return ceil(size / 8) * 8
|
|
end
|
|
|
|
local str_buf
|
|
local default_segment_size = 4096
|
|
|
|
local function get_str_buf(size)
|
|
if size > default_segment_size then
|
|
return ffi_new("char[?]", size)
|
|
end
|
|
|
|
if not str_buf then
|
|
str_buf = ffi_new("char[?]", default_segment_size)
|
|
end
|
|
return str_buf
|
|
end
|
|
|
|
-- Estimated from #nodes, not accurate
|
|
local _M = new_tab(0, %d)
|
|
|
|
]], #nodes))
|
|
end
|
|
|
|
function get_name(display_name)
|
|
local n = string.find(display_name, ":")
|
|
return string.sub(display_name, n + 1)
|
|
end
|
|
|
|
--- @see https://zap-proto.io/encoding#lists
|
|
local list_size_map = {
|
|
[0] = 0,
|
|
[1] = 0.125,
|
|
[2] = 1,
|
|
[3] = 2,
|
|
[4] = 4,
|
|
[5] = 8,
|
|
[6] = 8,
|
|
-- 7 = ?,
|
|
}
|
|
|
|
local size_map = {
|
|
void = 0,
|
|
bool = 1,
|
|
int8 = 8,
|
|
int16 = 16,
|
|
int32 = 32,
|
|
int64 = 64,
|
|
uint8 = 8,
|
|
uint16 = 16,
|
|
uint32 = 32,
|
|
uint64 = 64,
|
|
float32 = 32,
|
|
float64 = 64,
|
|
text = "2", -- list(uint8)
|
|
data = "2",
|
|
list = 2, -- size: list item size id, not actual size
|
|
struct = 8, -- struct pointer
|
|
enum = 16,
|
|
object = 8, -- FIXME object is a pointer ?
|
|
anyPointer = 8, -- FIXME object is a pointer ?
|
|
group = 0, -- TODO
|
|
}
|
|
|
|
local check_type = {
|
|
struct = 'type(value) == "table"',
|
|
group = 'type(value) == "table"',
|
|
enum = 'type(value) == "string" or type(value) == "number"',
|
|
list = 'type(value) == "table"',
|
|
text = 'type(value) == "string"',
|
|
data = 'type(value) == "string"',
|
|
}
|
|
|
|
|
|
function get_size(type_name)
|
|
local size = size_map[type_name]
|
|
if not size then
|
|
error("Unknown type_name:" .. type_name)
|
|
end
|
|
|
|
return size
|
|
end
|
|
|
|
function _set_field_default(nodes, field, slot)
|
|
local default
|
|
if slot.defaultValue
|
|
and field.type_name ~= "object"
|
|
and field.type_name ~= "anyPointer"
|
|
then
|
|
|
|
for k, v in pairs(slot.defaultValue) do
|
|
if field.type_name == "bool" then
|
|
field.print_default_value = v and 1 or 0
|
|
field.default_value = field.print_default_value
|
|
elseif field.type_name == "text" or field.type_name == "data" then
|
|
field.print_default_value = '"' .. v .. '"'
|
|
field.default_value = field.print_default_value
|
|
elseif field.type_name == "struct" or field.type_name == "list"
|
|
or field.type_name == "object"
|
|
or field.type_name == "anyPointer"
|
|
then
|
|
field.print_default_value = '"' .. v .. '"'
|
|
elseif field.type_name == "void" then
|
|
field.print_default_value = "\"Void\""
|
|
field.default_value = field.print_default_value
|
|
elseif field.type_name == "enum" then
|
|
local enum = assert(nodes[slot["type"].enum.typeId].enum)
|
|
field.print_default_value = '"' .. enum.enumerants[v + 1].name
|
|
.. '"'
|
|
|
|
field.default_value = v
|
|
else
|
|
field.print_default_value = v
|
|
field.default_value = field.print_default_value
|
|
end
|
|
break
|
|
end
|
|
dbgf("[%s] %s.print_default_value=%s", field.type_name, field.name,
|
|
field.print_default_value)
|
|
end
|
|
if not field.default_value then
|
|
field.default_value = "Nil"
|
|
end
|
|
if not field.print_default_value then
|
|
field.print_default_value = "Nil"
|
|
end
|
|
end
|
|
|
|
function _get_type(type_field)
|
|
local type_name
|
|
for k, v in pairs(type_field) do
|
|
type_name = k
|
|
break
|
|
end
|
|
return type_name
|
|
end
|
|
|
|
function _set_field_type(field, slot, nodes)
|
|
local type_name
|
|
if field.group then
|
|
field.type_name = "group"
|
|
field.type_display_name = get_name(
|
|
nodes[field.group.typeId].displayName)
|
|
else
|
|
for k, v in pairs(slot["type"]) do
|
|
type_name = k
|
|
if type_name == "struct" then
|
|
field.type_display_name = get_name(nodes[v.typeId].displayName)
|
|
elseif type_name == "enum" then
|
|
field.enum_id = v.typeId
|
|
field.type_display_name = get_name(nodes[v.typeId].displayName)
|
|
elseif type_name == "list" then
|
|
local list_type
|
|
for k, v in pairs(field.slot["type"].list.elementType) do
|
|
list_type = k
|
|
if list_type == "struct" then
|
|
field.type_display_name = get_name(
|
|
nodes[v.typeId].displayName)
|
|
end
|
|
break
|
|
end
|
|
field.element_type = list_type
|
|
else
|
|
-- default = v
|
|
end
|
|
|
|
field.type_name = type_name
|
|
--field.default = default
|
|
|
|
break
|
|
end
|
|
end
|
|
dbgf("field %s.type_name = %s", field.name, field.type_name)
|
|
assert(field.type_name)
|
|
end
|
|
|
|
function comp_field(res, nodes, field)
|
|
dbg("comp_field")
|
|
local slot = field.slot
|
|
if not slot then
|
|
slot = {}
|
|
field.slot = slot
|
|
end
|
|
if not slot.offset then
|
|
slot.offset = 0
|
|
end
|
|
|
|
field.name = config.default_naming_func(field.name)
|
|
|
|
_set_field_type(field, slot, nodes)
|
|
_set_field_default(nodes, field, slot)
|
|
|
|
-- print("default:", field.name, field.default_value)
|
|
if not field.type_name then
|
|
field.type_name = "void"
|
|
field.size = 0
|
|
else
|
|
field.size = get_size(field.type_name)
|
|
end
|
|
end
|
|
|
|
local function process_list_type(list_type, nodes)
|
|
-- first one is not element type, so remove it
|
|
--table.remove(list_type, 1)
|
|
if list_type[#list_type - 1] == "struct" then
|
|
local id = list_type[#list_type]
|
|
local struct_name = get_name(nodes[id].displayName)
|
|
for i=1, #list_type - 1 do
|
|
list_type[i] = '"' .. list_type[i] .. '"'
|
|
end
|
|
list_type[#list_type] = "_M." .. struct_name
|
|
else
|
|
for i, v in ipairs(list_type) do
|
|
list_type[i] = '"' .. v .. '"'
|
|
end
|
|
end
|
|
end
|
|
|
|
function comp_parse_struct_data(res, nodes, struct, fields, size, name)
|
|
insert(res, format([[
|
|
|
|
parse_struct_data = function(p32, data_word_count, pointer_count, header,
|
|
tab)
|
|
|
|
local s = tab
|
|
]], size))
|
|
|
|
if struct.discriminantCount and struct.discriminantCount > 0 then
|
|
insert(res, format([[
|
|
|
|
local dscrm = _M.%s.which(p32, %d)]], name, struct.discriminantOffset))
|
|
end
|
|
|
|
for i, field in ipairs(fields) do
|
|
if field.discriminantValue and field.discriminantValue ~= NOT_UNION then
|
|
insert(res, format([[
|
|
|
|
-- union
|
|
if dscrm == %d then
|
|
]],field.discriminantValue))
|
|
|
|
end
|
|
if field.group then
|
|
insert(res, format([[
|
|
|
|
-- group
|
|
if not s["%s"] then
|
|
s["%s"] = new_tab(0, 4)
|
|
end
|
|
_M.%s["%s"].parse_struct_data(p32, _M.%s.dataWordCount,
|
|
_M.%s.pointerCount, header, s["%s"])
|
|
]], field.name, field.name, name, field.name, name, name, field.name))
|
|
|
|
elseif field.type_name == "enum" then
|
|
insert(res, format([[
|
|
|
|
-- enum
|
|
local val = read_struct_field(p32, "uint16", %d, %d)
|
|
s["%s"] = get_enum_name(val, %d, _M.%sStr)
|
|
]], field.size, field.slot.offset, field.name, field.default_value,
|
|
field.type_display_name))
|
|
|
|
elseif field.type_name == "list" then
|
|
local off = field.slot.offset
|
|
local list_type = util.get_field_type(field)
|
|
table.remove(list_type, 1)
|
|
process_list_type(list_type, nodes)
|
|
|
|
local types = concat(list_type, ", ")
|
|
|
|
insert(res, format([[
|
|
|
|
-- list
|
|
local off, size, num = read_listp_struct(p32, header, _M.%s, %d)
|
|
if off and num then
|
|
-- dataWordCount + offset + pointerSize + off
|
|
s["%s"] = read_list_data(p32 + (%d + %d + 1 + off) * 2, header,
|
|
num, %s)
|
|
else
|
|
s["%s"] = nil
|
|
end
|
|
]], name, off, field.name, struct.dataWordCount, off, types, field.name))
|
|
|
|
elseif field.type_name == "struct" then
|
|
local off = field.slot.offset
|
|
|
|
insert(res, format([[
|
|
|
|
-- struct
|
|
local p = p32 + (%d + %d) * 2 -- p32, dataWordCount, offset
|
|
local off, dw, pw = read_struct_buf(p, header)
|
|
if off and dw and pw then
|
|
if not s["%s"] then
|
|
s["%s"] = new_tab(0, 2)
|
|
end
|
|
_M.%s.parse_struct_data(p + 2 + off * 2, dw, pw, header, s["%s"])
|
|
else
|
|
s["%s"] = nil
|
|
end
|
|
]], struct.dataWordCount, off, field.name, field.name, field.type_display_name,
|
|
field.name, field.name))
|
|
|
|
elseif field.type_name == "text" then
|
|
local off = field.slot.offset
|
|
insert(res, format([[
|
|
|
|
-- text
|
|
local off, size, num = read_listp_struct(p32, header, _M.%s, %d)
|
|
if off and num then
|
|
-- dataWordCount + offset + pointerSize + off
|
|
local p8 = ffi_cast(pint8, p32 + (%d + %d + 1 + off) * 2)
|
|
s["%s"] = ffi_string(p8, num - 1)
|
|
else
|
|
s["%s"] = nil
|
|
end
|
|
]], name, off, struct.dataWordCount, off, field.name, field.name))
|
|
|
|
elseif field.type_name == "data" then
|
|
local off = field.slot.offset
|
|
insert(res, format([[
|
|
|
|
-- data
|
|
local off, size, num = read_listp_struct(p32, header, _M.%s, %d)
|
|
if off and num then
|
|
-- dataWordCount + offset + pointerSize + off
|
|
local p8 = ffi_cast(pint8, p32 + (%d + %d + 1 + off) * 2)
|
|
s["%s"] = ffi_string(p8, num)
|
|
else
|
|
s["%s"] = nil
|
|
end
|
|
]], name, off, struct.dataWordCount, off, field.name, field.name))
|
|
|
|
elseif field.type_name == "anyPointer" then
|
|
-- TODO support anyPointer
|
|
elseif field.type_name == "void" then
|
|
insert(res, format([[
|
|
|
|
s["%s"] = "Void"]], field.name))
|
|
else
|
|
local default = field.default_value and field.default_value or "nil"
|
|
insert(res, format([[
|
|
|
|
s["%s"] = read_struct_field(p32, "%s", %d, %d, %s)
|
|
]], field.name, field.type_name, field.size, field.slot.offset, default))
|
|
|
|
end
|
|
|
|
if field.discriminantValue and field.discriminantValue ~= NOT_UNION then
|
|
insert(res, format([[
|
|
|
|
else
|
|
s["%s"] = nil
|
|
end
|
|
]],field.name))
|
|
end
|
|
end
|
|
|
|
insert(res, [[
|
|
|
|
return s
|
|
end,
|
|
]])
|
|
end
|
|
|
|
function comp_parse(res, name)
|
|
insert(res, format([[
|
|
|
|
parse = function(bin, tab)
|
|
if #bin < 16 then
|
|
return nil, "message too short"
|
|
end
|
|
|
|
local header = new_tab(0, 4)
|
|
local p32 = ffi_cast(puint32, bin)
|
|
header.base = p32
|
|
|
|
local nsegs = p32[0] + 1
|
|
header.seg_sizes = {}
|
|
for i=1, nsegs do
|
|
header.seg_sizes[i] = p32[i]
|
|
end
|
|
local pos = round8(4 + nsegs * 4)
|
|
header.header_size = pos / 8
|
|
p32 = p32 + pos / 4
|
|
|
|
if not tab then
|
|
tab = new_tab(0, 8)
|
|
end
|
|
local off, dw, pw = read_struct_buf(p32, header)
|
|
if off and dw and pw then
|
|
return _M.%s.parse_struct_data(p32 + 2 + off * 2, dw, pw,
|
|
header, tab)
|
|
else
|
|
return nil
|
|
end
|
|
end,
|
|
]], name))
|
|
end
|
|
|
|
function comp_serialize(res, name)
|
|
insert(res, format([[
|
|
|
|
-- Serialize and return pointer to char[] and size
|
|
serialize_cdata = function(data, p8, size)
|
|
if p8 == nil then
|
|
size = _M.%s.calc_size(data)
|
|
|
|
p8 = get_str_buf(size)
|
|
end
|
|
ffi_fill(p8, size)
|
|
local p32 = ffi_cast(puint32, p8)
|
|
|
|
-- Because needed size has been calculated, only 1 segment is needed
|
|
p32[0] = 0
|
|
p32[1] = (size - 8) / 8
|
|
|
|
-- skip header
|
|
write_structp(p32 + 2, _M.%s, 0)
|
|
|
|
-- skip header & struct pointer
|
|
_M.%s.flat_serialize(data, p32 + 4)
|
|
|
|
return p8, size
|
|
end,
|
|
|
|
serialize = function(data, p8, size)
|
|
p8, size = _M.%s.serialize_cdata(data, p8, size)
|
|
return ffi_string(p8, size)
|
|
end,
|
|
]], name, name, name, name))
|
|
end
|
|
|
|
function comp_flat_serialize(res, nodes, struct, fields, size, name)
|
|
dbgf("comp_flat_serialize")
|
|
insert(res, format([[
|
|
|
|
flat_serialize = function(data, p32, pos)
|
|
pos = pos and pos or %d -- struct size in bytes
|
|
local start = pos
|
|
local dscrm]], size))
|
|
|
|
insert(res, [[
|
|
|
|
local value]])
|
|
|
|
for i, field in ipairs(fields) do
|
|
insert(res, format([=[
|
|
|
|
|
|
value = data["%s"]]=], field.name))
|
|
--print("comp_field", field.name)
|
|
-- union
|
|
if field.discriminantValue and field.discriminantValue ~= NOT_UNION then
|
|
dbgf("field %s: union", field.name)
|
|
insert(res, format([[
|
|
|
|
if value then
|
|
dscrm = %d
|
|
end
|
|
]], field.discriminantValue))
|
|
end
|
|
if field.group then
|
|
dbgf("field %s: group", field.name)
|
|
-- group size is the same as the struct, so we can use "size" to
|
|
-- represent group size
|
|
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["group"] .. [[ then
|
|
-- groups are just namespaces, field offsets are set within parent
|
|
-- structs
|
|
pos = pos + _M.%s.%s.flat_serialize(value, p32, pos) - %d
|
|
end
|
|
]], name, field.name, size))
|
|
|
|
elseif field.type_name == "enum" then
|
|
dbgf("field %s: enum", field.name)
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["enum"] .. [[ then
|
|
local val = get_enum_val(value, %d, _M.%s, "%s.%s")
|
|
write_struct_field(p32, val, "uint16", %d, %d)
|
|
end]], field.default_value, field.type_display_name, name,
|
|
field.name, field.size, field.slot.offset))
|
|
|
|
elseif field.type_name == "list" then
|
|
dbgf("field %s: list", field.name)
|
|
local off = field.slot.offset
|
|
local list_type = util.get_field_type(field)
|
|
-- nested list
|
|
if #list_type > 1 then
|
|
-- composite
|
|
if list_type[#list_type -1] == "struct" then
|
|
field.size = 7
|
|
else
|
|
-- pointer
|
|
field.size = 6
|
|
end
|
|
end
|
|
process_list_type(list_type, nodes)
|
|
|
|
local types = concat(list_type, ", ")
|
|
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["list"] .. [[ then
|
|
local data_off = get_data_off(_M.%s, %d, pos)
|
|
pos = pos + write_list(p32 + _M.%s.dataWordCount * 2 + %d * 2,
|
|
value, (data_off + 1) * 8, %s)
|
|
end]], name, off, name, off, types))
|
|
|
|
elseif field.type_name == "struct" then
|
|
dbgf("field %s: struct", field.name)
|
|
local off = field.slot.offset
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["struct"] .. [[ then
|
|
local data_off = get_data_off(_M.%s, %d, pos)
|
|
write_structp_buf(p32, _M.%s, _M.%s, %d, data_off)
|
|
local size = _M.%s.flat_serialize(value, p32 + pos / 4)
|
|
pos = pos + size
|
|
end]], name, off, name, field.type_display_name,
|
|
off, field.type_display_name))
|
|
|
|
elseif field.type_name == "text" then
|
|
dbgf("field %s: text", field.name)
|
|
local off = field.slot.offset
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["text"] .. [[ then
|
|
local data_off = get_data_off(_M.%s, %d, pos)
|
|
|
|
local len = #value + 1
|
|
write_listp_buf(p32, _M.%s, %d, %d, len, data_off)
|
|
|
|
ffi_copy(p32 + pos / 4, value)
|
|
pos = pos + round8(len)
|
|
end]], name, off, name, off, 2))
|
|
|
|
elseif field.type_name == "data" then
|
|
dbgf("field %s: data", field.name)
|
|
local off = field.slot.offset
|
|
insert(res, format([[
|
|
|
|
if ]] .. check_type["data"] .. [[ then
|
|
local data_off = get_data_off(_M.%s, %d, pos)
|
|
|
|
local len = #value
|
|
write_listp_buf(p32, _M.%s, %d, %d, len, data_off)
|
|
|
|
-- prevent copying trailing '\0'
|
|
ffi_copy(p32 + pos / 4, value, len)
|
|
pos = pos + round8(len)
|
|
end]], name, off, name, off, 2))
|
|
|
|
else
|
|
dbgf("field %s: %s", field.name, field.type_name)
|
|
local default = field.default_value and field.default_value or "nil"
|
|
local cdata_condition = ""
|
|
if field.type_name == "uint64" or field.type_name == "int64" then
|
|
cdata_condition = 'or data_type == "cdata"'
|
|
end
|
|
if field.type_name ~= "void" then
|
|
insert(res, format([[
|
|
|
|
local data_type = type(value)
|
|
if (data_type == "number"
|
|
or data_type == "boolean" %s) then
|
|
|
|
write_struct_field(p32, value, "%s", %d, %d, %s)
|
|
end]], cdata_condition, field.type_name, field.size,
|
|
field.slot.offset, default))
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
if struct.discriminantCount and struct.discriminantCount ~= 0 then
|
|
insert(res, format([[
|
|
|
|
if dscrm then
|
|
--buf, discriminantOffset, discriminantValue
|
|
_M.%s.which(p32, %d, dscrm)
|
|
end
|
|
]], name, struct.discriminantOffset))
|
|
end
|
|
|
|
insert(res, format([[
|
|
|
|
return pos - start + %d
|
|
end,
|
|
]], size))
|
|
end
|
|
|
|
-- insert a list with indent level
|
|
function insertlt(res, level, data_table)
|
|
for i, v in ipairs(data_table) do
|
|
insertl(res, level, v)
|
|
end
|
|
end
|
|
|
|
-- insert with indent level
|
|
function insertl(res, level, data)
|
|
for i=1, level * 4 do
|
|
insert(res, " ")
|
|
end
|
|
insert(res, data)
|
|
end
|
|
|
|
function _M.comp_calc_list_size(res, field, nodes, name, level, elm_type, ...)
|
|
if not elm_type then
|
|
return
|
|
end
|
|
|
|
insertl(res, level, format("if %s and " ..
|
|
"type(%s) == \"table\" then\n", name, name))
|
|
|
|
if elm_type == "object" or elm_type == "anyPointer"
|
|
or elm_type == "group" then
|
|
|
|
error("List of object/anyPointer/group type is not supported yet.")
|
|
end
|
|
|
|
if elm_type ~= "struct" and elm_type ~= "list" and elm_type ~= "data"
|
|
and elm_type ~= "text" then
|
|
|
|
-- elm_type is a plain type.
|
|
local elm_size = get_size(elm_type) / 8
|
|
insertlt(res, level + 1, {
|
|
"-- num * acutal size\n",
|
|
format("size = size + round8(#%s * %d)\n",
|
|
name, elm_size)
|
|
})
|
|
else
|
|
-- struct tag
|
|
if elm_type == "struct" then
|
|
insertl(res, level + 1, format("size = size + 8\n"))
|
|
end
|
|
|
|
local new_name = name .. "[i" .. level .. "]"
|
|
-- calculate body size
|
|
insertl(res, level + 1, format("local num%d = #%s\n",
|
|
level, name))
|
|
insertl(res, level + 1, format("for %s=1, num%d do\n",
|
|
"i" .. level, level))
|
|
|
|
if elm_type == "list" then
|
|
insertl(res, level + 2, format("size = size + 8\n"))
|
|
_M.comp_calc_list_size(res, field, nodes, new_name, level + 2, ...)
|
|
elseif elm_type == "text" then
|
|
insertl(res, level + 2, format("size = size + 8\n"))
|
|
insertlt(res, level + 2, {
|
|
" -- num * acutal size\n",
|
|
format("size = size + round8(#%s * 1 + 1)\n", new_name)
|
|
})
|
|
elseif elm_type == "data" then
|
|
insertl(res, level + 2, format("size = size + 8\n"))
|
|
insertlt(res, level + 2, {
|
|
" -- num * acutal size\n",
|
|
format("size = size + round8(#%s * 1)\n", new_name)
|
|
})
|
|
elseif elm_type == "struct" then
|
|
local id = ...
|
|
local struct_name = get_name(nodes[id].displayName)
|
|
insertl(res, level + 2, format(
|
|
"size = size + _M.%s.calc_size_struct(%s)\n",
|
|
struct_name, new_name))
|
|
end
|
|
insertl(res, level + 1, "end\n")
|
|
end
|
|
insertl(res, level, "end")
|
|
end
|
|
|
|
function comp_calc_size(res, fields, size, name, nodes, is_group)
|
|
dbgf("comp_calc_size")
|
|
if is_group then
|
|
size = 0
|
|
end
|
|
insert(res, format([[
|
|
|
|
calc_size_struct = function(data)
|
|
local size = %d]], size))
|
|
|
|
insert(res, [[
|
|
|
|
local value]])
|
|
|
|
for i, field in ipairs(fields) do
|
|
dbgf("field %s is %s", field.name, field.type_name)
|
|
|
|
if field.type_name == "list" then
|
|
local list_type = util.get_field_type(field)
|
|
|
|
insert(res, "\n")
|
|
-- list_type[1] must be "list" and should be skipped because is
|
|
-- is not element type
|
|
insert(res, " -- list\n")
|
|
_M.comp_calc_list_size(res, field, nodes,
|
|
format("data[\"%s\"]", field.name), 2,
|
|
select(2, unpack(list_type)))
|
|
elseif field.type_name == "struct" or field.type_name == "group" then
|
|
insert(res, format([[
|
|
|
|
-- struct
|
|
value = data["%s"]
|
|
if ]] .. check_type["struct"] .. [[ then
|
|
size = size + _M.%s.calc_size_struct(value)
|
|
end]], field.name, field.type_display_name))
|
|
|
|
elseif field.type_name == "text" then
|
|
insert(res, format([[
|
|
|
|
-- text
|
|
value = data["%s"]
|
|
if ]] .. check_type["text"] .. [[ then
|
|
-- size 1, including trailing NULL
|
|
size = size + round8(#value + 1)
|
|
end]], field.name))
|
|
|
|
elseif field.type_name == "data" then
|
|
insert(res, format([[
|
|
|
|
-- data
|
|
value = data["%s"]
|
|
if ]] .. check_type["data"] .. [[ then
|
|
size = size + round8(#value)
|
|
end]], field.name))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
insert(res, format([[
|
|
|
|
return size
|
|
end,
|
|
|
|
calc_size = function(data)
|
|
local size = 16 -- header + root struct pointer
|
|
return size + _M.%s.calc_size_struct(data)
|
|
end,
|
|
]], name))
|
|
end
|
|
|
|
function comp_which(res)
|
|
insert(res, [[
|
|
|
|
which = function(buf, offset, n)
|
|
if n then
|
|
-- set value
|
|
write_struct_field(buf, n, "uint16", 16, offset)
|
|
else
|
|
-- get value
|
|
return read_struct_field(buf, "uint16", 16, offset)
|
|
end
|
|
end,
|
|
]])
|
|
end
|
|
|
|
function comp_fields(res, nodes, node, struct)
|
|
insert(res, [[
|
|
|
|
fields = {
|
|
]])
|
|
for i, field in ipairs(struct.fields) do
|
|
comp_field(res, nodes, field)
|
|
if field.group then
|
|
if not node.nestedNodes then
|
|
node.nestedNodes = {}
|
|
end
|
|
insert(node.nestedNodes,
|
|
{ name = field.name, id = field.group.typeId })
|
|
end
|
|
insert(res, format([[
|
|
{ name = "%s", default = %s, ["type"] = "%s" },
|
|
]], field.name, field.print_default_value, field.type_name))
|
|
end
|
|
dbg("struct:", name)
|
|
insert(res, format([[
|
|
},
|
|
]]))
|
|
end
|
|
|
|
function comp_struct(res, nodes, node, struct, name)
|
|
|
|
if not struct.dataWordCount then
|
|
struct.dataWordCount = 0
|
|
end
|
|
if not struct.pointerCount then
|
|
struct.pointerCount = 0
|
|
end
|
|
|
|
insert(res, " dataWordCount = ")
|
|
insert(res, struct.dataWordCount)
|
|
insert(res, ",\n")
|
|
|
|
insert(res, " pointerCount = ")
|
|
insert(res, struct.pointerCount)
|
|
insert(res, ",\n")
|
|
|
|
if struct.discriminantCount then
|
|
insert(res, " discriminantCount = ")
|
|
insert(res, struct.discriminantCount)
|
|
insert(res, ",\n")
|
|
end
|
|
if struct.discriminantOffset then
|
|
insert(res, " discriminantOffset = ")
|
|
insert(res, struct.discriminantOffset)
|
|
insert(res, ",\n")
|
|
end
|
|
if struct.isGroup then
|
|
insert(res, " isGroup = true,\n")
|
|
end
|
|
|
|
struct.size = struct.dataWordCount * 8 + struct.pointerCount * 8
|
|
|
|
if struct.fields then
|
|
insert(res, " field_count = ")
|
|
insert(res, #struct.fields)
|
|
insert(res, ",\n")
|
|
|
|
comp_fields(res, nodes, node, struct)
|
|
--if not struct.isGroup then
|
|
|
|
--end
|
|
comp_calc_size(res, struct.fields, struct.size,
|
|
struct.type_name, nodes, struct.isGroup)
|
|
comp_flat_serialize(res, nodes, struct, struct.fields, struct.size,
|
|
struct.type_name)
|
|
if not struct.isGroup then
|
|
comp_serialize(res, struct.type_name)
|
|
end
|
|
if struct.discriminantCount and struct.discriminantCount > 0 then
|
|
comp_which(res)
|
|
end
|
|
comp_parse_struct_data(res, nodes, struct, struct.fields,
|
|
struct.size, struct.type_name)
|
|
if not struct.isGroup then
|
|
comp_parse(res, struct.type_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
function comp_enum(res, nodes, enum, name, enum_naming_func)
|
|
if not enum_naming_func then
|
|
enum_naming_func = config.default_enum_naming_func
|
|
end
|
|
|
|
-- string to enum
|
|
insert(res, format([[
|
|
|
|
_M.%s = {
|
|
]], name))
|
|
|
|
for i, v in ipairs(enum.enumerants) do
|
|
-- inherent parent naming function
|
|
v.naming_func = enum_naming_func
|
|
|
|
if not v.codeOrder then
|
|
v.codeOrder = 0
|
|
end
|
|
|
|
if v.annotations then
|
|
local anno_res = {}
|
|
if debug then
|
|
dbgf("%s annotations: %s", name, cjson().encode(v.annotations))
|
|
end
|
|
process_annotations(v.annotations, nodes)
|
|
|
|
for i, anno in ipairs(v.annotations) do
|
|
if anno.name == "naming" then
|
|
v.naming_func = get_naming_func(anno.value)
|
|
dbgf("Naming function: %s", anno.value)
|
|
if not v.naming_func then
|
|
error("Unknown naming annotation: " .. anno.value)
|
|
end
|
|
elseif anno.name == "literal" then
|
|
dbgf("enumerant literal: %s", anno.value)
|
|
v.literal = anno.value
|
|
end
|
|
end
|
|
end
|
|
|
|
-- literal has higher priority
|
|
if v.literal then
|
|
insert(res, format(" [\"%s\"] = %s,\n",
|
|
v.literal, v.codeOrder))
|
|
else
|
|
insert(res, format(" [\"%s\"] = %s,\n",
|
|
v.naming_func(v.name), v.codeOrder))
|
|
end
|
|
end
|
|
insert(res, "\n}\n")
|
|
|
|
-- enum to string
|
|
insert(res, format([[
|
|
|
|
_M.%sStr = {
|
|
]], name))
|
|
|
|
for i, v in ipairs(enum.enumerants) do
|
|
if not v.codeOrder then
|
|
v.codeOrder = 0
|
|
end
|
|
if v.literal then
|
|
insert(res, format(" [%s] = \"%s\",\n",
|
|
v.codeOrder, v.literal))
|
|
else
|
|
insert(res, format(" [%s] = \"%s\",\n",
|
|
v.codeOrder, v.naming_func(v.name)))
|
|
end
|
|
end
|
|
insert(res, "\n}\n")
|
|
end
|
|
|
|
_M.naming_funcs = {
|
|
upper_dash = util.upper_dash_naming,
|
|
lower_underscore = util.lower_underscore_naming,
|
|
upper_underscore = util.upper_underscore_naming,
|
|
camel = util.camel_naming,
|
|
lower_space = util.lower_space_naming,
|
|
}
|
|
|
|
function process_annotations(annos, nodes)
|
|
if debug then
|
|
dbg("process_annotations:" .. cjson().encode(annos))
|
|
end
|
|
for i, anno in ipairs(annos) do
|
|
local id = anno.id
|
|
anno.name = get_name(nodes[id].displayName)
|
|
anno.value_saved = anno.value
|
|
assert(type(anno.value_saved) == "table", 'expected "table" but got "'
|
|
.. type(anno.value_saved) .. "\": " .. tostring(anno.value_saved))
|
|
|
|
for k, v in pairs(anno.value_saved) do
|
|
anno["type"] = k
|
|
anno["value"] = v
|
|
break
|
|
end
|
|
anno.value_saved = nil
|
|
end
|
|
end
|
|
|
|
function get_naming_func(name)
|
|
local func = _M.naming_funcs[name]
|
|
if not func then
|
|
error("unknown naming: " .. tostring(name))
|
|
end
|
|
|
|
return func
|
|
end
|
|
|
|
function comp_node(res, nodes, node, name)
|
|
dbgf("comp_node: %s, %s", name, node.id)
|
|
if not node then
|
|
print("Ignore node: ", name)
|
|
return
|
|
end
|
|
|
|
if node.annotation then
|
|
-- do not need to generation any code for annotations
|
|
return
|
|
end
|
|
|
|
node.name = name
|
|
node.type_name = get_name(node.displayName)
|
|
|
|
local s = node.struct
|
|
if s then
|
|
|
|
insert(res, format([[
|
|
|
|
_M.%s = {
|
|
]], name))
|
|
s.type_name = node.type_name
|
|
insert(res, format([[
|
|
id = "%s",
|
|
displayName = "%s",
|
|
]], node.id, node.displayName))
|
|
comp_struct(res, nodes, node, s, name)
|
|
insert(res, "\n}\n")
|
|
end
|
|
|
|
local e = node.enum
|
|
if e then
|
|
local anno_res = {}
|
|
if node.annotations then
|
|
process_annotations(node.annotations, nodes)
|
|
end
|
|
|
|
local naming_func
|
|
if node.annotations then
|
|
for i, anno in ipairs(node.annotations) do
|
|
if anno.name == "naming" then
|
|
naming_func = get_naming_func(anno.value)
|
|
end
|
|
break
|
|
end
|
|
end
|
|
comp_enum(res, nodes, e, name, naming_func)
|
|
end
|
|
|
|
if node.const then
|
|
dbgf("compile const: %s", name)
|
|
local const = node.const
|
|
local const_type = _get_type(const["type"])
|
|
|
|
if const_type == "text" or const_type == "data" or const_type == "void"
|
|
or const_type == "list" or const_type == "struct"
|
|
or const_type == "enum" or const_type == "group"
|
|
or const_type == "anyPointer"
|
|
then
|
|
insert(res, format([[
|
|
|
|
_M.%s = "%s"
|
|
]], name, const.value[const_type]))
|
|
else
|
|
insert(res, format([[
|
|
|
|
_M.%s = %s
|
|
]], name, const.value[const_type]))
|
|
|
|
end
|
|
end
|
|
|
|
if node.nestedNodes then
|
|
for i, child in ipairs(node.nestedNodes) do
|
|
comp_node(res, nodes, nodes[child.id], name .. "." .. child.name)
|
|
end
|
|
end
|
|
end
|
|
|
|
function comp_body(res, schema)
|
|
dbg("comp_body")
|
|
local nodes = schema.nodes
|
|
for i, v in ipairs(nodes) do
|
|
nodes[v.id] = v
|
|
end
|
|
|
|
local files = schema.requestedFiles
|
|
|
|
for i, file in ipairs(files) do
|
|
comp_file(res, nodes, file)
|
|
|
|
local imports = file.imports
|
|
for i, import in ipairs(imports) do
|
|
--import node are compiled later by comp_file
|
|
--comp_import(res, nodes, import)
|
|
check_import(files, import)
|
|
end
|
|
end
|
|
|
|
for k, v in pairs(missing_enums) do
|
|
insert(res, k .. ".enum_schema = _M." ..
|
|
get_name(nodes[v].displayName .. "\n"))
|
|
end
|
|
|
|
insert(res, "\nreturn _M\n")
|
|
end
|
|
|
|
function check_import(files, import)
|
|
local id = import.id
|
|
local name = import.name
|
|
|
|
for i, file in ipairs(files) do
|
|
if file.id == id then
|
|
return true
|
|
end
|
|
end
|
|
|
|
error('imported file "' .. name .. '" is missing, compile it together with'
|
|
.. ' other ZAP files')
|
|
end
|
|
|
|
function comp_import(res, nodes, import)
|
|
local id = import.id
|
|
|
|
dbgf("comp_import: %s", id)
|
|
|
|
local import_node = nodes[id]
|
|
for i, node in ipairs(import_node.nestedNodes) do
|
|
comp_node(res, nodes, nodes[node.id], node.name)
|
|
end
|
|
end
|
|
|
|
function comp_file(res, nodes, file)
|
|
dbg("comp_file")
|
|
local id = file.id
|
|
|
|
local file_node = nodes[id]
|
|
for i, node in ipairs(file_node.nestedNodes) do
|
|
comp_node(res, nodes, nodes[node.id], node.name)
|
|
end
|
|
end
|
|
|
|
function comp_dg_node(res, nodes, node)
|
|
if not node.struct then
|
|
return
|
|
end
|
|
|
|
local name = gsub(lower(node.name), "%.", "_")
|
|
insert(res, format([[
|
|
function gen_%s()
|
|
if rand.random_nil() then
|
|
return nil
|
|
end
|
|
|
|
]], name))
|
|
|
|
if node.nestedNodes then
|
|
for i, child in ipairs(node.nestedNodes) do
|
|
comp_dg_node(res, nodes, nodes[child.id])
|
|
end
|
|
end
|
|
|
|
insert(res, format(" local %s = {}\n", name))
|
|
for i, field in ipairs(node.struct.fields) do
|
|
if field.group then
|
|
-- TODO group stuffs
|
|
elseif field.type_name == "struct" then
|
|
insert(res, format(" %s.%s = gen_%s()\n", name,
|
|
field.name,
|
|
gsub(lower(field.type_display_name), "%.", "_")))
|
|
|
|
elseif field.type_name == "enum" then
|
|
elseif field.type_name == "list" then
|
|
local list_type = field.element_type
|
|
insert(res, format([[
|
|
%s["%s"] = rand.%s(rand.uint8(), rand.%s)]], name,
|
|
field.name, field.type_name, list_type))
|
|
|
|
else
|
|
insert(res, format(' %s["%s"] = rand.%s()\n', name,
|
|
field.name, field.type_name))
|
|
end
|
|
end
|
|
|
|
|
|
insert(res, format([[
|
|
return %s
|
|
end
|
|
|
|
]], name))
|
|
end
|
|
|
|
function _M.compile_data_generator(schema)
|
|
local res = {}
|
|
insert(res, [[
|
|
|
|
local rand = require("random")
|
|
local cjson = require("cjson")
|
|
local pairs = pairs
|
|
|
|
local ok, new_tab = pcall(require, "table.new")
|
|
|
|
if not ok then
|
|
new_tab = function (narr, nrec) return {} end
|
|
end
|
|
|
|
module(...)
|
|
]])
|
|
|
|
local files = schema.requestedFiles
|
|
local nodes = schema.nodes
|
|
|
|
for i, file in ipairs(files) do
|
|
local file_node = nodes[file.id]
|
|
|
|
for i, node in ipairs(file_node.nestedNodes) do
|
|
comp_dg_node(res, nodes, nodes[node.id])
|
|
end
|
|
end
|
|
|
|
return table.concat(res)
|
|
end
|
|
|
|
function _M.compile(schema)
|
|
local res = {}
|
|
|
|
comp_header(res, schema.nodes)
|
|
comp_body(res, schema)
|
|
|
|
return table.concat(res)
|
|
end
|
|
|
|
function _M.init(user_conf)
|
|
dbg("set config init")
|
|
for k, v in pairs(user_conf) do
|
|
--if not config[k] then
|
|
-- print(format("Unknown user config: %s, ignored.", k))
|
|
--end
|
|
config[k] = v
|
|
dbg("set config " .. k)
|
|
end
|
|
end
|
|
|
|
return _M
|