Files
Goober5000andClaude Opus 4.8 bed228d264 avoid undefined behavior from UNREACHABLE on reachable code paths (#7555)
* 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]>
2026-07-09 11:20:25 +09:00

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 &param: 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;
}
}