|
@@ -0,0 +1,367 @@
|
|
|
+-- We're disabling `@see` tags for the time being because they
|
|
|
+-- ended up confusing the language server a little, breaking
|
|
|
+-- all objects from `lovr.math.*`.
|
|
|
+local ENABLE_SEE_TAGS = false
|
|
|
+
|
|
|
+-- Whether to generate an extra file with the globals
|
|
|
+-- from the `lovr.math` module.
|
|
|
+local INCLUDE_GLOBALS = true
|
|
|
+
|
|
|
+local function getPath(filename, sep)
|
|
|
+ sep = sep or "/"
|
|
|
+ return filename:match("(.*" .. sep .. ")")
|
|
|
+end
|
|
|
+
|
|
|
+local function ensureDirectoryExists(path)
|
|
|
+ os.execute("mkdir -p " .. path)
|
|
|
+end
|
|
|
+
|
|
|
+--- Writes `contents` into a file identified by `filename`.
|
|
|
+local function writeFile(filename, contents)
|
|
|
+ -- make sure the directory exists
|
|
|
+ local path = getPath(filename)
|
|
|
+ if path then
|
|
|
+ ensureDirectoryExists(path)
|
|
|
+ end
|
|
|
+
|
|
|
+ local file = io.open(filename, "w")
|
|
|
+ if not file then
|
|
|
+ print("Failed to open file for writing: " .. filename)
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ file:write(contents)
|
|
|
+ file:write("\n")
|
|
|
+ file:close()
|
|
|
+end
|
|
|
+
|
|
|
+--- Adds a single value to a table.
|
|
|
+local function add(t, v)
|
|
|
+ t[#t + 1] = v
|
|
|
+end
|
|
|
+
|
|
|
+-- Shorthand to concatenate a table of strings with a delimiter.
|
|
|
+local function join(t, delimiter)
|
|
|
+ return table.concat(t, delimiter)
|
|
|
+end
|
|
|
+
|
|
|
+-- Shorthand to map a table with a function.
|
|
|
+local function map(t, fn)
|
|
|
+ local out = {}
|
|
|
+ for i, v in ipairs(t) do
|
|
|
+ out[i] = fn(v)
|
|
|
+ end
|
|
|
+ return out
|
|
|
+end
|
|
|
+
|
|
|
+--- Returns true if any element in the table `t` satisfies the predicate `fn`.
|
|
|
+local function any(t, fn)
|
|
|
+ for _, v in ipairs(t) do
|
|
|
+ if fn(v) then
|
|
|
+ return true
|
|
|
+ end
|
|
|
+ end
|
|
|
+ return false
|
|
|
+end
|
|
|
+
|
|
|
+--- Renders the given string as a docstring. If the string
|
|
|
+--- contains newlines, they are prefixed with `---`.
|
|
|
+local function doc(str)
|
|
|
+ str = str or ""
|
|
|
+ return "---" .. str:gsub("\n", "\n---")
|
|
|
+end
|
|
|
+
|
|
|
+--- Determines if a parameter is optional. If it is, returns a `?` suffix.
|
|
|
+--- If it's not, returns an empty string.
|
|
|
+local function optionalSuffix(param)
|
|
|
+ if param.type ~= 'table' then
|
|
|
+ return (param.default ~= nil) and '?' or ''
|
|
|
+ elseif not param.table then
|
|
|
+ return ''
|
|
|
+ elseif any(param.table, function(field) return field.default == nil end) then
|
|
|
+ return ''
|
|
|
+ else
|
|
|
+ return '?'
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function docHeader(str)
|
|
|
+ return doc("#### " .. str .. "\n")
|
|
|
+end
|
|
|
+
|
|
|
+--- Render `@see` tags for a definition.
|
|
|
+local function seeTags(out, def)
|
|
|
+ if not ENABLE_SEE_TAGS then
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ if def.related then
|
|
|
+ for _, related in ipairs(def.related) do
|
|
|
+ add(out, doc("@see " .. related))
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+-- Forward declaration for `argumentType`.
|
|
|
+local argumentType
|
|
|
+
|
|
|
+--- Returns the type of a table argument.
|
|
|
+local function tableType(arg)
|
|
|
+ local out = {}
|
|
|
+
|
|
|
+ for _, field in ipairs(arg.table) do
|
|
|
+ add(out, ("%s: %s"):format(field.name, argumentType(field)))
|
|
|
+ end
|
|
|
+
|
|
|
+ return ("{%s}"):format(join(out, ", "))
|
|
|
+end
|
|
|
+
|
|
|
+--- Returns the type of a function argument.
|
|
|
+function argumentType(arg)
|
|
|
+ if arg.type == "*" then
|
|
|
+ return "any"
|
|
|
+ elseif arg.type == 'table' and arg.table then
|
|
|
+ return tableType(arg)
|
|
|
+ else
|
|
|
+ return arg.type
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+--- Renders the header (description, notes, examples, etc.) for anything that's like a function.
|
|
|
+local function renderFunctionHeader(out, func)
|
|
|
+ add(out, "")
|
|
|
+ add(out, doc(func.description))
|
|
|
+
|
|
|
+ if (func.notes) then
|
|
|
+ add(out, doc(""))
|
|
|
+ add(out, docHeader("Notes:"))
|
|
|
+ add(out, doc(func.notes))
|
|
|
+ end
|
|
|
+
|
|
|
+ if (func.examples) then
|
|
|
+ for _, example in ipairs(func.examples) do
|
|
|
+ add(out, doc(""))
|
|
|
+ add(out, docHeader("Example:"))
|
|
|
+
|
|
|
+ if example.description then
|
|
|
+ add(out, doc(example.description))
|
|
|
+ add(out, doc(""))
|
|
|
+ end
|
|
|
+
|
|
|
+ if example.code then
|
|
|
+ add(out, doc("```lua"))
|
|
|
+ add(out, doc(example.code))
|
|
|
+ add(out, doc("```"))
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ add(out, doc(""))
|
|
|
+
|
|
|
+ seeTags(out, func)
|
|
|
+end
|
|
|
+
|
|
|
+local function renderType(out, tag, variant)
|
|
|
+ local returns
|
|
|
+ if #variant.returns > 0 then
|
|
|
+ returns = join(map(variant.returns, function(ret)
|
|
|
+ return ret.type
|
|
|
+ end), ", ")
|
|
|
+ else
|
|
|
+ returns = "nil"
|
|
|
+ end
|
|
|
+
|
|
|
+ local params = join(map(variant.arguments, function(arg)
|
|
|
+ return ("%s%s: %s"):format(arg.name, optionalSuffix(arg), argumentType(arg))
|
|
|
+ end), ", ")
|
|
|
+
|
|
|
+ add(out, doc(("%s fun(%s): %s"):format(tag, params, returns)))
|
|
|
+end
|
|
|
+
|
|
|
+local function renderFunctionVariant(out, func, variant)
|
|
|
+ renderFunctionHeader(out, func)
|
|
|
+
|
|
|
+ -- Document parameters
|
|
|
+ if variant.arguments then
|
|
|
+ for _, arg in ipairs(variant.arguments) do
|
|
|
+ add(out, doc(("@param %s%s %s %s"):format(arg.name, optionalSuffix(arg), argumentType(arg), arg.description)))
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Document return type
|
|
|
+ if variant.returns then
|
|
|
+ for _, ret in ipairs(variant.returns) do
|
|
|
+ add(out, doc(("@return %s %s %s"):format(ret.type, ret.name, ret.description)))
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Build function signature
|
|
|
+ local signature = join(map(variant.arguments, function(arg)
|
|
|
+ return arg.name
|
|
|
+ end), ", ")
|
|
|
+
|
|
|
+ add(out, ("function %s(%s) end"):format(func.key, signature))
|
|
|
+end
|
|
|
+
|
|
|
+local function renderFunction(out, func)
|
|
|
+ for _, variant in ipairs(func.variants) do
|
|
|
+ renderFunctionVariant(out, func, variant)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function renderEnum(out, enum)
|
|
|
+ add(out, "")
|
|
|
+ add(out, doc(enum.description))
|
|
|
+ add(out, doc("@alias " .. enum.key))
|
|
|
+ for _, value in ipairs(enum.values) do
|
|
|
+ add(out, doc(value.description))
|
|
|
+ add(out, doc("| \"%s\""):format(value.name))
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function generateSwizzlePermutations(fields, length)
|
|
|
+ local permutations = {}
|
|
|
+
|
|
|
+ local function go(swizzle)
|
|
|
+ swizzle = swizzle or ""
|
|
|
+
|
|
|
+ if #swizzle == length then
|
|
|
+ add(permutations, swizzle)
|
|
|
+ else
|
|
|
+ for _, field in ipairs(fields) do
|
|
|
+ go(swizzle .. field)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ go()
|
|
|
+
|
|
|
+ return permutations
|
|
|
+end
|
|
|
+
|
|
|
+local function renderSwizzleFields(out, fields)
|
|
|
+ for i = 1, 4 do
|
|
|
+ local type = i == 1 and "number" or "Vec" .. i
|
|
|
+ local comp = i == 1 and "component" or "components"
|
|
|
+
|
|
|
+ for _, swizzle in ipairs(generateSwizzlePermutations(fields, i)) do
|
|
|
+ local desc = ("The %s %s of the vector."):format(swizzle, comp)
|
|
|
+ add(out, doc(("@field %s %s %s"):format(swizzle, type, desc)))
|
|
|
+ end
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+--- Renders a module's information to CATS format.
|
|
|
+local function renderModule(mod)
|
|
|
+ local out = {}
|
|
|
+
|
|
|
+ add(out, doc("@meta"))
|
|
|
+ add(out, "")
|
|
|
+
|
|
|
+ if mod.description then
|
|
|
+ add(out, doc(mod.description))
|
|
|
+ add(out, doc(""))
|
|
|
+ end
|
|
|
+
|
|
|
+ add(out, doc(("@class %s"):format(mod.key)))
|
|
|
+ add(out, ("%s = {}"):format(mod.key))
|
|
|
+
|
|
|
+ -- Render functions
|
|
|
+ for _, func in ipairs(mod.functions) do
|
|
|
+ renderFunction(out, func)
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Render objects
|
|
|
+ for _, obj in ipairs(mod.objects) do
|
|
|
+ add(out, "")
|
|
|
+ add(out, doc(obj.description))
|
|
|
+ add(out, doc("@class %s"):format(obj.key))
|
|
|
+
|
|
|
+ -- fields
|
|
|
+ if obj.fields then
|
|
|
+ for _, field in ipairs(obj.fields) do
|
|
|
+ add(out, doc(("@field %s %s %s"):format(field.name, field.type, field.description)))
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- swizzles
|
|
|
+ if obj.swizzles then
|
|
|
+ for _, swizzle in ipairs(obj.swizzles.components) do
|
|
|
+ renderSwizzleFields(out, swizzle)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- see tags
|
|
|
+ seeTags(out, obj)
|
|
|
+
|
|
|
+ -- definition
|
|
|
+ add(out, ("local %s = {}"):format(obj.name))
|
|
|
+
|
|
|
+ -- Render object methods
|
|
|
+ for _, func in ipairs(obj.methods) do
|
|
|
+ renderFunction(out, func)
|
|
|
+ end
|
|
|
+ end
|
|
|
+
|
|
|
+ -- Render enums
|
|
|
+ for _, enum in ipairs(mod.enums) do
|
|
|
+ renderEnum(out, enum)
|
|
|
+ end
|
|
|
+
|
|
|
+
|
|
|
+ return join(out, "\n")
|
|
|
+end
|
|
|
+
|
|
|
+local function generateModuleDocumentation(api)
|
|
|
+ for _, v in ipairs(api.modules) do
|
|
|
+ local text = renderModule(v)
|
|
|
+ local filename = v.name
|
|
|
+ writeFile(("library/%s.lua"):format(filename), text)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function renderCallback(out, callback)
|
|
|
+ renderFunctionHeader(out, callback)
|
|
|
+ for i = 1, #callback.variants do
|
|
|
+ local variant = callback.variants[i]
|
|
|
+ renderType(out, "@type", variant)
|
|
|
+ end
|
|
|
+ add(out, ("%s = nil"):format(callback.key))
|
|
|
+end
|
|
|
+
|
|
|
+local function generateCallbackDocumentation(api)
|
|
|
+ local out = {}
|
|
|
+ add(out, doc("@meta"))
|
|
|
+
|
|
|
+ for _, callback in ipairs(api.callbacks) do
|
|
|
+ renderCallback(out, callback)
|
|
|
+ end
|
|
|
+
|
|
|
+ writeFile("library/callback.lua", join(out, "\n"))
|
|
|
+end
|
|
|
+
|
|
|
+local function generateGlobalsDocumentation()
|
|
|
+ local out = {}
|
|
|
+ add(out, doc("@meta"))
|
|
|
+ add(out, "vec2 = lovr.math.vec2")
|
|
|
+ add(out, "Vec2 = lovr.math.newVec2")
|
|
|
+ add(out, "vec3 = lovr.math.vec3")
|
|
|
+ add(out, "Vec3 = lovr.math.newVec3")
|
|
|
+ add(out, "vec4 = lovr.math.vec4")
|
|
|
+ add(out, "Vec4 = lovr.math.newVec4")
|
|
|
+ add(out, "mat4 = lovr.math.mat4")
|
|
|
+ add(out, "Mat4 = lovr.math.newMat4")
|
|
|
+ add(out, "quat = lovr.math.quat")
|
|
|
+ add(out, "Quat = lovr.math.newQuat")
|
|
|
+
|
|
|
+ writeFile("library/globals.lua", join(out, "\n"))
|
|
|
+end
|
|
|
+
|
|
|
+return function(api)
|
|
|
+ generateModuleDocumentation(api)
|
|
|
+ generateCallbackDocumentation(api)
|
|
|
+
|
|
|
+ if INCLUDE_GLOBALS then
|
|
|
+ generateGlobalsDocumentation()
|
|
|
+ end
|
|
|
+end
|