----------------------------------------------------------------- -- lua-capnproto compiler -- @copyright 2013-2016 Jiale Zhi (vipcalio@gmail.com) ----------------------------------------------------------------- local cjson = require("cjson") local encode = cjson.encode local util = require "capnp.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-capnproto %s on %s -- https://github.com/cloudflare/lua-capnproto.git ]], config.version, os.date())) insert(res, format([[ local ffi = require "ffi" local capnp = require "capnp" local bit = require "bit" local ceil = math.ceil local write_struct_field= capnp.write_struct_field local read_struct_field = capnp.read_struct_field local read_text = capnp.read_text local write_text = capnp.write_text local get_enum_val = capnp.get_enum_val local get_enum_name = capnp.get_enum_name local get_data_off = capnp.get_data_off local write_listp_buf = capnp.write_listp_buf local write_structp_buf = capnp.write_structp_buf local write_structp = capnp.write_structp local read_struct_buf = capnp.read_struct_buf local read_listp_struct = capnp.read_listp_struct local read_list_data = capnp.read_list_data local write_list = capnp.write_list local write_list_data = capnp.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 http://kentonv.github.io/_Mroto/encoding.html#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 = function(data, p8, size) if not p8 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 ffi_string(p8, size) end, ]], 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 data[\"%s\"] and " .. "type(data[\"%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(#data[\"%s\"] * %d)\n", name, elm_size) }) else -- struct tag if elm_type == "struct" then insertl(res, level + 1, format("size = size + 8\n", name)) end local new_name = "[\"" .. name .. "\"]" .. "[i" .. level .. "]" -- calculate body size insertl(res, level + 1, format("local num%d = #data[\"%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", name)) _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", name)) insertlt(res, level + 2, { " -- num * acutal size\n", format("size = size + round8(#data%s * 1 + 1)\n", new_name) }) elseif elm_type == "data" then insertl(res, level + 2, format("size = size + 8\n", name)) insertlt(res, level + 2, { " -- num * acutal size\n", format("size = size + round8(#data%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(data%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, 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 insert(res, " field_count = ") insert(res, #struct.fields) insert(res, ",\n") struct.size = struct.dataWordCount * 8 + struct.pointerCount * 8 if struct.fields then 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 = {} dbgf("%s annotations: %s", name, cjson.encode(v.annotations)) 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) dbg("process_annotations:" .. encode(annos)) 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 Cap\'n Proto 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