mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
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).
1218 lines
32 KiB
C++
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> </dd>\n", ReturnDescription);
|
|
} else {
|
|
fputs("<dd><b>Returns:</b> Nothing<br> </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); }
|
|
|
|
}
|