// LuaStateParams.h // Declares the cLuaStateParams class that provides high-level functions for reading API parameters from Lua state #pragma once #include "LuaState.h" #include "tolua++/include/tolua++.h" namespace Detail { /** Utility struct that provides the functionality to cLuaStateParams::GetTypeDescription. Supports decorators such as cLuaStateParams::cSelf. Must be a struct in order to support decorators, and must not be inside a class (gcc / clang complain about that). The general declaration must not be used by the code, all types need a specialization returning the correct type. */ template struct TypeDescription { static const char * desc() { static_assert(false, "Type is missing a TypeDescription<> template"); /* If compiler complains on the previous line, you need to make sure that the type passed as the template parameter to this structure has an appropriate TypeDescription specialization. Usually these are created automatically for all API classes by ToLua++ in the LuaStateParams_TypeDescs.inc file. For basic types, the specializations are below. */ } }; // Specializations are defined after the cLuaStateParams class declaration } /** A namespace-like class for reading parameters to API functions from Lua, using templates. The Read() function is the main entrypoint for this, and does all the work. Example usage: int Param1, Param2, Param3; AString ParamA, ParamB; cItem * Item; switch (cLuaStateParams::Read(LuaState, std::tie(cLuaStateParams::self(Item), Param1, Param2, Param3), std::tie(cLuaStateParams::self(Item), ParamA, ParamB) )) { case 0: { // The first overload was used; Item, Param1, Param2 and Param3 are valid Item->DoSomething(Param1, Param2, Param3); break; } case 1: { // The second overload was used; Item, ParamA and ParamB are valid Item->DoSomething(ParamA, ParamB); break; } // No need for default: if no overloads match, the function raises a Lua error, returning to the Lua's caller } */ class cLuaStateParams { public: /** A wrapper used in cLuaStateParams::Read() to signalize that the value must be a non-nil pointer when read from Lua. */ template class cNonNil { friend class cLuaStateParams; typedef T * Tptr; public: typedef T ParentType; cNonNil(Tptr & a_Dest): m_Dest(a_Dest) { } protected: /** The destination pointer where the param will be written. */ Tptr & m_Dest; }; template static cNonNil nonNil(T * & a_Dest) { return cNonNil(a_Dest); } /** A wrapper used in cLuaStateParams::Read() to signalize that the value is a "self" - it must be a non-nil pointer when read from Lua, and has a special error message when mismatched. */ template class cSelf { friend class cLuaStateParams; typedef T * Tptr; public: typedef T ParentType; cSelf(Tptr & a_Self): m_Self(a_Self) { } protected: Tptr & m_Self; }; template static cSelf self(T * & a_Self) { return cSelf(a_Self); } /** A wrapper used in cLuaState::ReadParams() to signalize that the value is a "static self", it must be the table representing the class T when read from Lua. */ template class cStaticSelf { public: typedef T ParentType; }; template static cStaticSelf staticSelf() { return cStaticSelf(); } protected: /** Attempts to match the params on the Lua stack to the API function overloads given (recursively). a_CurOverload is the index of the currently processed overload (recursion level), Returns the index of the overload that matches the parameters. If no overloads match, returns -1. */ template static int ReadInternal(cLuaState & a_LuaState, int a_CurOverload, std::tuple & a_Ovl, T2 &&... a_OtherParams) { // Try to read this overload if (ReadSingleOverload(a_LuaState, a_Ovl)) { return a_CurOverload; } // Try the next overload: return ReadInternal(a_LuaState, a_CurOverload + 1, std::forward(a_OtherParams)...); } /** Terminator for the template-based recursion of the function above - for a single overload. */ template static int ReadInternal(cLuaState & a_LuaState, int a_CurOverload, std::tuple & a_Ovl) { // Try to read this overload if (ReadSingleOverload(a_LuaState, a_Ovl)) { return a_CurOverload; } // No more overloads, report failure return -1; } /** Helper struct to implement iterating over std::tuple elements */ template struct SizeT {}; /** Attempts to match the params on the Lua stack to the given API function overload. Returns true if successful, false on failure. */ template static bool ReadSingleOverload(cLuaState & a_LuaState, std::tuple & a_Overload) { // Check that there exactly as many params as the tuple items: if (!lua_isnone(a_LuaState, sizeof...(T) + 1)) { // Too many params return false; } if (lua_isnone(a_LuaState, sizeof...(T))) { // Too few params return false; } // Read the tuple, compile-time-recursively: return ReadSingleOverloadRecurse(a_LuaState, std::forward>(a_Overload), SizeT()); } /** Attempts to match the params on the Lua stack to the given API function overload. The compile-time recursive worker implementation of ReadSingleOverload, recurses by the number of elements in the overload tuple. Returns true on success, false on failure. */ template static bool ReadSingleOverloadRecurse(cLuaState & a_LuaState, std::tuple & a_Overload, SizeT) { // First read the params from the lower tuple indices: if (!ReadSingleOverloadRecurse(a_LuaState, a_Overload, SizeT())) { return false; } // Then read the param pointed to by our index: return GetStackValue(a_LuaState, N, std::get(a_Overload)); } /** Terminator for the above compile-time-recursive function. */ template static bool ReadSingleOverloadRecurse(cLuaState & a_LuaState, std::tuple & a_Overload, SizeT<1>) { return GetStackValue(a_LuaState, 1, std::get<0>(a_Overload)); } /** Reads one value from the Lua stack. Returns true on success, false on failure. The hard work is delegated into cLuaState that already has this function, but we need to specialize it for decorators (cNonNil, cSelf etc.). */ template static bool GetStackValue(cLuaState & a_LuaState, int a_StackPos, T && a_ReturnedVal) { return a_LuaState.GetStackValue(a_StackPos, std::forward(a_ReturnedVal)); } /** Reads one value from the Lua stack. Returns true on success, false on failure. Specialization: Retrieves a value that should represent a non-nil pointer. Used primarily for ReadParams(... nonNil(...)) */ template static bool GetStackValue(cLuaState & a_LuaState, int a_StackPos, cNonNil & a_ReturnedVal) { auto res = GetStackValue(a_LuaState, a_StackPos, a_ReturnedVal.m_Dest); if (res) { if (a_ReturnedVal.m_Dest == nullptr) { return false; } } return res; } /** Reads one value from the Lua stack. Returns true on success, false on failure. Specialization: Retrieves a value that should represent a pointer to self - must not be nil. Used primarily for ReadParams(self(...)) */ template static bool GetStackValue(cLuaState & a_LuaState, int a_StackPos, cSelf & a_ReturnedVal) { auto res = GetStackValue(a_LuaState, a_StackPos, a_ReturnedVal.m_Self); if (res) { if (a_ReturnedVal.m_Self == nullptr) { return false; } } return res; } /** Reads one value from the Lua stack. Returns true on success, false on failure. Specialization: The value should represent a class and has no useful meaning, it is not read, just type-checked. Used primarily for ReadParams(staticSelf(...)) */ template static bool GetStackValue(cLuaState & a_LuaState, int a_StackPos, cStaticSelf & a_ReturnedVal) { return (CheckValueType(a_LuaState, a_StackPos, a_ReturnedVal).empty()); } /** Raises a Lua error that the parameters don't match the overloads. Builds and logs the whole error message, including the reason why each overload wasn't matched. */ template static void RaiseError(cLuaState & a_LuaState, T &&... a_Overloads) { auto matcherMsgs = BuildMatcherErrorMessages(a_LuaState, std::forward(a_Overloads)...); a_LuaState.ApiParamError("Parameters don't match function signatures:\n%s", StringJoin(matcherMsgs, "\n\t").c_str() ); } /** Returns a vector of string, each item representing a single overload's signature and the error message from the matcher why the signature cannot be used. */ template static AStringVector BuildMatcherErrorMessages( cLuaState & a_LuaState, const std::tuple & a_Overload1, T2 &&... a_OtherOverloads ) { auto res = BuildMatcherErrorMessages(a_LuaState, std::forward(a_OtherOverloads)...); auto signature = BuildSingleOverloadDescription(); auto msg = GetMatcherErrorMessage(a_LuaState, a_Overload1); res.emplace_back(std::move(Printf("(%s): %s", signature.c_str(), msg.c_str()))); return res; } /** Terminator for the function above. */ static AStringVector BuildMatcherErrorMessages(cLuaState & a_LuaState) { return AStringVector(); } /** Returns the error message why the specified overload signature cannot be used for current params. */ template static AString GetMatcherErrorMessage(cLuaState & a_LuaState, const std::tuple & a_Overload) { if (!lua_isnone(a_LuaState, static_cast(sizeof...(T) + 1))) { return Printf("There are more parameters present (%d) than the signature allows (%u)", lua_gettop(a_LuaState), static_cast(sizeof...(T)) ); } if (lua_isnone(a_LuaState, static_cast(sizeof...(T)))) { return Printf("There are not enough parameters present (%d) to match the signature (%u).", lua_gettop(a_LuaState), static_cast(sizeof...(T)) ); } return GetParamMatchError(a_LuaState, a_Overload, SizeT()); } /** Returns the string describing why the specified overload signature doesn't match current params. Checks each individual param using compile-time recursion. Doesn't check param end (checked by GetMatcherErrorMessage(), which is the only one calling this function). */ template static AString GetParamMatchError(cLuaState & a_LuaState, const std::tuple & a_Overload, SizeT) { // Try to read the param into a dummy variable of the proper type: auto res = CheckValueType(a_LuaState, static_cast(N), std::get(a_Overload)); if (!res.empty()) { return Printf("Parameter %u: %s", static_cast(N), res.c_str()); } // Reading succeeded, try the next param: return GetParamMatchError(a_LuaState, a_Overload, SizeT()); } /** Terminator for the above function We've checked all the params, so this should never be reached. */ template static AString GetParamMatchError(cLuaState & a_LuaState, const std::tuple & a_Overload, SizeT<0>) { return "[internal matcher error - no reason for mismatch can be found]"; } /** Returns an error message if the value on the specified index on the Lua stack is of the wrong (template) type. If the type matches, returns an empty string. This is the generic version for regular values. */ template static AString CheckValueType(cLuaState & a_LuaState, int a_StackPos, const T & a_Dest) { typename std::remove_reference::type dummy; if (!a_LuaState.GetStackValue(a_StackPos, dummy)) { return Printf("Mismatch, expected %s, got %s", GetTypeDescription::type>().c_str(), a_LuaState.GetTypeText(a_StackPos).c_str() ); } return AString(); } /** Returns an error message if the value on the specified index on the Lua stack is of the wrong (template) type. This is the specialization for "cNonNil"-decorated values. */ template static AString CheckValueType(cLuaState & a_LuaState, int a_StackPos, const cNonNil & a_Dest) { if (lua_isnil(a_LuaState, a_StackPos)) { return Printf("Expected a non-nil instance of %s, got a nil", GetTypeDescription::type>().c_str(), a_LuaState.GetTypeText(a_StackPos).c_str() ); } return CheckValueType(a_LuaState, a_StackPos, a_Dest.m_Dest); } /** Returns an error message if the value on the specified index on the Lua stack is of the wrong (template) type. This is the specialization for "cSelf"-decorated values. */ template static AString CheckValueType(cLuaState & a_LuaState, int a_StackPos, const cSelf & a_Dest) { if (lua_isnil(a_LuaState, a_StackPos)) { return Printf("Expected an instance of %s, got a %s. Did you use the right calling convention?", GetTypeDescription::type>().c_str(), a_LuaState.GetTypeText(a_StackPos).c_str() ); } return CheckValueType(a_LuaState, a_StackPos, a_Dest.m_Self); } /** Returns an error message if the value on the specified index on the Lua stack is of the wrong (template) type. This is the specialization for "cStaticSelf"-decorated values. */ template static AString CheckValueType(cLuaState & a_LuaState, int a_StackPos, const cStaticSelf & a_Dest) { tolua_Error err; auto type = GetTypeDescription::type>::type>(); if (lua_isnil(a_LuaState, a_StackPos)) { return Printf("Expected the class %s, got a nil", type.c_str()); } if (tolua_isusertype(a_LuaState, a_StackPos, type.c_str(), 0, &err)) { return Printf("Expected the class %s, got a %s. This function is static, remember to use the right calling convention.", type.c_str(), a_LuaState.GetTypeText(a_StackPos).c_str() ); } return AString(); } /** Returns a string describing the function overload composed of the specified (template) types. */ template static AString BuildSingleOverloadDescription() { return GetTypeDescription() + ", " + BuildSingleOverloadDescription(); } /** Terminator for the above function. */ template static AString BuildSingleOverloadDescription() { return GetTypeDescription(); } /** Returns the Lua type representing the specified C++ type. Supports decorators such as cSelf, references and pointers. */ template static AString GetTypeDescription(void) { return Detail::TypeDescription::type>::type>::desc(); } public: /** The main entrypoint for API param reading. Tries to match the parameters on the Lua stack onto the specified overloads. Each overload is a std::tuple<...> of references which are to be filled with the values read. Returns the 0-based index of the first overload that matched the Lua params. If no overloads match, raises a Lua error with detailed information about why there was no match. The design goals: - In case of success, use as little overhead as possible - In case of error, provide as much information as possible, even at the cost of huge overhead The caller should order the overloads in the order by expected usage - the most used ones first. Because the overloads are checked sequentially, this provides the least overhead. Uses compile-time template recursion to iterate over all overloads and all their items. Call tree: Read +- ReadInternal | +- ReadSingleOverload | +- ReadSingleOverloadRecurse | +- GetStackValue | +- cLuaState::GetStackValue | +- CheckValueType [for cStaticSelf] +- RaiseError | +- BuildMatcherErrorMessages | | +- BuildSingleOverloadDescription | | | +- GetTypeDescription | | | +- Detail::TypeDescription::desc | | +- GetMatcherErrorMessage | | +- GetParamMatchError | | +- CheckValueType | | +- cLuaState::GetStackValue | +- cLuaState::ApiParamError +- return [never reached] */ template static int Read(cLuaState & a_LuaState, OverloadTuples &&... a_Overloads) { // If the reading succeeded, return success auto res = ReadInternal(a_LuaState, 0, std::forward(a_Overloads)...); if (res >= 0) { return res; } // The reading failed, raise an error: RaiseError(a_LuaState, std::forward(a_Overloads)...); return 0; // Never reached, but undefined behavior if not present } }; namespace Detail { // Specializations for basic types: template <> struct TypeDescription { static const char * desc() { return "string"; } }; template <> struct TypeDescription { static const char * desc() { return "string"; } }; template <> struct TypeDescription { static const char * desc() { return "number"; } }; template <> struct TypeDescription { static const char * desc() { return "number"; } }; template <> struct TypeDescription { static const char * desc() { return "number"; } }; template <> struct TypeDescription { static const char * desc() { return "number"; } }; // Specializations for decorated types: template struct TypeDescription> { static const char * desc() { return TypeDescription::desc(); } }; template struct TypeDescription &> { static const char * desc() { return TypeDescription::desc(); } }; template struct TypeDescription> { static const char * desc() { return TypeDescription::desc(); } }; template struct TypeDescription &> { static const char * desc() { return TypeDescription::desc(); } }; template struct TypeDescription> { static const char * desc() { return TypeDescription::desc(); } }; template struct TypeDescription &> { static const char * desc() { return TypeDescription::desc(); } }; // Include the TypeDecription specializations generated for all known API classes: #include "LuaStateParams_TypeDescs.inc" }