mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
869 lines
27 KiB
C++
869 lines
27 KiB
C++
//
|
|
//
|
|
#include "LuaSEXP.h"
|
|
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "localization/localize.h"
|
|
#include "mission/missiongoals.h"
|
|
#include "mission/missionmessage.h"
|
|
#include "object/waypoint.h"
|
|
#include "parse/parselo.h"
|
|
#include "parse/sexp.h"
|
|
#include "parse/sexp/sexp_lookup.h"
|
|
#include "scripting/api/objs/enums.h"
|
|
#include "scripting/api/objs/event.h"
|
|
#include "scripting/api/objs/goal.h"
|
|
#include "scripting/api/objs/hudgauge.h"
|
|
#include "scripting/api/objs/message.h"
|
|
#include "scripting/api/objs/model.h"
|
|
#include "scripting/api/objs/oswpt.h"
|
|
#include "scripting/api/objs/prop.h"
|
|
#include "scripting/api/objs/propclass.h"
|
|
#include "scripting/api/objs/sexpvar.h"
|
|
#include "scripting/api/objs/ship.h"
|
|
#include "scripting/api/objs/shipclass.h"
|
|
#include "scripting/api/objs/sound.h"
|
|
#include "scripting/api/objs/subsystem.h"
|
|
#include "scripting/api/objs/team.h"
|
|
#include "scripting/api/objs/waypoint.h"
|
|
#include "scripting/api/objs/weaponclass.h"
|
|
#include "scripting/api/objs/wing.h"
|
|
#include "scripting/scripting.h"
|
|
#include "ship/ship.h"
|
|
#include "prop/prop.h"
|
|
#include "utils/string_utils.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
using namespace luacpp;
|
|
|
|
namespace sexp {
|
|
|
|
static SCP_unordered_map<SCP_string, int> parameter_type_mapping {
|
|
{ "boolean", OPF_BOOL },
|
|
{ "number", OPF_NUMBER },
|
|
{ "string", OPF_STRING },
|
|
{ "ship", OPF_SHIP },
|
|
{ "shipname", OPF_SHIP },
|
|
{ "prop", OPF_PROP },
|
|
{ "propname", OPF_PROP },
|
|
{ "team", OPF_IFF },
|
|
{ "waypointpath", OPF_WAYPOINT_PATH },
|
|
{ "waypoint", OPF_POINT },
|
|
{ "variable", OPF_VARIABLE_NAME },
|
|
{ "message", OPF_MESSAGE },
|
|
{ "wing", OPF_WING },
|
|
{ "shipclass", OPF_SHIP_CLASS_NAME },
|
|
{ "propclass", OPF_PROP_CLASS_NAME },
|
|
{ "weaponclass", OPF_WEAPON_NAME },
|
|
{ "soundentry", OPF_GAME_SND },
|
|
{ "ship+waypoint",OPF_SHIP_POINT },
|
|
{ "ship+wing", OPF_SHIP_WING },
|
|
{ "ship+wing+team", OPF_SHIP_WING_WHOLETEAM },
|
|
{ "ship+wing+ship_on_team+waypoint", OPF_SHIP_WING_SHIPONTEAM_POINT },
|
|
{ "ship+wing+waypoint", OPF_SHIP_WING_POINT },
|
|
{ "ship+wing+waypoint+none", OPF_SHIP_WING_POINT_OR_NONE },
|
|
{ "subsystem", OPF_SUBSYSTEM },
|
|
{ "subsystemname", OPF_SUBSYSTEM },
|
|
{ "dockpoint", OPF_DOCKER_POINT },
|
|
{ "hudgauge", OPF_ANY_HUD_GAUGE },
|
|
{ "event", OPF_EVENT_NAME },
|
|
{ "goal", OPF_GOAL_NAME },
|
|
{ "child_enum", OPF_CHILD_LUA_ENUM },
|
|
{ "custom_string",OPF_MISSION_CUSTOM_STRING },
|
|
{ "enum", First_available_opf_id } };
|
|
|
|
// If a parameter requires a parent parameter then it must be listed here!
|
|
static SCP_vector<SCP_string> parent_parameter_required{"subsystem", "subsystemname", "dockpoint", "child_enum"};
|
|
|
|
std::pair<SCP_string, int> LuaSEXP::get_parameter_type(const SCP_string& name)
|
|
{
|
|
SCP_string copy = name;
|
|
SCP_tolower(copy);
|
|
|
|
auto iter = parameter_type_mapping.find(copy);
|
|
if (iter == parameter_type_mapping.end()) {
|
|
return std::pair<SCP_string, int>(copy, -1);
|
|
} else {
|
|
return std::pair<SCP_string, int>(copy, iter->second);
|
|
}
|
|
}
|
|
|
|
static SCP_unordered_map<SCP_string, int> return_type_mapping{{ "number", OPR_NUMBER },
|
|
{ "boolean", OPR_BOOL },
|
|
{ "nothing", OPR_NULL }, };
|
|
int LuaSEXP::get_return_type(const SCP_string& name)
|
|
{
|
|
SCP_string copy = name;
|
|
SCP_tolower(copy);
|
|
|
|
auto iter = return_type_mapping.find(copy);
|
|
if (iter == return_type_mapping.end()) {
|
|
return -1;
|
|
} else {
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
LuaSEXP::LuaSEXP(const SCP_string& name) : DynamicSEXP(name) {
|
|
}
|
|
void LuaSEXP::initialize() {
|
|
// Nothing to do for this type
|
|
}
|
|
int LuaSEXP::getMinimumArguments() const {
|
|
return _min_args;
|
|
}
|
|
int LuaSEXP::getMaximumArguments() const {
|
|
return _max_args;
|
|
}
|
|
std::pair<SCP_string, int> LuaSEXP::getArgumentInternalType(int argnum) const {
|
|
if (argnum < 0) {
|
|
return std::pair<SCP_string, int>(SEXP_NONE_STRING, OPF_NONE);
|
|
}
|
|
|
|
if (argnum < (int) _argument_types.size()) {
|
|
// Normal, non variable argument types
|
|
return _argument_types[argnum];
|
|
}
|
|
|
|
// sanity check in case of bad table data
|
|
if (_varargs_type_pattern.empty()) {
|
|
Warning(LOCATION, "Not enough parameters specified for Lua SEXP %s!", getName().c_str());
|
|
return std::pair<SCP_string, int>(SEXP_NONE_STRING, OPF_NONE);
|
|
}
|
|
|
|
// We are in the variable argument types region
|
|
// First, adjust the argnum base index so that our variable index starts at 0
|
|
auto varargs_index = argnum - _argument_types.size();
|
|
|
|
// Then use modulo magic to bring the argument number into the right range
|
|
varargs_index = varargs_index % _varargs_type_pattern.size();
|
|
|
|
// And then use that to get the parameter type
|
|
return _varargs_type_pattern[varargs_index];
|
|
}
|
|
int LuaSEXP::getArgumentType(int argnum) const {
|
|
return getArgumentInternalType(argnum).second;
|
|
}
|
|
luacpp::LuaValue LuaSEXP::sexpToLua(int node, int argnum, int parent_node) const {
|
|
using namespace scripting::api;
|
|
auto argtype = getArgumentInternalType(argnum);
|
|
|
|
switch (argtype.second) {
|
|
case OPF_BOOL: {
|
|
auto value = is_sexp_true(node) != 0;
|
|
return LuaValue::createValue(_action.getLuaState(), value);
|
|
}
|
|
case OPF_NUMBER: {
|
|
bool is_nan, is_nan_forever;
|
|
auto res = eval_num(node, is_nan, is_nan_forever);
|
|
float value;
|
|
if (is_nan || is_nan_forever) {
|
|
value = std::numeric_limits<float>::quiet_NaN();
|
|
} else {
|
|
value = (float) res;
|
|
}
|
|
return LuaValue::createValue(_action.getLuaState(), value);
|
|
}
|
|
case OPF_VARIABLE_NAME: {
|
|
// Variable names work by getting the variable index from the text node
|
|
auto sexp_variable_index = sexp_get_variable_index(node);
|
|
|
|
// Add the variable to the parameter list as a SEXPVariable object handle
|
|
return LuaValue::createValue(_action.getLuaState(), l_SEXPVariable.Set(sexpvar_h(sexp_variable_index)));
|
|
}
|
|
case OPF_IFF: {
|
|
auto team_idx = iff_lookup(CTEXT(node));
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Team.Set(team_idx));
|
|
}
|
|
case OPF_WAYPOINT_PATH: {
|
|
waypoint_list *wp_list = find_matching_waypoint_list(CTEXT(node));
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_WaypointList.Set(waypointlist_h(wp_list)));
|
|
}
|
|
// The following argument types are all strings
|
|
case OPF_SHIP: {
|
|
auto ship_entry = eval_ship(node);
|
|
|
|
// if this is a shipname type, we want the name of a valid ship but not the ship itself
|
|
// (if the ship is not valid, return an empty string)
|
|
if (argtype.first == "shipname") {
|
|
return LuaValue::createValue(_action.getLuaState(), ship_entry ? ship_entry->name : "");
|
|
}
|
|
|
|
if (!ship_entry || !ship_entry->has_objp()) {
|
|
// Name is invalid
|
|
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h()));
|
|
}
|
|
|
|
auto objp = ship_entry->objp();
|
|
|
|
// The other SEXP code does not validate the object type so this should be safe
|
|
Assertion(objp->type == OBJ_SHIP,
|
|
"Ship '%s' was found in the Ships array but has a different object type in the Objects array. Get a coder!",
|
|
CTEXT(node));
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Ship.Set(object_h(objp)));
|
|
}
|
|
case OPF_PROP: {
|
|
auto prop_entry = eval_prop(node);
|
|
|
|
// if this is a propname type, we want the name of a valid prop but not the prop itself
|
|
// (if the prop is not valid, return an empty string)
|
|
if (argtype.first == "propname") {
|
|
return LuaValue::createValue(_action.getLuaState(), prop_entry ? prop_entry->prop_name : "");
|
|
}
|
|
|
|
if (!prop_entry || (prop_entry->objnum < 0)) {
|
|
// Name is invalid
|
|
return LuaValue::createValue(_action.getLuaState(), l_Prop.Set(object_h()));
|
|
}
|
|
|
|
auto objp = &Objects[prop_entry->objnum];
|
|
|
|
// The other SEXP code does not validate the object type so this should be safe
|
|
Assertion(objp->type == OBJ_PROP,
|
|
"Prop '%s' was found in the Props array but has a different object type in the Objects array. Get a coder!",
|
|
CTEXT(node));
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Prop.Set(object_h(objp)));
|
|
}
|
|
case OPF_MESSAGE: {
|
|
auto name = CTEXT(node);
|
|
|
|
auto idx = -1;
|
|
for (int i = Num_builtin_messages; i < (int) Messages.size(); i++)
|
|
{
|
|
if (!stricmp(Messages[i].name, name))
|
|
{
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Message.Set(idx));
|
|
}
|
|
case OPF_WING: {
|
|
auto wingp = eval_wing(node);
|
|
int wingnum = WING_INDEX(wingp);
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Wing.Set(wingnum));
|
|
}
|
|
case OPF_SHIP_CLASS_NAME: {
|
|
auto name = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), l_Shipclass.Set(ship_info_lookup(name)));
|
|
}
|
|
case OPF_PROP_CLASS_NAME: {
|
|
auto name = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), l_Propclass.Set(prop_info_lookup(name)));
|
|
}
|
|
case OPF_WEAPON_NAME: {
|
|
auto name = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), l_Weaponclass.Set(weapon_info_lookup(name)));
|
|
}
|
|
case OPF_GAME_SND: {
|
|
// handle a table index, an operator, or a name (matching what the core SEXPs accept);
|
|
// -1 or <none> yields an invalid handle that the script can check with :isValid()
|
|
return LuaValue::createValue(_action.getLuaState(), l_SoundEntry.Set(sound_entry_h(sexp_get_sound_index(node))));
|
|
}
|
|
case OPF_STRING: {
|
|
auto text = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), text);
|
|
}
|
|
case OPF_POINT: {
|
|
auto wpt = find_matching_waypoint(CTEXT(node));
|
|
return LuaValue::createValue(_action.getLuaState(), l_Waypoint.Set(object_h(wpt->get_objnum())));
|
|
}
|
|
case OPF_SHIP_POINT:
|
|
case OPF_SHIP_WING:
|
|
case OPF_SHIP_WING_WHOLETEAM:
|
|
case OPF_SHIP_WING_SHIPONTEAM_POINT:
|
|
case OPF_SHIP_WING_POINT:
|
|
case OPF_SHIP_WING_POINT_OR_NONE: {
|
|
object_ship_wing_point_team oswpt;
|
|
eval_object_ship_wing_point_team(&oswpt, node);
|
|
return LuaValue::createValue(_action.getLuaState(), l_OSWPT.Set(oswpt));
|
|
}
|
|
case OPF_SUBSYSTEM: {
|
|
auto name = CTEXT(node);
|
|
|
|
// if this is a subsystemname type, we want the name of a valid subsystem but not the subsystem itself
|
|
if (argtype.first == "subsystemname") {
|
|
return LuaValue::createValue(_action.getLuaState(), name);
|
|
}
|
|
|
|
//Use the parent node to get the index of the parameter we should
|
|
//look at to find the parent object
|
|
int index = get_dynamic_parameter_index(_name, argnum);
|
|
if (index < 0)
|
|
error_display(1, "Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!", argnum, _name.c_str());
|
|
|
|
int this_node = parent_node;
|
|
for (int i = 0; i <= index; i++) {
|
|
this_node = CDR(this_node);
|
|
}
|
|
|
|
auto ship_entry = eval_ship(this_node);
|
|
|
|
if (!ship_entry || !ship_entry->has_objp()) {
|
|
// Ship is not present in the mission (never arrived, destroyed, or departed)
|
|
return LuaValue::createValue(_action.getLuaState(), l_Subsystem.Set(ship_subsys_h()));
|
|
}
|
|
|
|
ship_subsys* ss = ship_get_subsys(ship_entry->shipp(), name);
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Subsystem.Set(ship_subsys_h(ship_entry->objp(), ss)));
|
|
}
|
|
case OPF_DOCKER_POINT: {
|
|
auto name = CTEXT(node);
|
|
|
|
// Use the parent node to get the index of the parameter we should
|
|
// look at to find the parent object
|
|
int index = get_dynamic_parameter_index(_name, argnum);
|
|
|
|
if (index < 0)
|
|
error_display(1,
|
|
"Expected to find a dynamic lua parent parameter for node %i in operator %s but found nothing!",
|
|
argnum,
|
|
_name.c_str());
|
|
|
|
int this_node = parent_node;
|
|
for (int i = 0; i <= index; i++) {
|
|
this_node = CDR(this_node);
|
|
}
|
|
|
|
auto ship_entry = eval_ship(this_node);
|
|
if (!ship_entry || !ship_entry->has_shipp()) {
|
|
// Name is invalid
|
|
return LuaValue::createValue(_action.getLuaState(), l_Dockingbay.Set(dockingbay_h()));
|
|
}
|
|
|
|
auto docker_pm = model_get(Ship_info[ship_entry->shipp()->ship_info_index].model_num);
|
|
auto dockindex = model_find_dock_name_index(Ship_info[ship_entry->shipp()->ship_info_index].model_num, name);
|
|
|
|
return LuaValue::createValue(_action.getLuaState(), l_Dockingbay.Set(dockingbay_h(docker_pm, dockindex)));
|
|
}
|
|
case OPF_ANY_HUD_GAUGE: {
|
|
auto name = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), l_HudGauge.Set(hud_get_gauge(name, true)));
|
|
}
|
|
case OPF_EVENT_NAME: {
|
|
auto name = CTEXT(node);
|
|
int i = mission_event_lookup(name);
|
|
return LuaValue::createValue(_action.getLuaState(), l_Event.Set(i));
|
|
}
|
|
case OPF_GOAL_NAME: {
|
|
auto name = CTEXT(node);
|
|
int i = mission_goal_lookup(name);
|
|
return LuaValue::createValue(_action.getLuaState(), l_Goal.Set(i));
|
|
}
|
|
case OPF_CHILD_LUA_ENUM: {
|
|
auto text = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), text);
|
|
}
|
|
case OPF_MISSION_CUSTOM_STRING: {
|
|
auto cs = get_custom_string_by_name(CTEXT(node));
|
|
auto table = luacpp::LuaTable::create(_action.getLuaState());
|
|
|
|
table.addValue("Name", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->name));
|
|
table.addValue("Value", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->value));
|
|
table.addValue("String", luacpp::LuaValue::createValue(Script_system.GetLuaSession(), cs->text));
|
|
|
|
return table;
|
|
}
|
|
default:
|
|
if ((strcmp(argtype.first.c_str(), "enum")) == 0) {
|
|
auto text = CTEXT(node);
|
|
return LuaValue::createValue(_action.getLuaState(), text);
|
|
} else {
|
|
Assertion(false,
|
|
"Unhandled argument type '%s'! Someone added an argument type but didn't add handling code to execute().", argtype.first.c_str());
|
|
return LuaValue::createNil(_action.getLuaState());
|
|
}
|
|
}
|
|
}
|
|
bool LuaSEXP::maybeExtractSexpSpecialRetVal(const luacpp::LuaValue& value, int& sexp_retval) {
|
|
// see if this return value is actually a SEXP_ enum
|
|
if (value.getValueType() == ValueType::USERDATA) {
|
|
try {
|
|
using namespace scripting::api;
|
|
enum_h enumeration;
|
|
value.getValue(l_Enum.Get(&enumeration)); // this might throw a LuaException
|
|
switch (enumeration.index) {
|
|
case LE_SEXP_TRUE:
|
|
case LE_SEXP_FALSE:
|
|
case LE_SEXP_KNOWN_FALSE:
|
|
case LE_SEXP_KNOWN_TRUE:
|
|
case LE_SEXP_UNKNOWN:
|
|
case LE_SEXP_NAN:
|
|
case LE_SEXP_NAN_FOREVER:
|
|
case LE_SEXP_CANT_EVAL:
|
|
sexp_retval = *enumeration.value;
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
} catch (const LuaException& le) {
|
|
// just ignore the exception because we follow a different code path if the conversion failed
|
|
SCP_UNUSED(le);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
int LuaSEXP::getSexpReturnValue(const LuaValueList& retVals) const {
|
|
switch (_return_type) {
|
|
case OPR_NUMBER:
|
|
if (retVals.size() != 1) {
|
|
Warning(LOCATION,
|
|
"Wrong number of return values for Lua SEXP '%s'! Expected 1, got " SIZE_T_ARG ".",
|
|
_name.c_str(),
|
|
retVals.size());
|
|
return 0;
|
|
} else if (retVals[0].getValueType() != ValueType::NUMBER) {
|
|
int sexp_retval;
|
|
if (maybeExtractSexpSpecialRetVal(retVals[0], sexp_retval)) {
|
|
return sexp_retval;
|
|
}
|
|
Warning(LOCATION, "Wrong return type detected for Lua SEXP '%s', expected a number.", _name.c_str());
|
|
return 0;
|
|
} else {
|
|
return retVals[0].getValue<int>();
|
|
}
|
|
case OPR_BOOL:
|
|
if (retVals.size() != 1) {
|
|
Warning(LOCATION,
|
|
"Wrong number of return values for Lua SEXP '%s'! Expected 1, got " SIZE_T_ARG ".",
|
|
_name.c_str(),
|
|
retVals.size());
|
|
return SEXP_FALSE;
|
|
} else if (retVals[0].getValueType() != ValueType::BOOLEAN) {
|
|
int sexp_retval;
|
|
if (maybeExtractSexpSpecialRetVal(retVals[0], sexp_retval)) {
|
|
return sexp_retval;
|
|
}
|
|
Warning(LOCATION, "Wrong return type detected for Lua SEXP '%s', expected a boolean.", _name.c_str());
|
|
return SEXP_FALSE;
|
|
} else {
|
|
return retVals[0].getValue<bool>() ? SEXP_TRUE : SEXP_FALSE;
|
|
}
|
|
case OPR_NULL:
|
|
if (retVals.size() != 0) {
|
|
Warning(LOCATION,
|
|
"Wrong number of return values for Lua SEXP '%s'! Expected 0, got " SIZE_T_ARG ".",
|
|
_name.c_str(),
|
|
retVals.size());
|
|
}
|
|
return SEXP_TRUE;
|
|
default:
|
|
return SEXP_TRUE;
|
|
}
|
|
}
|
|
|
|
luacpp::LuaValueList LuaSEXP::getSEXPArgumentList(int node, int parent_node) const {
|
|
LuaValueList luaParameters;
|
|
|
|
// We need to adapt how we handle parameters based on their type. We use this variable to keep track of which parameter
|
|
// we are currently looking at
|
|
int argnum = 0;
|
|
while (node != -1) {
|
|
if (argnum < (int)_argument_types.size()) {
|
|
// This is a parameter in the normal list so we add it to the normal parameter list
|
|
luaParameters.push_back(sexpToLua(node, argnum, parent_node));
|
|
|
|
node = CDR(node);
|
|
++argnum;
|
|
}
|
|
else {
|
|
// The varargs part is handled in chunks so that scripts can use the data more easily
|
|
// Every repeat pattern instance is put into its own table
|
|
LuaTable varargs_part = LuaTable::create(_action.getLuaState());
|
|
|
|
// Iterate over all parameters in this varargs pattern and put them all in the table
|
|
// If we reach the end of the parameter list inside this for loop we just exit since all parameters are optional
|
|
for (auto i = 0; i < (int)_varargs_type_pattern.size() && node != -1; ++i) {
|
|
// i + 1 since lua arrays are 1-indexed
|
|
varargs_part.addValue(i + 1, sexpToLua(node, argnum, parent_node));
|
|
|
|
node = CDR(node);
|
|
++argnum;
|
|
}
|
|
|
|
luaParameters.push_back(varargs_part);
|
|
}
|
|
}
|
|
|
|
return luaParameters;
|
|
}
|
|
|
|
int LuaSEXP::execute(int node, int parent_node) {
|
|
if (!_action.isValid()) {
|
|
|
|
Error(LOCATION,
|
|
"Lua SEXP %s called without a valid action function! A script probably failed to set the action for some reason.", _name.c_str());
|
|
return SEXP_CANT_EVAL;
|
|
}
|
|
|
|
LuaValueList luaParameters = getSEXPArgumentList(node, parent_node);
|
|
|
|
// All parameters are now in LuaValues, time to call our function
|
|
try {
|
|
auto retVals = _action.call(Script_system.GetLuaSession(), luaParameters);
|
|
|
|
return getSexpReturnValue(retVals);
|
|
} catch(const LuaException&) {
|
|
// This is only caused by an error in the function but those are already handled by our error function
|
|
// These values are close to what the existing SEXP code does if errors occur while processing a SEXP
|
|
switch(_return_type) {
|
|
case OPR_NUMBER:
|
|
case OPR_BOOL:
|
|
return SEXP_NAN;
|
|
case OPR_NULL:
|
|
return SEXP_TRUE;
|
|
default:
|
|
return SEXP_NAN;
|
|
}
|
|
}
|
|
}
|
|
int LuaSEXP::getReturnType() {
|
|
return _return_type;
|
|
}
|
|
|
|
int LuaSEXP::getSubcategory() {
|
|
return _subcategory;
|
|
}
|
|
int LuaSEXP::getCategory() {
|
|
return _category;
|
|
}
|
|
bool LuaSEXP::parseCheckEndOfDescription() {
|
|
// Since we're stuffing strings, this is the best way to "unstuff" a string
|
|
// if we determine we're finished with the description - and we also want
|
|
// to preserve whitespace (which the parser eats) while building the description
|
|
pause_parse();
|
|
|
|
// look for any token that can follow $Description
|
|
auto possible_tokens =
|
|
{
|
|
"$Operator:",
|
|
"#End",
|
|
"$Repeat",
|
|
"$Parameter:"
|
|
};
|
|
|
|
bool found = false;
|
|
for (auto token : possible_tokens)
|
|
{
|
|
if (optional_string(token))
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
unpause_parse();
|
|
return found;
|
|
}
|
|
void LuaSEXP::parseTable() {
|
|
required_string("$Category:");
|
|
SCP_string category;
|
|
stuff_string(category, F_NAME);
|
|
|
|
_category = get_category(category);
|
|
if (_category == OP_CATEGORY_NONE) {
|
|
// Unknown category so we need to add this one
|
|
_category = sexp::add_category(category);
|
|
}
|
|
|
|
if (optional_string("$Subcategory:")) {
|
|
SCP_string subcategory;
|
|
stuff_string(subcategory, F_NAME);
|
|
|
|
_subcategory = get_subcategory(subcategory, _category);
|
|
if (_subcategory == OP_SUBCATEGORY_NONE) {
|
|
// Unknown subcategory so we need to add this one
|
|
_subcategory = sexp::add_subcategory(_category, subcategory);
|
|
}
|
|
} else {
|
|
_subcategory = OP_SUBCATEGORY_NONE;
|
|
}
|
|
|
|
required_string("$Minimum Arguments:");
|
|
|
|
stuff_int(&_min_args);
|
|
|
|
if (_min_args < 0) {
|
|
error_display(0, "Minimum argument number must be at least 0! Got %d.", _min_args);
|
|
_min_args = 0;
|
|
}
|
|
|
|
if (optional_string("$Maximum Arguments:")) {
|
|
stuff_int(&_max_args);
|
|
|
|
if (_max_args < 0) {
|
|
error_display(0, "Maximum argument number must be at least 0! Got %d.", _max_args);
|
|
_max_args = 0;
|
|
}
|
|
} else {
|
|
_max_args = INT_MAX;
|
|
}
|
|
|
|
if (_max_args < _min_args) {
|
|
error_display(0, "Maximum argument number must be greater or equal to minimum number of arguments!");
|
|
std::swap(_min_args, _max_args);
|
|
}
|
|
|
|
if (optional_string("$Return Type:")) {
|
|
SCP_string type;
|
|
stuff_string(type, F_NAME);
|
|
|
|
_return_type = get_return_type(type);
|
|
|
|
if (_return_type < 0) {
|
|
error_display(0, "Unknown return type '%s'!", type.c_str());
|
|
_return_type = OPR_NULL;
|
|
}
|
|
} else {
|
|
_return_type = OPR_NULL;
|
|
}
|
|
|
|
SCP_stringstream help_text;
|
|
help_text << _name << "\r\n";
|
|
|
|
required_string("$Description:");
|
|
|
|
SCP_string description, extra;
|
|
stuff_string(description, F_NAME);
|
|
while (!parseCheckEndOfDescription()) {
|
|
while (skip_eoln()) {
|
|
description += "\r\n";
|
|
}
|
|
stuff_string(extra, F_NAME);
|
|
description += extra;
|
|
}
|
|
|
|
help_text << "\t" << description << "\r\n\r\n";
|
|
|
|
// argument string
|
|
help_text << "Takes ";
|
|
if (_max_args == INT_MAX) {
|
|
help_text << _min_args << " or more arguments:";
|
|
} else {
|
|
if (_max_args == 0) {
|
|
help_text << "no arguments.";
|
|
} else if (_min_args == _max_args) {
|
|
help_text << _min_args << " argument";
|
|
if (_min_args > 1) {
|
|
help_text << "s";
|
|
}
|
|
help_text << ":";
|
|
} else {
|
|
help_text << _min_args << " to " << _max_args << " arguments:";
|
|
}
|
|
}
|
|
help_text << "\r\n";
|
|
|
|
bool variable_arg_part = false;
|
|
if (optional_string("$Repeat")) {
|
|
help_text << "Rest: (The following pattern repeats)\r\n";
|
|
variable_arg_part = true;
|
|
}
|
|
int param_index = 0;
|
|
while (optional_string("$Parameter:")) {
|
|
|
|
required_string("+Description:");
|
|
|
|
SCP_string param_desc;
|
|
stuff_string(param_desc, F_NAME);
|
|
|
|
if (variable_arg_part) {
|
|
help_text << "\t" << _varargs_type_pattern.size() + 1;
|
|
} else {
|
|
help_text << "\t" << _argument_types.size() + 1;
|
|
}
|
|
help_text << ": " << param_desc << "\r\n";
|
|
|
|
required_string("+Type:");
|
|
SCP_string type_str;
|
|
stuff_string(type_str, F_NAME);
|
|
|
|
auto type = get_parameter_type(type_str);
|
|
if (type.second < 0) {
|
|
error_display(0, "Unknown parameter type '%s'!", type_str.c_str());
|
|
type = get_parameter_type("string");
|
|
}
|
|
|
|
if ((strcmp(type.first.c_str(), "enum")) == 0) {
|
|
|
|
required_string("+Enum Name:");
|
|
|
|
SCP_string enum_name;
|
|
stuff_string(enum_name, F_NAME);
|
|
|
|
dynamic_sexp_enum_list thisList;
|
|
int list_position = get_dynamic_enum_position(enum_name);
|
|
bool new_enum = false;
|
|
|
|
if (list_position >= 0) {
|
|
type.second = list_position + (int)First_available_opf_id;
|
|
|
|
//Do this just in case we're going to use this list as a template
|
|
thisList.name = Dynamic_enums[list_position].name;
|
|
for (const SCP_string& enum_item : Dynamic_enums[list_position].list) {
|
|
thisList.list.push_back(enum_item);
|
|
}
|
|
} else {
|
|
new_enum = true;
|
|
thisList.name = enum_name;
|
|
}
|
|
|
|
while (optional_string("+Enum:")) {
|
|
new_enum = true;
|
|
|
|
//If we're making a new Enum based off another one, let's give it a unique name
|
|
if (list_position >= 0) {
|
|
SCP_string newName;
|
|
newName = enum_name;
|
|
newName.append(std::to_string(list_position));
|
|
thisList.name = newName;
|
|
}
|
|
|
|
SCP_string item;
|
|
stuff_string(item, F_NAME);
|
|
|
|
// These characters may not appear in an Enum item
|
|
constexpr const char* ENUM_INVALID_CHARS = "()\"'\\/";
|
|
if (std::strpbrk(item.c_str(), ENUM_INVALID_CHARS) != nullptr) {
|
|
error_display(0, "ENUM item '%s' cannot include these characters [(,),\",',\\,/]. Skipping!\n", item.c_str());
|
|
|
|
// Skip the invalid entry
|
|
continue;
|
|
|
|
}
|
|
|
|
if (item.length() >= NAME_LENGTH) {
|
|
error_display(0, "Enum item '%s' is longer than %i characters. Truncating!\n", item.c_str(), NAME_LENGTH);
|
|
item.resize(NAME_LENGTH - 1);
|
|
}
|
|
|
|
bool skip = false;
|
|
// Case insensitive check if the item already exists in the list
|
|
for (int i = 0; i < (int)thisList.list.size(); i++) {
|
|
if (lcase_equal(item, thisList.list[i])) {
|
|
error_display(0, "Enum item '%s' already exists in list %s. Skipping!\n", item.c_str(), thisList.name.c_str());
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (skip)
|
|
continue;
|
|
|
|
thisList.list.push_back(std::move(item));
|
|
}
|
|
|
|
if (thisList.list.size() == 0) {
|
|
mprintf(("Parsed empty enum list '%s'. Adding <none>.\n", thisList.name.c_str()));
|
|
thisList.list.push_back("<none>");
|
|
}
|
|
|
|
if (new_enum) {
|
|
type.second = increment_enum_list_id();
|
|
Dynamic_enums.push_back(std::move(thisList));
|
|
} else {
|
|
// Not an error but large mods may lose track of their enum names and I thought this would be helpful -Mjn
|
|
mprintf(("Found previously existing Lua Enum '%s'. Using that for sexp '%s'!\n",
|
|
enum_name.c_str(),
|
|
_name.c_str()));
|
|
}
|
|
|
|
}
|
|
|
|
bool parent_required = util::isStringOneOf(type.first, parent_parameter_required);
|
|
|
|
// parse this even if this parameter doesn't need a parent, and we'll validate that next
|
|
int parent_param_index = -1;
|
|
if (optional_string("+Parent Parameter Index:")) {
|
|
stuff_int(&parent_param_index);
|
|
parent_param_index--;
|
|
}
|
|
if (parent_param_index >= (param_index + 1)) {
|
|
error_display(1,
|
|
"Parent Parameter Index '%i' cannot be greater or equal to %i (the current parameter index)!\n",
|
|
parent_param_index,
|
|
param_index + 1);
|
|
}
|
|
|
|
if (parent_required)
|
|
{
|
|
std::pair<int, int> param_map(param_index, parent_param_index);
|
|
|
|
// check if this operator already has an entry for dynamic parameters
|
|
int dyn_index = -1;
|
|
for (int i = 0; i < (int)Dynamic_parameters.size(); i++) {
|
|
if (lcase_equal(Dynamic_parameters[i].operator_name, _name)) {
|
|
dyn_index = i;
|
|
}
|
|
}
|
|
|
|
if (dyn_index >= 0) {
|
|
Dynamic_parameters[dyn_index].parameter_map.push_back(param_map);
|
|
} else {
|
|
dynamic_sexp_parameter_list dyn_param;
|
|
dyn_param.operator_name = _name;
|
|
dyn_param.parameter_map.push_back(param_map);
|
|
|
|
Dynamic_parameters.push_back(std::move(dyn_param));
|
|
}
|
|
}
|
|
else if (parent_param_index >= 0)
|
|
{
|
|
SCP_string msg = "Parent Parameter Index field was specified in SEXP '";
|
|
msg += _name;
|
|
msg += "' for a parameter type '";
|
|
msg += type.first;
|
|
msg += "' that does not need a parent! Parameter types requiring parents are:";
|
|
for (const auto ¶m: parent_parameter_required)
|
|
{
|
|
msg += "\n\t";
|
|
msg += param;
|
|
}
|
|
error_display(1, "%s", msg.c_str());
|
|
}
|
|
|
|
if (type_str == "child_enum") {
|
|
if (optional_string("+Suffix:")) {
|
|
SCP_string suffix;
|
|
stuff_string(suffix, F_NAME);
|
|
Dynamic_enum_suffixes.push_back({_name, param_index, std::move(suffix)});
|
|
}
|
|
}
|
|
|
|
if (variable_arg_part) {
|
|
_varargs_type_pattern.push_back(std::move(type));
|
|
} else {
|
|
_argument_types.push_back(std::move(type));
|
|
}
|
|
|
|
if (optional_string("$Repeat")) {
|
|
if (!variable_arg_part) {
|
|
help_text << "Rest: (The following pattern repeats)\r\n";
|
|
variable_arg_part = true;
|
|
} else {
|
|
error_display(0, "A second $Repeat has been encountered! Only one may appear in the parameter list.");
|
|
}
|
|
}
|
|
param_index++;
|
|
}
|
|
|
|
_help_text = help_text.str();
|
|
lcl_replace_stuff(_help_text, true);
|
|
}
|
|
void LuaSEXP::setAction(const luacpp::LuaFunction& action) {
|
|
Assertion(action.isValid(), "Invalid function handle supplied!");
|
|
_action = action;
|
|
}
|
|
|
|
luacpp::LuaFunction LuaSEXP::getAction() const {
|
|
return _action;
|
|
}
|
|
}
|