diff options
Diffstat (limited to '')
-rw-r--r-- | src/Bindings/DiffAPIDesc.lua | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/src/Bindings/DiffAPIDesc.lua b/src/Bindings/DiffAPIDesc.lua new file mode 100644 index 000000000..b9314c9cd --- /dev/null +++ b/src/Bindings/DiffAPIDesc.lua @@ -0,0 +1,550 @@ +-- DiffAPIDesc.lua + +-- Creates a diff file containing documentation that is available from ToLua++'s doxycomment parsing, but not yet included in APIDesc.lua + +require("lfs") + + + + + +--- Translation for function names whose representation in APIDesc is different from the one in Docs +-- Dictionary of "DocsName" -> "DescName" +local g_FunctionNameDocsToDesc = +{ + ["new"] = "constructor", + ["delete"] = "destructor", + [".add"] = "operator_plus", + [".div"] = "operator_div", + [".eq"] = "operator_eq", + [".mul"] = "operator_mul", + [".sub"] = "operator_sub", +} + + + + + +--- Translation from C types to Lua types +-- Dictionary of "CType" -> "LuaType" +local g_CTypeToLuaType = +{ + AString = "string", + bool = "boolean", + Byte = "number", + char = "number", + double = "number", + float = "number", + ForEachChunkProvider = "cWorld", + int = "number", + size_t = "number", + unsigned = "number", + ["const AString"] = "string", + ["const char*"] = "string", + ["Vector3<int>"] = "Vector3i", + ["Vector3<float>"] = "Vector3f", + ["Vector3<double>"] = "Vector3d", +} + + + + + +--- Functions that should be ignored +-- Dictionary of "FunctionName" -> true for each ignored function +local g_IgnoreFunction = +{ + destructor = true, +} + + + + + +local function caseInsensitiveCompare(a_Text1, a_Text2) + return (a_Text1:lower() < a_Text2:lower()) +end + + + + + +--- Loads the APIDesc.lua and its child files, returns the complete description +-- Returns a table with Classes and Hooks members, Classes being a dictionary of "ClassName" -> { desc } +local function loadAPIDesc() + -- Load the main APIDesc.lua file: + local apiDescPath = "../../Server/Plugins/APIDump/" + local desc = dofile(apiDescPath .. "APIDesc.lua") + if not(desc) then + error("Failed to load APIDesc") + end + + -- Merge in the information from all files in the Classes subfolder: + local classesPath = apiDescPath .. "Classes/" + for fnam in lfs.dir(apiDescPath .. "Classes") do + if (string.find(fnam, ".*%.lua$")) then + local tbls = dofile(classesPath .. fnam) + for k, cls in pairs(tbls) do + desc.Classes[k] = cls; + end + end + end + return desc +end + + + + + +--- Loads the API documentation generated by ToLua++'s parser +-- Returns a dictionary of "ClassName" -> { docs } +local function loadAPIDocs() + -- Get the filelist: + local files = dofile("docs/_files.lua") + if not(files) then + error("Failed to load _files.lua from docs") + end + + -- Load the docs from all files, merge into a single dictionary: + local res = {} + for _, fnam in ipairs(files) do + local docs = dofile("docs/" .. fnam) + if (docs) then + for k, v in pairs(docs) do + assert(not(res[k])) -- Do we have a duplicate documentation entry? + res[k] = v + end + end + end + return res +end + + + + + +--- Returns whether the function signature in the description matches the function documentation +-- a_FunctionDesc is a single description for a function, as loaded from APIDesc.lua (one <FnDesc> item) +-- a_FunctionDoc is a single documentation item for a function, as loaded from ToLua++'s parser +local function functionDescMatchesDocs(a_FunctionDesc, a_FunctionDoc) + -- Check the number of parameters: + local numParams + if (not(a_FunctionDesc.Params) or (a_FunctionDesc.Params == "")) then + numParams = 0 + else + _, numParams = string.gsub(a_FunctionDesc.Params, ",", "") + numParams = numParams + 1 + end + if (#(a_FunctionDoc.Params) ~= numParams) then + return false + end + + return true +end + + + + + +--- Returns an array of function descriptions that are in a_FunctionDocs but are missing from a_FunctionDescs +-- a_FunctionDescs is an array of function descriptions, as loaded from APIDesc.lua (normalized into array) +-- a_FunctionDocs is an array of function documentation items, as loaded from ToLua++'s parser +-- If all descriptions match, nil is returned instead +local function listMissingClassSingleFunctionDescs(a_FunctionDescs, a_FunctionDocs) + -- Generate a helper map of index -> true that monitors a_FunctionDescs' items' usage: + local freeDescs = {} + for i = 1, #a_FunctionDescs do + freeDescs[i] = true + end + + -- For each documentation item, try to find a match in a_FunctionDescs that hasn't been used yet: + local res = {} + for _, docs in ipairs(a_FunctionDocs) do + local hasFound = false + for idx, _ in pairs(freeDescs) do + local desc = a_FunctionDescs[idx] + if (functionDescMatchesDocs(desc, docs)) then + freeDescs[idx] = nil + hasFound = true + break + end + end -- for idx - freeDescs[] + if not(hasFound) then + table.insert(res, docs) + end + end -- for docs - a_FunctionDocs[] + + -- If no result, return nil instead of an empty table: + if not(res[1]) then + return nil + end + return res +end + + + + + +--- Returns a dict of "FnName" -> { { <FnDesc> }, ... } that are documented in a_FunctionDocs but missing from a_FunctionDescs +-- If there are no such descriptions, returns nil instead +-- a_FunctionDescs is a dict of "FnName" -> { <FnDescs> } loaded from APIDesc.lua et al +-- <FnDescs> may be a single desc or an array of those +-- a_FunctionDocs is a dict og "FnName" -> { { <FnDesc> }, ... } loaded from ToLua++'s parser +local function listMissingClassFunctionDescs(a_FunctionDescs, a_FunctionDocs) + -- Match the docs and descriptions for each separate function: + local res = {} + local hasSome = false + a_FunctionDescs = a_FunctionDescs or {} + a_FunctionDocs = a_FunctionDocs or {} + for fnName, fnDocs in pairs(a_FunctionDocs) do + local fnDescName = g_FunctionNameDocsToDesc[fnName] or fnName + if not(g_IgnoreFunction[fnDescName]) then + local fnDescs = a_FunctionDescs[fnDescName] + if not(fnDescs) then + -- Function not described at all, insert a dummy empty description for the matching: + fnDescs = {} + elseif not(fnDescs[1]) then + -- Function has a single description, convert it to the same format as multi-overload functions use: + fnDescs = { fnDescs } + end + local missingDocs = listMissingClassSingleFunctionDescs(fnDescs, fnDocs) + if (missingDocs) then + res[fnName] = missingDocs + hasSome = true + end + end -- not ignored + end -- for fnName, fnDocs - a_FunctionDocs[] + if not(hasSome) then + return nil + end + return res +end + + + + + +--- Returns a dictionary of "SymbolName" -> { <desc> } for any variable or constant that is documented but not described +-- a_VarConstDescs is an array of variable or constant descriptions, as loaded from APIDesc.lua +-- a_VarConstDocs is an array of variable or constant documentation items, as loaded from ToLua++'s parser +-- If no symbol is to be returned, returns nil instead +local function listMissingClassVarConstDescs(a_VarConstDescs, a_VarConstDocs) + -- Match the docs and descriptions for each separate function: + local res = {} + local hasSome = false + a_VarConstDescs = a_VarConstDescs or {} + a_VarConstDocs = a_VarConstDocs or {} + for symName, symDocs in pairs(a_VarConstDocs) do + local symDesc = a_VarConstDescs[symName] + if ( + not(symDesc) or -- Symbol not described at all + not(symDesc.Notes) or -- Non-existent description + ( + (symDesc.Notes == "") and -- Empty description + (type(symDocs.Notes) == "string") and -- Docs has a string ... + (symDocs.Notes ~= "") -- ... that is not empty + ) + ) then + res[symName] = symDocs + hasSome = true + end + end + if not(hasSome) then + return nil + end + return res +end + + + + + +--- Fills a_Missing with descriptions that are documented in a_ClassDocs but missing from a_ClassDesc +-- a_ClassDesc is the class' description loaded from APIDesc et al +-- a_ClassDocs is the class' documentation loaded from ToLua++'s parser +local function listMissingClassDescs(a_ClassName, a_ClassDesc, a_ClassDocs, a_Missing) + local missing = + { + Functions = listMissingClassFunctionDescs(a_ClassDesc.Functions, a_ClassDocs.Functions), + Constants = listMissingClassVarConstDescs(a_ClassDesc.Constants, a_ClassDocs.Constants), + Variables = listMissingClassVarConstDescs(a_ClassDesc.Variables, a_ClassDocs.Variables), + } + if not(missing.Functions) and not(missing.Constants) and not(missing.Variables) then + -- Nothing missing, don't add anything + return + end + a_Missing[a_ClassName] = missing +end + + + + + +--- Returns a dictionary of "ClassName" -> { { <desc> }, ... } of descriptions that are documented in a_Docs but missing from a_Descs +-- a_Descs is the descriptions loaded from APIDesc et al +-- a_Docs is the documentation loaded from ToLua++'s parser +local function findMissingDescs(a_Descs, a_Docs) + local descClasses = a_Descs.Classes + local res = {} + for clsName, clsDocs in pairs(a_Docs) do + local clsDesc = descClasses[clsName] or {} + listMissingClassDescs(clsName, clsDesc, clsDocs, res) + end + return res +end + + + + + +local function outputTable(a_File, a_Table, a_Indent) + -- Extract all indices first: + local allIndices = {} + for k, _ in pairs(a_Table) do + table.insert(allIndices, k) + end + + -- Sort the indices: + table.sort(allIndices, + function (a_Index1, a_Index2) + if (type(a_Index1) == "number") then + if (type(a_Index2) == "number") then + -- Both indices are numeric, sort by value + return (a_Index1 < a_Index2) + end + -- a_Index2 is non-numeric, always goes after a_Index1 + return true + end + if (type(a_Index2) == "number") then + -- a_Index2 is numeric, a_Index1 is not + return false + end + -- Neither index is numeric, use regular string comparison: + return caseInsensitiveCompare(tostring(a_Index1), tostring(a_Index2)) + end + ) + + -- Output by using the index order: + a_File:write(a_Indent, "{\n") + local indent = a_Indent .. "\t" + for _, index in ipairs(allIndices) do + -- Write the index: + a_File:write(indent, "[") + if (type(index) == "string") then + a_File:write(string.format("%q", index)) + else + a_File:write(index) + end + a_File:write("] =") + + -- Write the value: + local v = a_Table[index] + if (type(v) == "table") then + a_File:write("\n") + outputTable(a_File, v, indent) + elseif (type(v) == "string") then + a_File:write(string.format(" %q", v)) + else + a_File:write(" ", tostring(v)) + end + a_File:write(",\n") + end + a_File:write(a_Indent, "}") +end + + + + + +--- Returns a description of function params, as used for output +-- a_Params is nil or an array of parameters from ToLua++'s parser +-- a_ClassMap is a dictionary of "ClassName" -> true for all known classes +local function extractParamsForOutput(a_Params, a_ClassMap) + if not(a_Params) then + return "" + end + assert(a_ClassMap) + + local params = {} + for _, param in ipairs(a_Params) do + local paramType = param.Type or "" + paramType = g_CTypeToLuaType[paramType] or paramType -- Translate from C type to Lua type + local paramName = param.Name or paramType or "[unknown]" + paramName = paramName:gsub("^a_", "") -- Remove the "a_" prefix, if present + local idxColon = paramType:find("::") -- Extract children classes and enums within classes + local paramTypeAnchor = "" + if (idxColon) then + paramTypeAnchor = "#" .. paramType:sub(idxColon + 2) + paramType = paramType:sub(1, idxColon - 1) + end + if (a_ClassMap[paramType]) then + -- Param type is a class name, make it a link + if not(param.Name) then + paramName = "{{" .. paramType .. paramTypeAnchor .. "}}" + else + paramName = "{{" .. paramType .. paramTypeAnchor .. "|" .. paramName .. "}}" + end + end + table.insert(params, paramName) + end + return table.concat(params, ", ") +end + + + + + +--- Returns a single line of function description for output +-- a_Desc is the function description +-- a_ClassMap is a dictionary of "ClassName" -> true for all known classes +local function formatFunctionDesc(a_Docs, a_ClassMap) + local staticClause = "" + if (a_Docs.IsStatic) then + staticClause = "IsStatic = true, " + end + return string.format("{ Params = %q, Return = %q, %sNotes = %q },\n", + extractParamsForOutput(a_Docs.Params, a_ClassMap), + extractParamsForOutput(a_Docs.Returns, a_ClassMap), + staticClause, + (a_Docs.Desc or ""):gsub("%.\n", ". "):gsub("\n", ". "):gsub("%s+", " ") + ) +end + + + + + +--- Outputs differences in function descriptions into a file +-- a_File is the output file +-- a_Functions is nil or a dictionary of "FunctionName" -> { { <desc> }, ... } +-- a_ClassMap is a dictionary of "ClassName" -> true for all known classes +local function outputFunctions(a_File, a_Functions, a_ClassMap) + assert(a_File) + if not(a_Functions) then + return + end + + -- Get a sorted array of all function names: + local fnNames = {} + for fnName, _ in pairs(a_Functions) do + table.insert(fnNames, fnName) + end + table.sort(fnNames, caseInsensitiveCompare) + + -- Output the function descs: + a_File:write("\t\tFunctions =\n\t\t{\n") + for _, fnName in ipairs(fnNames) do + a_File:write("\t\t\t", g_FunctionNameDocsToDesc[fnName] or fnName, " =") + local docs = a_Functions[fnName] + if (docs[2]) then + -- There are at least two descriptions, use the array format: + a_File:write("\n\t\t\t{\n") + for _, doc in ipairs(docs) do + a_File:write("\t\t\t\t", formatFunctionDesc(doc, a_ClassMap)) + end + a_File:write("\t\t\t},\n") + else + -- There's only one description, use the simpler one-line format: + a_File:write(" ", formatFunctionDesc(docs[1], a_ClassMap)) + end + end + a_File:write("\t\t},\n") +end + + + + + +--- Returns the description of a single variable or constant +-- a_Docs is the ToLua++'s documentation of the symbol +-- a_ClassMap is a dictionary of "ClassName" -> true for all known classes +local function formatVarConstDesc(a_Docs, a_ClassMap) + local descType = "" + if (a_Docs.Type) then + local luaType = g_CTypeToLuaType[a_Docs.Type] or a_Docs.Type + if (a_ClassMap[a_Docs.Type]) then + descType = string.format("Type = {{%q}}, ", luaType); + else + descType = string.format("Type = %q, ", luaType); + end + end + return string.format("{ %sNotes = %q },\n", descType, a_Docs.Desc or "") +end + + + + + +--- Outputs differences in variables' or constants' descriptions into a file +-- a_File is the output file +-- a_VarConst is nil or a dictionary of "VariableOrConstantName" -> { <desc> } +-- a_Header is a string, either "Variables" or "Constants" +-- a_ClassMap is a dictionary of "ClassName" -> true for all known classes +local function outputVarConst(a_File, a_VarConst, a_Header, a_ClassMap) + assert(a_File) + assert(type(a_Header) == "string") + if not(a_VarConst) then + return + end + + -- Get a sorted array of all symbol names: + local symNames = {} + for symName, _ in pairs(a_VarConst) do + table.insert(symNames, symName) + end + table.sort(symNames, caseInsensitiveCompare) + + -- Output the symbol descs: + a_File:write("\t\t", a_Header, " =\n\t\t{\n") + for _, symName in ipairs(symNames) do + local docs = a_VarConst[symName] + a_File:write("\t\t\t", symName, " = ", formatVarConstDesc(docs, a_ClassMap)) + end + a_File:write("\t\t},\n") +end + + + + + +--- Outputs the diff into a file +-- a_Diff is the diff calculated by findMissingDescs() +-- The output file is written as a Lua source file formatted to match APIDesc.lua +local function outputDiff(a_Diff) + -- Sort the classnames: + local classNames = {} + local classMap = {} + for clsName, _ in pairs(a_Diff) do + table.insert(classNames, clsName) + classMap[clsName] = true + end + table.sort(classNames, caseInsensitiveCompare) + + -- Output each class: + local f = assert(io.open("APIDiff.lua", "w")) + -- outputTable(f, diff, "") + f:write("return\n{\n") + for _, clsName in ipairs(classNames) do + f:write("\t", clsName, " =\n\t{\n") + local desc = a_Diff[clsName] + outputFunctions(f, desc.Functions, classMap) + outputVarConst(f, desc.Variables, "Variables", classMap) + outputVarConst(f, desc.Constants, "Constants", classMap) + f:write("\t},\n") + end + f:write("}\n") + f:close() +end + + + + + +local apiDesc = loadAPIDesc() +local apiDocs = loadAPIDocs() +local diff = findMissingDescs(apiDesc, apiDocs) +outputDiff(diff) +print("Diff has been output to file APIDiff.lua.") + + + |