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).
243 lines
5.7 KiB
Lua
243 lines
5.7 KiB
Lua
local ffi = require "ffi"
|
|
local bit = require "bit"
|
|
local lower = string.lower
|
|
local upper = string.upper
|
|
local gsub = string.gsub
|
|
local format = string.format
|
|
local concat = table.concat
|
|
local insert = table.insert
|
|
|
|
local tohex = bit.tohex
|
|
|
|
local _M = {}
|
|
|
|
function _M.upper_dash_naming(name)
|
|
return upper(gsub(name, "(%u+)", "-%1"))
|
|
end
|
|
|
|
function _M.lower_underscore_naming(name)
|
|
return lower(gsub(name, "(%u+)", "_%1"))
|
|
end
|
|
|
|
function _M.upper_underscore_naming(name)
|
|
return upper(gsub(name, "(%u+)", "_%1"))
|
|
end
|
|
|
|
function _M.lower_space_naming(name)
|
|
return lower(gsub(name, "(%u+)", " %1"))
|
|
end
|
|
|
|
-- zap only allow camel naming for enums
|
|
function _M.camel_naming(name)
|
|
return name
|
|
end
|
|
|
|
function _M.parse_zap_txt(s)
|
|
s = gsub(s, "%(", "{")
|
|
s = gsub(s, "%)", "}")
|
|
s = gsub(s, "%[", "{")
|
|
s = gsub(s, "%]", "}")
|
|
s = gsub(s, "%<", "'")
|
|
s = gsub(s, "%>", "'")
|
|
s = gsub(s, "id = (%d+)", "id = \"%1\"")
|
|
s = gsub(s, "typeId = (%d+)", "typeId = \"%1\"")
|
|
s = gsub(s, "scopeId = (%d+)", "scopeId = \"%1\"")
|
|
s = gsub(s, "= void([^\"])", "= \"void\"%1")
|
|
s = gsub(s, "type = {", '[\"type\"] = {')
|
|
s = "return " .. s
|
|
|
|
return s
|
|
end
|
|
|
|
function _M.parse_zap_decode_txt(infile)
|
|
local f = io.open(infile)
|
|
if not f then
|
|
return nil, "Can't open file: " .. tostring(infile)
|
|
end
|
|
|
|
local s = f:read("*a")
|
|
f:close()
|
|
|
|
return _M.parse_zap_txt(s)
|
|
end
|
|
|
|
function _M.table_diff(t1, t2, namespace)
|
|
local keys = {}
|
|
|
|
if not namespace then
|
|
namespace = ""
|
|
end
|
|
|
|
for k, v in pairs(t1) do
|
|
k = _M.lower_underscore_naming(k)
|
|
keys[k] = true
|
|
t1[k] = v
|
|
end
|
|
|
|
for k, v in pairs(t2) do
|
|
k = _M.lower_underscore_naming(k)
|
|
keys[k] = true
|
|
t2[k] = v
|
|
end
|
|
|
|
for k, v in pairs(keys) do
|
|
local name = namespace .. "." .. k
|
|
local v1 = t1[k]
|
|
local v2 = t2[k]
|
|
|
|
local t1 = type(v1)
|
|
local t2 = type(v2)
|
|
|
|
if t1 ~= t2 then
|
|
print(format("%s: different type: %s %s, value: %s %s", name,
|
|
t1, t2, tostring(v1), tostring(v2)))
|
|
elseif t1 == "table" then
|
|
_M.table_diff(v1, v2, namespace .. "." .. k)
|
|
elseif v1 ~= v2 then
|
|
print(format("%s: different value: %s %s", name,
|
|
tostring(v1), tostring(v2)))
|
|
end
|
|
end
|
|
end
|
|
|
|
function _M.read_file(name)
|
|
local f = assert(io.open(name, "r"))
|
|
local content = f:read("*a")
|
|
f:close()
|
|
return content
|
|
end
|
|
|
|
function _M.write_file(name, content)
|
|
local f = assert(io.open(name, "w"))
|
|
f:write(content)
|
|
f:close()
|
|
end
|
|
|
|
function _M.get_output_name(schema)
|
|
return string.gsub(schema.requestedFiles[1].filename, "%.zap", "_zap")
|
|
end
|
|
|
|
function _M.hex_buf_str(buf, len)
|
|
local str = ffi.string(buf, len)
|
|
local t = {}
|
|
for i = 1, len do
|
|
table.insert(t, tohex(string.byte(str, i), 2))
|
|
end
|
|
return table.concat(t, " ")
|
|
end
|
|
function _M.print_hex_buf(buf, len)
|
|
local str = _M.hex_buf_str(buf, len)
|
|
print(str)
|
|
end
|
|
|
|
function _M.new_buf(hex, ct)
|
|
if type(hex) ~= "table" then
|
|
error("expected the first argument as a table")
|
|
end
|
|
local len = #hex
|
|
local buf = ffi.new("char[?]", len)
|
|
for i=1, len do
|
|
buf[i - 1] = hex[i]
|
|
end
|
|
if not ct then
|
|
ct = "uint32_t *"
|
|
end
|
|
return ffi.cast(ct, buf)
|
|
end
|
|
|
|
local function equal(a, b)
|
|
if type(a) == "boolean" then
|
|
a = a and 1 or 0
|
|
end
|
|
if type(b) == "boolean" then
|
|
b = b and 1 or 0
|
|
end
|
|
return a == b
|
|
end
|
|
|
|
local function to_text_core(val, T, res)
|
|
local typ = type(val)
|
|
if typ == "table" then
|
|
if #val > 0 then
|
|
-- list
|
|
insert(res, "[")
|
|
for i = 1, #val do
|
|
if i ~= 1 then
|
|
insert(res, ", ")
|
|
end
|
|
insert(res, '"')
|
|
insert(res, val[i])
|
|
insert(res, '"')
|
|
end
|
|
insert(res, "]")
|
|
else
|
|
-- struct
|
|
insert(res, "(")
|
|
local i = 1
|
|
for _, item in pairs(T.fields) do
|
|
local k = item.name
|
|
local default = item.default
|
|
if type(default) == "boolean" then
|
|
default = default and 1 or 0
|
|
end
|
|
if val[k] ~= nil then
|
|
-- if not equal(val[k], default) then
|
|
if i ~= 1 then
|
|
insert(res, ", ")
|
|
end
|
|
insert(res, k)
|
|
insert(res, " = ")
|
|
to_text_core(val[k], T[k], res)
|
|
i = i + 1
|
|
-- end
|
|
end
|
|
end
|
|
insert(res, ")")
|
|
end
|
|
elseif typ == "string" then
|
|
if val == "Void" then
|
|
insert(res, "void")
|
|
else
|
|
insert(res, '"')
|
|
insert(res, val)
|
|
insert(res, '"')
|
|
end
|
|
elseif typ == "boolean" then
|
|
insert(res, val and "true" or "false")
|
|
else
|
|
if type(val) == "cdata" then
|
|
--val = string.sub(tostring(val), 1, -3)
|
|
val = tostring(val)
|
|
end
|
|
insert(res, val)
|
|
end
|
|
end
|
|
|
|
function _M.to_text(val, T)
|
|
local res = {}
|
|
to_text_core(val, T, res)
|
|
return concat(res)
|
|
end
|
|
|
|
local function get_type(typ)
|
|
if not typ then
|
|
return
|
|
end
|
|
|
|
for k, v in pairs(typ) do
|
|
if k == "struct" then
|
|
return k, typ[k].typeId
|
|
end
|
|
return k, get_type(typ[k].elementType)
|
|
end
|
|
end
|
|
|
|
function _M.get_field_type(field)
|
|
if field and field.slot and field.slot["type"] then
|
|
return { get_type(field.slot["type"]) }
|
|
end
|
|
|
|
return
|
|
end
|
|
return _M
|