-- -- Copyright (c) 2008-2014 the Urho3D project. -- -- Permission is hereby granted, free of charge, to any person obtaining a copy -- of this software and associated documentation files (the "Software"), to deal -- in the Software without restriction, including without limitation the rights -- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -- copies of the Software, and to permit persons to whom the Software is -- furnished to do so, subject to the following conditions: -- -- The above copyright notice and this permission notice shall be included in -- all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -- THE SOFTWARE. -- classes = {} enumerates = {} globalConstants = {} globalFunctions = {} globalProperties = {} currentClass = nil currentFunction = nil curDir = nil function classClass:print(ident,close) local class = {} class.name = self.name class.base = self.base class.lname = self.lname class.type = self.type class.btype = self.btype class.ctype = self.ctype currentClass = class local i = 1 while self[i] do self[i]:print(ident.." ",",") i = i + 1 end currentClass = nil table.insert(classes, class) end function classCode:print(ident,close) end function classDeclaration:print(ident,close) local declaration = {} declaration.mod = self.mod declaration.type = self.type declaration.ptr = self.ptr declaration.name = self.name declaration.dim = self.dim declaration.def = self.def declaration.ret = self.ret if currentFunction ~= nil then if currentFunction.declarations == nil then currentFunction.declarations = { declaration } else table.insert(currentFunction.declarations, declaration) end end end function classEnumerate:print(ident,close) local enumerate = {} enumerate.name = self.name local i = 1 while self[i] do if self[i] ~= "" then if enumerate.values == nil then enumerate.values = { self[i] } else table.insert(enumerate.values, self[i]) end end i = i + 1 end if enumerate.values ~= nil then table.insert(enumerates, enumerate) end end function deepCopy(t) if type(t) ~= "table" then return t end local mt = getmetatable(t) local ret = {} for k, v in pairs(t) do if type(v) == "table" then v = deepCopy(v) end ret[k] = v end setmetatable(ret, mt) return ret end function printFunction(self,ident,close,isfunc) local func = {} func.mod = self.mod func.type = self.type func.ptr = self.ptr func.name = self.name func.lname = self.lname func.const = self.const func.cname = self.cname func.lname = self.lname if isfunc then func.name = func.lname end currentFunction = func local i = 1 while self.args[i] do self.args[i]:print(ident.." ",",") i = i + 1 end currentFunction = nil if currentClass == nil then table.insert(globalFunctions, func) else if func.name == "new" then -- add construct function local ctor = deepCopy(func) ctor.type = "" ctor.ptr = "" ctor.name = currentClass.name ctor.lname = currentClass.name ctor.const = "(GC)" ctor.cname = currentClass.name ctor.lname = currentClass.name if currentClass.functions == nil then currentClass.functions = { ctor } else table.insert(currentClass.functions, ctor) end end if func.name == "delete" then func.type = "void" end if currentClass.functions == nil then currentClass.functions = { func } else table.insert(currentClass.functions, func) end end end function classFunction:print(ident,close) printFunction(self,ident,close, true) end function classOperator:print(ident,close) printFunction(self,ident,close, false) end function classVariable:print(ident,close) local property = {} property.mod = self.mod property.type = self.type property.ptr = self.ptr property.name = self.lname property.def = self.def property.ret = self.ret if currentClass == nil then if property.mod:find("tolua_property__") == nil then table.insert(globalConstants, property) else table.insert(globalProperties, property) end else if currentClass.properties == nil then currentClass.properties = { property } else table.insert(currentClass.properties, property) end end end function classVerbatim:print(ident,close) end function sortByName(t) table.sort(t, function(a, b) return a.name < b.name end) end function getCurrentDirectory() local separator = (package.config):gsub("\n.*","") local path = "" local tmpFile = os.tmpname() if separator == "\\" then -- Workaround broken os.tmpname() on Windows platform tmpFile = os.getenv('TMP') .. tmpFile os.execute("cd > " .. tmpFile) else os.execute("pwd > " .. tmpFile) end local tmpHandler = io.open(tmpFile, "r") path = tmpHandler:read("*a"):gsub("\n.*","") tmpHandler:close() os.remove(tmpFile) return {path = path, separator = separator} end function isTypeEquivalent(headerType, packageType) -- {["headerType"] = {"packageType1", "packageType2", ...}} --local equivalenceTable = {["StringHash"] = {"const String"}} if headerType == packageType then return true else if equivalenceTable ~= nil then for headerEqType, packageEqTypes in pairs(equivalenceTable) do if headerEqType == headerType then if packageEqTypes ~= nil then for i, packageEqType in ipairs(packageEqTypes) do if packageEqType == packageType then return true end end end return false end end end end return false end function isSameFilename(str1, str2, excludeExtensions) str1 = str1:gsub("^.*" .. curDir.separator.. "(.*)$", "%1", 1) str2 = str2:gsub("^.*" .. curDir.separator.. "(.*)$", "%1", 1) if excludeExtensions == true then str1 = str1:gsub("^(.*)%..*$", "%1", 1) str2 = str2:gsub("^(.*)%..*$", "%1", 1) end if str1 == str2 then return true else return false end end function isSameFunction(headerFunc, packageFunc, strict) if headerFunc.name == packageFunc.name then if strict == true then if headerFunc.declarations ~= nil and packageFunc.declarations ~= nil then --for _, decl in ipairs(headerFunc.declarations) do print("FuncHeader Param: \""..decl.type.."\", \""..decl.ptr.."\", \""..decl.name.."\", \""..decl.def.."\"") end --for _, decl in ipairs(packageFunc.declarations) do print("FuncPackage Param: \""..decl.type.."\", \""..decl.ptr.."\", \""..decl.name.."\", \""..decl.def.."\"") end for i, headerDecl in ipairs(headerFunc.declarations) do if packageFunc.declarations[i] ~= nil then if not isTypeEquivalent(headerDecl.type, packageFunc.declarations[i].type) then return false end else if headerDecl.def == "" then return false end end end return true else return true end else return true end end return false end function printDescriptionsFromPackageFile(filename, directory) for line in io.lines(filename) do line = line:gsub("%c", "") if line:find("^%s*%$pfile%s+\"(.+)\"") ~= nil then -- If it's another package file, load it (recursive) local nextPath = curDir.path .. curDir.separator nextPath = nextPath .. line:gsub("^%s*%$pfile%s+\"(.+)\"", "%1", 1):gsub("/", curDir.separator) local nextDirectory = line:gsub("^%s*%$pfile%s+\"(.+)\"", "%1", 1):gsub("/.*$", ""):gsub("/", curDir.separator) printDescriptionsFromPackageFile(nextPath, nextDirectory) elseif line:find("^%s*%$#include%s+\"(.+)\"") ~= nil then -- If it's an include, load it to fetch the descriptions local nextFilename = line:gsub("^%s*%$#include%s+\"(.+)\"", "%1", 1):gsub("/", curDir.separator) if isSameFilename(filename, nextFilename, true) then -- Must be same as Package Name printDescriptions(nextFilename, directory) end end end end function printDescriptions(filename, directory) -- Search Descriptions local className = nil local classScope = nil local description = nil local sourceEnginePath = curDir.path .. curDir.separator .. ".." .. curDir.separator .. ".." .. curDir.separator if directory ~= nil then sourceEnginePath = sourceEnginePath .. directory .. curDir.separator end for line in io.lines(sourceEnginePath .. filename) do line = line:gsub("%c", "") -- Entering Class if line:find("^%s*[Cc]lass%s+(.+)") ~= nil then local classDefine = line:gsub("^%s*[Cc]lass%s+([%w_][^:;]*)%s*:*.*", "%1") className = classDefine:gsub("[%w_]+%s+([%w_]+).*", "%1") -- Struct Defined (same as Class) elseif line:find("^%s*[Ss]truct%s+(.+)") ~= nil then local classDefine = line:gsub("^%s*[Ss]truct%s+([%w_][^:;]*)%s*:*.*", "%1") className = classDefine:gsub("[%w_]+%s+([%w_]+).*", "%1") elseif className ~= nil then -- Detecting Scope if line:find("^%s*(%w+)%s*:%s*$") ~= nil then classScope = line:gsub("^%s*(%w)%s*:%s*$", "%1") -- Leaving Class elseif line:find("^%s*}%s*$") ~= nil then className = nil classScope = nil description = nil -- Gather Informations elseif className ~= nil and classScope ~= nil then -- Line stating with "///" (Description) if line:find("^%s*///") ~= nil then description = line:gsub("^%s*///%s*", "") -- Not Empty Line (Function) elseif line:find("^%s*$") == nil and description ~= nil then printElementDescription(className, classScope, line, description) description = nil end end end end end function getElementFromLine(line) local element = {} element.name = nil element.params = nil element.type = nil -- Type Detect (Function) if line:find("^.*%s*([%w_~]+)%s*%(.*%).*$") ~= nil then element.name = line:gsub("^.*%s+([%w_~]+)%s*%(.*%).*$", "%1") element.type = "functions" if line:find("^.+%(.*%)") ~= nil then element.params = {} local params_str = line:gsub("^.+%((.*)%).*$", "%1", 1) --print("Current Params: "..params_str) if params_str ~= "" then for param_str in params_str:gmatch("[^,;]+") do local param = {} param.type = "" if param_str:find("^%s*(const%s+)") ~= nil then param.type = "const " param_str = param_str:gsub("^%s*((const%s+)", "") end param.type = param.type..param_str:gsub("^%s*([%w_]+).*$", "%1") param.ptr = param_str:gsub("^%s*[%w_]+([%*&]?).*$", "%1") param.name = param_str:gsub("^%s*[%w_]+[%*&]?%s+([%w_]+).*$", "%1") param.def = "" if param_str:find(".+=.+") then param.def = param_str:gsub("^.*=%s*(.*)$", "%1") end table.insert(element.params, param) end else local param = {} param.type = "void" param.ptr = "" param.name = "" param.def = "" table.insert(element.params, param) end end -- Type Detect (Property) elseif line:find("^.*%s+([%w_]+)%s*.*;%s*$") ~= nil then element.name = line:gsub("^.*%s+([%w_]+)%s*.*;%s*$", "%1") element.type = "properties" end return element end function printElementDescription(className, classScope, line, description) if description ~= "" then local currentElement = getElementFromLine(line) -- Search Class & Function/Property, Then Map if currentElement.name ~= nil and currentElement.type ~= nil then --print("Name: " .. currentElement.name) --print("ok (name = \"" .. currentElement.name .. "\", type = \"" .. currentElement.type .. "\", className = \"" .. className .. "\")") for i, class in ipairs(classes) do if class.name == className then --print("Class: "..class.name.." = "..className) if class[currentElement.type] ~= nil then for j, storedElement in ipairs(class[currentElement.type]) do local isSameName = false if storedElement.name == currentElement.name then isSameName = true -- Consider that the name is the same if it has an additionnal "_" at the end and if it's a property elseif storedElement.name .. "_" == currentElement.name and currentElement.type == "properties" then isSameName = true end if isSameName == true then --print("Element: " .. storedElement.name .. " = " .. currentElement.name) -- Confirm that the function is the same and not an overloading one local isSameElement = true if currentElement.type == "functions" then local candidateElement = {declarations = currentElement.params} candidateElement.name = currentElement.name isSameElement = isSameFunction(candidateElement, storedElement, true) --if isSameElement == true then print("Is same element? True") else print("Is same element? False") end end if isSameElement == true then --print("Element: " .. storedElement.name .. " = " .. currentElement.name) if storedElement.descriptions == nil then --print("[New description table]") classes[i][currentElement.type][j].descriptions = {} end --print("Description: "..description) --print("") table.insert(classes[i][currentElement.type][j].descriptions, description) return end end end end end end --print("") end end end function writeClass(file, class) file:write("\n") if class.base == "" then file:write("### " .. class.name .. "\n\n") else file:write("### " .. class.name .. " : " .. class.base .. "\n") end if class.functions ~= nil then file:write("\nMethods:\n\n") for i, func in ipairs(class.functions) do writeFunction(file, func) end end if class.properties ~= nil then file:write("\nProperties:\n\n") for i, property in ipairs(class.properties) do writeProperty(file, property) end end file:write("\n") end function writeTableOfContents(file) file:write("\n\\section LuaScriptAPI_TableOfContents Table of contents\n\n") file:write("\\ref LuaScriptAPI_ClassList \"Class list\"
\n") file:write("\\ref LuaScriptAPI_Classes \"Classes\"
\n") file:write("\\ref LuaScriptAPI_Enums \"Enumerations\"
\n") file:write("\\ref LuaScriptAPI_GlobalFunctions \"Global functions\"
\n") file:write("\\ref LuaScriptAPI_GlobalProperties \"Global properties\"
\n") file:write("\\ref LuaScriptAPI_GlobalConstants \"Global constants\"
\n") end function writeClassList(file) sortByName(classes) file:write("\n\\section LuaScriptAPI_ClassList Class list\n\n") for i, class in ipairs(classes) do file:write("" .. class.name .. "\n") end end function writeClasses(file) file:write("\n\\section LuaScriptAPI_Classes Classes\n\n") for i, class in ipairs(classes) do writeClass(file, class) end end function writeEnumerates(file) sortByName(enumerates) file:write("\\section LuaScriptAPI_Enums Enumerations\n\n") for i, enumerate in ipairs(enumerates) do file:write("### " .. enumerate.name .. "\n\n") for i, value in ipairs(enumerate.values) do file:write("- int " .. value .. "\n") end file:write("\n") end end function writeFunction(file, func) local line = "- " -- construct function if func.type == "" and func.ptr == "" then line = line .. func.name .. "(" else line = line .. func.type .. func.ptr .. " " .. func.name .. "(" end -- write parameters if func.declarations ~= nil then local count = table.maxn(func.declarations) for i = 1, count do local declaration = func.declarations[i] if declaration.type ~= "void" then line = line .. declaration.type .. declaration.ptr .. " " .. declaration.name -- add paramter default value if declaration.def ~= "" then line = line .. " = " .. declaration.def end end if i ~= count then line = line .. ", " end end end line = line .. ")" -- add const if func.const ~= "" then line = line .. " " .. func.const end file:write(line .. "\n") end function writeGlobalConstants(file) sortByName(globalConstants) file:write("\n\\section LuaScriptAPI_GlobalConstants Global constants\n") for i, constant in ipairs(globalConstants) do local line = "- " .. constant.type:gsub("const ", "") .. constant.ptr .. " " .. constant.name file:write(line .. "\n") end file:write("\n") end function writeGlobalFunctions(file) sortByName(globalFunctions) file:write("\n\\section LuaScriptAPI_GlobalFunctions Global functions\n") for i, func in ipairs(globalFunctions) do writeFunction(file, func) end file:write("\n") end function writeGlobalProperties(file) sortByName(globalProperties) file:write("\n\\section LuaScriptAPI_GlobalProperties Global properties\n") for i, property in ipairs(globalProperties) do writeProperty(file, property) end end function writeProperty(file, property) file:write("- " .. property.type .. property.ptr .. " " .. property.name) if property.mod:find("tolua_readonly") == nil then file:write("\n") else file:write(" (readonly)\n") end end function classPackage:print() curDir = getCurrentDirectory() if flags.o == nil then print("Invalid output filename"); return end local filename = flags.o local file = io.open(filename, "wt") file:write("namespace Urho3D\n") file:write("{\n") file:write("\n") file:write("/**\n") file:write("\\page LuaScriptAPI Lua scripting API\n") local i = 1 while self[i] do self[i]:print("","") i = i + 1 end printDescriptionsFromPackageFile(flags.f) writeTableOfContents(file) writeClassList(file) writeClasses(file) writeEnumerates(file) writeGlobalFunctions(file) writeGlobalProperties(file) writeGlobalConstants(file) file:write("*/\n") file:write("\n") file:write("}\n") file:close() end