// // #ifndef FS2_OPEN_ADE_ARGS_H #define FS2_OPEN_ADE_ARGS_H #include "globalincs/pstypes.h" #include "mod_table/mod_table.h" #include "scripting/ade.h" #include "scripting/lua/LuaConvert.h" #include "scripting/lua/LuaFunction.h" #include "scripting/lua/LuaTable.h" #include extern "C" { #include #include } namespace scripting { namespace internal { //*************************Lua hacks************************* // WMC - Hack to allow for quick&easy return value parsing extern int Ade_get_args_skip; // WMC - Tell ade_get_args it is parsing scripting functions, // which have no upvalues extern bool Ade_get_args_lfunction; struct get_args_state { int nargs = -1; int counted_args = -1; int needed_args = -1; int total_args = -1; bool optional_args = false; char funcname[128] = "\0"; }; bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, bool* b); template typename std::enable_if::value, bool>::type get_single_arg(lua_State* L, const get_args_state& state, char fmt, T* f) { Assertion(fmt == 'f' || fmt == 'd', "Invalid character '%c' for number type!", fmt); if (lua_isnumber(L, state.nargs)) { *f = (T)lua_tonumber(L, state.nargs); } else { LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", state.funcname, state.nargs, ade_get_type_string(L, state.nargs)); return false; } return true; } template typename std::enable_if::value, bool>::type get_single_arg(lua_State* L, const get_args_state& state, char fmt, T* i) { // fix is also an int for C++ so we need to check the format character to determine what should be done Assertion(fmt == 'i' || fmt == 'x', "Invalid character '%c' for number type!", fmt); if (lua_isnumber(L, state.nargs)) { if (fmt == 'x') { *i = (T)fl2f((float)lua_tonumber(L, state.nargs)); } else { *i = (T)lua_tonumber(L, state.nargs); } } else { LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", state.funcname, state.nargs, ade_get_type_string(L, state.nargs)); return false; } return true; } bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, const char** s); template bool ade_odata_getter_helper(lua_State* L, const get_args_state& state, char fmt, T&& od) { Assertion(fmt == 'o', "Invalid character '%c' for object type!", fmt); if (lua_isuserdata(L, state.nargs)) { // Use the helper function if (!luacpp::convert::popValue(L, std::forward(od), state.nargs, false)) { return false; } } else if (lua_isnil(L, state.nargs) && state.optional_args) { // WMC - Modder has chosen to ignore this argument } else { LuaError(L, "%s: Argument %d is an invalid type '%s'; type '%s' expected", state.funcname, state.nargs, ade_get_type_string(L, state.nargs), internal::getTableEntry(od.idx).GetName()); return false; } return true; } template bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, ade_odata_getter&& od) { return ade_odata_getter_helper(L, state, fmt, std::forward>(od)); } template bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, ade_odata_ptr_getter&& od) { return ade_odata_getter_helper(L, state, fmt, std::forward>(od)); } bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaTable* t); bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaFunction* f); bool get_single_arg(lua_State* L, const get_args_state& state, char fmt, luacpp::LuaValue* f); // This is not a template function so we can put the implementation in a source file inline bool get_args_actual(lua_State* /*L*/, get_args_state& state, const char* fmt) { while (*fmt != '\0') { switch(*fmt) { case '|': state.optional_args = true; break; case '*': state.nargs++; state.counted_args++; break; default: Assertion(false, "Invalid format string '%s'!", fmt); return false; } // Ignored parameters are still valid here so we skip them until we reach the end ++fmt; } // Now there should be nothing left in the parameter string Assertion(strlen(fmt) == 0, "No class parameters left but format is not empty!"); return true; } template bool get_args_actual(lua_State* L, get_args_state& state, const char* fmt, T&& current, Args&&... args) { Assertion(strlen(fmt) > 0, "Format was empty but there were still parameters in the argument list!"); if (state.nargs > state.total_args) { return true; } if (*fmt == '*') { state.nargs++; state.counted_args++; return get_args_actual(L, state, fmt + 1, std::forward(current), std::forward(args)...); } if (*fmt == '|') { state.optional_args = true; return get_args_actual(L, state, fmt + 1, std::forward(current), std::forward(args)...); } if (!get_single_arg(L, state, *fmt, std::forward(current))) { if (!state.optional_args) { return false; } } state.nargs++; state.counted_args++; return get_args_actual(L, state, fmt + 1, std::forward(args)...); } } // namespace internal /** * @brief Parses arguments from the Lua stack * * based on "Programming in Lua" * * Parses arguments from string to variables given * a '|' divides required and optional arguments. * Returns 0 if a required argument is invalid, * or there are too few arguments actually passed * * @note This function essentially takes objects * from the stack in series, so it can easily be used * to get the return values from a chunk of Lua code * after it has been executed. See RunByteCode() * * @param L The lua state * @param fmt The argument format string * @param ... The parameters * @return The number of arguments taken from the stack or 0 on error */ template inline int ade_get_args(lua_State* L, const char* fmt, Args&&... args) { // Capture these variables locally and reset them. This is needed in case we indirectly call this function again // from below which would cause some weird issues. This can happen if we call a Lua API function which then causes a // GC run which then calls one of our destructors. const auto get_args_skip = internal::Ade_get_args_skip; const auto get_args_lfunction = internal::Ade_get_args_lfunction; internal::Ade_get_args_skip = 0; internal::Ade_get_args_lfunction = false; // Check that we have all the arguments that we need // If we don't, return 0 internal::get_args_state state; state.needed_args = (int)strlen(fmt); state.total_args = lua_gettop(L) - get_args_skip; if (strchr(fmt, '|') != nullptr) { state.needed_args = (int)(strchr(fmt, '|') - fmt); } memset(state.funcname, 0, sizeof(state.funcname)); #ifndef NDEBUG lua_Debug ar; memset(&ar, 0, sizeof(ar)); if (lua_getstack(L, 0, &ar)) { lua_getinfo(L, "nl", &ar); strcpy_s(state.funcname, ""); if (ar.name != nullptr) { strcat_s(state.funcname, ar.name); } if (ar.currentline > -1) { char buf[33]; sprintf(buf, "%d", ar.currentline); strcat_s(state.funcname, " (Line "); strcat_s(state.funcname, buf); strcat_s(state.funcname, ")"); } } #endif if (!strlen(state.funcname)) { // WMC - This was causing crashes with user-defined functions. // WMC - Try and get at function name from upvalue if (!get_args_lfunction && !lua_isnone(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX))) { if (lua_type(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)) == LUA_TSTRING) strcpy_s(state.funcname, lua_tostring_nullsafe(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX))); } // WMC - Totally unknown function if (!strlen(state.funcname)) { strcpy_s(state.funcname, ""); } } if (state.total_args < state.needed_args) { LuaError(L, "Not enough arguments for '%s' - need %d, had %d. If you are using objects or handles, make sure that " "you are using \":\" to access member functions, rather than \".\"", state.funcname, state.needed_args, state.total_args); return 0; } // Start throught state.counted_args = 0; // Are we parsing optional args yet? state.optional_args = false; state.nargs = 1 + get_args_skip; state.total_args += get_args_skip; if (!internal::get_args_actual(L, state, fmt, std::forward(args)...)) { return 0; } return state.counted_args; } namespace internal { struct set_args_state { int nargs; int setargs; // args actually set }; // This is an extremely complicated way of telling the compiler that this function should not be called with pointers template typename std::enable_if::type, bool>::value, void>::type set_single_arg(lua_State* L, char fmt, T b) { Assertion(fmt == 'b', "Invalid format character '%c' for boolean type!", fmt); lua_pushboolean(L, b ? 1 : 0); } template typename std::enable_if::value, void>::type set_single_arg(lua_State* L, char fmt, T f) { Assertion(fmt == 'f' || fmt == 'd', "Invalid character '%c' for number type!", fmt); lua_pushnumber(L, static_cast(f)); } template typename std::enable_if::value && std::is_integral::value, void>::type set_single_arg(lua_State* L, char fmt, T i) { // fix is also an int for C++ so we need to check the format character to determine what should be done Assertion(fmt == 'i' || fmt == 'x', "Invalid character '%c' for number type!", fmt); if (fmt == 'x') { lua_pushnumber(L, static_cast(f2fl((fix)i))); } else { lua_pushinteger(L, static_cast(i)); } } void set_single_arg(lua_State* L, char fmt, const char* s); void set_single_arg(lua_State* L, char fmt, const SCP_string& s); template void set_single_arg(lua_State* L, char fmt, ade_odata_setter&& od) { Assertion(fmt == 'o', "Invalid format character '%c' for object type!", fmt); if (Lua_API_returns_nil_instead_of_invalid_object) { // If the underlying type has an isValid, it'll be evaluated; otherwise it'll be always true. bool isValid = ade_is_valid::get(od.value); if (!isValid) { lua_pushnil(L); return; } } // Use the common helper method luacpp::convert::pushValue(L, std::forward>(od)); } void set_single_arg(lua_State* /*L*/, char fmt, luacpp::LuaTable* table); void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaTable& table); void set_single_arg(lua_State* /*L*/, char fmt, luacpp::LuaFunction* func); void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaFunction& func); void set_single_arg(lua_State* /*L*/, char fmt, const luacpp::LuaValue& func); // This is not a template function so we can put the implementation in a source file inline void set_args_actual(lua_State* /*L*/, set_args_state& /*state*/, const char* fmt) { Assertion(strlen(fmt) == 0, "No class parameters left but format is not empty!"); } template void set_args_actual(lua_State* L, set_args_state& state, const char* fmt, T&& current, Args&&... args) { Assertion(strlen(fmt) > 0, "Format was empty but there were still parameters in the argument list!"); if (*fmt == '*') { lua_pushnil(L); state.nargs++; state.setargs++; return set_args_actual(L, state, fmt + 1, std::forward(current), std::forward(args)...); } set_single_arg(L, *fmt, std::forward(current)); state.nargs++; state.setargs++; return set_args_actual(L, state, fmt + 1, std::forward(args)...); } } // namespace internal // ade_set_args(state, arguments, variables) //---------------------------------------------- // based on "Programming in Lua" // // Takes variables given and pushes them onto the // Lua stack. Use it to return variables from a // Lua scripting function. // // NOTE: You can also use this to push arguments // on to the stack in series. See script_state::SetHookVar template int ade_set_args(lua_State* L, const char* fmt, Args&&... args) { // Start throught internal::set_args_state state{}; state.nargs = 0; state.setargs = 0; internal::set_args_actual(L, state, fmt, std::forward(args)...); return state.setargs; } /** * @brief Return a value in error. Normally this behaves just like ade_set_args, but it can be configured to return nil instead. * * Should be used if the value is not valid * * @ingroup ade_api */ template inline int ade_set_error(lua_State* L, const char* fmt, Args&&... args) { if (Lua_API_returns_nil_instead_of_invalid_object) return 0; // ADE_RETURN_NIL else return ade_set_args(L, fmt, std::forward(args)...); } } #endif //FS2_OPEN_ADE_ARGS_H