Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

395 lines (350 sloc) 12.982 kb
-- setup
local addInaccesible = false -- if the key to a table is a function/table/uservalue, it cannot be indexed (well) - should those still be added?
print("Luabind documentation generation")
-- walk through the globals, following every table, finding luabind generated stuff, remembering how to get there
local todo = {[_G] = "_G"}
-- remember what you already processed in case of cycles
local processed = {[_G] = true}
local writeableTypes = { string = true, number = true, boolean = true }
local unaryOperators = { __unm = true, __len = true }
local unaryOperatorSymbols = { __unm = "-", __len = "#" }
local binaryOperators = { __add = true, __sub = true, __mul = true, __div = true, __mul = true, __mod = true, __pow = true, __concat = true, __lt = true, __le = true, __eq = true }
local binaryOperatorSymbols = { __add = "+", __sub = "-", __mul = "*", __div = "/", __mul = "/", __mod = "%", __pow = "^", __concat = "..", __lt = "<", __le = "<=", __eq = "==" }
-- for each (internal) name, an array of luabind class info tables (usually just 1, except when names clash - used for improving signatures by replacing these with the actual names)
local luabindClassesByName = {}
-- all the Luabind classes without internal names (no idea how that's possible, but let's assume it is, okay?)
local unnamedLuabindClasses = {}
-- all the Luabind functions by their actual name
local luabindFunctionsByActualName = {}
local function AddToTodo(parentName, name, value)
-- only add unprocessed ones
if processed[value] == nil then
local nameType = type(name)
if writeableTypes[nameType] or addInaccesible then
assert(todo[value] == nil)
processed[value] = true
if nameType == "string" then
if parentName == "_G" then
todo[value] = name
else
todo[value] = parentName .. "." .. name
end
elseif writeableTypes[nameType] then
todo[value] = parentName .. "[" .. name .. "]"
else
assert(addInaccessible)
todo[value] = "<inaccesible>"
end
end
end
end
local done = false
while not done do
-- get the next entry from the todo list
local object, name = next(todo)
-- if there is no next entry, we're done.
if object == nil then
done = true
else
-- remove entry from todo list
todo[object] = nil
-- if this is a table, add all its unprocessed entries
if type(object) == "table" then
for key, value in pairs(object) do
AddToTodo(name, key, value)
end
else -- not a table
-- try to get information on this object
local info = jar.GetLuabindInfo(object)
-- this only succeeds if it's a luabind function or class.
if info then
info.actualName = name
if info.type == "class" then
-- no idea if classes without names are even possible here, but if they are, they are put in a different table
if info.name then
-- I create a table for each (simple) name in case of nameclashes (since luabind ignores namespaces here)
if luabindClassesByName[info.name] == nil then
luabindClassesByName[info.name] = {}
else
if #luabindClassesByName[info.name] == 1 then
print(info.name .. " is ambiguous" .. ", used by " .. luabindClassesByName[info.name][1].actualName .. " and " .. info.actualName)
else
print(info.name .. " also used by " .. info.actualName)
end
end
table.insert(luabindClassesByName[info.name], info)
else
table.insert(unnamedLuabindClasses, info)
end
-- add stuff from the scope to the todo list
for key, value in pairs(info.scope) do
AddToTodo(name, key, value)
end
else
assert(info.type == "function")
-- the same actual name shouldn't exist more than once
assert(luabindFunctionsByActualName[info.actualName] == nil)
-- add all overloads
luabindFunctionsByActualName[info.actualName] = {}
for _, signature in ipairs(info.overloads) do
table.insert(luabindFunctionsByActualName[info.actualName], {
name = info.name, -- for searching in the signature
actualName = info.actualName, -- for replacing in the signature
signature = signature,
type = info.type,
})
end
end
end
end
end
end
local unambiguousLuabindClassesByName = {}
local luabindClassesByActualName = {}
for name, classes in pairs(luabindClassesByName) do
if #classes == 1 then -- don't replace ambiguous names
unambiguousLuabindClassesByName[name] = classes[1]
end
for _, class in ipairs(classes) do
luabindClassesByActualName[class.actualName] = class
end
end
for _, class in ipairs(unnamedLuabindClasses) do
luabindClassesByActualName[class.actualName] = class
end
-- puts a % before all % in the string to escape for use in patterns
local function escapePercents(s)
local parts = {}
while true do
local index = s:find("%%")
if index == nil then
parts[#parts + 1] = s
break
end
parts[#parts + 1] = s:sub(1, index)
parts[#parts + 1] = "%"
s = s:sub(index + 1)
end
return table.concat(parts)
end
-- replaces the internal names with the actual ones, optionally formatting them
local function processSignature(signature, functionInfo, objectFormatter)
-- split into return type(, name) and parameters
retVals, parameters = signature:match(string.format("^(.*)%s(%%(.+)$", functionInfo.name))
if not retVals then
error("Signature does not match the pattern!")
end
-- remove optional lua_State* parameter(s?)
parameters = parameters:gsub("([^%%a%%._])lua_State%*,?([^%%a_])", "%1%2")
-- replace all types with their "actual" name, if that is unambiguous
-- prefix return value(s) with a space (which will later be removed) so I can replace only whole words
retVals = " " .. retVals
for name, class in pairs(unambiguousLuabindClassesByName) do
local pattern = "([^%.%a%d_])"..name.."([^%.%a%d_])"
local replace = "%1" .. escapePercents(objectFormatter(class.actualName, class)) .. "%2"
retVals = retVals:gsub(pattern, replace)
parameters = parameters:gsub(pattern, replace)
end
-- remove prefixed space
assert(retVals:sub(1, 1) == " ")
retVals = retVals:sub(2)
return string.format("%s%s%s", retVals, functionInfo.actualName, parameters)
end
-- delete tables
todo = nil
processed = nil
-- sort by name
-- table.sort(luabindObjects, function(lhs, rhs) return lhs.name < rhs.name end)
-- function to format object names (e.g. jar.Vector2f -> <a href="jar/Vector2f.html")
local function objectFormatter(text, classInfo)
return text
end
local function objectFormatter(text, classInfo)
return "<class>" .. text .. "</class>"
end
-- preprocess function signatures
for _, overloads in pairs(luabindFunctionsByActualName) do
for _, info in ipairs(overloads) do
assert(info.type == "function")
info.processedSignature = processSignature(info.signature, info, objectFormatter)
end
end
-- preprocess methods, separating constructors and moving overloads into separate copies
local function processMethods(class)
local methods = {} -- can't overwrite/change during iteration
class.constructors = {}
class.unaryOperators = {}
class.binaryOperators = {}
for _, method in ipairs(class.methods) do
for _, signature in ipairs(method.overloads) do
local newMethod = {}
-- copy old function info
for k, v in pairs(method) do
if k ~= "overloads" then
newMethod[k] = v
end
end
assert(newMethod.signature == nil)
assert(newMethod.actualName == nil)
-- different handling for constructors, operators and "normal" member functions
-- constructors
if newMethod.name == "__init" then
-- remove the luabind::argument part of the signature
local replaced
newMethod.signature, replaced = signature:gsub("void __init%(luabind::argument const&,?", "__init(")
assert(replaced == 1)
newMethod.actualName = class.actualName
-- process the signature
newMethod.processedSignature = processSignature(newMethod.signature, newMethod, objectFormatter)
table.insert(class.constructors, newMethod)
-- unary operators - #obj and -obj
elseif unaryOperators[newMethod.name] then
newMethod.actualName = newMethod.name
local sig = processSignature(signature, newMethod, function (text, _) return text end) --no formatting yet
local returnType, arg = sig:match("^(.+) " .. newMethod.name .. "%(([^%)]+)%)")
assert(returnType and arg)
-- beautify
local className, suffix = arg:match("^([_%a][_%a%d%.]+)(.*)$")
local argClass = luabindClassesByActualName[className]
if argClass then
arg = objectFormatter(className, argClass) .. suffix
end
table.insert(class.unaryOperators, {name = newMethod.name, argument = arg, returnType = returnType})
-- binary operators
elseif binaryOperators[newMethod.name] then
newMethod.actualName = newMethod.name
local sig = processSignature(signature, newMethod, function (text, _) return text end) --no formatting yet
local arg1, arg2 = sig:match("^.+ " .. newMethod.name .. "%(([^,]+),([^%)]+)%)")
assert(arg1 and arg2)
local args = {arg1, arg2}
-- beautify
for i = 1, 2 do
local className, suffix = args[i]:match("^([_%a][_%a%d%.]+)(.*)$")
local argClass = luabindClassesByActualName[className]
if argClass then
args[i] = objectFormatter(className, argClass) .. suffix
end
end
table.insert(class.binaryOperators, {name = newMethod.name, arguments = args})
-- "ordinary" member functions
else
-- remove "self" first argument (attention: is either this class or one of its bases!)
local pattern = "%(([%a%d%%._]+)[&%*],?"
match = signature:match(pattern)
if match == nil then
pattern = "%(([%a%d%%._]+) const[&%*],?"
match = signature:match(pattern)
end
if not match then
print(signature)
end
assert(match)
if match ~= class.name and match ~= "void" then -- some libraries cast down to void* to the data opaque
--todo: check base classes
--print(("first member function parameter is %s, not %s - this is probably due to inheritance, add that."):format(match, class.name))
end
newMethod.signature = signature:gsub(pattern, "(")
newMethod.actualName = newMethod.name
-- process the signature
newMethod.processedSignature = processSignature(newMethod.signature, newMethod, objectFormatter)
table.insert(methods, newMethod)
end
end
end
class.methods = methods
end
for _, t in pairs(luabindClassesByName) do
for _, info in ipairs(t) do
processMethods(info)
end
end
for _, info in ipairs(unnamedLuabindClasses) do
processMethods(info)
end
-- todo: generate documentation
print()
print("== Classes == ")
print()
local function PrintClassInfo(info)
assert(info.type == "class")
print(("= %s ="):format(info.actualName))
print()
-- print constructors, if any
if #info.constructors > 0 then
print("- constructors -")
for _, funcInfo in ipairs(info.constructors) do
print(funcInfo.processedSignature)
end
print()
end
-- print methods, if any
if #info.methods > 0 then
print("- methods -")
for _, funcInfo in ipairs(info.methods) do
print(funcInfo.processedSignature)
end
print()
end
-- print properties, if any
if #info.properties > 0 then
print("- properties -")
for _, propInfo in ipairs(info.properties) do
print(propInfo.name)
end
print()
end
-- print constants, if any
if #info.constants > 0 then
-- sort constants by value (as opposed to by name, the default order)
table.sort(info.constants, function(lhs, rhs) return lhs.value < rhs.value end)
print("- constants -")
for _, constantInfo in ipairs(info.constants) do
print(("%s (%i)"):format(constantInfo.name, constantInfo.value))
end
print()
end
-- print operators, if any
if #info.unaryOperators > 0 then
print("- unary operators -")
for _, op in ipairs(info.unaryOperators) do
print(op.returnType .. " " .. unaryOperatorSymbols[op.name] .. " " .. op.argument)
end
print()
end
if #info.binaryOperators > 0 then
print("- binary operators -")
for _, op in ipairs(info.binaryOperators) do
print(op.arguments[1] .. " " .. binaryOperatorSymbols[op.name] .. " " .. op.arguments[2])
end
print()
end
-- print static member functions, if any
if #info.staticMemberFunctions > 0 then
print("- static member functions -")
-- might want to skip __init here?
for _, funcInfo in ipairs(info.staticMemberFunctions) do
for _, signature in ipairs(funcInfo.overloads) do
print(processSignature(signature, funcInfo, objectFormatter))
end
end
print()
end
end
for _, t in pairs(luabindClassesByName) do
for _, info in ipairs(t) do
PrintClassInfo(info)
end
end
for _, info in ipairs(unnamedLuabindClasses) do
PrintClassInfo(info)
end
print()
print("== Functions ==")
print()
for _, overloads in pairs(luabindFunctionsByActualName) do
for _, info in ipairs(overloads) do
assert(info.type == "function")
print(info.processedSignature)
end
end
print()
Jump to Line
Something went wrong with that request. Please try again.