2015-05-12 03:20:54 -04:00
-- BindingsProcessor.lua
-- Implements additional processing that is done while generating the Lua bindings
2016-05-22 05:54:31 -04:00
--[[
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
2016-05-22 07:51:41 -04:00
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
2016-09-08 17:05:52 -04:00
- \ 19 and \ 20 : DoxyComment for previous item ( " ///< ... \n " ) via an ID lookup
2016-05-22 05:54:31 -04:00
--]]
--- 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
2015-05-12 03:20:54 -04:00
2016-05-22 07:51:41 -04:00
local access =
{
public = 0 ,
protected = 1 ,
private = 2
}
2015-05-12 03:20:54 -04:00
--- Defines classes that have a custom manual Push() implementation and should not generate the automatic one
-- Map of classname -> true
local g_HasCustomPushImplementation =
{
2020-04-14 10:43:21 -04:00
cEntity = true ,
}
--- Defines classes that have a custom manual GetStackValue() implementation and should not generate the automatic one
-- Map of classname -> true
local g_HasCustomGetImplementation =
{
Vector3d = true ,
Vector3f = true ,
Vector3i = true ,
2015-05-12 03:20:54 -04:00
}
2016-05-22 07:51:41 -04:00
--- 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 ,
}
2015-05-12 03:20:54 -04:00
2016-05-22 07:51:41 -04:00
--- 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)
2015-05-12 03:20:54 -04:00
do
2016-05-22 07:51:41 -04:00
local b , e , label = string.find ( a_Code , " ^%s*(%w*)%s*:[^:] " ) -- we need to check for [^:], otherwise it would match 'namespace::type'
2015-05-12 03:20:54 -04:00
if b then
2016-05-22 07:51:41 -04:00
-- Found a label, get the new access value for it:
2015-05-12 03:20:54 -04:00
if access [ label ] then
2016-05-22 07:51:41 -04:00
classContainer.curr . curr_member_access = access [ label ]
2015-05-12 03:20:54 -04:00
end -- else ?
2016-05-22 07:51:41 -04:00
return strsub ( a_Code , e ) -- normally we would use 'e + 1', but we need to preserve the [^:]
end
end
2016-09-08 17:05:52 -04:00
2016-05-22 07:51:41 -04:00
-- 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 ) ) ]
2016-06-05 15:32:24 -04:00
else
curr.first_child_DoxyComment = g_ForwardDoxyComments [ tonumber ( comment : sub ( 2 , - 2 ) ) ]
2016-05-22 07:51:41 -04:00
end
return strsub ( a_Code , e + 1 )
end
end
2016-09-08 17:05:52 -04:00
2016-05-22 07:51:41 -04:00
-- 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 " <no name> " ) )
end
return strsub ( a_Code , e + 1 )
2015-05-12 03:20:54 -04:00
end
end
end
--- Outputs the helper files supplementing the cLuaState class
-- Writes:
-- LuaState_Declaration.inc
-- LuaState_Implementation.cpp
-- LuaState_Typedefs.inc
local function OutputLuaStateHelpers ( a_Package )
-- Collect all class types from ToLua:
local types = { }
for idx , item in ipairs ( a_Package ) do
local mt = getmetatable ( item ) or { }
if ( mt.classtype == " class " ) then
table.insert ( types , { name = item.name , lname = item.lname } )
end
end
table.sort ( types ,
function ( a_Item1 , a_Item2 )
return ( a_Item1.name : lower ( ) < a_Item2.name : lower ( ) )
end
)
-- Output the typedefs:
do
local f = assert ( io.open ( " LuaState_Typedefs.inc " , " w " ) )
f : write ( " \n // LuaState_Typedefs.inc \n \n // This file is generated along with the Lua bindings by ToLua. Do not edit manually, do not commit to repo. \n " )
f : write ( " // Provides a forward declaration and a typedef for a pointer to each class exported to the Lua API. \n " )
f : write ( " \n \n \n \n \n " )
for _ , item in ipairs ( types ) do
if not ( item.name : match ( " .*<.* " ) ) then -- Skip templates altogether
-- Classes start with a "c", everything else is a struct:
if ( item.name : sub ( 1 , 1 ) == " c " ) then
f : write ( " class " .. item.name .. " ; \n " )
else
f : write ( " struct " .. item.name .. " ; \n " )
end
end
end
f : write ( " \n \n \n \n \n " )
for _ , item in ipairs ( types ) do
f : write ( " typedef " .. item.name .. " * Ptr " .. item.lname .. " ; \n " )
2016-06-05 11:20:50 -04:00
f : write ( " typedef const " .. item.name .. " * ConstPtr " .. item.lname .. " ; \n " )
2015-05-12 03:20:54 -04:00
end
f : write ( " \n \n \n \n \n " )
f : close ( )
end
2016-09-08 17:05:52 -04:00
2015-05-12 03:20:54 -04:00
-- Output the Push() and GetStackValue() function declarations:
do
local f = assert ( io.open ( " LuaState_Declaration.inc " , " w " ) )
f : write ( " \n // LuaState_Declaration.inc \n \n // This file is generated along with the Lua bindings by ToLua. Do not edit manually, do not commit to repo. \n " )
f : write ( " // Implements a Push() and GetStackValue() function for each class exported to the Lua API. \n " )
f : write ( " // This file expects to be included form inside the cLuaState class definition \n " )
f : write ( " \n \n \n \n \n " )
for _ , item in ipairs ( types ) do
2020-04-14 10:43:21 -04:00
if not ( g_HasCustomPushImplementation [ item.lname ] ) then
2015-06-18 17:30:41 -04:00
f : write ( " void Push( " .. item.name .. " * a_Value); \n " )
end
2015-05-12 03:20:54 -04:00
end
for _ , item in ipairs ( types ) do
2020-04-14 10:43:21 -04:00
if not ( g_HasCustomGetImplementation [ item.lname ] ) then
f : write ( " bool GetStackValue(int a_StackPos, Ptr " .. item.lname .. " & a_ReturnedVal); \n " )
f : write ( " bool GetStackValue(int a_StackPos, ConstPtr " .. item.lname .. " & a_ReturnedVal); \n " )
end
2015-05-12 03:20:54 -04:00
end
f : write ( " \n \n \n \n \n " )
f : close ( )
end
-- Output the Push() and GetStackValue() function implementations:
do
local f = assert ( io.open ( " LuaState_Implementation.cpp " , " w " ) )
f : write ( " \n // LuaState_Implementation.cpp \n \n // This file is generated along with the Lua bindings by ToLua. Do not edit manually, do not commit to repo. \n " )
f : write ( " // Implements a Push() and GetStackValue() function for each class exported to the Lua API. \n " )
f : write ( " // This file expects to be compiled as a separate translation unit \n " )
f : write ( " \n \n \n \n \n " )
f : write ( " #include \" Globals.h \" \n #include \" LuaState.h \" \n #include \" tolua++/include/tolua++.h \" \n " )
f : write ( " \n \n \n \n \n " )
for _ , item in ipairs ( types ) do
2020-04-14 10:43:21 -04:00
if not ( g_HasCustomPushImplementation [ item.lname ] ) then
2015-05-12 03:20:54 -04:00
f : write ( " void cLuaState::Push( " .. item.name .. " * a_Value) \n { \n \t ASSERT(IsValid()); \n " )
f : write ( " \t tolua_pushusertype(m_LuaState, a_Value, \" " .. item.name .. " \" ); \n " ) ;
f : write ( " } \n \n \n \n \n \n " )
end
end
for _ , item in ipairs ( types ) do
2020-04-14 10:43:21 -04:00
if not ( g_HasCustomGetImplementation [ item.lname ] ) then
f : write ( " bool cLuaState::GetStackValue(int a_StackPos, Ptr " .. item.lname .. " & a_ReturnedVal) \n { \n \t ASSERT(IsValid()); \n " )
f : write ( " \t if (lua_isnil(m_LuaState, a_StackPos)) \n \t { \n " )
f : write ( " \t \t a_ReturnedVal = nullptr; \n " )
f : write ( " \t \t return false; \n \t } \n " )
f : write ( " \t tolua_Error err; \n " )
f : write ( " \t if (tolua_isusertype(m_LuaState, a_StackPos, \" " .. item.name .. " \" , false, &err)) \n " )
f : write ( " \t { \n " )
f : write ( " \t \t a_ReturnedVal = *(static_cast< " .. item.name .. " **>(lua_touserdata(m_LuaState, a_StackPos))); \n " )
f : write ( " \t \t return true; \n " ) ;
f : write ( " \t } \n " )
f : write ( " \t return false; \n " )
f : write ( " } \n \n \n \n \n \n " )
f : write ( " bool cLuaState::GetStackValue(int a_StackPos, ConstPtr " .. item.lname .. " & a_ReturnedVal) \n { \n \t ASSERT(IsValid()); \n " )
f : write ( " \t if (lua_isnil(m_LuaState, a_StackPos)) \n \t { \n " )
f : write ( " \t \t a_ReturnedVal = nullptr; \n " )
f : write ( " \t \t return false; \n \t } \n " )
f : write ( " \t tolua_Error err; \n " )
f : write ( " \t if (tolua_isusertype(m_LuaState, a_StackPos, \" const " .. item.name .. " \" , false, &err)) \n " )
f : write ( " \t { \n " )
f : write ( " \t \t a_ReturnedVal = *(static_cast<const " .. item.name .. " **>(lua_touserdata(m_LuaState, a_StackPos))); \n " )
f : write ( " \t \t return true; \n " ) ;
f : write ( " \t } \n " )
f : write ( " \t return false; \n " )
f : write ( " } \n \n \n \n \n \n " )
end
2015-05-12 03:20:54 -04:00
end
f : close ( )
end
end
2016-05-22 05:54:31 -04:00
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" -> { {<description>}, ...}
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
)
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- If there are no functions, bail out:
if not ( functions [ 1 ] ) then
return
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the descriptions:
a_File : write ( " \t \t Functions = \n \t \t { \n " )
for _ , fn in ipairs ( functions ) do
2016-05-29 04:25:46 -04:00
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 " )
2016-05-22 05:54:31 -04:00
for _ , desc in ipairs ( fn.Descs ) do
a_File : write ( " \t \t \t \t { \n \t \t \t \t \t Params = \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 \t Type = \" " , param.Type , " \" , \n " )
a_File : write ( " \t \t \t \t \t \t \t Name = \" " , 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 \t Returns = \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 \t Type = \" " , ret.Type , " \" , \n \t \t \t \t \t \t }, \n " )
end
2016-05-29 04:25:46 -04:00
a_File : write ( " \t \t \t \t \t }, \n " )
2016-06-05 15:32:24 -04:00
if ( desc.IsStatic ) then
a_File : write ( " \t \t \t \t \t IsStatic = true, \n " )
end
2016-05-22 11:00:07 -04:00
if ( desc.DoxyComment ) then
a_File : write ( " \t \t \t \t \t Desc = " , string.format ( " %q " , desc.DoxyComment ) , " , \n " )
end
2016-05-22 05:54:31 -04:00
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" -> {<description>}
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
)
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- If there are no variables, bail out:
if not ( variables [ 1 ] ) then
return
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the descriptions:
a_File : write ( " \t \t Variables = \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 \t Type = \" " , v.Desc . Type , " \" , \n " )
2016-05-22 11:00:07 -04:00
if ( v.Desc . DoxyComment ) then
a_File : write ( " \t \t \t \t Desc = " , string.format ( " %q " , v.Desc . DoxyComment ) , " , \n " )
end
2016-05-22 05:54:31 -04:00
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" -> {<description>}
-- 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
)
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- If there are no constants, bail out:
if not ( constants [ 1 ] ) then
return
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the descriptions:
a_File : write ( " \t \t Constants = \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 \t Type = \" " , con.Desc . Type , " \" , \n " )
2016-05-22 11:00:07 -04:00
if ( con.Desc . DoxyComment ) then
a_File : write ( " \t \t \t \t Desc = " , string.format ( " %q " , con.Desc . DoxyComment ) , " , \n " )
end
2016-05-22 05:54:31 -04:00
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
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Sort the enums by name:
table.sort ( a_Enums ,
function ( a_Enum1 , a_Enum2 )
return ( a_Enum1.name < a_Enum2.name )
end
)
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the enums:
a_File : write ( " \t \t Enums = \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 = { }
2016-05-22 11:00:07 -04:00
-- Make a copy of enum.lnames so that we can sort it:
2016-05-22 05:54:31 -04:00
local idx = 1
2016-05-22 11:00:07 -04:00
for i , valname in ipairs ( enum.lnames ) do
valnames [ idx ] = { Name = valname , DoxyComment = enum.DoxyComments [ i ] }
2016-05-22 05:54:31 -04:00
idx = idx + 1
end
2016-05-22 11:00:07 -04:00
table.sort ( valnames ,
function ( a_Val1 , a_Val2 )
return ( a_Val1.Name < a_Val2.Name )
end
)
2016-05-22 05:54:31 -04:00
for _ , valname in ipairs ( valnames ) do
2016-05-22 11:00:07 -04:00
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 \t Name = \" " , valname.Name , " \" , \n " )
if ( valname.DoxyComment ) then
a_File : write ( " \t \t \t \t \t Desc = " , string.format ( " %q " , valname.DoxyComment ) , " , \n " )
end
a_File : write ( " \t \t \t \t }, \n " )
2016-05-22 05:54:31 -04:00
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" -> { {<description>}, ...}
-- a_Variables is a dictionary of variable descriptions: "name" -> {<description>}
-- a_Constants is a dictionary of constant descriptions: "name" -> {<description>}
-- 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 " )
2016-05-22 11:00:07 -04:00
if ( a_Class.DoxyComment ) then
f : write ( " \t \t Desc = " , string.format ( " %q " , a_Class.DoxyComment ) , " , \n " )
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- 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 \t Inherits = \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
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the functions:
outputClassFunctionDocs ( f , a_Class , a_Functions )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the variables:
outputClassVariableDocs ( f , a_Class , a_Variables )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the constants:
outputClassConstantDocs ( f , a_Class , a_Constants , ignoredConstants )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the enums:
outputClassEnumDocs ( f , a_Class , a_Class.enums )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- Output the footer:
2016-05-29 04:25:46 -04:00
f : write ( " \t }, \n } \n " )
2016-05-22 05:54:31 -04:00
f : close ( )
end
2016-06-05 15:32:24 -04:00
--- Recursively applies the next_DoxyComment member to the next item, and first_child_DoxyComment to first child item in the container.
2016-05-22 07:51:41 -04:00
-- a_Container is the ToLua++'s table potentially containing children as its array members
local function applyNextDoxyComments ( a_Container )
2016-06-05 15:32:24 -04:00
-- 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:
2016-05-22 07:51:41 -04:00
local i = 1
while ( a_Container [ i ] ) do
if ( a_Container [ i ] . next_DoxyComment ) then
if ( a_Container [ i + 1 ] ) then
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.
2016-05-22 05:54:31 -04:00
local function genPackageDocs ( a_Self )
2016-05-22 07:51:41 -04:00
-- 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
2016-09-08 17:05:52 -04:00
2016-05-22 07:51:41 -04:00
applyNextDoxyComments ( a_Self )
2016-05-22 05:54:31 -04:00
-- Generate docs for each member:
local i = 1
local filenames = { }
while ( a_Self [ i ] ) do
2016-06-05 15:32:24 -04:00
if (
a_Self [ i ] : check_public_access ( ) and -- Do not export private and protected members
a_Self [ i ] . genDocs
) then
2016-05-22 05:54:31 -04:00
a_Self [ i ] : genDocs ( filenames )
end
i = i + 1
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- 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
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- 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 \n Generating docs for class " .. a_Self.lname )
local visited = { [ a_Self.parent ] = " <package> " }
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
2016-06-05 15:32:24 -04:00
if (
a_Self [ i ] : check_public_access ( ) and -- Don't export private and protected members
a_Self [ i ] . genMemberDocs
) then
2016-05-22 05:54:31 -04:00
a_Self [ i ] : genMemberDocs ( functions , variables , constants )
end
i = i + 1
end
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
-- 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
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
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 )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
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 ) ,
}
2016-06-05 15:32:24 -04:00
local _ , _ , hasStatic = string.find ( a_Self.mod , " ^%s*(static) " )
if ( hasStatic ) then
desc.IsStatic = true
end
2016-05-22 05:54:31 -04:00
table.insert ( fn , desc )
end
local function genVariableMemberDocs ( a_Self , a_Functions , a_Variables , a_Constants )
assert ( a_Self.lname )
2016-09-08 17:05:52 -04:00
2016-05-22 05:54:31 -04:00
local desc =
{
Name = a_Self.lname ,
Type = a_Self.type ,
DoxyComment = a_Self.DoxyComment ,
}
2016-09-08 17:05:52 -04:00
2016-06-05 16:04:56 -04:00
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
2016-05-22 05:54:31 -04:00
a_Constants [ a_Self.lname ] = desc
2016-06-05 16:04:56 -04:00
else
a_Variables [ a_Self.lname ] = desc
2016-05-22 05:54:31 -04:00
end
end
2016-05-22 07:51:41 -04:00
--- 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 ( )
2016-09-08 17:05:52 -04:00
2016-05-22 07:51:41 -04:00
-- 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
2015-05-12 03:20:54 -04:00
function pre_output_hook ( a_Package )
OutputLuaStateHelpers ( a_Package )
2016-06-05 15:32:24 -04:00
-- 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 " <no message> " ) )
else
print ( " API docs have been generated. " ) ;
end
2015-05-12 03:20:54 -04:00
end
2016-05-22 07:51:41 -04:00
--- Outputs the documentation files.
-- Called by ToLua++ after writing its generated files.
-- a_Package is ToLua++'s classPackage object
2016-05-22 05:54:31 -04:00
function post_output_hook ( a_Package )
2016-06-05 15:32:24 -04:00
print ( " Lua bindings have been generated. " )
2016-05-22 05:54:31 -04:00
end
2016-05-22 07:51:41 -04:00
--- 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 " )
2016-09-08 17:05:52 -04:00
2016-05-22 07:51:41 -04:00
-- Replace all DoxyComments with placeholders so that they aren't erased later on:
a_Package.code = a_Package.code
2016-05-22 11:00:07 -04:00
: gsub ( " /%*%*%s*(.-)%s*%*/ " ,
2016-05-22 07:51:41 -04:00
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
2016-05-22 11:00:07 -04:00
return " \19 " .. n .. " \20 \n "
2016-05-22 07:51:41 -04:00
end
2016-06-05 15:32:24 -04:00
) -- Replace ///< comments with an ID into a lookup table wrapped in DC3 and DC4
2016-08-06 12:28:57 -04:00
--[[
-- Output the preprocessed code out to a file for manual inspection:
2016-05-22 11:00:07 -04:00
local f = io.open ( " code_out.cpp " , " wb " )
2016-05-22 07:51:41 -04:00
f : write ( a_Package.code )
f : close ( )
2016-08-06 12:28:57 -04:00
--]]
2016-05-22 07:51:41 -04:00
end
2016-05-22 11:00:07 -04:00
--- 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 ( )
2017-06-21 09:10:32 -04:00
--Hook for normal enums
2016-05-22 11:00:07 -04:00
local oldEnumerate = Enumerate
2017-06-21 09:10:32 -04:00
Enumerate = function ( a_Name , a_Body , a_VarName , a_Type )
2016-05-22 11:00:07 -04:00
-- 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
2017-06-21 09:10:32 -04:00
local res = oldEnumerate ( a_Name , " { " .. table.concat ( enumValues , " , " ) .. " } " , a_VarName , a_Type )
res.DoxyComments = doxyComments
return res
end
--Hook for scoped enums
local oldScopedEnum = ScopedEnum
ScopedEnum = function ( a_Name , a_Body , a_VarName , a_Type )
-- We need to remove the DoxyComment items from the enum class
-- 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 = oldScopedEnum ( a_Name , " { " .. table.concat ( enumValues , " , " ) .. " } " , a_VarName , a_Type )
2016-05-22 11:00:07 -04:00
res.DoxyComments = doxyComments
return res
end
end
2016-05-22 05:54:31 -04:00
if not ( TOLUA_VERSION ) then
-- BindingsProcessor has been called standalone, invoke the entire ToLua++ machinery:
print ( " Generating Lua bindings and docs... " )
invokeToLua ( )
return
2016-05-22 11:00:07 -04:00
else
-- We're being executed from inside the ToLua++ parser. Install the needed hooks:
installEnumHook ( )
2015-05-12 03:20:54 -04:00
end