Files
fs2open.github.com/code/scripting/ade_args.cpp
T
asarium f780d9e22a Add ability to dynamically add new SEXPs at runtime
This add a system for adding generic instances of the DynamicSEXP class
to the SEXP system. The only implementation at the moment is the LuaSEXP
which is used by the mn.addSEXP lua API to allow Lua to add new SEXPs.

Since this requires that FRED also runs lua scripts I added a mod table
option to enable scripting in FRED. Since the current scripts are not
built with FRED in mind it would likely break all these scripts if FRED
would be opened with normal scripting enabled.

This fixes #1320.
2017-12-20 10:45:40 +01:00

359 lines
9.7 KiB
C++

#include "scripting/ade_args.h"
#include "scripting/ade.h"
#include "scripting/lua/LuaTable.h"
#include "scripting/lua/LuaFunction.h"
#include "scripting/lua/LuaConvert.h"
#include "mod_table/mod_table.h"
#include <utf8.h>
namespace {
using namespace scripting;
ade_table_entry& getTableEntry(size_t idx) {
return ade_manager::getInstance()->getEntry(idx);
}
}
namespace scripting {
//WMC - hack to skip X number of arguments on the stack
//Lets me use ade_get_args for global hook return values
int Ade_get_args_skip = 0;
bool Ade_get_args_lfunction = false;
//ade_get_args(state, arguments, variables)
//----------------------------------------------
//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()
int ade_get_args(lua_State *L, const char *fmt, ...)
{
//Check that we have all the arguments that we need
//If we don't, return 0
int needed_args = (int)strlen(fmt);
int total_args = lua_gettop(L) - Ade_get_args_skip;
if(strchr(fmt, '|') != NULL) {
needed_args = (int)(strchr(fmt, '|') - fmt);
}
char funcname[128] = "\0";
#ifndef NDEBUG
lua_Debug ar;
memset(&ar, 0, sizeof(ar));
if(lua_getstack(L, 0, &ar))
{
lua_getinfo(L, "nl", &ar);
strcpy_s(funcname, "");
if(ar.name != NULL) {
strcat_s(funcname, ar.name);
}
if(ar.currentline > -1) {
char buf[33];
sprintf(buf, "%d", ar.currentline);
strcat_s(funcname, " (Line ");
strcat_s(funcname, buf);
strcat_s(funcname, ")");
}
}
#endif
if(!strlen(funcname)) {
//WMC - This was causing crashes with user-defined functions.
//WMC - Try and get at function name from upvalue
if(!Ade_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(funcname, lua_tostring(L, lua_upvalueindex(ADE_FUNCNAME_UPVALUE_INDEX)));
}
//WMC - Totally unknown function
if(!strlen(funcname)) {
strcpy_s(funcname, "<UNKNOWN>");
}
}
if(total_args < 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 \".\"", funcname, needed_args, total_args);
return 0;
}
//Start throught
va_list vl;
int nargs;
int counted_args = 0;
//Are we parsing optional args yet?
bool optional_args = false;
va_start(vl, fmt);
nargs = 1 + Ade_get_args_skip;
total_args += Ade_get_args_skip;
while(*fmt && nargs <= total_args)
{
switch(*fmt++)
{
case 'b':
if(lua_isboolean(L, nargs)) {
*va_arg(vl, bool*) = lua_toboolean(L, nargs) > 0 ? true : false;
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; boolean expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'd':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, double*) = (double)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'f':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, float*) = (float)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'i':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, int*) = (int)lua_tonumber(L, nargs);
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 's':
if(lua_isstring(L, nargs)) {
auto value = lua_tostring(L, nargs);
if (Unicode_text_mode) {
// Validate the string when we are in unicode mode to ensure that FSO doesn't just crash when a script
// passes an invalid UTF-8 sequence to the API
auto end = value + strlen(value);
auto invalid = utf8::find_invalid(value, end);
if (invalid != end) {
if (invalid == value) {
// The first character is the invalid sequence
LuaError(L, "An invalid UTF-8 encoding sequence was detected! The first invalid character was as the start of the string.");
} else {
// If the invalid sequence is inside the string then we try to give some context to make
// finding the bug easier
auto display_text_start = std::max(value, invalid - 32);
SCP_string context_text(display_text_start, invalid);
LuaError(L,
"An invalid UTF-8 encoding sequence was detected! The error was detected directly after this string \"%s\".",
context_text.c_str());
}
// Finally, assign a default value so that we can continue
value = "Invalid UTF-8 sequence detected!";
}
}
*va_arg(vl, const char **) = value;
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; string expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'x':
if(lua_isnumber(L, nargs)) {
*va_arg(vl, fix*) = fl2f((float)lua_tonumber(L, nargs));
} else {
LuaError(L, "%s: Argument %d is an invalid type '%s'; number expected", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
case 'o':
{
ade_odata od = va_arg(vl, ade_odata);
if(lua_isuserdata(L, nargs))
{
// Use the helper function
if (!luacpp::convert::popValue(L, od, nargs, false)) {
if (!optional_args) {
va_end(vl);
return 0;
}
}
}
else if(lua_isnil(L, nargs) && optional_args)
{
//WMC - Modder has chosen to ignore this argument
}
else
{
LuaError(L, "%s: Argument %d is an invalid type '%s'; type '%s' expected", funcname, nargs, ade_get_type_string(L, nargs), getTableEntry(od.idx).GetName());
if(!optional_args) {
va_end(vl);
return 0;
}
}
}
break;
case 't':
{
// Get a table
auto target = va_arg(vl, luacpp::LuaTable*);
if (!luacpp::convert::popValue(L, *target, nargs, false)) {
LuaError(L, "%s: Argument %d is an invalid type '%s'; table expected.", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
break;
}
case 'u':
{
// Get a function
auto target = va_arg(vl, luacpp::LuaFunction*);
if (!luacpp::convert::popValue(L, *target, nargs, false)) {
LuaError(L, "%s: Argument %d is an invalid type '%s'; function expected.", funcname, nargs, ade_get_type_string(L, nargs));
if(!optional_args) {
va_end(vl);
return 0;
}
}
// Automatically assign our error function to function values retrieved from the API
target->setErrorFunction(luacpp::LuaFunction::createFromCFunction(L, ade_friendly_error));
break;
}
case '|':
nargs--; //cancel out the nargs++ at the end
counted_args--;
optional_args = true;
break;
case '*':
//WMC - Ignore one spot
break;
default:
Error(LOCATION, "%s: Bad character passed to ade_get_args; (%c)", funcname, *(fmt-1));
break;
}
nargs++;
counted_args++;
}
va_end(vl);
return counted_args;
}
//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
int ade_set_args(lua_State *L, const char *fmt, ...)
{
//Start throught
va_list vl;
int nargs;
int setargs; //args actually set
va_start(vl, fmt);
nargs = 0;
setargs = 0;
while(*fmt != '\0')
{
switch(*fmt++)
{
case '*':
lua_pushnil(L);
break;
case 'b': //WMC - Bool is actually int for GCC (Why...?)
lua_pushboolean(L, va_arg(vl, int) ? 1 : 0);
break;
case 'd':
lua_pushnumber(L, va_arg(vl, double));
break;
case 'f':
lua_pushnumber(L, va_arg(vl, double));
break;
case 'i':
lua_pushnumber(L, va_arg(vl, int));
break;
case 's':
{
//WMC - Isn't working with HookVar for some strange reason
char *s = va_arg(vl, char*);
lua_pushstring(L, s);
break;
}
case 'x':
lua_pushnumber(L, f2fl(va_arg(vl, fix)));
break;
case 'o':
{
//Copy over objectdata
ade_odata od = (ade_odata) va_arg(vl, ade_odata);
// Use the common helper method
luacpp::convert::pushValue(L, od);
break;
}
case 't':
{
// Set a table
luacpp::LuaTable* table = va_arg(vl, luacpp::LuaTable*); // This must be a pointer since C++ classes can't be passed by vararg
table->pushValue();
break;
}
case 'u':
{
// Set a function
luacpp::LuaFunction* func = va_arg(vl, luacpp::LuaFunction*); // This must be a pointer since C++ classes can't be passed by vararg
func->pushValue();
break;
}
//WMC - Don't forget to update lua_set_arg
default:
Error(LOCATION, "Bad character passed to ade_set_args; (%c)", *(fmt-1));
setargs--;
}
nargs++;
setargs++;
}
va_end(vl);
return setargs;
}
}