-- $LastChangedDate: 2011-03-15 14:18:37 -0400 (Tue, 15 Mar 2011) $
-- LEGAL: COPYRIGHT (C) 2010 JIM E. BROOKS WWW.PALOMINO3D.ORG
-- Macro processor written in Lua to expand C++ code for bindings between C++/Lua.
-- Usage: inputFile outputFile

inputFile  = arg[1]  -- this is Lua's argv which is indeed 0-based
outputFile = arg[2]

if ( not string.find( inputFile, ".lua" )
  or not string.find( outputFile, ".cc" ) ) then
    print( "bad args" )
    os.exit( 1 )
end

-- Infer src dir from input file.
srcDir = string.match( inputFile, ".*/" )
if ( not srcDir ) then
    srcDir = "./"
end

dofile( inputFile )

-- =============================================================================

-- (optional) Expand #if/#endif for conditional-compile of entire C++ src file.
-- bind.condCompile would be "COMPILE_*" or similar #define.
function ProcessBeginConditionalCompile( bindTable )

    if ( bindTable.conditionalCompile ) then
        return "\n#if " .. bindTable.conditionalCompile .. "\n"
    else
        return ""
    end

end

function ProcessEndConditionalCompile( bindTable )

    if ( bindTable.conditionalCompile ) then
        return "\n#endif // " .. bindTable.conditionalCompile .. "\n"
    else
        return ""
    end

end

-- Expand C++ top comments.
function ProcessTopComments( bindTable )

    local moreComments = bindTable.moreComments or "@remarks module.txt documents how to export new C++ classes to Lua.\n"

