From 1a30c2262b6bd7e63b80e96ad995723276bc53ea Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 22 May 2016 11:54:31 +0200 Subject: Added a Pure-Lua implementation for bindings generation. The BindingsProcessor.lua script can be opened in ZeroBraneStudio and debugged from there, it invokes the entire ToLua++ processing. Also added docs-generation to the ToLua++ processor. --- src/Bindings/BindingsProcessor.lua | 509 ++++++++++++++++++++++++++++++++++++- 1 file changed, 507 insertions(+), 2 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index e7b909ded..e71684cdc 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -3,6 +3,47 @@ -- Implements additional processing that is done while generating the Lua bindings +--[[ +The primary purpose of this file is to provide transformations for ToLua - it is loaded by ToLua++ +before processing the C++ code. + +This file can also be used as a standalone Lua program to actually generate the bindings, it invokes +ToLua++ if executed by a regular Lua interpreter + +The transformations implemented: + - Modify ToLua++ behavior so that it doesn't generate bindings for private and protected members + - Export additional files to be included in cLuaState: + - Forward declarations and typedefs for custom classes' pointers + - Pushing and popping of bindings' classes +--]] + + + + + +--- Invokes the ToLua++ parser +-- Called when this script detects it has been run outside of ToLua++'s processing +local function invokeToLua() + -- The values used by ToLua scripts, normally filled from the cmdline params: + flags = + { + L = "BindingsProcessor.lua", + o = "Bindings.cpp", + H = "Bindings.h", + f = "AllToLua.pkg", + -- P = true, -- Prints the structure to stdout, doesn't generate cpp file + } + _extra_parameters = {} + TOLUA_VERSION = "tolua++-1.0.92" + TOLUA_LUA_VERSION = "Lua 5.1" + + -- Path to the ToLua scripts + path = "../../lib/tolua++/src/bin/lua/" + + -- Run the ToLua processing: + dofile(path .. "all.lua") +end + @@ -163,6 +204,452 @@ end +local function FormatString(a_Str) + local fmt = string.format("%q", a_Str) + return (string.gsub(string.gsub(fmt, "\\\n", "\\n"), "\\\r\n", "\\r\\n")) +end + + + + + +local function OutputTable(a_File, a_Table, a_Name, a_Indent, a_Visited, a_Metas) + -- Check and update the "visited" status: + if (a_Visited[a_Table]) then + a_File:write(a_Indent .. "{ \"visited: " .. a_Visited[a_Table] .. "\", }") + return + end + a_Visited[a_Table] = a_Name + + -- Output the table contents: + a_File:write(a_Indent .. "{\n") + local indent = a_Indent .. "\t" + for k, v in pairs(a_Table) do + if (type(k) == "string") then + a_File:write(indent .. "[" .. FormatString(k) .. "] =") + else + a_File:write(indent .. "[" .. tostring(k) .. "] =") + end + local t = type(v) + if ( + (t == "number") or + (t == "boolean") + ) then + a_File:write(" ", tostring(v)) + elseif (t == "string") then + a_File:write(" ", FormatString(v)) + elseif (t == "table") then + local metatab = getmetatable(v) + if (metatab) then + a_File:write(" -- meta: " .. tostring(metatab)) + a_Metas[metatab] = metatab + end + a_File:write("\n") + OutputTable(a_File, v, a_Name .. "." .. tostring(k), indent, a_Visited, a_Metas) + else + print("Unhandled type: " .. t .. ": " .. tostring(v)) + a_File:write(" ", tostring(v)) + end + a_File:write(",\n") + end -- for k, v - a_Table + a_File:write(a_Indent .. "}") +end + + + + + +--- Outputs the docs for all the functions in the specified class +-- a_File is the output file +-- a_Class is the ToLua's classClass object +-- a_Functions is a dictionary of function descriptions: "name" -> { {}, ...} +local function outputClassFunctionDocs(a_File, a_Class, a_Functions) + -- Sort the functions by name: + local functions = {} + for name, descs in pairs(a_Functions) do + table.insert(functions, { Name = name, Descs = descs }) + end + table.sort(functions, + function (a_Fn1, a_Fn2) + return (a_Fn1.Name < a_Fn2.Name) + end + ) + + -- If there are no functions, bail out: + if not(functions[1]) then + return + end + + -- Output the descriptions: + a_File:write("\t\tFunctions =\n\t\t{\n") + for _, fn in ipairs(functions) do + a_File:write("\t\t\t", fn.Name, " =\n\t\t\t{\n") + for _, desc in ipairs(fn.Descs) do + a_File:write("\t\t\t\t{\n\t\t\t\t\tParams =\n\t\t\t\t\t{\n") + for _, param in ipairs(desc.Parameters) do + a_File:write("\t\t\t\t\t\t{\n") + a_File:write("\t\t\t\t\t\t\tType = \"", param.Type, "\",\n") + a_File:write("\t\t\t\t\t\t\tName = \"", param.Name, "\",\n") + a_File:write("\t\t\t\t\t\t},\n") + end + a_File:write("\t\t\t\t\t},\n\t\t\t\t\tReturns =\n\t\t\t\t\t{\n") + for _, ret in ipairs(desc.Returns) do + a_File:write("\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType = \"", ret.Type, "\",\n\t\t\t\t\t\t},\n") + end + a_File:write("\t\t\t\t\t}\n\t\t\t\t\tDesc = ", string.format("%q", desc.DoxyComment or ""), ",\n") + a_File:write("\t\t\t\t},\n") + end + a_File:write("\t\t\t},\n") + end + a_File:write("\t\t},\n") +end + + + + + +--- Outputs the docs for all the member variables in the specified class +-- a_File is the output file +-- a_Class is the ToLua's classClass object +-- a_Variables is a dictionary of variable descriptions: "name" -> {} +local function outputClassVariableDocs(a_File, a_Class, a_Variables) + -- Sort the variables by name: + local variables = {} + for name, desc in pairs(a_Variables) do + table.insert(variables, { Name = name, Desc = desc }) + end + table.sort(variables, + function (a_Var1, a_Var2) + return (a_Var1.Name < a_Var2.Name) + end + ) + + -- If there are no variables, bail out: + if not(variables[1]) then + return + end + + -- Output the descriptions: + a_File:write("\t\tVariables =\n\t\t{\n") + for _, v in ipairs(variables) do + a_File:write("\t\t\t", v.Name, " =\n\t\t\t{\n") + a_File:write("\t\t\t\tType = \"", v.Desc.Type, "\",\n") + a_File:write("\t\t\t\tDesc = [[", v.Desc.DoxyComment or "", "]],\n") + a_File:write("\t\t\t},\n") + end + a_File:write("\t\t},\n") +end + + + + + +--- Outputs the docs for all the member constants in the specified class +-- a_File is the output file +-- a_Class is the ToLua's classClass object +-- a_Constants is a dictionary of constant descriptions: "name" -> {} +-- a_IgnoredConstants is a dictionary of constants not to be exported: "name" -> true (used for ToLua++'s multi-inheritance) +local function outputClassConstantDocs(a_File, a_Class, a_Constants, a_IgnoredConstants) + -- Sort the constants by name: + local constants = {} + for name, desc in pairs(a_Constants) do + if not(a_IgnoredConstants[name]) then + table.insert(constants, { Name = name, Desc = desc }) + end + end + table.sort(constants, + function (a_Var1, a_Var2) + return (a_Var1.Name < a_Var2.Name) + end + ) + + -- If there are no constants, bail out: + if not(constants[1]) then + return + end + + -- Output the descriptions: + a_File:write("\t\tConstants =\n\t\t{\n") + for _, con in ipairs(constants) do + a_File:write("\t\t\t", con.Name, " =\n\t\t\t{\n") + a_File:write("\t\t\t\tType = \"", con.Desc.Type, "\",\n") + a_File:write("\t\t\t\tDesc = [[", con.Desc.DoxyComment or "", "]],\n") + a_File:write("\t\t\t},\n") + end + a_File:write("\t\t},\n") +end + + + + + +--- Outputs the docs for all the member enums in the specified class +-- a_File is the output file +-- a_Class is the ToLua's classClass object +-- a_Enums is an array of ToLua's classEnum objects +local function outputClassEnumDocs(a_File, a_Class, a_Enums) + -- If there are no enums, bail out: + if (not(a_Enums) or not(a_Enums[1])) then + return + end + + -- Sort the enums by name: + table.sort(a_Enums, + function (a_Enum1, a_Enum2) + return (a_Enum1.name < a_Enum2.name) + end + ) + + -- Output the enums: + a_File:write("\t\tEnums =\n\t\t{\n") + for i, enum in ipairs(a_Enums) do + local name = enum.name + if (not(name) or (name == "")) then + name = string.format("unnamedEnum_%d", i) + end + a_File:write("\t\t\t", name, " =\n\t\t\t{\n") + local valnames = {} + local idx = 1 + for _, valname in ipairs(enum.lnames) do + valnames[idx] = valname + idx = idx + 1 + end + table.sort(valnames) + for _, valname in ipairs(valnames) do + a_File:write("\t\t\t\t\"", valname, "\",\n") + end + a_File:write("\t\t\t},\n") + end + a_File:write("\t\t},\n") +end + + + + + +--- Outputs the docs for the specified class, which has been parsed for its functions, variables and constants +-- a_Class is the ToLua's classClass object +-- a_Functions is a dictionary of function descriptions: "name" -> { {}, ...} +-- a_Variables is a dictionary of variable descriptions: "name" -> {} +-- a_Constants is a dictionary of constant descriptions: "name" -> {} +-- a_Filenames is an array into which the name of the docs file is to be appended +local function outputClassDocs(a_Class, a_Functions, a_Variables, a_Constants, a_Filenames) + -- Add the output file to list of filenames: + local fnam = a_Class.lname .. ".lua" + table.insert(a_Filenames, fnam) + + -- Output the header: + local f = assert(io.open("docs/" .. fnam, "w")) + f:write("return\n{\n\t", a_Class.lname, " =\n\t{\n") + f:write("\t\tDesc = [[", a_Class.doxycomment or "", "]],\n") + + -- If the class inherits from anything, output it here: + local ignoredConstants = {} + if (a_Class.base and (a_Class.base ~= "")) then + local bases = {a_Class.base} + local idx = 2 + for _, b in ipairs(a_Class.extra_bases or {}) do + bases[idx] = b + idx = idx + 1 + -- ToLua++ handles multiple inheritance by adding "constants" for the base types; ignore those: + ignoredConstants["__" .. b .. "__"] = true + end + table.sort(bases) + f:write("\t\tInherits =\n\t\t{\n") + for _, b in ipairs(bases) do + f:write("\t\t\t", string.format("%q", b), ",\n") + end + f:write("\t\t},\n") + end + + -- Output the functions: + outputClassFunctionDocs(f, a_Class, a_Functions) + + -- Output the variables: + outputClassVariableDocs(f, a_Class, a_Variables) + + -- Output the constants: + outputClassConstantDocs(f, a_Class, a_Constants, ignoredConstants) + + -- Output the enums: + outputClassEnumDocs(f, a_Class, a_Class.enums) + + -- Output the footer: + f:write("\t}\n}\n") + f:close() +end + + + + + +local function genPackageDocs(a_Self) + -- Generate docs for each member: + local i = 1 + local filenames = {} + while (a_Self[i]) do + if (a_Self[i].genDocs) then + a_Self[i]:genDocs(filenames) + end + i = i + 1 + end + + -- Output the globals' docs: + local functions = {} + local variables = {} + local constants = {} + while (a_Self[i]) do + if (a_Self[i].genMemberDocs) then + a_Self[i]:genMemberDocs(functions, variables, constants) + end + i = i + 1 + end + local oldName = a_Self.lname + a_Self.lname = "Globals" + outputClassDocs(a_Self, functions, variables, constants, filenames) + a_Self.lname = oldName + + -- Output the list of docs files: + table.sort(filenames) + local f = assert(io.open("docs/_files.lua", "w")) + f:write("return\n{\n") + for _, fnam in ipairs(filenames) do + f:write("\t\"", fnam, "\",\n") + end + f:write("}\n") + f:close() +end + + + + + +local function genClassDocs(a_Self, a_Filenames) + assert(a_Self.lname) + assert(type(a_Filenames) == "table") + --[[ + print("\n\nGenerating docs for class " .. a_Self.lname) + local visited = {[a_Self.parent] = ""} + local metas = {} + OutputTable(io.stdout, a_Self, a_Self.lname, "", visited, metas) + --]] + + -- Collect the function, variable and constant docs: + local i = 1 + local functions = {} + local variables = {} + local constants = {} + while (a_Self[i]) do + if (a_Self[i].genMemberDocs) then + a_Self[i]:genMemberDocs(functions, variables, constants) + end + i = i + 1 + end + + -- Output the individual docs + outputClassDocs(a_Self, functions, variables, constants, a_Filenames) +end + + + + + +--- Parses the specified function's parameters and returns their description as a table +-- a_Function is the ToLua's classFunction object +local function parseFunctionParameters(a_Function) + -- If the only param is a "void", then report no params: + if ( + a_Function.args and -- The params are present + (#(a_Function.args) == 1) and -- There is exactly one param + (a_Function.args[1].type == "void") -- The param is a void + ) then + return {} + end + + local res = {} + local idx = 1 + for _, param in ipairs(a_Function.args or {}) do + local t = param.type + t = t:gsub("^const ", "") -- Remove the "const" keyword, if present + res[idx] = + { + Name = param.name, + Type = t, + IsConst = (param.type:match("^const ") ~= nil), + } + idx = idx + 1 + end + return res +end + + + + + +--- Parses the specified function's return values and returns their description as a table +-- a_Function is the ToLua's classFunction object +local function parseFunctionReturns(a_Function) + local res = {} + local idx = 1 + if (a_Function.type and (a_Function.type ~= "void")) then + res[idx] = { Type = a_Function.type } + idx = idx + 1 + end + for _, param in ipairs(a_Function.args or {}) do + if ((param.mod == "&") or (param.ret == "&")) then + res[idx] = { Type = param.type:gsub("^const ", "") } + idx = idx + 1 + end + end + return res +end + + + + + +local function genFunctionMemberDocs(a_Self, a_Functions, a_Variables, a_Constants) + assert(a_Self.lname) + + local fn = a_Functions[a_Self.lname] or {} + a_Functions[a_Self.lname] = fn + + local desc = + { + LuaName = a_Self.lname, + CType = a_Self.type, + DoxyComment = a_Self.DoxyComment, + Parameters = parseFunctionParameters(a_Self), + Returns = parseFunctionReturns(a_Self), + } + table.insert(fn, desc) +end + + + + + +local function genVariableMemberDocs(a_Self, a_Functions, a_Variables, a_Constants) + assert(a_Self.lname) + + local desc = + { + Name = a_Self.lname, + Type = a_Self.type, + DoxyComment = a_Self.DoxyComment, + } + + if (a_Self.csetname) then + a_Variables[a_Self.lname] = desc + else + a_Constants[a_Self.lname] = desc + end +end + + + + + function pre_output_hook(a_Package) OutputLuaStateHelpers(a_Package) end @@ -171,8 +658,26 @@ end -function post_output_hook() - print("Bindings have been generated.") +function post_output_hook(a_Package) + -- Generate the documentation: + classPackage.genDocs = genPackageDocs + classClass.genDocs = genClassDocs + classFunction.genMemberDocs = genFunctionMemberDocs + classVariable.genMemberDocs = genVariableMemberDocs + a_Package:genDocs() + + print("Lua bindings and docs have been generated.") +end + + + + + +if not(TOLUA_VERSION) then + -- BindingsProcessor has been called standalone, invoke the entire ToLua++ machinery: + print("Generating Lua bindings and docs...") + invokeToLua() + return end -- cgit v1.2.3 From c5714f6e4bcb07144de99d5bdf909a662e8beaca Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 22 May 2016 13:51:41 +0200 Subject: Bindings: Extract DoxyComments --- src/Bindings/BindingsProcessor.lua | 205 +++++++++++++++++++++++++++++++++---- 1 file changed, 187 insertions(+), 18 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index e71684cdc..7f8a4caf0 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -15,6 +15,17 @@ The transformations implemented: - Export additional files to be included in cLuaState: - Forward declarations and typedefs for custom classes' pointers - Pushing and popping of bindings' classes + +To parse DoxyComments, the preprocessor first replaces them with markers and then the parser uses +those markers to apply the DoxyComment to the next or previous item in the container, based on +the DoxyComment type. + +Placeholders in use (i = internal ToLua++): + - \1 and \2: (i) Embedded Lua code + - \3 and \4: (i) Embedded C code ("<>") + - \5 and \6: (i) Embedded C code ("{}") + - \17 and \18: DoxyComment for next item ("/** ... */") via an ID lookup + - \19 and \20: DocyComment for previous item ("///< ... \n") via an ID lookup --]] @@ -48,7 +59,12 @@ end -local access = {public = 0, protected = 1, private = 2} +local access = +{ + public = 0, + protected = 1, + private = 2 +} @@ -65,20 +81,71 @@ local g_HasCustomPushImplementation = -function parser_hook(s) - local container = classContainer.curr -- get the current container +--- Array-table of forward DoxyComments that are replaced in preprocess_hook() and substituted back in parser_hook() +-- We need to use a lookup table because the comments themselves may contain "//" which the preprocessor +-- would eliminate, thus breaking the code +-- The "n" member is a counter for faster insertion +local g_ForwardDoxyComments = +{ + n = 0 +} + + +--- Array-table of backward DoxyComments that are replaced in preprocess_hook() and substituted back in parser_hook() +-- We need to use a lookup table because the comments themselves may contain "//" which the preprocessor +-- would eliminate, thus breaking the code +-- The "n" member is a counter for faster insertion +local g_BackwardDoxyComments = +{ + n = 0, +} - -- process access-specifying labels (public, private, etc) + + + + +--- Provides extra parsing in addition to ToLua++'s own +-- Called by ToLua++ each time it extracts the next piece of code to parse +-- a_Code is the string representing the code to be parsed +-- The return value is the remaining code to be parsed in the next iteration +-- Processes the class access specifiers (public, protected, private), and doxycomments +function parser_hook(a_Code) + -- Process access-specifying labels (public, private, etc) do - local b, e, label = string.find(s, "^%s*(%w*)%s*:[^:]") -- we need to check for [^:], otherwise it would match 'namespace::type' + local b, e, label = string.find(a_Code, "^%s*(%w*)%s*:[^:]") -- we need to check for [^:], otherwise it would match 'namespace::type' if b then - - -- found a label, get the new access value from the global 'access' table + -- Found a label, get the new access value for it: if access[label] then - container.curr_member_access = access[label] + classContainer.curr.curr_member_access = access[label] end -- else ? - - return strsub(s, e) -- normally we would use 'e+1', but we need to preserve the [^:] + return strsub(a_Code, e) -- normally we would use 'e + 1', but we need to preserve the [^:] + end + end + + -- Process forward DoxyComments: + do + local b, e, comment = a_Code:find("^%s*(%b\17\18)") + if (b) then + local curr = classContainer.curr + if (curr.n and (curr.n > 0)) then + curr[curr.n].next_DoxyComment = g_ForwardDoxyComments[tonumber(comment:sub(2, -2))] + end + return strsub(a_Code, e + 1) + end + end + + -- Process backward DoxyComments: + do + local b, e, comment = a_Code:find("^%s*(%b\19\20)") + if (b) then + comment = g_BackwardDoxyComments[tonumber(comment:sub(2, -2))] + local currContainer = classContainer.curr + if (currContainer.n > 0) then + currContainer[currContainer.n].DoxyComment = comment + else + print("Backward DoxyComment lost in " .. (currContainer.name or currContainer.lname or currContainer.cname or "")) + end + return strsub(a_Code, e + 1) end end end @@ -334,7 +401,7 @@ local function outputClassVariableDocs(a_File, a_Class, a_Variables) for _, v in ipairs(variables) do a_File:write("\t\t\t", v.Name, " =\n\t\t\t{\n") a_File:write("\t\t\t\tType = \"", v.Desc.Type, "\",\n") - a_File:write("\t\t\t\tDesc = [[", v.Desc.DoxyComment or "", "]],\n") + a_File:write("\t\t\t\tDesc = ", string.format("%q", v.Desc.DoxyComment or ""), ",\n") a_File:write("\t\t\t},\n") end a_File:write("\t\t},\n") @@ -373,7 +440,7 @@ local function outputClassConstantDocs(a_File, a_Class, a_Constants, a_IgnoredCo for _, con in ipairs(constants) do a_File:write("\t\t\t", con.Name, " =\n\t\t\t{\n") a_File:write("\t\t\t\tType = \"", con.Desc.Type, "\",\n") - a_File:write("\t\t\t\tDesc = [[", con.Desc.DoxyComment or "", "]],\n") + a_File:write("\t\t\t\tDesc = ", string.format("%q", con.Desc.DoxyComment or ""), ",\n") a_File:write("\t\t\t},\n") end a_File:write("\t\t},\n") @@ -441,7 +508,7 @@ local function outputClassDocs(a_Class, a_Functions, a_Variables, a_Constants, a -- Output the header: local f = assert(io.open("docs/" .. fnam, "w")) f:write("return\n{\n\t", a_Class.lname, " =\n\t{\n") - f:write("\t\tDesc = [[", a_Class.doxycomment or "", "]],\n") + f:write("\t\tDesc = ", string.format("%q", a_Class.DoxyComment or ""), ",\n") -- If the class inherits from anything, output it here: local ignoredConstants = {} @@ -483,7 +550,39 @@ end +--- Recursively applies the next_DoxyComment member to the next item in the container +-- a_Container is the ToLua++'s table potentially containing children as its array members +local function applyNextDoxyComments(a_Container) + local i = 1 + while (a_Container[i]) do + if (a_Container[i].next_DoxyComment) then + if (a_Container[i + 1]) then + print("Applying forward DoxyComment for " .. (a_Container[i].name or a_Container[i].cname or a_Container[i].lname or "")) + a_Container[i + 1].DoxyComment = a_Container[i].next_DoxyComment + end + end + applyNextDoxyComments(a_Container[i]) + i = i + 1 + end +end + + + + + +--- Generates documentation for a package. local function genPackageDocs(a_Self) + -- DEBUG: Output the raw package object: + do + local f = io.open("docs/_raw.lua", "w") + if (f) then + OutputTable(f, a_Self, "", "", {}, {}) + f:close() + end + end + + applyNextDoxyComments(a_Self) + -- Generate docs for each member: local i = 1 local filenames = {} @@ -650,6 +749,34 @@ end +--- Generates the entire documentation for the API +-- a_Package is ToLua++'s classPackage object +-- Checks if the documentation folder is writable, if not, skips the docs generation +-- Returns true if documentation was generated, false and message if not +local function generateDocs(a_Package) + -- Check if the docs folder is writable: + local f, msg = io.open("docs/_files.lua", "w") + if not(f) then + return false, "Cannot access the docs folder: " .. msg + end + f:close() + + -- Generate the docs: + classPackage.genDocs = genPackageDocs + classClass.genDocs = genClassDocs + classFunction.genMemberDocs = genFunctionMemberDocs + classVariable.genMemberDocs = genVariableMemberDocs + a_Package:genDocs() + return true +end + + + + + +--- Outputs the cLuaState helper files. +-- Called by ToLua++ before it starts outputting its generated files. +-- a_Package is ToLua++'s classPackage object function pre_output_hook(a_Package) OutputLuaStateHelpers(a_Package) end @@ -658,13 +785,17 @@ end +--- Outputs the documentation files. +-- Called by ToLua++ after writing its generated files. +-- a_Package is ToLua++'s classPackage object function post_output_hook(a_Package) -- Generate the documentation: - classPackage.genDocs = genPackageDocs - classClass.genDocs = genClassDocs - classFunction.genMemberDocs = genFunctionMemberDocs - classVariable.genMemberDocs = genVariableMemberDocs - a_Package:genDocs() + local isSuccess, msg = generateDocs(a_Package) + if not(isSuccess) then + print("Lua bindings have been generated.") + print("API docs haven't been generated due to an error: " .. (msg or "")) + return + end print("Lua bindings and docs have been generated.") end @@ -673,6 +804,44 @@ end +--- Provides DoxyComment processing while parsing the C++ code. +-- Called by ToLua++ parser before it starts parsing the code. +-- a_Package is the ToLua++'s classPackage object, currently unparsed +-- The C++ code to be parsed is in a_Packade.code +function preprocess_hook(a_Package) + assert(a_Package) + assert(type(a_Package.code) == "string") + + -- Replace all DoxyComments with placeholders so that they aren't erased later on: + local f = assert(io.open("code_in.cpp", "wb")) + f:write(a_Package.code) + f:close() + a_Package.code = a_Package.code + :gsub("/%*%*%s*(.-)%s*%*/%s*", + function (a_Comment) + local n = g_ForwardDoxyComments.n + 1 + g_ForwardDoxyComments[n] = a_Comment + g_ForwardDoxyComments.n = n + return "\17" .. n .."\18" + end + ) -- Replace /** ... */ with an ID into a lookup table wrapped in DC1 and DC2 + :gsub("///<%s*(.-)%s*\n%s*", + function (a_Comment) + local n = g_BackwardDoxyComments.n + 1 + g_BackwardDoxyComments[n] = a_Comment + g_BackwardDoxyComments.n = n + return "\19" .. n .."\20" + end + ) + f = assert(io.open("code_out.cpp", "wb")) + f:write(a_Package.code) + f:close() +end + + + + + if not(TOLUA_VERSION) then -- BindingsProcessor has been called standalone, invoke the entire ToLua++ machinery: print("Generating Lua bindings and docs...") -- cgit v1.2.3 From 751d0d0736656888fab9dbb088c3c755880c9a06 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 22 May 2016 17:00:07 +0200 Subject: Bindings: Extract unexported DoxyComments. --- src/Bindings/BindingsProcessor.lua | 149 +++++++++++++++++++++++++++++++++---- 1 file changed, 134 insertions(+), 15 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index 7f8a4caf0..3ea7dff4f 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -363,7 +363,10 @@ local function outputClassFunctionDocs(a_File, a_Class, a_Functions) for _, ret in ipairs(desc.Returns) do a_File:write("\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType = \"", ret.Type, "\",\n\t\t\t\t\t\t},\n") end - a_File:write("\t\t\t\t\t}\n\t\t\t\t\tDesc = ", string.format("%q", desc.DoxyComment or ""), ",\n") + a_File:write("\t\t\t\t\t}\n") + if (desc.DoxyComment) then + a_File:write("\t\t\t\t\tDesc = ", string.format("%q", desc.DoxyComment), ",\n") + end a_File:write("\t\t\t\t},\n") end a_File:write("\t\t\t},\n") @@ -401,7 +404,9 @@ local function outputClassVariableDocs(a_File, a_Class, a_Variables) for _, v in ipairs(variables) do a_File:write("\t\t\t", v.Name, " =\n\t\t\t{\n") a_File:write("\t\t\t\tType = \"", v.Desc.Type, "\",\n") - a_File:write("\t\t\t\tDesc = ", string.format("%q", v.Desc.DoxyComment or ""), ",\n") + if (v.Desc.DoxyComment) then + a_File:write("\t\t\t\tDesc = ", string.format("%q", v.Desc.DoxyComment), ",\n") + end a_File:write("\t\t\t},\n") end a_File:write("\t\t},\n") @@ -440,7 +445,9 @@ local function outputClassConstantDocs(a_File, a_Class, a_Constants, a_IgnoredCo for _, con in ipairs(constants) do a_File:write("\t\t\t", con.Name, " =\n\t\t\t{\n") a_File:write("\t\t\t\tType = \"", con.Desc.Type, "\",\n") - a_File:write("\t\t\t\tDesc = ", string.format("%q", con.Desc.DoxyComment or ""), ",\n") + if (con.Desc.DoxyComment) then + a_File:write("\t\t\t\tDesc = ", string.format("%q", con.Desc.DoxyComment), ",\n") + end a_File:write("\t\t\t},\n") end a_File:write("\t\t},\n") @@ -476,14 +483,28 @@ local function outputClassEnumDocs(a_File, a_Class, a_Enums) end a_File:write("\t\t\t", name, " =\n\t\t\t{\n") local valnames = {} + -- Make a copy of enum.lnames so that we can sort it: local idx = 1 - for _, valname in ipairs(enum.lnames) do - valnames[idx] = valname + for i, valname in ipairs(enum.lnames) do + valnames[idx] = { Name = valname, DoxyComment = enum.DoxyComments[i] } idx = idx + 1 end - table.sort(valnames) + table.sort(valnames, + function (a_Val1, a_Val2) + return (a_Val1.Name < a_Val2.Name) + end + ) for _, valname in ipairs(valnames) do - a_File:write("\t\t\t\t\"", valname, "\",\n") + assert(not(valname.Name:find("\17"))) + assert(not(valname.Name:find("\18"))) + assert(not(valname.Name:find("\19"))) + assert(not(valname.Name:find("\20"))) + a_File:write("\t\t\t\t{\n") + a_File:write("\t\t\t\t\tName = \"", valname.Name, "\",\n") + if (valname.DoxyComment) then + a_File:write("\t\t\t\t\tDesc = ", string.format("%q", valname.DoxyComment), ",\n") + end + a_File:write("\t\t\t\t},\n") end a_File:write("\t\t\t},\n") end @@ -508,7 +529,9 @@ local function outputClassDocs(a_Class, a_Functions, a_Variables, a_Constants, a -- Output the header: local f = assert(io.open("docs/" .. fnam, "w")) f:write("return\n{\n\t", a_Class.lname, " =\n\t{\n") - f:write("\t\tDesc = ", string.format("%q", a_Class.DoxyComment or ""), ",\n") + if (a_Class.DoxyComment) then + f:write("\t\tDesc = ", string.format("%q", a_Class.DoxyComment), ",\n") + end -- If the class inherits from anything, output it here: local ignoredConstants = {} @@ -557,7 +580,6 @@ local function applyNextDoxyComments(a_Container) while (a_Container[i]) do if (a_Container[i].next_DoxyComment) then if (a_Container[i + 1]) then - print("Applying forward DoxyComment for " .. (a_Container[i].name or a_Container[i].cname or a_Container[i].lname or "")) a_Container[i + 1].DoxyComment = a_Container[i].next_DoxyComment end end @@ -813,11 +835,8 @@ function preprocess_hook(a_Package) assert(type(a_Package.code) == "string") -- Replace all DoxyComments with placeholders so that they aren't erased later on: - local f = assert(io.open("code_in.cpp", "wb")) - f:write(a_Package.code) - f:close() a_Package.code = a_Package.code - :gsub("/%*%*%s*(.-)%s*%*/%s*", + :gsub("/%*%*%s*(.-)%s*%*/", function (a_Comment) local n = g_ForwardDoxyComments.n + 1 g_ForwardDoxyComments[n] = a_Comment @@ -830,10 +849,10 @@ function preprocess_hook(a_Package) local n = g_BackwardDoxyComments.n + 1 g_BackwardDoxyComments[n] = a_Comment g_BackwardDoxyComments.n = n - return "\19" .. n .."\20" + return "\19" .. n .."\20\n" end ) - f = assert(io.open("code_out.cpp", "wb")) + local f = io.open("code_out.cpp", "wb") f:write(a_Package.code) f:close() end @@ -842,11 +861,111 @@ end +--- Chooses the smaller of the indices, and the number indicating whether it chose the first or the second +-- If one of the indices is nil, returns the other one +-- If both indices are nil, returns nil +local function chooseNextIndex(a_Index1, a_Index2) + if not(a_Index1) then + return a_Index2, 2 + end + if not(a_Index2) then + return a_Index1, 1 + end + if (a_Index1 > a_Index2) then + return a_Index2, 2 + else + return a_Index1, 1 + end +end + + + + + +--- Override for ToLua++'s own code extraction +-- Called for each "$cfile" and "$hfile" directive in the package file +-- a_FileName is the C++ header filename +-- a_Contents is the code contents of the header file +-- The function returns the code to be parsed by ToLua++ +-- In addition to the original function, this override extracts all DoxyComments as well +-- This is needed when a function is marked with "// tolua_export" but its DoxyComment is not included +function extract_code(a_FileName, a_Contents) + local code = '\n$#include "' .. a_FileName .. '"\n' + a_Contents= "\n" .. a_Contents .. "\n" -- add blank lines as sentinels + local _, e, c, t = strfind(a_Contents, "\n([^\n]-)[Tt][Oo][Ll][Uu][Aa]_([^%s]*)[^\n]*\n") + local dcb, dce, dc = strfind(a_Contents, "/%*%*.-%*/") + local nextEnd, whichOne = chooseNextIndex(e, dce) + while (nextEnd) do + if (whichOne == 2) then + code = code .. a_Contents:sub(dcb, dce) .. "\n" + else + t = strlower(t) + if (t == "begin") then + _, nextEnd, c = strfind(a_Contents,"(.-)\n[^\n]*[Tt][Oo][Ll][Uu][Aa]_[Ee][Nn][Dd][^\n]*\n", e) + if not(nextEnd) then + tolua_error("Unbalanced 'tolua_begin' directive in header file " .. a_FileName) + end + end + code = code .. c .. "\n" + end + _, e, c, t = strfind(a_Contents, "\n([^\n]-)[Tt][Oo][Ll][Uu][Aa]_([^%s]*)[^\n]*\n", nextEnd) + dcb, dce, dc = strfind(a_Contents, "/%*%*.-%*/", nextEnd) + nextEnd, whichOne = chooseNextIndex(e, dce) + end + return code +end + + + + + +--- Installs a hook that is called by ToLua++ for each instantiation of classEnumerate +-- The hook is used to fix DoxyComments in enums +local function installEnumHook() + local oldEnumerate = Enumerate + Enumerate = function (a_Name, a_Body, a_VarName) + -- We need to remove the DoxyComment items from the enum + -- otherwise ToLua++ parser would make an enum value out of them + a_Body = string.gsub(a_Body, ",[%s\n]*}", "\n}") -- eliminate last ',' + local t = split(strsub(a_Body, 2, -2), ',') -- eliminate braces + local doxyComments = {} + local enumValues = {} + local numEnumValues = 0 + for _, txt in ipairs(t) do + txt = txt:gsub("(%b\17\18)", + function (a_CommentID) + doxyComments[numEnumValues + 1] = g_ForwardDoxyComments[tonumber(a_CommentID:sub(2, -2))] + return "" + end + ):gsub("(%b\19\20)", + function (a_CommentID) + doxyComments[numEnumValues] = g_BackwardDoxyComments[tonumber(a_CommentID:sub(2, -2))] + return "" + end + ) + if (txt ~= "") then + numEnumValues = numEnumValues + 1 + enumValues[numEnumValues] = txt + end + end + local res = oldEnumerate(a_Name, "{" .. table.concat(enumValues, ",") .. "}", a_VarName) + res.DoxyComments = doxyComments + return res + end +end + + + + + if not(TOLUA_VERSION) then -- BindingsProcessor has been called standalone, invoke the entire ToLua++ machinery: print("Generating Lua bindings and docs...") invokeToLua() return +else + -- We're being executed from inside the ToLua++ parser. Install the needed hooks: + installEnumHook() end -- cgit v1.2.3 From 61f76dd7a5c5efc7fad18acabb5fc67bb490226c Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 29 May 2016 10:25:46 +0200 Subject: Bindings: Output description is valid Lua file. --- src/Bindings/BindingsProcessor.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index 3ea7dff4f..417a8f48c 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -350,7 +350,11 @@ local function outputClassFunctionDocs(a_File, a_Class, a_Functions) -- Output the descriptions: a_File:write("\t\tFunctions =\n\t\t{\n") for _, fn in ipairs(functions) do - a_File:write("\t\t\t", fn.Name, " =\n\t\t\t{\n") + local name = fn.Name + if (name:sub(1, 1) == ".") then + name = "[\"" .. name .. "\"]" + end + a_File:write("\t\t\t", name, " =\n\t\t\t{\n") for _, desc in ipairs(fn.Descs) do a_File:write("\t\t\t\t{\n\t\t\t\t\tParams =\n\t\t\t\t\t{\n") for _, param in ipairs(desc.Parameters) do @@ -363,7 +367,7 @@ local function outputClassFunctionDocs(a_File, a_Class, a_Functions) for _, ret in ipairs(desc.Returns) do a_File:write("\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType = \"", ret.Type, "\",\n\t\t\t\t\t\t},\n") end - a_File:write("\t\t\t\t\t}\n") + a_File:write("\t\t\t\t\t},\n") if (desc.DoxyComment) then a_File:write("\t\t\t\t\tDesc = ", string.format("%q", desc.DoxyComment), ",\n") end @@ -565,7 +569,7 @@ local function outputClassDocs(a_Class, a_Functions, a_Variables, a_Constants, a outputClassEnumDocs(f, a_Class, a_Class.enums) -- Output the footer: - f:write("\t}\n}\n") + f:write("\t},\n}\n") f:close() end -- cgit v1.2.3 From 984c0a2cef204c595703009416a0d8cd87147484 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 5 Jun 2016 21:32:24 +0200 Subject: Bindings: Don't generate docs for private symbols, mark static symbols. --- src/Bindings/BindingsProcessor.lua | 48 +++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index 417a8f48c..f48bc455f 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -129,6 +129,8 @@ function parser_hook(a_Code) local curr = classContainer.curr if (curr.n and (curr.n > 0)) then curr[curr.n].next_DoxyComment = g_ForwardDoxyComments[tonumber(comment:sub(2, -2))] + else + curr.first_child_DoxyComment = g_ForwardDoxyComments[tonumber(comment:sub(2, -2))] end return strsub(a_Code, e + 1) end @@ -368,6 +370,9 @@ local function outputClassFunctionDocs(a_File, a_Class, a_Functions) a_File:write("\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType = \"", ret.Type, "\",\n\t\t\t\t\t\t},\n") end a_File:write("\t\t\t\t\t},\n") + if (desc.IsStatic) then + a_File:write("\t\t\t\t\tIsStatic = true,\n") + end if (desc.DoxyComment) then a_File:write("\t\t\t\t\tDesc = ", string.format("%q", desc.DoxyComment), ",\n") end @@ -577,9 +582,15 @@ end ---- Recursively applies the next_DoxyComment member to the next item in the container +--- Recursively applies the next_DoxyComment member to the next item, and first_child_DoxyComment to first child item in the container. -- a_Container is the ToLua++'s table potentially containing children as its array members local function applyNextDoxyComments(a_Container) + -- Apply the DoxyComment to the first child, if appropriate: + if (a_Container[1] and a_Container.first_child_DoxyComment) then + a_Container[1].DoxyComment = a_Container.first_child_DoxyComment + end + + -- Apply each child's next_DoxyComment to the actual next child: local i = 1 while (a_Container[i]) do if (a_Container[i].next_DoxyComment) then @@ -613,7 +624,10 @@ local function genPackageDocs(a_Self) local i = 1 local filenames = {} while (a_Self[i]) do - if (a_Self[i].genDocs) then + if ( + a_Self[i]:check_public_access() and -- Do not export private and protected members + a_Self[i].genDocs + ) then a_Self[i]:genDocs(filenames) end i = i + 1 @@ -665,7 +679,10 @@ local function genClassDocs(a_Self, a_Filenames) local variables = {} local constants = {} while (a_Self[i]) do - if (a_Self[i].genMemberDocs) then + if ( + a_Self[i]:check_public_access() and -- Don't export private and protected members + a_Self[i].genMemberDocs + ) then a_Self[i]:genMemberDocs(functions, variables, constants) end i = i + 1 @@ -747,6 +764,10 @@ local function genFunctionMemberDocs(a_Self, a_Functions, a_Variables, a_Constan Parameters = parseFunctionParameters(a_Self), Returns = parseFunctionReturns(a_Self), } + local _, _, hasStatic = string.find(a_Self.mod, "^%s*(static)") + if (hasStatic) then + desc.IsStatic = true + end table.insert(fn, desc) end @@ -805,6 +826,15 @@ end -- a_Package is ToLua++'s classPackage object function pre_output_hook(a_Package) OutputLuaStateHelpers(a_Package) + + -- Generate the documentation: + -- (must generate documentation before ToLua++ writes the bindings, because "static" information is lost at that point) + local isSuccess, msg = generateDocs(a_Package) + if not(isSuccess) then + print("API docs haven't been generated due to an error: " .. (msg or "")) + else + print("API docs have been generated."); + end end @@ -815,15 +845,7 @@ end -- Called by ToLua++ after writing its generated files. -- a_Package is ToLua++'s classPackage object function post_output_hook(a_Package) - -- Generate the documentation: - local isSuccess, msg = generateDocs(a_Package) - if not(isSuccess) then - print("Lua bindings have been generated.") - print("API docs haven't been generated due to an error: " .. (msg or "")) - return - end - - print("Lua bindings and docs have been generated.") + print("Lua bindings have been generated.") end @@ -855,7 +877,7 @@ function preprocess_hook(a_Package) g_BackwardDoxyComments.n = n return "\19" .. n .."\20\n" end - ) + ) -- Replace ///< comments with an ID into a lookup table wrapped in DC3 and DC4 local f = io.open("code_out.cpp", "wb") f:write(a_Package.code) f:close() -- cgit v1.2.3 From b11605e951fe43a55d89099d5b6cbec2d51d90a5 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 5 Jun 2016 22:04:56 +0200 Subject: Bindings: Added a script to generate a diff between APIDesc and ToLua. This allows a simple copy operation from the DoxyComments into APIDesc. --- src/Bindings/BindingsProcessor.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/Bindings/BindingsProcessor.lua') diff --git a/src/Bindings/BindingsProcessor.lua b/src/Bindings/BindingsProcessor.lua index f48bc455f..28a6a053c 100644 --- a/src/Bindings/BindingsProcessor.lua +++ b/src/Bindings/BindingsProcessor.lua @@ -785,10 +785,10 @@ local function genVariableMemberDocs(a_Self, a_Functions, a_Variables, a_Constan DoxyComment = a_Self.DoxyComment, } - if (a_Self.csetname) then - a_Variables[a_Self.lname] = desc - else + if (string.find(a_Self.type,'const%s+') or string.find(a_Self.mod, 'tolua_readonly') or string.find(a_Self.mod, 'tolua_inherits')) then a_Constants[a_Self.lname] = desc + else + a_Variables[a_Self.lname] = desc end end -- cgit v1.2.3