Files
fs2open.github.com/code/scripting/ade.cpp
T
asarium 367564733e Add parser for scripting argument documentation
This adds a simple parser that takes the existing argument
documentations and parses them so that better documentation can be
generated from that information. This should be sufficient for providing
proper documentation generation for the most common and simple cases of
argument lists (type, name, possible default value, and optional
groups).
2020-06-06 14:02:33 +02:00

1218 lines
32 KiB
C++

//
//
#include "scripting/ade.h"
#include "globalincs/version.h"
#include "ade.h"
#include "ade_api.h"
#include "ade_args.h"
#include "scripting.h"
#include "def_files/def_files.h"
#include "mod_table/mod_table.h"
#include "scripting/api/objs/asteroid.h"
#include "scripting/api/objs/beam.h"
#include "scripting/api/objs/debris.h"
#include "scripting/api/objs/object.h"
#include "scripting/api/objs/ship.h"
#include "scripting/api/objs/waypoint.h"
#include "scripting/api/objs/weapon.h"
#include "scripting/lua/LuaFunction.h"
#include "ship/ship.h"
#include <utility>
namespace {
using namespace scripting;
//1: Userspace variables (ie in object table)
//2: Handle-specific values
//3: Entries in metatable (ie defined by ADE)
//4: Virtual variables
//5: Use the indexer, if possible
//6: Set userspace variable
//7: Set handle-specific variables
//X: Mission failed.
//
//On the stack when this is called:
//Index 1 - Object (Can be anything with Lua 5.1; Number to a library)
//Index 2 - String (ie the key we're trying to access; Object.string, Object:string, Object['string'], etc)
//Index 3 - (Optional) Argument we are trying to set Object.String = Argument
int ade_index_handler(lua_State* L) {
Assert(L != NULL);
const int obj_ldx = 1;
const int key_ldx = 2;
const int arg_ldx = 3;
int last_arg_ldx = lua_gettop(L);
const char* type_name = NULL;
uint ade_id = UINT_MAX;
int mtb_ldx = INT_MAX;
ade_table_entry* entry = 0;
//*****STEP 1: Check for user-defined objects
if (lua_istable(L, obj_ldx) && !ADE_SETTING_VAR) {
lua_pushvalue(L, key_ldx);
lua_rawget(L, obj_ldx);
if (!lua_isnil(L, -1)) {
return 1;
} else
lua_pop(L, 1); //nil value
}
//*****STEP 1.5: Set-up metatable
if (lua_getmetatable(L, obj_ldx)) {
mtb_ldx = lua_gettop(L);
lua_pushcfunction(L, ade_friendly_error);
int err_ldx = lua_gettop(L);
int i;
//*****WMC - go for the type name
lua_pushstring(L, "__adeid");
lua_rawget(L, mtb_ldx);
if (lua_isnumber(L, -1)) {
ade_id = (uint) lua_tonumber(L, -1);
if (ade_id < ade_manager::getInstance()->getNumEntries()) {
entry = &ade_manager::getInstance()->getEntry(ade_id);
type_name = entry->Name;
}
}
lua_pop(L, 1);
//*****STEP 2: Check for __ademember objects (ie defaults)
lua_pushstring(L, "__ademembers");
lua_rawget(L, mtb_ldx);
if (lua_istable(L, -1)) {
int amt_ldx = lua_gettop(L);
lua_pushvalue(L, key_ldx);
lua_rawget(L, amt_ldx);
if (!lua_isnil(L, -1)) {
return 1;
} else
lua_pop(L, 1); //nil value
}
lua_pop(L, 1); //member table
//*****STEP 3: Check for virtual variables
lua_pushstring(L, "__virtvars");
lua_rawget(L, mtb_ldx);
if (lua_istable(L, -1)) {
//Index virtvar function
int vvt_ldx = lua_gettop(L);
lua_pushvalue(L, key_ldx);
lua_rawget(L, vvt_ldx);
if (lua_isfunction(L, -1)) {
//Set upvalue
lua_pushvalue(L, lua_upvalueindex(ADE_SETTING_UPVALUE_INDEX));
if (lua_setupvalue(L, -2, ADE_SETTING_UPVALUE_INDEX) == NULL) {
LuaError(L, "Unable to set upvalue for virtual variable");
}
//Set arguments
//WMC - Skip setting the key
lua_pushvalue(L, obj_ldx);
int numargs = 1;
for (i = arg_ldx; i <= last_arg_ldx; i++) {
lua_pushvalue(L, i);
numargs++;
}
//Execute function
lua_pcall(L, numargs, LUA_MULTRET, err_ldx);
return (lua_gettop(L) - vvt_ldx);
} else {
lua_pop(L, 1); //non-function value
}
}
lua_pop(L, 1); //virtvar table
//*****STEP 4: Use the indexer
//NOTE: Requires metatable from step 1.5
//Get indexer
lua_pushstring(L, "__indexer");
lua_rawget(L, mtb_ldx);
if (lua_isfunction(L, -1)) {
//Function already on stack
//Set upvalue
lua_pushvalue(L, lua_upvalueindex(ADE_SETTING_UPVALUE_INDEX));
if (lua_setupvalue(L, -2, ADE_SETTING_UPVALUE_INDEX) == NULL) {
LuaError(L, "Unable to set upvalue for indexer");
}
//Set arguments
for (i = 1; i <= last_arg_ldx; i++) {
lua_pushvalue(L, i);
}
//Execute function
lua_pcall(L, last_arg_ldx, LUA_MULTRET, err_ldx);
return (lua_gettop(L) - err_ldx);
}
lua_pop(L, 2); //WMC - Don't need __indexer or error handler
}
//*****STEP 6: Set a new variable or die.
if (ADE_SETTING_VAR && lua_istable(L, obj_ldx)) {
lua_pushvalue(L, key_ldx);
lua_pushvalue(L, arg_ldx);
lua_rawset(L, obj_ldx);
lua_pushvalue(L, key_ldx);
lua_rawget(L, obj_ldx);
return 1;
}
lua_pop(L, 1); //WMC - metatable
if (type_name != NULL) {
LuaError(L, "Could not find index '%s' in type '%s'", lua_tostring(L, key_ldx), type_name);
} else {
LuaError(L, "Could not find index '%s'", lua_tostring(L, key_ldx));
}
return 0;
}
ade_table_entry& getTableEntry(size_t idx) {
return ade_manager::getInstance()->getEntry(idx);
}
//Struct for converting one string for another. whee!
struct string_conv {
const char *src;
const char *dest;
};
//*************************Lua operators*************************
//These are the various types of operators you can
//set in Lua. Use these as function name to activate.
//
//Format string should be "*o" or "o*", where "*" is the type of
//variable you want to deal with.
//The order varies with order of variables
string_conv ade_Operators[] = {
{"__add", "+"}, //var + obj
{"__sub", "-"}, //var - obj
{"__mul", "*"}, //var * obj
{"__div", "/"}, //var / obj
{"__mod", "%"}, //var % obj
{"__pow", "^"}, //var ^ obj
{"__unm", "~"}, //var ~ obj
{"__concat", ".."}, //var .. obj
{"__len", "#"}, //#var
{"__eq", "=="}, //var == obj
{"__lt", "<"}, //var < obj
{"__le", "<="}, //var <= obj
{"__newindex", "="}, //var = obj
{"__call", ""}, //*shrug*
{"__gc", "__gc"}, //Lua's equivelant of a destructor
//WMC - Used with tostring() scripting operator.
{"__tostring", "(string)"}, //tostring(var)
//WMC - This is NOT a Lua type, but for the LUA_INDEXER define
{"__indexer", "[]"}, //obj[var]
};
int ade_get_operator(const char *tablename)
{
for(auto iter = std::begin(ade_Operators); iter != std::end(ade_Operators); ++iter)
{
if(!strcmp(tablename, iter->src))
return (int)std::distance(std::begin(ade_Operators), iter);
}
return -1;
}
//WMC - Sometimes this gets out of sync between Lua versions
static const char *Lua_type_names[] = {
"nil",
"boolean",
"light userdata",
"number",
"string",
"table",
"function",
"userdata",
"thread",
};
const ptrdiff_t Lua_type_names_num = std::distance(std::begin(Lua_type_names), std::end(Lua_type_names));
static bool is_internal_type(const char* typeName) {
for (int i = 0; i < Lua_type_names_num; i++) {
if (!stricmp(Lua_type_names[i], typeName)) {
return true;
}
}
return false;
}
}
namespace scripting {
void ade_output_type_link(FILE* fp, const ade_type_info& type_info) {
switch (type_info.getType()) {
case ade_type_info_type::Empty:
fputs("nothing", fp);
break;
case ade_type_info_type::Simple:
if (is_internal_type(type_info.getSimpleName())) {
fputs(type_info.getSimpleName(), fp);
} else {
fprintf(fp, "<a href=\"#%s\">%s</a>", type_info.getSimpleName(), type_info.getSimpleName());
}
break;
case ade_type_info_type::Tuple: {
bool first = true;
for(const auto& type : type_info.elements()) {
if (!first) {
fprintf(fp, ", ");
}
first = false;
ade_output_type_link(fp, type);
}
break;
}
case ade_type_info_type::Array:
fputs("{ ", fp);
ade_output_type_link(fp, type_info.arrayType());
fputs(" ... }", fp);
break;
case ade_type_info_type::Map:
fputs("{ ", fp);
ade_output_type_link(fp, type_info.elements()[0]);
fputs(" -> ", fp);
ade_output_type_link(fp, type_info.elements()[1]);
fputs(" ... }", fp);
break;
case ade_type_info_type::Iterator:
fputs("iterator< ", fp);
ade_output_type_link(fp, type_info.arrayType());
fputs(" >", fp);
break;
case ade_type_info_type::Alternative: {
bool first = true;
for (const auto& type : type_info.elements()) {
if (!first) {
fputs(" | ", fp);
}
first = false;
ade_output_type_link(fp, type);
}
}
}
}
ade_manager* ade_manager::getInstance() {
static std::unique_ptr<ade_manager> manager(new ade_manager());
return manager.get();
}
size_t ade_manager::addTableEntry(const ade_table_entry& entry) {
_table_entries.push_back(entry);
_table_entries.back().Idx = _table_entries.size() - 1;
return _table_entries.size() - 1;
}
ade_table_entry& ade_manager::getEntry(size_t idx) {
Assertion(idx < _table_entries.size(), "Invalid index " SIZE_T_ARG " specified!", idx);
return _table_entries[idx];
}
const ade_table_entry& ade_manager::getEntry(size_t idx) const
{
Assertion(idx < _table_entries.size(), "Invalid index " SIZE_T_ARG " specified!", idx);
return _table_entries[idx];
}
static int deprecatedFunctionHandler(lua_State* L)
{
const char* functionName = lua_tostring(L, lua_upvalueindex(1));
LuaError(L,
"Deprecated function '%s' has been called that is not available in the targetted engine version. Check "
"the documentation for a possible replacement.",
functionName);
return 0;
}
ade_table_entry::ade_table_entry() { memset(Subentries, 0, sizeof(Subentries)); }
// Think of n_mtb_ldx as the parent metatable
int ade_table_entry::SetTable(lua_State* L, int p_amt_ldx, int p_mtb_ldx)
{
uint i;
int cleanup_items = 0;
int mtb_ldx = INT_MAX;
int data_ldx = INT_MAX;
int desttable_ldx = INT_MAX;
int amt_ldx = INT_MAX;
if (Instanced) {
//Set any actual data
int nset = 0;
switch (Type) {
case 'o': {
// Create an empty userdata value and use it as the value for this library
lua_newuserdata(L, 0);
// Create or get object metatable
luaL_getmetatable(L, Name);
// Set the metatable for the object
lua_setmetatable(L, -2);
nset++;
break;
}
case 'u':
case 'v': {
if (DeprecationVersion.isValid() &&
mod_supports_version(DeprecationVersion.major, DeprecationVersion.minor, DeprecationVersion.build)) {
// The deprecation mechanism. Set a function that always errors instead.
if (Name != nullptr) {
lua_pushstring(L, Name);
} else if (ShortName != nullptr) {
lua_pushstring(L, ShortName);
} else {
lua_pushliteral(L, "<UNNAMED FUNCTION>");
}
lua_pushcclosure(L, deprecatedFunctionHandler, 1);
} else {
// WMC - This hack by taylor is a necessary evil.
// 64-bit function pointers do not get passed properly
// when using va_args for some reason.
lua_pushstring(L, "<UNNAMED FUNCTION>");
lua_pushboolean(L, 0);
lua_pushcclosure(L, Function, 2);
}
nset++;
break;
}
default:
UNREACHABLE("Unhandled value type '%c'!", Type);
break;
}
if (nset) {
data_ldx = lua_gettop(L);
} else {
LuaError(L, "ade_table_entry::SetTable - Could not set data for '%s' (" SIZE_T_ARG ")", GetName(), Idx);
}
if (data_ldx != INT_MAX) {
//WMC - Cannot delete libs and stuff off here.
if (p_amt_ldx != LUA_GLOBALSINDEX) {
cleanup_items++;
}
//WMC - Handle virtual variables by getting their table
if (Type == 'v') {
//Get virtvars table
lua_pushstring(L, "__virtvars");
lua_rawget(L, p_mtb_ldx);
if (lua_istable(L, -1)) {
cleanup_items++;
//Virtual variables are stored in virtvar table,
//rather than the parent table
desttable_ldx = lua_gettop(L);
} else {
lua_pop(L, 1);
}
} else {
//WMC - Member objects prefixed with __ are assumed to be metatable objects
if (strnicmp("__", GetName(), 2) != 0 && lua_istable(L, p_amt_ldx)) {
desttable_ldx = p_amt_ldx;
} else if (lua_istable(L, p_mtb_ldx)) {
desttable_ldx = p_mtb_ldx;
}
}
if (desttable_ldx != INT_MAX) {
//If we are setting a function...
if (lua_isfunction(L, data_ldx)) {
//Set the FIRST upvalue to its name,
//so we can always find out what it is for debugging
lua_pushstring(L, GetName());
if (lua_setupvalue(L, data_ldx, 1) == NULL) {
LuaError(L,
"ade_table_entry::SetTable - Could not set upvalue for '%s' (" SIZE_T_ARG ")",
GetName(),
Idx);
}
}
//Register name and shortname
if (Name != NULL) {
lua_pushstring(L, Name);
lua_pushvalue(L, data_ldx);
lua_rawset(L, desttable_ldx);
}
if (ShortName != NULL) {
lua_pushstring(L, ShortName);
lua_pushvalue(L, data_ldx);
lua_rawset(L, desttable_ldx);
}
} else {
LuaError(L, "ade_table_entry::SetTable - Could not instance '%s' (" SIZE_T_ARG ")", GetName(), Idx);
}
}
}
//If subentries, create a metatable pointer and set it
if (Num_subentries
|| (DerivatorIdx != UINT_MAX && ade_manager::getInstance()->getEntry(DerivatorIdx).Num_subentries)) {
//Create the new metatable
if (!luaL_newmetatable(L, Name)) {
LuaError(L,
"ade_table_entry::SetTable - Couldn't create metatable for table entry '%s' - does a Lua object already exist with this name?",
Name);
return 0;
}
mtb_ldx = lua_gettop(L);
cleanup_items++;
//Push a copy of the metatable and set it for this object
//WMC - Make sure it's instanced, too. This helps keep crashes from happening...
if (data_ldx != INT_MAX) {
lua_pushvalue(L, mtb_ldx);
lua_setmetatable(L, data_ldx);
}
//***Create index handler entry
lua_pushstring(L, "__index");
lua_pushstring(L, "ade_index_handler(get)"); //upvalue(1) = function name
lua_pushboolean(L, 0); //upvalue(2) = setting true/false
lua_pushcclosure(L, ade_index_handler, 2);
lua_rawset(L, mtb_ldx);
//***Create newindex handler entry
lua_pushstring(L, "__newindex");
lua_pushstring(L, "ade_index_handler(set)"); //upvalue(1) = function name
lua_pushboolean(L, 1); //upvalue(2) = setting true/false
lua_pushcclosure(L, ade_index_handler, 2);
lua_rawset(L, mtb_ldx);
if (Destructor != nullptr) {
// Set up the destructor of this type if it exists
lua_pushstring(L, "__gc");
lua_pushfstring(L, "%s Destructor", GetName()); // upvalue(1) = function name
lua_pushboolean(L, 0); // upvalue(2) = setting true/false
lua_pushlightuserdata(L, Destructor_upvalue); // upvalue(3) = Reference to ade_obj
lua_pushcclosure(L, Destructor, 3);
lua_rawset(L, mtb_ldx);
}
//***Create virtvar storage facility
lua_pushstring(L, "__virtvars");
lua_newtable(L);
lua_rawset(L, mtb_ldx);
//***Create ade members table
lua_createtable(L, 0, (int) Num_subentries);
if (lua_istable(L, -1)) {
//WMC - was lua_gettop(L) - 1 for soem
amt_ldx = lua_gettop(L);
cleanup_items++;
//Set it
lua_pushstring(L, "__ademembers");
lua_pushvalue(L, amt_ldx); //dup
lua_rawset(L, mtb_ldx);
}
//***Create ID entries
lua_pushstring(L, "__adeid");
lua_pushnumber(L, static_cast<lua_Number>(Idx));
lua_rawset(L, mtb_ldx);
if (DerivatorIdx != UINT_MAX) {
lua_pushstring(L, "__adederivid");
lua_pushinteger(L, DerivatorIdx);
lua_rawset(L, mtb_ldx);
}
}
if (amt_ldx != INT_MAX) {
//Fill out ze metatable
if (DerivatorIdx != UINT_MAX) {
for (i = 0; i < ade_manager::getInstance()->getEntry(DerivatorIdx).Num_subentries; i++) {
ade_manager::getInstance()->getEntry(ade_manager::getInstance()->getEntry(DerivatorIdx).Subentries[i]).SetTable(
L,
amt_ldx,
mtb_ldx);
}
}
for (i = 0; i < Num_subentries; i++) {
ade_manager::getInstance()->getEntry(Subentries[i]).SetTable(L, amt_ldx, mtb_ldx);
}
}
//Pop the metatable and data (cleanup)
lua_pop(L, cleanup_items);
return 1;
}
size_t ade_table_entry::AddSubentry(ade_table_entry& n_ate) {
ade_table_entry ate = n_ate;
ate.ParentIdx = Idx;
size_t new_idx = ade_manager::getInstance()->addTableEntry(ate);
//WMC - Oi. Moving the Ade_table_entries vector
//invalidates the "this" pointer. Workaround time.
auto new_this = &ade_manager::getInstance()->getEntry(ate.ParentIdx);
size_t idx = new_this->Num_subentries++;
new_this->Subentries[idx] = new_idx;
return new_idx;
}
void ade_table_entry::OutputMeta(FILE *fp)
{
if(Name == NULL && ShortName == NULL) {
Warning(LOCATION, "Data entry with no name or shortname");
return;
}
uint i;
bool skip_this = false;
//WMC - Hack
if(ParentIdx != UINT_MAX)
{
for(i = 0; i < Num_subentries; i++)
{
if(!stricmp(ade_manager::getInstance()->getEntry(Subentries[i]).Name, "__indexer"))
{
ade_manager::getInstance()->getEntry(Subentries[i]).OutputMeta(fp);
skip_this = true;
break;
}
}
}
//***Begin entry
if(!skip_this)
{
fputs("<dd><dl>", fp);
switch(Type)
{
case 'o':
{
//***Name (ShortName)
if(ParentIdx == UINT_MAX) {
fprintf(fp, "<dt id=\"%s\">", GetName());
}
if(Name == NULL)
{
if(DerivatorIdx == UINT_MAX)
fprintf(fp, "<h2>%s</h2>\n", ShortName);
else
fprintf(fp, "<h2>%s:<a href=\"#%s\">%s</a></h2>\n", getTableEntry(DerivatorIdx).GetName(), getTableEntry(DerivatorIdx).GetName(), getTableEntry(DerivatorIdx).GetName());
}
else
{
fprintf(fp, "<h2>%s", Name);
if(ShortName != NULL)
fprintf(fp, " (%s)", ShortName);
if(DerivatorIdx != UINT_MAX)
fprintf(fp, ":<a href=\"#%s\">%s</a>", getTableEntry(DerivatorIdx).GetName(), getTableEntry(DerivatorIdx).GetName());
fputs("</h2>\n", fp);
}
fputs("</dt>\n", fp);
//***Description
if(Description != NULL) {
fprintf(fp, "<dd>%s</dd>\n", this->Description);
}
}
break;
case 'u':
{
//***Name(ShortName)(Arguments)
fputs("<dt>", fp);
int ao = -1;
fputs("<i>", fp);
if (!ReturnType.isEmpty())
ade_output_type_link(fp, ReturnType);
else
fputs("nil", fp);
fputs(" </i>", fp);
if(Name == NULL) {
fprintf(fp, "<b>%s", ShortName);
}
else
{
ao = ade_get_operator(Name);
//WMC - Do we have an operator?
if(ao < 0)
{
fprintf(fp, "<b>%s", Name);
if(ShortName != NULL)
{
int ao2 = ade_get_operator(ShortName);
if(ao2 < 0)
fprintf(fp, "(%s)", ShortName);
else
fprintf(fp, "(%s)", ade_Operators[ao2].dest);
}
}
else
{
//WMC - Hack
if(ParentIdx != UINT_MAX && getTableEntry(ParentIdx).ParentIdx != UINT_MAX && getTableEntry(ParentIdx).Name != NULL && !stricmp(Name, "__indexer"))
{
fprintf(fp, "<b>%s%s", getTableEntry(ParentIdx).Name, ade_Operators[ao].dest);
}
else
fprintf(fp, "<b>%s", ade_Operators[ao].dest);
}
}
if(ao < 0) {
fputs("(</b>", fp);
if (Arguments != nullptr) {
argument_list_parser arg_parser;
if (arg_parser.parse(Arguments)) {
bool first = true;
bool optional = false;
for (const auto& arg : arg_parser.getArgList()) {
if (!first) {
fputs(", ", fp);
}
first = false;
if (arg.optional && !optional) {
fputs("[", fp);
optional = true;
}
ade_output_type_link(fp, arg.type);
if (!arg.name.empty()) {
fprintf(fp, " <i>%s</i>", arg.name.c_str());
if (!arg.def_val.empty()) {
fprintf(fp, " = <i>%s</i>", arg.def_val.c_str());
}
}
}
if (optional) {
fputs("]", fp);
}
} else {
fprintf(fp, "<i>%s</i>", Arguments);
}
}
fputs("<b>)</b>\n", fp);
} else
{
fputs("</b>", fp);
if(Arguments != NULL) {
fprintf(fp, " <i>%s</i>\n", Arguments);
}
}
fputs("</dt>\n", fp);
//***Description
if(Description != NULL) {
fprintf(fp, "<dd>%s</dd>\n", Description);
}
if (DeprecationVersion.isValid()) {
if (DeprecationMessage != nullptr) {
fprintf(fp, "<dd><b>Deprecated starting with version %d.%d.%d:</b> %s</dd>\n",
DeprecationVersion.major, DeprecationVersion.minor, DeprecationVersion.build,
DeprecationMessage);
} else {
fprintf(fp, "<dd><b>Deprecated starting with version %d.%d.%d.</b></dd>\n",
DeprecationVersion.major, DeprecationVersion.minor, DeprecationVersion.build);
}
}
//***Result: ReturnDescription
if (ReturnDescription != nullptr) {
fprintf(fp, "<dd><b>Returns:</b> %s<br>&nbsp;</dd>\n", ReturnDescription);
} else {
fputs("<dd><b>Returns:</b> Nothing<br>&nbsp;</dd>\n", fp);
}
}
break;
default:
Warning(LOCATION, "Unknown type '%c' passed to ade_table_entry::OutputMeta", Type);
FALLTHROUGH;
case 'b':
case 'd':
case 'f':
case 'i':
case 's':
case 'x':
case 'v':
{
//***Type Name(ShortName)
fputs("<dt>\n", fp);
if (!ReturnType.isEmpty()) {
fputs("<i>", fp);
ade_output_type_link(fp, ReturnType);
fputs(" </i>", fp);
}
if(Name == nullptr) {
fprintf(fp, "<b>%s</b>\n", ShortName);
}
else
{
fprintf(fp, "<b>%s", Name);
if(ShortName != NULL)
fputs(ShortName, fp);
fputs("</b>\n", fp);
}
if(Arguments != NULL)
fprintf(fp, " = <i>%s</i>", Arguments);
fputs("</dt>\n", fp);
//***Description
if(Description != NULL)
fprintf(fp, "<dd>%s</dd>\n", Description);
//***Also settable with: Arguments
if(ReturnDescription != NULL)
fprintf(fp, "<dd><b>Value:</b> %s</b></dd>\n", ReturnDescription);
}
break;
}
}
fputs("<dd><dl>\n", fp);
for(i = 0; i < Num_subentries; i++)
{
auto entry = &getTableEntry(Subentries[i]);
if(ParentIdx == UINT_MAX || stricmp(entry->Name, "__indexer") != 0)
entry->OutputMeta(fp);
}
fputs("</dl></dd>\n", fp);
if(!skip_this)
fputs("<br></dl></dd>\n", fp);
}
std::unique_ptr<DocumentationElement> ade_table_entry::ToDocumentationElement()
{
if (Name == nullptr && ShortName == nullptr) {
Warning(LOCATION, "Data entry with no name or shortname");
return std::unique_ptr<DocumentationElement>();
}
// WMC - Hack
std::unique_ptr<DocumentationElement> element;
//***Begin entry
switch (Type) {
case 'o': {
if (!Instanced) {
// Classes
std::unique_ptr<DocumentationElementClass> obj(new DocumentationElementClass());
obj->type = ElementType::Class;
if (DerivatorIdx != UINT_MAX) {
obj->superClass = getTableEntry(DerivatorIdx).GetName();
}
element = std::move(obj);
} else {
// Libraries
std::unique_ptr<DocumentationElement> obj(new DocumentationElement());
obj->type = ElementType::Library;
element = std::move(obj);
}
break;
}
case 'u': {
// Functions
std::unique_ptr<DocumentationElementFunction> obj(new DocumentationElementFunction());
auto ao = ade_get_operator(Name);
if (ao >= 0) {
obj->type = ElementType::Operator;
} else {
obj->type = ElementType::Function;
}
obj->returnType = ReturnType;
if (Arguments != nullptr) {
obj->parameters = Arguments;
}
if (ReturnDescription != nullptr) {
obj->returnDocumentation = ReturnDescription;
}
element = std::move(obj);
break;
}
case 'v': {
std::unique_ptr<DocumentationElementProperty> obj(new DocumentationElementProperty());
obj->type = ElementType::Property;
//***Type Name(ShortName)
obj->getterType = ReturnType;
if (Arguments != nullptr) {
obj->setterType = Arguments;
}
if (ReturnDescription != nullptr) {
obj->returnDocumentation = ReturnDescription;
}
element = std::move(obj);
break;
}
case 'b':
case 'd':
case 'f':
case 'i':
case 's':
case 'x':
default:
UNREACHABLE("Unknown Type %c was used used!", Type);
break;
}
if (Name != nullptr) {
element->name = Name;
}
if (ShortName != nullptr) {
element->shortName = ShortName;
}
if (Description != nullptr) {
element->description = Description;
}
for (uint32_t i = 0; i < Num_subentries; i++) {
element->children.emplace_back(getTableEntry(Subentries[i]).ToDocumentationElement());
}
return element;
}
ade_lib::ade_lib(const char* in_name, const ade_lib_handle* parent, const char* in_shortname, const char* in_desc) {
ade_table_entry ate;
ate.Name = in_name;
ate.ShortName = in_shortname;
ate.Instanced = true;
//WMC - Here's a little hack.
//Lua did not work with __len on standard table objects.
//So instead, all FS2 libraries are now userdata.
//This means that no new functions can be added from
//within the scripting environment, but I don't think
//there will be any catastrophic consequences.
ate.Type = 'o';
ate.Description = in_desc;
if(parent != NULL)
LibIdx = getTableEntry(parent->GetIdx()).AddSubentry(ate);
else
LibIdx = ade_manager::getInstance()->addTableEntry(ate);
}
const char *ade_lib::GetName() const
{
if(GetIdx() == UINT_MAX)
return "<Invalid>";
return getTableEntry(GetIdx()).GetName();
}
ade_func::ade_func(const char* name, lua_CFunction func, const ade_lib_handle& parent, const char* args,
const char* desc, ade_type_info ret_type, const char* ret_desc,
const gameversion::version& deprecation_version, const char* deprecation_message)
{
Assertion(strcmp(name, "__gc") != 0, "__gc is a reserved function name! An API function may not use it!");
ade_table_entry ate;
ate.Name = name;
ate.Instanced = true;
ate.Type = 'u';
ate.Function = func;
ate.Arguments = args;
ate.Description = desc;
ate.ReturnType = ret_type;
ate.ReturnDescription = ret_desc;
ate.DeprecationVersion = deprecation_version;
ate.DeprecationMessage = deprecation_message;
LibIdx = ade_manager::getInstance()->getEntry(parent.GetIdx()).AddSubentry(ate);
}
ade_virtvar::ade_virtvar(const char* name, lua_CFunction func, const ade_lib_handle& parent, const char* args,
const char* desc, ade_type_info ret_type, const char* ret_desc,
const gameversion::version& deprecation_version, const char* deprecation_message)
{
Assertion(strcmp(name, "__gc") != 0, "__gc is a reserved function name! An API function may not use it!");
ade_table_entry ate;
ate.Name = name;
ate.Instanced = true;
ate.Type = 'v';
ate.Function = func;
ate.Arguments = args;
ate.Description = desc;
ate.ReturnType = ret_type;
ate.ReturnDescription = ret_desc;
ate.DeprecationVersion = deprecation_version;
ate.DeprecationMessage = deprecation_message;
LibIdx = ade_manager::getInstance()->getEntry(parent.GetIdx()).AddSubentry(ate);
}
ade_indexer::ade_indexer(lua_CFunction func, const ade_lib_handle& parent, const char* args, const char* desc,
ade_type_info ret_type, const char* ret_desc)
{
//Add function for meta
ade_table_entry ate;
ate.Name = "__indexer";
ate.Instanced = true;
ate.Type = 'u';
ate.Function = func;
ate.Arguments = args;
ate.Description = desc;
ate.ReturnType = ret_type;
ate.ReturnDescription = ret_desc;
LibIdx = ade_manager::getInstance()->getEntry(parent.GetIdx()).AddSubentry(ate);
}
//*************************Lua functions*************************
//WMC - Spits out the current Lua stack to "stackdump"
//This includes variable values, but not names
void ade_stackdump(lua_State *L, char *stackdump)
{
char buf[512];
int stacksize = lua_gettop(L);
//Lua temps
double d;
int b;
char *s;
for(int argnum = 1; argnum <= stacksize; argnum++)
{
int type = lua_type(L, argnum);
sprintf(buf, "\r\n%d: ", argnum);
strcat(stackdump, buf);
switch(type)
{
case LUA_TNIL:
strcat(stackdump, "NIL");
break;
case LUA_TNUMBER:
d = lua_tonumber(L, argnum);
sprintf(buf, "Number [%f]",d);
strcat(stackdump, buf);
break;
case LUA_TBOOLEAN:
b = lua_toboolean(L, argnum);
sprintf(buf, "Boolean [%d]",b);
strcat(stackdump, buf);
break;
case LUA_TSTRING:
s = (char *)lua_tostring(L, argnum);
sprintf(buf, "String [%s]",s);
strcat(stackdump, buf);
break;
case LUA_TTABLE:
{
if(lua_getmetatable(L, argnum))
{
lua_pushstring(L, "__adeid");
lua_rawget(L, -2);
if(lua_isnumber(L, -1))
{
sprintf(buf, "Table [%s]", getTableEntry((size_t)lua_tonumber(L, -1)).Name);
strcat(stackdump, buf);
}
else
strcat(stackdump, "non-default Table");
lua_pop(L, 2); //metatable and nil/adeid
}
else
strcat(stackdump, "Table w/ no metatable");
//Maybe get first key?
char *firstkey = NULL;
lua_pushnil(L);
if(lua_next(L, argnum))
{
firstkey = (char *)lua_tostring(L, -2);
if(firstkey != NULL)
{
strcat(stackdump, ", First key: [");
strcat(stackdump, firstkey);
strcat(stackdump, "]");
}
lua_pop(L, 1); //Key
}
lua_pop(L, 1); //Nil
}
break;
case LUA_TFUNCTION:
strcat(stackdump, "Function");
{
char *upname = (char*)lua_getupvalue(L, argnum, ADE_FUNCNAME_UPVALUE_INDEX);
if(upname != NULL)
{
strcat(stackdump, " ");
strcat(stackdump, lua_tostring(L, -1));
strcat(stackdump, "()");
lua_pop(L, 1);
}
}
break;
case LUA_TUSERDATA:
if(lua_getmetatable(L, argnum))
{
lua_pushstring(L, "__adeid");
lua_rawget(L, -2);
if(lua_isnumber(L, -1))
{
sprintf(buf, "Userdata [%s]", getTableEntry((size_t)lua_tonumber(L, -1)).Name);
}
else
sprintf(buf, "non-default Userdata");
lua_pop(L, 2); //metatable and nil/adeid
}
else
sprintf(buf, "Userdata w/ no metatable");
strcat(stackdump, buf);
break;
case LUA_TTHREAD:
sprintf(buf, "Thread");
strcat(stackdump, buf);
break;
case LUA_TLIGHTUSERDATA:
sprintf(buf, "Light userdata");
strcat(stackdump, buf);
break;
default:
sprintf(buf, "<UNKNOWN>: %s (%f) (%s)", lua_typename(L, type), lua_tonumber(L, argnum), lua_tostring(L, argnum));
strcat(stackdump, buf);
break;
}
}
}
int ade_friendly_error(lua_State *L)
{
LuaError(L);
//WMC - According to documentation, this will always be the error
//if error handler is called
return LUA_ERRRUN;
}
//WMC - Gets type of object
const char *ade_get_type_string(lua_State *L, int argnum)
{
int type = lua_type(L, argnum);
if(type < 0 || type >= Lua_type_names_num)
return "Unknown";
return Lua_type_names[type];
}
ade_odata_setter<object_h> ade_object_to_odata(int obj_idx)
{
using namespace scripting::api;
if(obj_idx < 0 || obj_idx >= MAX_OBJECTS)
return l_Object.Set(object_h());
object *objp = &Objects[obj_idx];
switch(objp->type)
{
case OBJ_SHIP:
return l_Ship.Set(object_h(objp));
case OBJ_ASTEROID:
return l_Asteroid.Set(object_h(objp));
case OBJ_DEBRIS:
return l_Debris.Set(object_h(objp));
case OBJ_WAYPOINT:
return l_Waypoint.Set(object_h(objp));
case OBJ_WEAPON:
return l_Weapon.Set(object_h(objp));
case OBJ_BEAM:
return l_Beam.Set(object_h(objp));
default:
return l_Object.Set(object_h(objp));
}
}
int ade_set_object_with_breed(lua_State *L, int obj_idx)
{
return ade_set_args(L, "o", ade_object_to_odata(obj_idx));
}
void load_default_script(lua_State* L, const char* name)
{
using namespace luacpp;
SCP_string source;
SCP_string source_name;
if (Enable_external_default_scripts && cf_exists(name, CF_TYPE_SCRIPTS)) {
// Load from disk (or built-in file)
source_name = name;
auto cfp = cfopen(name, "rb", CFILE_NORMAL, CF_TYPE_SCRIPTS);
Assertion(cfp != nullptr, "Failed to open default file!");
auto length = cfilelength(cfp);
source.resize((size_t)length);
cfread(&source[0], 1, length, cfp);
cfclose(cfp);
} else {
// Load from default files
source_name = SCP_string("default ") + name;
auto def = defaults_get_file(name);
auto c = reinterpret_cast<const char*>(def.data);
source.assign(c, c + def.size);
}
try {
auto func = LuaFunction::createFromCode(L, source, source_name);
func.setErrorFunction(LuaFunction::createFromCFunction(L, ade_friendly_error));
try {
func();
} catch (const LuaException&) {
// The execution of the function may also throw an exception but that should have been handled by a LuaError
// before
}
} catch (const LuaException& e) {
LuaError(L, "Error while loading default script: %s", e.what());
}
}
ade_table_entry& internal::getTableEntry(size_t idx) { return ade_manager::getInstance()->getEntry(idx); }
}