-- Copyright 2019 云风 https://github.com/cloudwu . All rights reserved. -- License (the same with bgfx) : https://github.com/bkaradzic/bgfx/blob/master/LICENSE local codegen = {} local DEFAULT_NAME_ALIGN = 20 local DEFINE_NAME_ALIGN = 41 local function namealign(name, align) align = align or DEFAULT_NAME_ALIGN return string.rep(" ", align - #name) end local function camelcase_to_underscorecase(name) local tmp = {} for v in name:gmatch "[%u%d]+%l*" do tmp[#tmp+1] = v:lower() end return table.concat(tmp, "_") end local function to_underscorecase(name) local tmp = {} for v in name:gmatch "[_%u][%l%d]*" do if v:byte() == 95 then -- '_' v = v:sub(2) -- remove _ end tmp[#tmp+1] = v end return table.concat(tmp, "_") end local function underscorecase_to_camelcase(name) local tmp = {} for v in name:gmatch "[^_]+" do tmp[#tmp+1] = v:sub(1,1):upper() .. v:sub(2) end return table.concat(tmp) end local function convert_funcname(name) name = name:gsub("^%l", string.upper) -- Change to upper CamlCase return camelcase_to_underscorecase(name) end local function convert_arg(all_types, arg, namespace) local fulltype, array = arg.fulltype:match "(.-)%s*(%[%s*[%d%a_:]*%s*%])" if array then arg.fulltype = fulltype arg.array = array local enum, value = array:match "%[%s*([%a%d]+)::([%a%d]+)%]" if enum then local typedef = all_types[ enum .. "::Enum" ] if typedef == nil then error ("Unknown Enum " .. enum) end arg.carray = "[BGFX_" .. camelcase_to_underscorecase(enum):upper() .. "_" .. value:upper() .. "]" end end local t, postfix = arg.fulltype:match "(%a[%a%d_:]*)%s*([*&]+)%s*$" if t then arg.type = t if postfix == "&" then arg.ref = true end else local prefix, t = arg.fulltype:match "^%s*(%a+)%s+(%S+)" if prefix then arg.type = t else arg.type = arg.fulltype end end local ctype local substruct = namespace.substruct if substruct then ctype = substruct[arg.type] end if not ctype then ctype = all_types[arg.type] end if not ctype then error ("Undefined type " .. arg.fulltype .. " in " .. namespace.name) end arg.ctype = arg.fulltype:gsub(arg.type, ctype.cname):gsub("&", "*") if ctype.cname ~= arg.type then arg.cpptype = arg.fulltype:gsub(arg.type, "bgfx::"..arg.type) else arg.cpptype = arg.fulltype end if arg.ref then arg.ptype = arg.cpptype:gsub("&", "*") end end local function alternative_name(name) if name:sub(1,1) == "_" then return name:sub(2) else return name .. "_" end end local function gen_arg_conversion(all_types, arg) if arg.ctype == arg.fulltype then -- do not need conversion return end local ctype = all_types[arg.type] if ctype.handle and arg.type == arg.fulltype then local aname = alternative_name(arg.name) arg.aname = aname .. ".cpp" arg.aname_cpp2c = aname .. ".c" arg.conversion = string.format( "union { %s c; bgfx::%s cpp; } %s = { %s };" , ctype.cname, arg.type, aname, arg.name) arg.conversion_back = string.format( "union { bgfx::%s cpp; %s c; } %s = { %s };" , arg.type, ctype.cname, aname, arg.name) elseif arg.ref then if ctype.cname == arg.type then arg.aname = "*" .. arg.name arg.aname_cpp2c = "&" .. arg.name elseif arg.out and ctype.enum then local aname = alternative_name(arg.name) local cpptype = arg.cpptype:match "(.-)%s*&" -- remove & local c99type = arg.ctype:match "(.-)%s*%*" -- remove * arg.aname = aname arg.aname_cpp2c = "&" .. aname arg.conversion = string.format("%s %s;", cpptype, aname) arg.conversion_back = string.format("%s %s;", c99type, aname); arg.out_conversion = string.format("*%s = (%s)%s;", arg.name, ctype.cname, aname) arg.out_conversion_back = string.format("%s = (%s)%s;", arg.name, c99type, aname) else arg.aname = alternative_name(arg.name) arg.aname_cpp2c = string.format("(%s)&%s" , arg.ctype , arg.name) arg.conversion = string.format( "%s %s = *(%s)%s;", arg.cpptype, arg.aname, arg.ptype, arg.name) end else local cpptype = arg.cpptype local ctype = arg.ctype if arg.array then cpptype = cpptype .. "*" ctype = ctype .. "*" end arg.aname = string.format( "(%s)%s", cpptype, arg.name) arg.aname_cpp2c = string.format( "(%s)%s", ctype, arg.name) end end local function gen_ret_conversion(all_types, func) local postfix = { func.vararg and "va_end(argList);" } local postfix_cpp2c = { postfix[1] } func.ret_postfix = postfix func.ret_postfix_cpp2c = postfix_cpp2c for _, arg in ipairs(func.args) do if arg.out_conversion then postfix[#postfix+1] = arg.out_conversion postfix_cpp2c[#postfix_cpp2c+1] = arg.out_conversion_back end end local ctype = all_types[func.ret.type] if ctype.handle then func.ret_conversion = string.format( "union { %s c; bgfx::%s cpp; } handle_ret;" , ctype.cname, ctype.name) func.ret_conversion_cpp2c = string.format( "union { bgfx::%s cpp; %s c; } handle_ret;" , ctype.name, ctype.cname) func.ret_prefix = "handle_ret.cpp = " func.ret_prefix_cpp2c = "handle_ret.c = " postfix[#postfix+1] = "return handle_ret.c;" postfix_cpp2c[#postfix_cpp2c+1] = "return handle_ret.cpp;" elseif func.ret.fulltype ~= "void" then local ctype_conversion = "" local conversion_back = "" if ctype.name ~= ctype.cname then if func.ret.ref then ctype_conversion = "(" .. func.ret.ctype .. ")&" conversion_back = "*(" .. func.ret.ptype .. ")" else ctype_conversion = "(" .. func.ret.ctype .. ")" conversion_back = "(" .. func.ret.cpptype .. ")" end end if #postfix > 0 then func.ret_prefix = string.format("%s retValue = %s", func.ret.ctype , ctype_conversion) func.ret_prefix_cpp2c = string.format("%s retValue = %s", func.ret.cpptype , conversion_back) local ret = "return retValue;" postfix[#postfix+1] = ret postfix_cpp2c[#postfix_cpp2c+1] = ret else func.ret_prefix = string.format("return %s", ctype_conversion) func.ret_prefix_cpp2c = string.format("return %s", conversion_back) end end end local function convert_vararg(v) if v.vararg then local args = v.args local vararg = { name = "", fulltype = "...", type = "...", ctype = "...", aname = "argList", conversion = string.format( "va_list argList;\n\tva_start(argList, %s);", args[#args].name), } args[#args + 1] = vararg v.alter_name = v.vararg end end local function calc_flag_values(flag) local shift = flag.shift local base = flag.base or 0 local cap = 1 << (flag.range or 0) if flag.range then if flag.range == 64 then flag.mask = 0xffffffffffffffff else flag.mask = ((1 << flag.range) - 1) << shift end end local values = {} for index, item in ipairs(flag.flag) do local value = item.value if flag.const then -- use value directly elseif shift then if value then if value > 0 then value = value - 1 end else value = index + base - 1 end if value >= cap then error (string.format("Out of range for %s.%s (%d/%d)", flag.name, item.name, value, cap)) end value = value << shift elseif #item == 0 then if value then if value > 0 then value = 1 << (value - 1) end else local s = index + base - 2 if s >= 0 then value = 1 << s else value = 0 end end end if not value then -- It's a combine flags value = 0 for _, name in ipairs(item) do local v = values[name] if v then value = value | v else -- todo : it's a undefined flag value = nil break end end end item.value = value values[item.name] = value end end function codegen.nameconversion(all_types, all_funcs) for _,v in ipairs(all_types) do local name = v.name local cname = v.cname if cname == nil then if name:match "^%u" then cname = camelcase_to_underscorecase(name) elseif not v.flag then v.cname = name end end if cname and not v.flag then if v.namespace then cname = camelcase_to_underscorecase(v.namespace) .. "_" .. cname end v.cname = "bgfx_".. cname .. "_t" end if v.enum then v.typename = v.name v.name = v.name .. "::Enum" end if v.flag then calc_flag_values(v) end end -- make index for _,v in ipairs(all_types) do if not v.namespace then if all_types[v.name] then error ("Duplicate type " .. v.name) elseif not v.flag then all_types[v.name] = v end end end -- make sub struct index for _,v in ipairs(all_types) do if v.namespace then local super = all_types[v.namespace] if not super then error ("Define " .. v.namespace .. " first") end local substruct = super.substruct if not substruct then substruct = {} super.substruct = substruct end if substruct[v.name] then error ( "Duplicate sub struct " .. v.name .. " in " .. v.namespace) end v.parent_class = super substruct[#substruct+1] = v substruct[v.name] = v end end for _,v in ipairs(all_types) do if v.struct then for _, item in ipairs(v.struct) do convert_arg(all_types, item, v) end elseif v.args then -- funcptr for _, arg in ipairs(v.args) do convert_arg(all_types, arg, v) end convert_vararg(v) convert_arg(all_types, v.ret, v) end end local funcs = {} local funcs_conly = {} local funcs_alter = {} for _,v in ipairs(all_funcs) do if v.cname == nil then v.cname = convert_funcname(v.name) end if v.class then v.cname = convert_funcname(v.class) .. "_" .. v.cname local classtype = all_types[v.class] if classtype then local methods = classtype.methods if not methods then methods = {} classtype.methods = methods end methods[#methods+1] = v end elseif not v.conly then funcs[v.name] = v end if v.conly then table.insert(funcs_conly, v) end for _, arg in ipairs(v.args) do convert_arg(all_types, arg, v) gen_arg_conversion(all_types, arg) end convert_vararg(v) if v.alter_name then funcs_alter[#funcs_alter+1] = v end convert_arg(all_types, v.ret, v) gen_ret_conversion(all_types, v) local namespace = v.class if namespace then local classname = namespace if v.const then classname = "const " .. classname end local classtype = { fulltype = classname .. "*" } convert_arg(all_types, classtype, v) v.this = classtype.ctype v.this_type = classtype v.this_conversion = string.format( "%s This = (%s)_this;", classtype.cpptype, classtype.cpptype) v.this_to_c = string.format("(%s)this", classtype.ctype) end end for _, v in ipairs(funcs_conly) do local func = funcs[v.name] if func then func.multicfunc = func.multicfunc or { func.cname } table.insert(func.multicfunc, v.cname) end end for _, v in ipairs(funcs_alter) do local func = funcs[v.alter_name] v.alter_cname = func.cname end end local function lines(tbl) if not tbl or #tbl == 0 then return "//EMPTYLINE" else return table.concat(tbl, "\n\t") end end local function remove_emptylines(txt) return (txt:gsub("\t//EMPTYLINE\n", "")) end local function codetemp(func) local conversion = {} local conversion_c2cpp = {} local args = {} local cargs = {} local callargs_conversion = {} local callargs_conversion_back = {} local callargs = {} local cppfunc local classname if func.class then -- It's a member function cargs[1] = func.this .. " _this" conversion[1] = func.this_conversion cppfunc = "This->" .. func.name callargs[1] = "_this" callargs_conversion_back[1] = func.this_to_c classname = func.class .. "::" else cppfunc = "bgfx::" .. tostring(func.alter_name or func.name) classname = "" end for _, arg in ipairs(func.args) do conversion[#conversion+1] = arg.conversion conversion_c2cpp[#conversion_c2cpp+1] = arg.conversion_back local cname = arg.ctype .. " " .. arg.name if arg.array then cname = cname .. (arg.carray or arg.array) end local name = arg.fulltype .. " " .. arg.name if arg.array then name = name .. arg.array end if arg.default ~= nil then name = name .. " = " .. tostring(arg.default) end cargs[#cargs+1] = cname args[#args+1] = name callargs_conversion[#callargs_conversion+1] = arg.aname or arg.name callargs_conversion_back[#callargs_conversion_back+1] = arg.aname_cpp2c or arg.name callargs[#callargs+1] = arg.name end conversion[#conversion+1] = func.ret_conversion conversion_c2cpp[#conversion_c2cpp+1] = func.ret_conversion_cpp2c local ARGS local args_n = #args if args_n == 0 then ARGS = "" elseif args_n == 1 then ARGS = args[1] else ARGS = "\n\t " .. table.concat(args, "\n\t, ") .. "\n\t" end local preret_c2c local postret_c2c = {} local conversion_c2c = {} local callfunc_c2c if func.vararg then postret_c2c[1] = "va_end(argList);" local vararg = func.args[#func.args] callargs[#callargs] = vararg.aname callargs_conversion_back[#callargs_conversion_back] = vararg.aname conversion_c2c[1] = vararg.conversion conversion_c2cpp[1] = vararg.conversion if func.ret.fulltype == "void" then preret_c2c = "" else preret_c2c = func.ret.ctype .. " retValue = " postret_c2c[#postret_c2c+1] = "return retValue;" end callfunc_c2c = func.alter_cname or func.cname else if func.ret.fulltype == "void" then preret_c2c = "" else preret_c2c = "return " end callfunc_c2c = func.cname end outCargs = table.concat(cargs, ", ") if outCargs == "" then outCargs = "void" end return { RET = func.ret.fulltype, CRET = func.ret.ctype, CFUNCNAME = func.cname, CFUNCNAMEUPPER = func.cname:upper(), CFUNCNAMECAML = underscorecase_to_camelcase(func.cname), FUNCNAME = func.name, CARGS = outCargs, CPPARGS = table.concat(args, ", "), ARGS = ARGS, CONVERSION = lines(conversion), CONVERSIONCTOC = lines(conversion_c2c), CONVERSIONCTOCPP = lines(conversion_c2cpp), PRERET = func.ret_prefix or "", PRERETCPPTOC = func.ret_prefix_cpp2c or "", CPPFUNC = cppfunc, CALLFUNCCTOC = callfunc_c2c, CALLARGSCTOCPP = table.concat(callargs_conversion, ", "), CALLARGSCPPTOC = table.concat(callargs_conversion_back, ", "), CALLARGS = table.concat(callargs, ", "), POSTRET = lines(func.ret_postfix), POSTRETCPPTOC = lines(func.ret_postfix_cpp2c), PRERETCTOC = preret_c2c, POSTRETCTOC = lines(postret_c2c), CLASSNAME = classname, CONST = func.const and " const" or "", } end local function apply_template(func, temp) func.codetemp = func.codetemp or codetemp(func) return (temp:gsub("$(%u+)", func.codetemp)) end function codegen.apply_functemp(func, temp) return remove_emptylines(apply_template(func, temp)) end function codegen.gen_funcptr(funcptr) return apply_template(funcptr, "typedef $RET (*$FUNCNAME)($ARGS);") end function codegen.gen_cfuncptr(funcptr) return apply_template(funcptr, "typedef $CRET (*$CFUNCNAME)($CARGS);") end local function doxygen_funcret(r, func, prefix) if not func or func.ret.fulltype == "void" or func.ret.comment == nil then return end r[#r+1] = prefix r[#r+1] = string.format("%s @returns %s", prefix, func.ret.comment[1]) for i = 2,#func.ret.comment do r[#r+1] = string.format("%s %s", prefix, func.ret.comment[i]) end return r end local function doxygen_func(r, func, prefix) if not func or not func.args or #func.args == 0 then return end r[#r+1] = prefix for _, arg in ipairs(func.args) do local inout if arg.out then inout = "out" elseif arg.inout then inout = "inout" else inout = "in" end local comment = string.format("%s @param[%s] %s", prefix, inout, arg.name) if arg.comment then r[#r+1] = comment .. " " .. arg.comment[1] for i = 2,#arg.comment do r[#r+1] = string.format("%s %s", prefix, arg.comment[i]) end else r[#r+1] = comment end end doxygen_funcret(r, func, prefix) return r end function codegen.doxygen_type(doxygen, func, cname) if doxygen == nil then return end local result = {} for _, line in ipairs(doxygen) do result[#result+1] = "/// " .. line end doxygen_func(result, func, "///") if cname then result[#result+1] = "///" if type(cname) == "string" then result[#result+1] = string.format("/// @attention C99's equivalent binding is `%s`.", cname) else local names = {} for _, v in ipairs(cname) do names[#names+1] = "`" .. v .. "`" end result[#result+1] = string.format("/// @attention C99's equivalent bindings are %s.", table.concat(names, ",")) end end result[#result+1] = "///" return table.concat(result, "\n") end function codegen.doxygen_ctype(doxygen, func) if doxygen == nil then return end local result = { "/**", } for _, line in ipairs(doxygen) do result[#result+1] = " * " .. line end doxygen_func(result, func, " *") result[#result+1] = " *" result[#result+1] = " */" return table.concat(result, "\n") end local enum_temp = [[ struct $NAME { $COMMENT enum Enum { $ITEMS Count }; }; ]] function codegen.gen_enum_define(enum) assert(type(enum.enum) == "table", "Not an enum") local items = {} for _, item in ipairs(enum.enum) do local text if not item.comment then text = item.name .. "," else local comment = table.concat(item.comment, " ") text = string.format("%s,%s //!< %s", item.name, namealign(item.name), comment) end items[#items+1] = text end local comment = "" if enum.comment then comment = "/// " .. enum.comment end local temp = { NAME = enum.typename, COMMENT = comment, ITEMS = table.concat(items, "\n\t\t"), } return (enum_temp:gsub("$(%u+)", temp)) end local cenum_temp = [[ typedef enum $NAME { $ITEMS $COUNT } $NAME_t; ]] function codegen.gen_enum_cdefine(enum) assert(type(enum.enum) == "table", "Not an enum") local cname = enum.cname:match "(.-)_t$" local uname = cname:upper() local items = {} for index , item in ipairs(enum.enum) do local comment = "" if item.comment then comment = table.concat(item.comment, " ") end local ename = item.cname if not ename then if enum.underscore then ename = camelcase_to_underscorecase(item.name) else ename = item.name end ename = ename:upper() end local name = uname .. "_" .. ename items[#items+1] = string.format("%s,%s /** (%2d) %s%s */", name, namealign(name, 40), index - 1, comment, namealign(comment, 30)) end local temp = { NAME = cname, COUNT = uname .. "_COUNT", ITEMS = table.concat(items, "\n\t"), } return (cenum_temp:gsub("$(%u+)", temp)) end local function flag_format(flag) if not flag.format then flag.format = "%0" .. (flag.bits // 4) .. "x" end end function codegen.gen_flag_cdefine(flag) assert(type(flag.flag) == "table", "Not a flag") flag_format(flag) local cname = "BGFX_" .. (flag.cname or to_underscorecase(flag.name):upper()) local s = {} local shift = flag.shift for index, item in ipairs(flag.flag) do local name if item.cname then name = cname .. "_" .. item.cname else name = cname .. "_" .. to_underscorecase(item.name):upper() end local value = item.value -- combine flags if #item > 0 then if item.comment then for _, c in ipairs(item.comment) do s[#s+1] = "/// " .. c end end local sets = { "" } for _, v in ipairs(item) do sets[#sets+1] = cname .. "_" .. to_underscorecase(v):upper() end s[#s+1] = string.format("#define %s (0%s \\\n\t)\n", name, table.concat(sets, " \\\n\t| ")) else local comment = "" if item.comment then if #item.comment > 1 then s[#s+1] = "" for _, c in ipairs(item.comment) do s[#s+1] = "/// " .. c end else comment = " //!< " .. item.comment[1] end end value = string.format(flag.format, value) local code = string.format("#define %s %sUINT%d_C(0x%s)%s", name, namealign(name, DEFINE_NAME_ALIGN), flag.bits, value, comment) s[#s+1] = code end end local mask if flag.mask then mask = string.format(flag.format, flag.mask) mask = string.format("UINT%d_C(0x%s)", flag.bits, mask) end if shift then local name = cname .. "_SHIFT" local comment = flag.desc or "" local shift_align = tostring(shift) shift_align = shift_align .. namealign(shift_align, #mask) local comment = "" if flag.desc then comment = string.format(" //!< %s bit shift", flag.desc) end local code = string.format("#define %s %s%s%s", name, namealign(name, DEFINE_NAME_ALIGN), shift_align, comment) s[#s+1] = code end if flag.range then local name = cname .. "_MASK" local comment = "" if flag.desc then comment = string.format(" //!< %s bit mask", flag.desc) end local code = string.format("#define %s %s%s%s", name, namealign(name, DEFINE_NAME_ALIGN), mask, comment) s[#s+1] = code end if flag.helper then s[#s+1] = string.format( "#define %s(v) ( ( (uint%d_t)(v)<<%s )&%s)", cname, flag.bits, (cname .. "_SHIFT"), (cname .. "_MASK")) end s[#s+1] = "" return table.concat(s, "\n") end local function text_with_comments(items, item, cstyle, is_classmember) local name = item.name if item.array then if cstyle then name = name .. (item.carray or item.array) else name = name .. item.array end end local typename if cstyle then typename = item.ctype else typename = item.fulltype end if is_classmember then name = "m_" .. name end local text = string.format("%s%s %s;", typename, namealign(typename), name) if item.comment then if #item.comment > 1 then table.insert(items, "") if cstyle then table.insert(items, "/**") for _, c in ipairs(item.comment) do table.insert(items, " * " .. c) end table.insert(items, " */") else for _, c in ipairs(item.comment) do table.insert(items, "/// " .. c) end end else text = string.format( cstyle and "%s %s/** %s%s */" or "%s %s//!< %s", text, namealign(text, 40), item.comment[1], namealign(item.comment[1], 40)) end end items[#items+1] = text end local struct_temp = [[ struct $NAME { $METHODS $SUBSTRUCTS $ITEMS }; ]] function codegen.gen_struct_define(struct, methods) assert(type(struct.struct) == "table", "Not a struct") local items = {} for _, item in ipairs(struct.struct) do text_with_comments(items, item, false, methods ~= nil and not struct.shortname) end local ctor = {} if struct.ctor then ctor[1] = struct.name .. "();" ctor[2] = "" end if methods then for _, m in ipairs(methods) do if m:sub(-1) ~= "\n" then m = m .. "\n" end for line in m:gmatch "(.-)\n" do ctor[#ctor+1] = line end ctor[#ctor+1] = "" end end local subs = {} if struct.substruct then for _, v in ipairs(struct.substruct) do local s = codegen.gen_struct_define(v) s = s:gsub("\n", "\n\t") subs[#subs+1] = s end end local temp = { NAME = struct.name, SUBSTRUCTS = lines(subs), ITEMS = table.concat(items, "\n\t"), METHODS = lines(ctor), } return remove_emptylines(struct_temp:gsub("$(%u+)", temp)) end local cstruct_temp = [[ typedef struct $NAME_s { $ITEMS } $NAME_t; ]] local cstruct_empty_temp = [[ struct $NAME_s; typedef struct $NAME_s $NAME_t; ]] function codegen.gen_struct_cdefine(struct) assert(type(struct.struct) == "table", "Not a struct") local cname = struct.cname:match "(.-)_t$" local items = {} for _, item in ipairs(struct.struct) do text_with_comments(items, item, true) end local temp = { NAME = cname, ITEMS = table.concat(items, "\n\t"), } local codetemp = #struct.struct == 0 and cstruct_empty_temp or cstruct_temp return (codetemp:gsub("$(%u+)", temp)) end local chandle_temp = [[ typedef struct $NAME_s { uint16_t idx; } $NAME_t; ]] function codegen.gen_chandle(handle) assert(handle.handle, "Not a handle") return (chandle_temp:gsub("$(%u+)", { NAME = handle.cname:match "(.-)_t$" })) end local handle_temp = [[ struct $NAME { uint16_t idx; }; inline bool isValid($NAME _handle) { return bgfx::kInvalidHandle != _handle.idx; } ]] function codegen.gen_handle(handle) assert(handle.handle, "Not a handle") return (handle_temp:gsub("$(%u+)", { NAME = handle.name })) end local idl = require "idl" local doxygen = require "doxygen" local conversion local idlfile = {} function codegen.load(filename) assert(conversion == nil, "Don't call codegen.load() after codegen.idl()") assert(idlfile[filename] == nil, "Duplicate load " .. filename) local source = doxygen.load(filename) local f = assert(load(source, filename , "t", idl)) f() idlfile[filename] = true end function codegen.idl(filename) if conversion == nil then if filename and not idlfile[filename] then codegen.load(filename) end assert(next(idlfile), "call codegen.load() first") conversion = true codegen.nameconversion(idl.types, idl.funcs) end return idl end return codegen