diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 72bf91e62..ddc1837a8 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -27,6 +27,8 @@ include/Console.h include/Core.h include/ColorText.h include/DataDefs.h +include/DataIdentity.h +include/LuaWrapper.h include/Error.h include/Export.h include/Hooks.h @@ -56,6 +58,7 @@ Core.cpp ColorText.cpp DataDefs.cpp LuaWrapper.cpp +LuaTypes.cpp DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp new file mode 100644 index 000000000..4d0adcdf4 --- /dev/null +++ b/library/LuaTypes.cpp @@ -0,0 +1,872 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" + +#include +#include +#include + +#include "MemAccess.h" +#include "Core.h" +#include "VersionInfo.h" +#include "tinythread.h" +// must be last due to MS stupidity +#include "DataDefs.h" +#include "DataIdentity.h" +#include "LuaWrapper.h" + +#include "MiscUtils.h" + +#include +#include + +using namespace DFHack; +using namespace DFHack::LuaWrapper; + +/************************************** + * Identity object read/write methods * + **************************************/ + +void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + push_object_internal(state, this, ptr); +} + +void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + field_error(state, fname_idx, "complex object", "write"); +} + +void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + base_type->lua_read(state, fname_idx, ptr); +} + +void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + base_type->lua_write(state, fname_idx, ptr, val_index); +} + +void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushnumber(state, read(ptr)); +} + +void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + if (!lua_isnumber(state, val_index)) + field_error(state, fname_idx, "number expected", "write"); + + write(ptr, lua_tonumber(state, val_index)); +} + +void df::bool_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_pushboolean(state, *(bool*)ptr); +} + +void df::bool_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + char *pb = (char*)ptr; + + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + *pb = lua_toboolean(state, val_index); + else if (lua_isnumber(state, val_index)) + *pb = lua_tointeger(state, val_index); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +void df::stl_string_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + auto pstr = (std::string*)ptr; + lua_pushlstring(state, pstr->data(), pstr->size()); +} + +void df::stl_string_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + size_t size; + const char *bytes = lua_tolstring(state, val_index, &size); + if (!bytes) + field_error(state, fname_idx, "string expected", "write"); + + *(std::string*)ptr = std::string(bytes, size); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target) +{ + push_object_internal(state, target, *(void**)ptr); +} + +void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) +{ + lua_read(state, fname_idx, ptr, target); +} + +void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, + type_identity *target, int val_index) +{ + auto pptr = (void**)ptr; + + if (lua_isnil(state, val_index)) + *pptr = NULL; + else + { + void *nval = get_object_internal(state, target, val_index, false); + if (nval) + *pptr = nval; + else + field_error(state, fname_idx, "incompatible pointer type", "write"); + } +} + +void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) +{ + lua_write(state, fname_idx, ptr, target, val_index); +} + +int container_identity::lua_item_count(lua_State *state, void *ptr) +{ + if (lua_isnumber(state, UPVAL_ITEM_COUNT)) + return lua_tointeger(state, UPVAL_ITEM_COUNT); + else + return item_count(ptr); +} + +void container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_object_internal(state, id, pitem); +} + +void container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_read(state, fname_idx, pitem); +} + +void container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + id->lua_write(state, fname_idx, pitem, val_index); +} + +void ptr_container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(id, ptr, idx); + push_adhoc_pointer(state, pitem, id); +} + +void ptr_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_read(state, fname_idx, pitem, id); +} + +void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); + void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); + df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); +} + +void bit_container_identity::lua_item_reference(lua_State *state, int, void *, int) +{ + lua_pushnil(state); +} + +void bit_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) +{ + lua_pushboolean(state, get_item(ptr, idx)); +} + +void bit_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) +{ + if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) + set_item(ptr, idx, lua_toboolean(state, val_index)); + else if (lua_isnumber(state, val_index)) + set_item(ptr, idx, lua_tointeger(state, val_index) != 0); + else + field_error(state, fname_idx, "boolean or number expected", "write"); +} + +/** + * Resolve the field name in UPVAL_FIELDTABLE, die if not found. + */ +static void lookup_field(lua_State *state, int index, const char *mode) +{ + lua_pushvalue(state, index); + lua_gettable(state, UPVAL_FIELDTABLE); // uses metatable with enum keys + + if (lua_isnil(state, -1)) + field_error(state, index, "not found", mode); +} + +static void *find_field(lua_State *state, int index, const char *mode) +{ + lookup_field(state, index, mode); + + void *p = lua_touserdata(state, -1); + lua_pop(state, 1); + return p; +} + +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field); + +static void read_field(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + int len = strnlen((char*)ptr, field->count); + lua_pushlstring(state, (char*)ptr, len); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + field->type->lua_read(state, 2, ptr); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_read(state, 2, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + if (!field->eid || !field->type->isContainer() || + field->eid == ((container_identity*)field->type)->getIndexEnumType()) + { + field->type->lua_read(state, 2, ptr); + return; + } + // fallthrough + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void field_reference(lua_State *state, const struct_field_info *field, void *ptr) +{ + switch (field->mode) + { + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + push_object_internal(state, field->type, ptr); + return; + + case struct_field_info::POINTER: + push_adhoc_pointer(state, ptr, field->type); + return; + + case struct_field_info::CONTAINER: + read_field(state, field, ptr); + return; + + case struct_field_info::STATIC_STRING: + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + GetAdHocMetatable(state, field); + push_object_ref(state, ptr); + return; + + case struct_field_info::END: + break; + } + + lua_pushnil(state); +} + +static void write_field(lua_State *state, const struct_field_info *field, void *ptr, int value_idx) +{ + switch (field->mode) + { + case struct_field_info::STATIC_STRING: + { + size_t size; + const char *str = lua_tolstring(state, value_idx, &size); + if (!str) + field_error(state, 2, "string expected", "write"); + memcpy(ptr, str, std::min(size+1, size_t(field->count))); + return; + } + + case struct_field_info::PRIMITIVE: + case struct_field_info::SUBSTRUCT: + case struct_field_info::CONTAINER: + field->type->lua_write(state, 2, ptr, value_idx); + return; + + case struct_field_info::POINTER: + df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); + + case struct_field_info::STATIC_ARRAY: + case struct_field_info::STL_VECTOR_PTR: + field_error(state, 2, "complex object", "write"); + + case struct_field_info::END: + return; + } +} + +/** + * Metamethod: represent a type node as string. + */ +static int meta_type_tostring(lua_State *state) +{ + if (!lua_getmetatable(state, 1)) + return 0; + + lua_getfield(state, -1, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("", cname).c_str()); + return 1; +} + +/** + * Metamethod: represent a DF object reference as string. + */ +static int meta_ptr_tostring(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "access"); + + lua_getfield(state, UPVAL_METATABLE, "__metatable"); + const char *cname = lua_tostring(state, -1); + + lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); + return 1; +} + +// Resolve the field in the metatable and return +static int get_metafield(lua_State *state) +{ + lua_rawget(state, UPVAL_METATABLE); + return 1; +} + +/** + * Metamethod: __index for structures. + */ +static int meta_struct_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + read_field(state, field, ptr + field->offset); + return 1; +} + +/** + * Method: _field for structures. + */ +static int meta_struct_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(name)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + auto field = (struct_field_info*)find_field(state, 2, "reference"); + if (!field) + field_error(state, 2, "builtin property", "reference"); + field_reference(state, field, ptr + field->offset); + return 1; +} + +/** + * Metamethod: __newindex for structures. + */ +static int meta_struct_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + write_field(state, field, ptr + field->offset, 3); + return 0; +} + +/** + * Metamethod: __index for primitives, i.e. simple object references. + * Fields point to identity, or NULL for metafields. + */ +static int meta_primitive_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + auto type = (type_identity*)find_field(state, 2, "read"); + if (!type) + return get_metafield(state); + type->lua_read(state, 2, ptr); + return 1; +} + +/** + * Metamethod: __newindex for primitives. + */ +static int meta_primitive_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + auto type = (type_identity*)find_field(state, 2, "write"); + if (!type) + field_error(state, 2, "builtin property", "write"); + type->lua_write(state, 2, ptr, 3); + return 0; +} + +/** + * Metamethod: __len for containers. + */ +static int meta_container_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get length"); + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + lua_pushinteger(state, len); + return 1; +} + +/** + * Field lookup for containers: + * + * - Numbers are indices and handled directly. + * - NULL userdata are metafields; push and exit; + */ +static int lookup_container_field(lua_State *state, int field, const char *mode = NULL) +{ + if (lua_type(state, field) == LUA_TNUMBER) + return field; + + lookup_field(state, field, mode ? mode : "read"); + + if (lua_isuserdata(state, -1) && !lua_touserdata(state, -1)) + { + if (mode) + field_error(state, field, "builtin property", mode); + + lua_pop(state, 1); + get_metafield(state); + return 0; + } + + return -1; +} + +/** + * Index verification: number and in range. + */ +static int check_container_index(lua_State *state, int len, + int fidx, int iidx, const char *mode) +{ + if (!lua_isnumber(state, iidx)) + field_error(state, fidx, "invalid index", mode); + + int idx = lua_tointeger(state, iidx); + if (idx < 0 || idx >= len) + field_error(state, fidx, "index out of bounds", mode); + + return idx; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "read"); + id->lua_item_read(state, 2, ptr, idx); + return 1; +} + +/** + * Method: _field for containers. + */ +static int meta_container_field_reference(lua_State *state) +{ + if (lua_gettop(state) != 2) + luaL_error(state, "Usage: object._field(index)"); + uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); + int iidx = lookup_container_field(state, 2, "reference"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "reference"); + id->lua_item_reference(state, 2, ptr, idx); + return 1; +} + +/** + * Metamethod: __index for containers. + */ +static int meta_container_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + int len = id->lua_item_count(state, ptr); + int idx = check_container_index(state, len, 2, iidx, "write"); + id->lua_item_write(state, 2, ptr, idx, 3); + return 0; +} + +/** + * Metamethod: __len for bitfields. + */ +static int meta_bitfield_len(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 0, "get size"); + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + lua_pushinteger(state, id->getNumBits()); + return 1; +} + +/** + * Metamethod: __index for bitfields. + */ +static int meta_bitfield_index(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "read"); + int iidx = lookup_container_field(state, 2); + if (!iidx) + return 1; + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + size_t intv = 0; + memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); + lua_pushnumber(state, intv); + return 1; + } + + int idx = check_container_index(state, id->getNumBits(), 2, iidx, "read"); + int size = id->getBits()[idx].size; + + int value = getBitfieldField(ptr, idx, size); + if (size <= 1) + lua_pushboolean(state, value != 0); + else + lua_pushinteger(state, value); + return 1; +} + +/** + * Metamethod: __newindex for bitfields. + */ +static int meta_bitfield_newindex(lua_State *state) +{ + uint8_t *ptr = get_object_addr(state, 1, 2, "write"); + int iidx = lookup_container_field(state, 2, "write"); + + auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); + + // whole + if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) + { + if (!lua_isnumber(state, 3)) + field_error(state, 2, "number expected", "write"); + + size_t intv = (size_t)lua_tonumber(state, 3); + memcpy(ptr, &intv, std::min(sizeof(intv), size_t(id->byte_size()))); + return 0; + } + + int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write"); + int size = id->getBits()[idx].size; + + if (lua_isboolean(state, 3) || lua_isnil(state, 3)) + setBitfieldField(ptr, idx, size, lua_toboolean(state, 3)); + else if (lua_isnumber(state, 3)) + setBitfieldField(ptr, idx, size, lua_tointeger(state, 3)); + else + field_error(state, 2, "number or boolean expected", "write"); + return 0; +} + +/** + * Metamethod: __index for df.global + */ +static int meta_global_index(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "read"); + if (!field) + return get_metafield(state); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "read"); + read_field(state, field, ptr); + return 1; +} + +/** + * Metamethod: __newindex for df.global + */ +static int meta_global_newindex(lua_State *state) +{ + auto field = (struct_field_info*)find_field(state, 2, "write"); + if (!field) + field_error(state, 2, "builtin property", "write"); + void *ptr = *(void**)field->offset; + if (!ptr) + field_error(state, 2, "global address not known", "write"); + write_field(state, field, ptr, 3); + return 0; +} + +/** + * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. + */ +static void IndexFields(lua_State *state, struct_identity *pstruct) +{ + // stack: fieldtable + + int base = lua_gettop(state); + + for (struct_identity *p = pstruct; p; p = p->getParent()) + { + auto fields = p->getFields(); + + for (; fields; ++fields) + { + if (fields->mode == struct_field_info::END) + break; + + lua_pushstring(state,fields->name); + lua_pushlightuserdata(state,(void*)fields); + lua_rawset(state,base); + } + } +} + +/** + * Make a struct-style object metatable. + */ +static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, + lua_CFunction reader, lua_CFunction writer) +{ + int base = lua_gettop(state); + + MakeMetatable(state, pstruct, "struct"); // meta, fields + + IndexFields(state, pstruct); + + SetStructMethod(state, base+1, base+2, reader, "__index"); + SetStructMethod(state, base+1, base+2, writer, "__newindex"); + + // returns: [metatable readfields writefields]; +} + +/** + * Make a primitive-style metatable + */ +static void MakePrimitiveMetatable(lua_State *state, type_identity *type) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "primitive"); + + SetPtrMethods(state, base+1, base+2); + + EnableMetaField(state, base+2, "value", type); + + SetStructMethod(state, base+1, base+2, meta_primitive_index, "__index"); + SetStructMethod(state, base+1, base+2, meta_primitive_newindex, "__newindex"); +} + +/** + * Make a container-style object metatable. + */ +static void MakeContainerMetatable(lua_State *state, container_identity *type, + type_identity *item, int count, type_identity *ienum) +{ + int base = lua_gettop(state); + + MakeMetatable(state, type, "container"); + SetPtrMethods(state, base+1, base+2); + + // Update the type name using full info + lua_pushstring(state, type->getFullName(item).c_str()); + lua_dup(state); + lua_setfield(state, base+1, "__metatable"); + lua_setfield(state, base+1, "_type"); + + lua_pushlightuserdata(state, item); + lua_setfield(state, base+1, "_field_identity"); + + if (count >= 0) + { + lua_pushinteger(state, count); + lua_setfield(state, base+1, "_count"); + } + + SetContainerMethod(state, base+1, base+2, meta_container_len, "__len", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_index, "__index", type, item, count); + SetContainerMethod(state, base+1, base+2, meta_container_newindex, "__newindex", type, item, count); + + SetContainerMethod(state, base+1, base+2, meta_container_field_reference, "_field", type, item, count); + + AttachEnumKeys(state, base+1, base+2, ienum); +} + +/* + * Metatable construction identity methods. + */ +void type_identity::build_metatable(lua_State *state) +{ + MakePrimitiveMetatable(state, this); +} + +void container_identity::build_metatable(lua_State *state) +{ + MakeContainerMetatable(state, this, getItemType(), -1, getIndexEnumType()); +} + +void bitfield_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + + MakeMetatable(state, this, "bitfield"); + + SetPtrMethods(state, base+1, base+2); + + SetContainerMethod(state, base+1, base+2, meta_bitfield_len, "__len", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_index, "__index", this, NULL, -1); + SetContainerMethod(state, base+1, base+2, meta_bitfield_newindex, "__newindex", this, NULL, -1); + + AttachEnumKeys(state, base+1, base+2, this); + + EnableMetaField(state, base+2, "whole", this); +} + +void struct_identity::build_metatable(lua_State *state) +{ + int base = lua_gettop(state); + MakeFieldMetatable(state, this, meta_struct_index, meta_struct_newindex); + SetStructMethod(state, base+1, base+2, meta_struct_field_reference, "_field"); + SetPtrMethods(state, base+1, base+2); +} + +void global_identity::build_metatable(lua_State *state) +{ + MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex); +} + +/** + * Construct a metatable for an object type folded into the field descriptor. + * This is done to reduce compile-time symbol table bloat due to templates. + */ +static void GetAdHocMetatable(lua_State *state, const struct_field_info *field) +{ + lua_pushlightuserdata(state, (void*)field); + + if (!LookupTypeInfo(state, true)) + { + switch (field->mode) + { + case struct_field_info::CONTAINER: + { + auto ctype = (container_identity*)field->type; + MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->eid); + break; + } + + case struct_field_info::STATIC_STRING: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + &df::identity_traits::identity, field->count, NULL); + break; + + case struct_field_info::STATIC_ARRAY: + MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, + field->type, field->count, field->eid); + break; + + case struct_field_info::STL_VECTOR_PTR: + MakeContainerMetatable(state, &df::identity_traits >::identity, + field->type, -1, field->eid); + break; + + default: + luaL_error(state, "Invalid ad-hoc field: %d", field->mode); + } + + lua_pop(state, 1); + + SaveTypeInfo(state, (void*)field); + } +} + +void LuaWrapper::push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target) +{ + if (!target) + { + push_object_internal(state, &df::identity_traits::identity, ptr); + return; + } + + LookupInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + + type_identity *id = (type_identity*)lua_touserdata(state, -1); + lua_pop(state, 1); + + if (!id) + { + /* + * HACK: relies on + * 1) pointer_identity destructor being no-op + * 2) lua gc never moving objects in memory + */ + + void *newobj = lua_newuserdata(state, sizeof(pointer_identity)); + id = new (newobj) pointer_identity(target); + + SaveInTable(state, target, DFHACK_PTR_IDTABLE_NAME); + lua_pop(state, 1); + } + + push_object_internal(state, id, ptr); +} diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 9ba0b7eb1..1e4574fd6 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -35,6 +35,7 @@ distribution. // must be last due to MS stupidity #include "DataDefs.h" #include "DataIdentity.h" +#include "LuaWrapper.h" #include "MiscUtils.h" @@ -42,97 +43,14 @@ distribution. #include using namespace DFHack; +using namespace DFHack::LuaWrapper; static luaL_Reg no_functions[] = { { NULL, NULL } }; -inline void lua_dup(lua_State *state) { lua_pushvalue(state, -1); } -inline void lua_swap(lua_State *state) { lua_insert(state, -2); } - -/* - * Registry name: hash of type metatables <-> type identities. - */ -#define DFHACK_TYPETABLE_NAME "DFHack::DFTypes" - -/* - * Registry name: hash of type identity -> node in df.etc... - */ -#define DFHACK_TYPEID_TABLE_NAME "DFHack::DFTypeIds" - -/* - * Registry name: hash of enum/bitfield identity -> index lookup table - */ -#define DFHACK_ENUM_TABLE_NAME "DFHack::DFEnums" - -/* - * Registry name: hash of pointer target identity <-> adhoc pointer identity userdata. - */ -#define DFHACK_PTR_IDTABLE_NAME "DFHack::PtrDFTypes" - -// Function registry names -#define DFHACK_CHANGEERROR_NAME "DFHack::ChangeError" -#define DFHACK_COMPARE_NAME "DFHack::ComparePtrs" -#define DFHACK_TYPE_TOSTRING_NAME "DFHack::TypeToString" -#define DFHACK_SIZEOF_NAME "DFHack::Sizeof" -#define DFHACK_DISPLACE_NAME "DFHack::Displace" -#define DFHACK_NEW_NAME "DFHack::New" -#define DFHACK_ASSIGN_NAME "DFHack::Assign" - -/* - * Upvalue: contents of DFHACK_TYPETABLE_NAME - */ -#define UPVAL_TYPETABLE lua_upvalueindex(1) - -/* - * Expected metatable of the current object. - */ -#define UPVAL_METATABLE lua_upvalueindex(2) - -/* - * Table mapping field names to indices or data structure pointers. - * Enum index table is linked into here via getmetatable($).__index. - * Fields that are actually in UPVAL_METATABLE are marked with NULL light udata. - */ -#define UPVAL_FIELDTABLE lua_upvalueindex(3) - -/* - * Only for containers: light udata with container identity. - */ -#define UPVAL_CONTAINER_ID lua_upvalueindex(4) - -/* - * Only for containers: light udata with item identity. - */ -#define UPVAL_ITEM_ID lua_upvalueindex(5) - -/* - * Only for containers: if not nil, overrides the item count. - */ -#define UPVAL_ITEM_COUNT lua_upvalueindex(6) - -namespace { - /** - * Object references are represented as userdata instances - * with an appropriate metatable; the payload of userdata is - * this structure: - */ - struct DFRefHeader { - void *ptr; - }; - - /* - * The system might be extended to carry some simple - * objects inline inside the reference buffer. - */ - inline bool is_self_contained(DFRefHeader *ptr) { - void **pp = &ptr->ptr; - return **(void****)pp == (pp + 1); - } -} - /** * Report an error while accessing a field (index = field name). */ -static void field_error(lua_State *state, int index, const char *err, const char *mode) +void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) { lua_getfield(state, UPVAL_METATABLE, "__metatable"); const char *cname = lua_tostring(state, -1); @@ -141,12 +59,6 @@ static void field_error(lua_State *state, int index, const char *err, const char mode, (cname ? cname : "?"), (fname ? fname : "?"), err); } -/* - * If is_method is true, these use UPVAL_TYPETABLE to save a hash lookup. - */ -static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method = true); -static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool exact_type, bool in_method = true); - void DFHack::PushDFObject(lua_State *state, type_identity *type, void *ptr) { push_object_internal(state, type, ptr, false); @@ -157,180 +69,6 @@ void *DFHack::GetDFObject(lua_State *state, type_identity *type, int val_index, return get_object_internal(state, type, val_index, exact_type, false); } -static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target); - -/************************************** - * Identity object read/write methods * - **************************************/ - -void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - push_object_internal(state, this, ptr); -} - -void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - field_error(state, fname_idx, "complex object", "write"); -} - -void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - base_type->lua_read(state, fname_idx, ptr); -} - -void enum_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - base_type->lua_write(state, fname_idx, ptr, val_index); -} - -void df::number_identity_base::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - lua_pushnumber(state, read(ptr)); -} - -void df::number_identity_base::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - if (!lua_isnumber(state, val_index)) - field_error(state, fname_idx, "number expected", "write"); - - write(ptr, lua_tonumber(state, val_index)); -} - -void df::bool_identity::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - lua_pushboolean(state, *(bool*)ptr); -} - -void df::bool_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - char *pb = (char*)ptr; - - if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) - *pb = lua_toboolean(state, val_index); - else if (lua_isnumber(state, val_index)) - *pb = lua_tointeger(state, val_index); - else - field_error(state, fname_idx, "boolean or number expected", "write"); -} - -void df::stl_string_identity::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - auto pstr = (std::string*)ptr; - lua_pushlstring(state, pstr->data(), pstr->size()); -} - -void df::stl_string_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - size_t size; - const char *bytes = lua_tolstring(state, val_index, &size); - if (!bytes) - field_error(state, fname_idx, "string expected", "write"); - - *(std::string*)ptr = std::string(bytes, size); -} - -void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr, type_identity *target) -{ - push_object_internal(state, target, *(void**)ptr); -} - -void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) -{ - lua_read(state, fname_idx, ptr, target); -} - -void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, - type_identity *target, int val_index) -{ - auto pptr = (void**)ptr; - - if (lua_isnil(state, val_index)) - *pptr = NULL; - else - { - void *nval = get_object_internal(state, target, val_index, false); - if (nval) - *pptr = nval; - else - field_error(state, fname_idx, "incompatible pointer type", "write"); - } -} - -void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) -{ - lua_write(state, fname_idx, ptr, target, val_index); -} - -int container_identity::lua_item_count(lua_State *state, void *ptr) -{ - if (lua_isnumber(state, UPVAL_ITEM_COUNT)) - return lua_tointeger(state, UPVAL_ITEM_COUNT); - else - return item_count(ptr); -} - -void container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(id, ptr, idx); - push_object_internal(state, id, pitem); -} - -void container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(id, ptr, idx); - id->lua_read(state, fname_idx, pitem); -} - -void container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(id, ptr, idx); - id->lua_write(state, fname_idx, pitem, val_index); -} - -void ptr_container_identity::lua_item_reference(lua_State *state, int fname_idx, void *ptr, int idx) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(id, ptr, idx); - push_adhoc_pointer(state, pitem, id); -} - -void ptr_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); - df::pointer_identity::lua_read(state, fname_idx, pitem, id); -} - -void ptr_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) -{ - auto id = (type_identity*)lua_touserdata(state, UPVAL_ITEM_ID); - void *pitem = item_pointer(&df::identity_traits::identity, ptr, idx); - df::pointer_identity::lua_write(state, fname_idx, pitem, id, val_index); -} - -void bit_container_identity::lua_item_reference(lua_State *state, int, void *, int) -{ - lua_pushnil(state); -} - -void bit_container_identity::lua_item_read(lua_State *state, int fname_idx, void *ptr, int idx) -{ - lua_pushboolean(state, get_item(ptr, idx)); -} - -void bit_container_identity::lua_item_write(lua_State *state, int fname_idx, void *ptr, int idx, int val_index) -{ - if (lua_isboolean(state, val_index) || lua_isnil(state, val_index)) - set_item(ptr, idx, lua_toboolean(state, val_index)); - else if (lua_isnumber(state, val_index)) - set_item(ptr, idx, lua_tointeger(state, val_index) != 0); - else - field_error(state, fname_idx, "boolean or number expected", "write"); -} - /* */ static int change_error(lua_State *state) @@ -377,7 +115,7 @@ static void LookupInTable(lua_State *state, const char *tname) * Look up the key on the stack in DFHACK_TYPETABLE; * if found, put result on the stack and return true. */ -static bool LookupTypeInfo(lua_State *state, bool in_method) +bool LuaWrapper::LookupTypeInfo(lua_State *state, bool in_method) { // stack: [lookup key] @@ -401,7 +139,7 @@ static bool LookupTypeInfo(lua_State *state, bool in_method) return true; } -static void LookupInTable(lua_State *state, void *id, const char *tname) +void LuaWrapper::LookupInTable(lua_State *state, void *id, const char *tname) { lua_getfield(state, LUA_REGISTRYINDEX, tname); lua_pushlightuserdata(state, id); @@ -409,7 +147,7 @@ static void LookupInTable(lua_State *state, void *id, const char *tname) lua_remove(state, -2); } -static void SaveInTable(lua_State *state, void *node, const char *tname) +void LuaWrapper::SaveInTable(lua_State *state, void *node, const char *tname) { // stack: [info] lua_getfield(state, LUA_REGISTRYINDEX, tname); @@ -426,7 +164,7 @@ static void SaveInTable(lua_State *state, void *node, const char *tname) // stack: [info] } -static void SaveTypeInfo(lua_State *state, void *node) +void LuaWrapper::SaveTypeInfo(lua_State *state, void *node) { SaveInTable(state, node, DFHACK_TYPETABLE_NAME); } @@ -436,7 +174,7 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type); /** * Push the pointer as DF object ref using metatable on the stack. */ -static void push_object_ref(lua_State *state, void *ptr) +void LuaWrapper::push_object_ref(lua_State *state, void *ptr) { // stack: [metatable] auto ref = (DFRefHeader*)lua_newuserdata(state, sizeof(DFRefHeader)); @@ -447,7 +185,7 @@ static void push_object_ref(lua_State *state, void *ptr) // stack: [userdata] } -static void *get_object_ref(lua_State *state, int val_index) +void *LuaWrapper::get_object_ref(lua_State *state, int val_index) { assert(!lua_islightuserdata(state, val_index)); @@ -458,7 +196,7 @@ static void *get_object_ref(lua_State *state, int val_index) /** * Push the pointer using given identity. */ -static void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method) +void LuaWrapper::push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method) { /* * If NULL pointer or no type, push something simple @@ -605,7 +343,7 @@ static bool is_type_compatible(lua_State *state, int meta1, int meta2, bool exac /** * Verify that the value matches the identity, and return ptr if so. */ -static void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool exact_type, bool in_method) +void *LuaWrapper::get_object_internal(lua_State *state, type_identity *type, int val_index, bool exact_type, bool in_method) { /* * Non-userdata results in NULL; nil for NULL gets handled here too. @@ -889,32 +627,11 @@ static int meta_assign(lua_State *state) return 0; } -/** - * Resolve the field name in UPVAL_FIELDTABLE, die if not found. - */ -static void lookup_field(lua_State *state, int index, const char *mode) -{ - lua_pushvalue(state, index); - lua_gettable(state, UPVAL_FIELDTABLE); // uses metatable with enum keys - - if (lua_isnil(state, -1)) - field_error(state, index, "not found", mode); -} - -static void *find_field(lua_State *state, int index, const char *mode) -{ - lookup_field(state, index, mode); - - void *p = lua_touserdata(state, -1); - lua_pop(state, 1); - return p; -} - /** * Verify that the object is a DF ref with UPVAL_METATABLE. * If everything ok, extract the address. */ -static uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode) +uint8_t *LuaWrapper::get_object_addr(lua_State *state, int obj, int field, const char *mode) { if (!lua_isuserdata(state, obj) || !lua_getmetatable(state, obj)) @@ -928,113 +645,6 @@ static uint8_t *get_object_addr(lua_State *state, int obj, int field, const char return (uint8_t*)get_object_ref(state, obj); } -static void GetAdHocMetatable(lua_State *state, const struct_field_info *field); - -static void read_field(lua_State *state, const struct_field_info *field, void *ptr) -{ - switch (field->mode) - { - case struct_field_info::STATIC_STRING: - { - int len = strnlen((char*)ptr, field->count); - lua_pushlstring(state, (char*)ptr, len); - return; - } - - case struct_field_info::PRIMITIVE: - case struct_field_info::SUBSTRUCT: - field->type->lua_read(state, 2, ptr); - return; - - case struct_field_info::POINTER: - df::pointer_identity::lua_read(state, 2, ptr, field->type); - return; - - case struct_field_info::CONTAINER: - if (!field->eid || !field->type->isContainer() || - field->eid == ((container_identity*)field->type)->getIndexEnumType()) - { - field->type->lua_read(state, 2, ptr); - return; - } - // fallthrough - - case struct_field_info::STATIC_ARRAY: - case struct_field_info::STL_VECTOR_PTR: - GetAdHocMetatable(state, field); - push_object_ref(state, ptr); - return; - - case struct_field_info::END: - break; - } - - lua_pushnil(state); -} - -static void field_reference(lua_State *state, const struct_field_info *field, void *ptr) -{ - switch (field->mode) - { - case struct_field_info::PRIMITIVE: - case struct_field_info::SUBSTRUCT: - push_object_internal(state, field->type, ptr); - return; - - case struct_field_info::POINTER: - push_adhoc_pointer(state, ptr, field->type); - return; - - case struct_field_info::CONTAINER: - read_field(state, field, ptr); - return; - - case struct_field_info::STATIC_STRING: - case struct_field_info::STATIC_ARRAY: - case struct_field_info::STL_VECTOR_PTR: - GetAdHocMetatable(state, field); - push_object_ref(state, ptr); - return; - - case struct_field_info::END: - break; - } - - lua_pushnil(state); -} - -static void write_field(lua_State *state, const struct_field_info *field, void *ptr, int value_idx) -{ - switch (field->mode) - { - case struct_field_info::STATIC_STRING: - { - size_t size; - const char *str = lua_tolstring(state, value_idx, &size); - if (!str) - field_error(state, 2, "string expected", "write"); - memcpy(ptr, str, std::min(size+1, size_t(field->count))); - return; - } - - case struct_field_info::PRIMITIVE: - case struct_field_info::SUBSTRUCT: - case struct_field_info::CONTAINER: - field->type->lua_write(state, 2, ptr, value_idx); - return; - - case struct_field_info::POINTER: - df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); - - case struct_field_info::STATIC_ARRAY: - case struct_field_info::STL_VECTOR_PTR: - field_error(state, 2, "complex object", "write"); - - case struct_field_info::END: - return; - } -} - /** * Metamethod: represent a type node as string. */ @@ -1064,319 +674,39 @@ static int meta_ptr_tostring(lua_State *state) return 1; } -// Resolve the field in the metatable and return -static int get_metafield(lua_State *state) -{ - lua_rawget(state, UPVAL_METATABLE); - return 1; -} - -/** - * Metamethod: __index for structures. - */ -static int meta_struct_index(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "read"); - auto field = (struct_field_info*)find_field(state, 2, "read"); - if (!field) - return get_metafield(state); - read_field(state, field, ptr + field->offset); - return 1; -} - -/** - * Method: _field for structures. - */ -static int meta_struct_field_reference(lua_State *state) -{ - if (lua_gettop(state) != 2) - luaL_error(state, "Usage: object._field(name)"); - uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); - auto field = (struct_field_info*)find_field(state, 2, "reference"); - if (!field) - field_error(state, 2, "builtin property", "reference"); - field_reference(state, field, ptr + field->offset); - return 1; -} - -/** - * Metamethod: __newindex for structures. - */ -static int meta_struct_newindex(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "write"); - auto field = (struct_field_info*)find_field(state, 2, "write"); - if (!field) - field_error(state, 2, "builtin property", "write"); - write_field(state, field, ptr + field->offset, 3); - return 0; -} - -/** - * Metamethod: __index for primitives, i.e. simple object references. - * Fields point to identity, or NULL for metafields. - */ -static int meta_primitive_index(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "read"); - auto type = (type_identity*)find_field(state, 2, "read"); - if (!type) - return get_metafield(state); - type->lua_read(state, 2, ptr); - return 1; -} - -/** - * Metamethod: __newindex for primitives. - */ -static int meta_primitive_newindex(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "write"); - auto type = (type_identity*)find_field(state, 2, "write"); - if (!type) - field_error(state, 2, "builtin property", "write"); - type->lua_write(state, 2, ptr, 3); - return 0; -} - /** - * Metamethod: __len for containers. + * Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE. */ -static int meta_container_len(lua_State *state) +void LuaWrapper::MakeMetatable(lua_State *state, type_identity *type, const char *kind) { - uint8_t *ptr = get_object_addr(state, 1, 0, "get length"); - auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - int len = id->lua_item_count(state, ptr); - lua_pushinteger(state, len); - return 1; -} + int base = lua_gettop(state); + lua_newtable(state); // metatable -/** - * Field lookup for containers: - * - * - Numbers are indices and handled directly. - * - NULL userdata are metafields; push and exit; - */ -static int lookup_container_field(lua_State *state, int field, const char *mode = NULL) -{ - if (lua_type(state, field) == LUA_TNUMBER) - return field; + lua_pushstring(state, type->getFullName().c_str()); + lua_setfield(state, base+1, "__metatable"); - lookup_field(state, field, mode ? mode : "read"); + lua_pushlightuserdata(state, type); + lua_setfield(state, base+1, "_identity"); - if (lua_isuserdata(state, -1) && !lua_touserdata(state, -1)) + LookupInTable(state, type, DFHACK_TYPEID_TABLE_NAME); + if (lua_isnil(state, -1)) { - if (mode) - field_error(state, field, "builtin property", mode); - + // Copy the string from __metatable if no real type lua_pop(state, 1); - get_metafield(state); - return 0; - } - - return -1; -} - -/** - * Index verification: number and in range. - */ -static int check_container_index(lua_State *state, int len, - int fidx, int iidx, const char *mode) -{ - if (!lua_isnumber(state, iidx)) - field_error(state, fidx, "invalid index", mode); - - int idx = lua_tointeger(state, iidx); - if (idx < 0 || idx >= len) - field_error(state, fidx, "index out of bounds", mode); - - return idx; -} - -/** - * Metamethod: __index for containers. - */ -static int meta_container_index(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "read"); - int iidx = lookup_container_field(state, 2); - if (!iidx) - return 1; - - auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - int len = id->lua_item_count(state, ptr); - int idx = check_container_index(state, len, 2, iidx, "read"); - id->lua_item_read(state, 2, ptr, idx); - return 1; -} - -/** - * Method: _field for containers. - */ -static int meta_container_field_reference(lua_State *state) -{ - if (lua_gettop(state) != 2) - luaL_error(state, "Usage: object._field(index)"); - uint8_t *ptr = get_object_addr(state, 1, 2, "reference"); - int iidx = lookup_container_field(state, 2, "reference"); - - auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - int len = id->lua_item_count(state, ptr); - int idx = check_container_index(state, len, 2, iidx, "reference"); - id->lua_item_reference(state, 2, ptr, idx); - return 1; -} - -/** - * Metamethod: __index for containers. - */ -static int meta_container_newindex(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "write"); - int iidx = lookup_container_field(state, 2, "write"); - - auto id = (container_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - int len = id->lua_item_count(state, ptr); - int idx = check_container_index(state, len, 2, iidx, "write"); - id->lua_item_write(state, 2, ptr, idx, 3); - return 0; -} - -/** - * Metamethod: __len for bitfields. - */ -static int meta_bitfield_len(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 0, "get size"); - auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - lua_pushinteger(state, id->getNumBits()); - return 1; -} - -/** - * Metamethod: __index for bitfields. - */ -static int meta_bitfield_index(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "read"); - int iidx = lookup_container_field(state, 2); - if (!iidx) - return 1; - - auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - - // whole - if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) - { - size_t intv = 0; - memcpy(&intv, ptr, std::min(sizeof(intv), size_t(id->byte_size()))); - lua_pushnumber(state, intv); - return 1; - } - - int idx = check_container_index(state, id->getNumBits(), 2, iidx, "read"); - int size = id->getBits()[idx].size; - - int value = getBitfieldField(ptr, idx, size); - if (size <= 1) - lua_pushboolean(state, value != 0); - else - lua_pushinteger(state, value); - return 1; -} - -/** - * Metamethod: __newindex for bitfields. - */ -static int meta_bitfield_newindex(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 2, "write"); - int iidx = lookup_container_field(state, 2, "write"); - - auto id = (bitfield_identity*)lua_touserdata(state, UPVAL_CONTAINER_ID); - - // whole - if (lua_isuserdata(state, iidx) && lua_touserdata(state, iidx) == id) - { - if (!lua_isnumber(state, 3)) - field_error(state, 2, "number expected", "write"); - - size_t intv = (size_t)lua_tonumber(state, 3); - memcpy(ptr, &intv, std::min(sizeof(intv), size_t(id->byte_size()))); - return 0; + lua_getfield(state, base+1, "__metatable"); } + lua_setfield(state, base+1, "_type"); - int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write"); - int size = id->getBits()[idx].size; - - if (lua_isboolean(state, 3) || lua_isnil(state, 3)) - setBitfieldField(ptr, idx, size, lua_toboolean(state, 3)); - else if (lua_isnumber(state, 3)) - setBitfieldField(ptr, idx, size, lua_tointeger(state, 3)); - else - field_error(state, 2, "number or boolean expected", "write"); - return 0; -} - -/** - * Metamethod: __index for df.global - */ -static int meta_global_index(lua_State *state) -{ - auto field = (struct_field_info*)find_field(state, 2, "read"); - if (!field) - return get_metafield(state); - void *ptr = *(void**)field->offset; - if (!ptr) - field_error(state, 2, "global address not known", "read"); - read_field(state, field, ptr); - return 1; -} - -/** - * Metamethod: __newindex for df.global - */ -static int meta_global_newindex(lua_State *state) -{ - auto field = (struct_field_info*)find_field(state, 2, "write"); - if (!field) - field_error(state, 2, "builtin property", "write"); - void *ptr = *(void**)field->offset; - if (!ptr) - field_error(state, 2, "global address not known", "write"); - write_field(state, field, ptr, 3); - return 0; -} - -/** - * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. - */ -static void IndexFields(lua_State *state, struct_identity *pstruct) -{ - // stack: fieldtable - - int base = lua_gettop(state); - - for (struct_identity *p = pstruct; p; p = p->getParent()) - { - auto fields = p->getFields(); - - for (; fields; ++fields) - { - if (fields->mode == struct_field_info::END) - break; + lua_pushstring(state, kind); + lua_setfield(state, base+1, "_kind"); - lua_pushstring(state,fields->name); - lua_pushlightuserdata(state,(void*)fields); - lua_rawset(state,base); - } - } + lua_newtable(state); // fieldtable } /** * Enable a metafield by injecting an entry into a UPVAL_FIELDTABLE. */ -static void EnableMetaField(lua_State *state, int ftable_idx, const char *name, void *id = NULL) +void LuaWrapper::EnableMetaField(lua_State *state, int ftable_idx, const char *name, void *id) { lua_pushlightuserdata(state, id); lua_setfield(state, ftable_idx, name); @@ -1385,7 +715,7 @@ static void EnableMetaField(lua_State *state, int ftable_idx, const char *name, /** * Set metatable properties common to all actual DF object references. */ -static void SetPtrMethods(lua_State *state, int meta_idx, int read_idx) +void LuaWrapper::SetPtrMethods(lua_State *state, int meta_idx, int read_idx) { lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_COMPARE_NAME); lua_setfield(state, meta_idx, "__eq"); @@ -1420,8 +750,8 @@ static void SetPtrMethods(lua_State *state, int meta_idx, int read_idx) /** * Add a struct-style (3 upvalues) metamethod to the metatable. */ -static void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, - lua_CFunction function, const char *name) +void LuaWrapper::SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name) { lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); lua_pushvalue(state, meta_idx); @@ -1430,76 +760,12 @@ static void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, lua_setfield(state, meta_idx, name); } -/** - * Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE. - */ -static void MakeMetatable(lua_State *state, type_identity *type, const char *kind) -{ - int base = lua_gettop(state); - lua_newtable(state); // metatable - - lua_pushstring(state, type->getFullName().c_str()); - lua_setfield(state, base+1, "__metatable"); - - lua_pushlightuserdata(state, type); - lua_setfield(state, base+1, "_identity"); - - LookupInTable(state, type, DFHACK_TYPEID_TABLE_NAME); - if (lua_isnil(state, -1)) - { - // Copy the string from __metatable if no real type - lua_pop(state, 1); - lua_getfield(state, base+1, "__metatable"); - } - lua_setfield(state, base+1, "_type"); - - lua_pushstring(state, kind); - lua_setfield(state, base+1, "_kind"); - - lua_newtable(state); // fieldtable -} - -/** - * Make a struct-style object metatable. - */ -static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, - lua_CFunction reader, lua_CFunction writer) -{ - int base = lua_gettop(state); - - MakeMetatable(state, pstruct, "struct"); // meta, fields - - IndexFields(state, pstruct); - - SetStructMethod(state, base+1, base+2, reader, "__index"); - SetStructMethod(state, base+1, base+2, writer, "__newindex"); - - // returns: [metatable readfields writefields]; -} - -/** - * Make a primitive-style metatable - */ -static void MakePrimitiveMetatable(lua_State *state, type_identity *type) -{ - int base = lua_gettop(state); - - MakeMetatable(state, type, "primitive"); - - SetPtrMethods(state, base+1, base+2); - - EnableMetaField(state, base+2, "value", type); - - SetStructMethod(state, base+1, base+2, meta_primitive_index, "__index"); - SetStructMethod(state, base+1, base+2, meta_primitive_newindex, "__newindex"); -} - /** * Add a 6 upvalue metamethod to the metatable. */ -static void SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, - lua_CFunction function, const char *name, - type_identity *container, type_identity *item, int count) +void LuaWrapper::SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name, + type_identity *container, type_identity *item, int count) { lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPETABLE_NAME); lua_pushvalue(state, meta_idx); @@ -1520,7 +786,7 @@ static void SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, * If ienum refers to a valid enum, attach its keys to UPVAL_FIELDTABLE, * and the enum itself to the _enum metafield. */ -static void AttachEnumKeys(lua_State *state, int base, type_identity *ienum) +void LuaWrapper::AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum) { LookupInTable(state, ienum, DFHACK_ENUM_TABLE_NAME); @@ -1529,93 +795,15 @@ static void AttachEnumKeys(lua_State *state, int base, type_identity *ienum) lua_newtable(state); lua_swap(state); lua_setfield(state, -2, "__index"); - lua_setmetatable(state, base+2); + lua_setmetatable(state, ftable_idx); } else lua_pop(state, 1); LookupInTable(state, ienum, DFHACK_TYPEID_TABLE_NAME); - lua_setfield(state, base+1, "_enum"); - - EnableMetaField(state, base+2, "_enum"); -} - -/** - * Make a container-style object metatable. - */ -static void MakeContainerMetatable(lua_State *state, container_identity *type, - type_identity *item, int count, type_identity *ienum) -{ - int base = lua_gettop(state); - - MakeMetatable(state, type, "container"); - SetPtrMethods(state, base+1, base+2); - - // Update the type name using full info - lua_pushstring(state, type->getFullName(item).c_str()); - lua_dup(state); - lua_setfield(state, base+1, "__metatable"); - lua_setfield(state, base+1, "_type"); - - lua_pushlightuserdata(state, item); - lua_setfield(state, base+1, "_field_identity"); - - if (count >= 0) - { - lua_pushinteger(state, count); - lua_setfield(state, base+1, "_count"); - } - - SetContainerMethod(state, base+1, base+2, meta_container_len, "__len", type, item, count); - SetContainerMethod(state, base+1, base+2, meta_container_index, "__index", type, item, count); - SetContainerMethod(state, base+1, base+2, meta_container_newindex, "__newindex", type, item, count); - - SetContainerMethod(state, base+1, base+2, meta_container_field_reference, "_field", type, item, count); - - AttachEnumKeys(state, base, ienum); -} - -/* - * Metatable construction identity methods. - */ -void type_identity::build_metatable(lua_State *state) -{ - MakePrimitiveMetatable(state, this); -} + lua_setfield(state, meta_idx, "_enum"); -void container_identity::build_metatable(lua_State *state) -{ - MakeContainerMetatable(state, this, getItemType(), -1, getIndexEnumType()); -} - -void bitfield_identity::build_metatable(lua_State *state) -{ - int base = lua_gettop(state); - - MakeMetatable(state, this, "bitfield"); - - SetPtrMethods(state, base+1, base+2); - - SetContainerMethod(state, base+1, base+2, meta_bitfield_len, "__len", this, NULL, -1); - SetContainerMethod(state, base+1, base+2, meta_bitfield_index, "__index", this, NULL, -1); - SetContainerMethod(state, base+1, base+2, meta_bitfield_newindex, "__newindex", this, NULL, -1); - - AttachEnumKeys(state, base, this); - - EnableMetaField(state, base+2, "whole", this); -} - -void struct_identity::build_metatable(lua_State *state) -{ - int base = lua_gettop(state); - MakeFieldMetatable(state, this, meta_struct_index, meta_struct_newindex); - SetStructMethod(state, base+1, base+2, meta_struct_field_reference, "_field"); - SetPtrMethods(state, base+1, base+2); -} - -void global_identity::build_metatable(lua_State *state) -{ - MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex); + EnableMetaField(state, ftable_idx, "_enum"); } static void BuildTypeMetatable(lua_State *state, type_identity *type) @@ -1627,81 +815,6 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type) SaveTypeInfo(state, type); } -/** - * Construct a metatable for an object type folded into the field descriptor. - * This is done to reduce compile-time symbol table bloat due to templates. - */ -static void GetAdHocMetatable(lua_State *state, const struct_field_info *field) -{ - lua_pushlightuserdata(state, (void*)field); - - if (!LookupTypeInfo(state, true)) - { - switch (field->mode) - { - case struct_field_info::CONTAINER: - { - auto ctype = (container_identity*)field->type; - MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->eid); - break; - } - - case struct_field_info::STATIC_STRING: - MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, - &df::identity_traits::identity, field->count, NULL); - break; - - case struct_field_info::STATIC_ARRAY: - MakeContainerMetatable(state, &df::buffer_container_identity::base_instance, - field->type, field->count, field->eid); - break; - - case struct_field_info::STL_VECTOR_PTR: - MakeContainerMetatable(state, &df::identity_traits >::identity, - field->type, -1, field->eid); - break; - - default: - luaL_error(state, "Invalid ad-hoc field: %d", field->mode); - } - - lua_pop(state, 1); - - SaveTypeInfo(state, (void*)field); - } -} - -static void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target) -{ - if (!target) - { - push_object_internal(state, &df::identity_traits::identity, ptr); - return; - } - - LookupInTable(state, target, DFHACK_PTR_IDTABLE_NAME); - - type_identity *id = (type_identity*)lua_touserdata(state, -1); - lua_pop(state, 1); - - if (!id) - { - /* - * HACK: relies on - * 1) pointer_identity destructor being no-op - * 2) lua gc never moving objects in memory - */ - - void *newobj = lua_newuserdata(state, sizeof(pointer_identity)); - id = new (newobj) pointer_identity(target); - - SaveInTable(state, target, DFHACK_PTR_IDTABLE_NAME); - lua_pop(state, 1); - } - - push_object_internal(state, id, ptr); -} - /* * Recursive walk of scopes to construct the df... tree. */ diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h new file mode 100644 index 000000000..09e160355 --- /dev/null +++ b/library/include/LuaWrapper.h @@ -0,0 +1,189 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once + +#include +#include +#include +#include + +#include "DataDefs.h" + +#include +#include + +/** + * Internal header file of the lua wrapper. + */ + +namespace DFHack { namespace LuaWrapper { + +/* + * Registry name: hash of type metatables <-> type identities. + */ +#define DFHACK_TYPETABLE_NAME "DFHack::DFTypes" + +/* + * Registry name: hash of type identity -> node in df.etc... + */ +#define DFHACK_TYPEID_TABLE_NAME "DFHack::DFTypeIds" + +/* + * Registry name: hash of enum/bitfield identity -> index lookup table + */ +#define DFHACK_ENUM_TABLE_NAME "DFHack::DFEnums" + +/* + * Registry name: hash of pointer target identity <-> adhoc pointer identity userdata. + */ +#define DFHACK_PTR_IDTABLE_NAME "DFHack::PtrDFTypes" + +// Function registry names +#define DFHACK_CHANGEERROR_NAME "DFHack::ChangeError" +#define DFHACK_COMPARE_NAME "DFHack::ComparePtrs" +#define DFHACK_TYPE_TOSTRING_NAME "DFHack::TypeToString" +#define DFHACK_SIZEOF_NAME "DFHack::Sizeof" +#define DFHACK_DISPLACE_NAME "DFHack::Displace" +#define DFHACK_NEW_NAME "DFHack::New" +#define DFHACK_ASSIGN_NAME "DFHack::Assign" + +/* + * Upvalue: contents of DFHACK_TYPETABLE_NAME + */ +#define UPVAL_TYPETABLE lua_upvalueindex(1) + +/* + * Expected metatable of the current object. + */ +#define UPVAL_METATABLE lua_upvalueindex(2) + +/* + * Table mapping field names to indices or data structure pointers. + * Enum index table is linked into here via getmetatable($).__index. + * Fields that are actually in UPVAL_METATABLE are marked with NULL light udata. + */ +#define UPVAL_FIELDTABLE lua_upvalueindex(3) + +/* + * Only for containers: light udata with container identity. + */ +#define UPVAL_CONTAINER_ID lua_upvalueindex(4) + +/* + * Only for containers: light udata with item identity. + */ +#define UPVAL_ITEM_ID lua_upvalueindex(5) + +/* + * Only for containers: if not nil, overrides the item count. + */ +#define UPVAL_ITEM_COUNT lua_upvalueindex(6) + + inline void lua_dup(lua_State *state) { lua_pushvalue(state, -1); } + inline void lua_swap(lua_State *state) { lua_insert(state, -2); } + + /** + * Object references are represented as userdata instances + * with an appropriate metatable; the payload of userdata is + * this structure: + */ + struct DFRefHeader { + void *ptr; + }; + + /** + * Push the pointer as DF object ref using metatable on the stack. + */ + void push_object_ref(lua_State *state, void *ptr); + void *get_object_ref(lua_State *state, int val_index); + + /* + * The system might be extended to carry some simple + * objects inline inside the reference buffer. + */ + inline bool is_self_contained(DFRefHeader *ptr) { + void **pp = &ptr->ptr; + return **(void****)pp == (pp + 1); + } + + /** + * Report an error while accessing a field (index = field name). + */ + void field_error(lua_State *state, int index, const char *err, const char *mode); + + /* + * If is_method is true, these use UPVAL_TYPETABLE to save a hash lookup. + */ + void push_object_internal(lua_State *state, type_identity *type, void *ptr, bool in_method = true); + void *get_object_internal(lua_State *state, type_identity *type, int val_index, bool exact_type, bool in_method = true); + + void push_adhoc_pointer(lua_State *state, void *ptr, type_identity *target); + + /** + * Verify that the object is a DF ref with UPVAL_METATABLE. + * If everything ok, extract the address. + */ + uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + + void LookupInTable(lua_State *state, void *id, const char *tname); + void SaveInTable(lua_State *state, void *node, const char *tname); + void SaveTypeInfo(lua_State *state, void *node); + + /** + * Look up the key on the stack in DFHACK_TYPETABLE; + * if found, put result on the stack and return true. + */ + bool LookupTypeInfo(lua_State *state, bool in_method); + + /** + * Make a metatable with most common fields, and an empty table for UPVAL_FIELDTABLE. + */ + void MakeMetatable(lua_State *state, type_identity *type, const char *kind); + /** + * Enable a metafield by injecting an entry into a UPVAL_FIELDTABLE. + */ + void EnableMetaField(lua_State *state, int ftable_idx, const char *name, void *id = NULL); + /** + * Set metatable properties common to all actual DF object references. + */ + void SetPtrMethods(lua_State *state, int meta_idx, int read_idx); + /** + * Add a struct-style (3 upvalues) metamethod to the metatable. + */ + void SetStructMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name); + /** + * Add a 6 upvalue metamethod to the metatable. + */ + void SetContainerMethod(lua_State *state, int meta_idx, int ftable_idx, + lua_CFunction function, const char *name, + type_identity *container, type_identity *item, int count); + /** + * If ienum refers to a valid enum, attach its keys to UPVAL_FIELDTABLE, + * and the enum itself to the _enum metafield. + */ + void AttachEnumKeys(lua_State *state, int meta_idx, int ftable_idx, type_identity *ienum); +}} +