return [[
/*****************************************************************************
 * $LastChangedDate: 2011-03-15 14:18:37 -0400 (Tue, 15 Mar 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Interface of C functions for Lua scripts.
 * @remarks ]] .. moreComments .. [[
 *
 * ----------------------------
 * -- THIS IS AUTO-GENERATED --
 * ----------------------------
 *
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/
]]

end

-- Expand C++ header includes.
function ProcessHeaders( bindTable )

    local buf = "\n" .. bindTable.headers

    buf = buf .. [[
#include "lua_bind/module.hh"
#include "lua_bind/bind.hh"
]]

    return buf

end

-- Expand #defines for C++ class.
function ProcessClass( bindTable )

return [[
#define BIND_CLASS_NAME      "]] .. bindTable.class .. [["
#define BIND_METATABLE_NAME  LUABIND_METATABLE_STRING(BIND_CLASS_NAME)
]]

end

-- Expand additional C++ code (optional).
function ProcessMoreCode( bindTable )

    if ( bindTable.moreCode ) then
        return bindTable.moreCode
    else
        return ""
    end

end

-- Expand typesig (optional).
function ProcessTypesig( bindTable )

    if ( not bindTable.typesig ) then
        return ""
    end

return [[
// Need a function to check typesig because of macro/template limitations.
#if ! DOXYGEN
INTERN void
CheckTypesig]] .. bindTable.class .. [[( shptr<]] .. bindTable.class .. [[> obj )
{
    CHECK_TYPESIG(obj,]] .. bindTable.typesig .. [[);
}
#endif // ! DOXYGEN
]]

end

-- Expand all C++/Lua binding functions
function ProcessFunctions( bindTable )

    local buf = ""
    for i=1, #bindTable.funcNames do
        local funcName = bindTable.funcNames[i]
        local bindFuncName = bindTable.class .. "_" .. funcName .. "( lua_State* L ) \n"  -- "Class_Func()"
        buf = buf .. "\n/*****************************************************************************\n"
        buf = buf .. " * " .. bindTable.funcComments[funcName] .. "\n"
        buf = buf .. " *****************************************************************************/\n"
        buf = buf .. "INTERN int \n"
        buf = buf .. bindFuncName
        buf = buf .. "{ \n"
        buf = buf .. "   try { \n\n"
        buf = buf .. bindTable.funcCode[funcName] .. "\n"
        buf = buf .. "   } CATCH_LUA_EXCEPTION \n"
        buf = buf .. "   return 0; \n"
        buf = buf .. "} \n"
    end

    return buf
end

-- Generate sMethodTable[].
function ProcessMethodTable( bindTable )

    local buf = [[
INTERN const struct luaL_reg sMethodTable[] =
{
]]

    if ( bindTable.luaCanInstantiate ) then

        buf = buf .. [[
  { "New", ]] .. bindTable.class .. [[_New },
  { "__gc", ]] .. bindTable.class .. [[_Destroy },
]]

    else

        buf = buf .. [[
  // New(),__gc() omitted as Lua cannot instantiate this C++ class.
]]

    end

    for i=1, #bindTable.funcNames do
        local funcName = bindTable.funcNames[i]
        -- New/__gc are special-cases.
        if ( (funcName ~= "Destroy") and (funcName ~= "New") ) then
            buf = buf .. [[  { "]] .. funcName .. [[", ]] .. bindTable.class .. [[_]] .. bindTable.funcNames[i] .. [[ },]]
            buf = buf .. "\n"
        end
    end

    buf = buf .. [[
  { NULL, NULL }
};
]]

    return buf

end

-- Expand ProcessRegisterBinding() function.
function ProcessRegisterBinding( bindTable )

    local moreCodeRegister = bindTable.moreCodeRegister or ""

    return [[

void
Register]] .. bindTable.class .. [[( lua_State* L )
{
    REGISTER_BINDING( L,
                      BIND_METATABLE_NAME,
                      BIND_CLASS_NAME,
                      sMethodTable );
]] .. moreCodeRegister .. [[
}
]]

end

-- Top-level.
function Process( bindTable, outputFile )

    -- Build sorted table of function names.
    -- And map function comments/code by function name.
    bindTable.funcNames = {}
    bindTable.funcComments = {}
    bindTable.funcCode = {}
    for k,v in pairs(bindTable) do
        if ( string.find( k, "func_" ) ) then
            local funcName = string.sub( k, 6, -1 )
            if ( (not funcName) or (funcName == "") ) then
                print( "ERROR: parsing function name " .. k )
                os.exit( 1 )
            end

            table.insert( bindTable.funcNames, funcName )  -- array form
            bindTable.funcComments[funcName] = v.comments  -- map form
            bindTable.funcCode[funcName] = v.code  -- map form

            if ( (not v.comments) or (not v.code) ) then
                print( "ERROR: missing comments or C++ code for C++/Lua binding '" .. funcName .. "'" )
                os.exit( 1 )
            end
        end
    end
    table.sort( bindTable.funcNames )

    local buf = ProcessTopComments( bindTable )
    buf = buf .. ProcessBeginConditionalCompile( bindTable )
    buf = buf .. ProcessHeaders( bindTable )
    buf = buf .. "\nnamespace lua {\n\n"
    buf = buf .. "\nnamespace bind {\n\n"
    buf = buf .. ProcessClass( bindTable )
    buf = buf .. ProcessMoreCode( bindTable )
    buf = buf .. "\n//==============================================================================\n"
    buf = buf .. "// C functions called by Lua that access C++ objects.\n"
    buf = buf .. "//==============================================================================\n"
    buf = buf .. ProcessTypesig( bindTable )
    buf = buf .. ProcessFunctions( bindTable )
    buf = buf .. "\n//==============================================================================\n"
    buf = buf .. "// Register binding with Lua. \n"
    buf = buf .. "//==============================================================================\n\n"
    buf = buf .. "#if ! DOXYGEN \n\n"
    buf = buf .. ProcessMethodTable( bindTable )
    buf = buf .. ProcessRegisterBinding( bindTable )
    buf = buf .. "\n#endif // ! DOXYGEN \n"
    buf = buf .. "\n} // namespace bind \n"
    buf = buf .. "\n} // namespace lua \n"
    buf = buf .. ProcessEndConditionalCompile( bindTable )

    -- Write to file.
    io.output( outputFile )
    io.write( buf )
    io.close()

end

-- =============================================================================

Process( bindTable, outputFile )
