From 92549f3c561e6c8f3678dac424b942eae94653c7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 03:09:18 -0400 Subject: [PATCH 1/8] Add _fields table to struct types --- library/LuaTypes.cpp | 106 ++++++++++++++++++++++++++++++++++++++--- library/LuaWrapper.cpp | 1 + 2 files changed, 100 insertions(+), 7 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index ddc166abc..7e1ab67cb 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1291,11 +1291,27 @@ void LuaWrapper::SetFunctionWrappers(lua_State *state, const FunctionReg *reg) /** * Add fields in the array to the UPVAL_FIELDTABLE candidates on the stack. + * + * flags: + * GLOBALS: if true, pstruct is a global_identity and fields with addresses of 0 are skipped + * RAW: if true, no fields are skipped (supersedes `GLOBALS` flag) and + * special-case fields like OBJ_METHODs are not added to the metatable + * + * Stack in & out: + * base+1: metatable + * base+2: fields table (to be populated, map of name -> struct_field_info*) + * base+3: field iter table (to be populated, bimap of name <-> integer index) */ -static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bool globals) +namespace IndexFieldsFlags { + enum IndexFieldsFlags { + GLOBALS = 1 << 0, + RAW = 1 << 1, + }; +} +static void IndexFields(lua_State *state, int base, struct_identity *pstruct, int flags) { if (pstruct->getParent()) - IndexFields(state, base, pstruct->getParent(), globals); + IndexFields(state, base, pstruct->getParent(), flags); auto fields = pstruct->getFields(); if (!fields) @@ -1315,6 +1331,7 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo bool add_to_enum = true; + if (!(flags & IndexFieldsFlags::RAW)) // Handle the field switch (fields[i].mode) { @@ -1337,10 +1354,10 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo } // Do not add invalid globals to the enumeration order - if (globals && !*(void**)fields[i].offset) + if ((flags & IndexFieldsFlags::GLOBALS) && !*(void**)fields[i].offset) add_to_enum = false; - if (add_to_enum) + if (add_to_enum || (flags & IndexFieldsFlags::RAW)) AssociateId(state, base+3, ++cnt, name.c_str()); lua_pushlightuserdata(state, (void*)&fields[i]); @@ -1348,10 +1365,86 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo } } +static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity *pstruct) +{ + Lua::StackUnwinder base{state}; + + // metatable + lua_newtable(state); + int ix_meta = lua_gettop(state); + + // field info table (name -> struct_field_info*) + lua_newtable(state); + int ix_fieldinfo = lua_gettop(state); + + // field iter table (int <-> name) + lua_newtable(state); + int ix_fielditer = lua_gettop(state); + IndexFields(state, base, pstruct, IndexFieldsFlags::RAW); + + PushStructMethod(state, ix_meta, ix_fielditer, meta_struct_next); + SetPairsMethod(state, ix_meta, "__pairs"); + + // field table (name -> table representation of struct_field_info) + lua_newtable(state); + int ix_fields = lua_gettop(state); + + // wrapper table (empty, indexes into field table with metamethods) + lua_newtable(state); + int ix_wrapper = lua_gettop(state); + + // set up metatable for the wrapper + // use field table for __index + lua_pushstring(state, "__index"); + lua_pushvalue(state, ix_fields); + lua_settable(state, ix_meta); + + // use change_error() for __newindex + lua_pushstring(state, "__newindex"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_CHANGEERROR_NAME); + lua_settable(state, ix_meta); + + lua_pushvalue(state, ix_meta); + lua_setmetatable(state, ix_wrapper); + + // convert field info table (struct_field_info) to field table (lua tables) + lua_pushnil(state); // initial key for next() + while (lua_next(state, ix_fieldinfo)) { + auto field = static_cast(lua_touserdata(state, -1)); + lua_pushvalue(state, -2); // field name + lua_newtable(state); // new field info + Lua::TableInsert(state, "name", field->name); + Lua::TableInsert(state, "offset", field->offset); + + if (field->type) { + Lua::TableInsert(state, "type_name", field->type->getFullName()); + + lua_pushstring(state, "type"); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPEID_TABLE_TOKEN); + lua_rawgetp(state, -1, field->type); + lua_remove(state, -2); // TYPEID_TABLE + lua_settable(state, -3); + } + + lua_settable(state, ix_fields); + + lua_pop(state, 1); // field name + } + + lua_pushvalue(state, ix_wrapper); + lua_setfield(state, ftable_idx, "_fields"); + lua_pushvalue(state, ix_fieldinfo); + lua_setfield(state, ftable_idx, "_fieldsinfo"); + lua_pushvalue(state, ix_meta); + lua_setfield(state, ftable_idx, "_fieldsmeta"); + lua_pushvalue(state, ix_fielditer); + lua_setfield(state, ftable_idx, "_fieldsiter"); +} + void LuaWrapper::IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct) { // stack: metatable fieldtable - + AddFieldInfoTable(state, ftable_idx, pstruct); for (struct_identity *p = pstruct; p; p = p->getParent()) { auto fields = p->getFields(); @@ -1387,8 +1480,7 @@ static void MakeFieldMetatable(lua_State *state, struct_identity *pstruct, // Index the fields lua_newtable(state); - - IndexFields(state, base, pstruct, globals); + IndexFields(state, base, pstruct, globals ? IndexFieldsFlags::GLOBALS : 0); // Add the iteration metamethods PushStructMethod(state, base+1, base+3, iterator); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 1ea4865bd..9d2357c70 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1670,6 +1670,7 @@ static void RenderType(lua_State *state, compound_identity *node) { RenderTypeChildren(state, node->getScopeChildren()); + IndexStatics(state, ix_meta, ftable, (struct_identity*)node); lua_pushlightuserdata(state, node); lua_setfield(state, ftable, "_identity"); From 29f99733fe67b12facd4f10997c0b08f8b8cef16 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 19:00:24 -0400 Subject: [PATCH 2/8] Add tests --- library/LuaTypes.cpp | 9 ++---- test/structures/struct_fields.lua | 54 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 test/structures/struct_fields.lua diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 7e1ab67cb..1cc83be31 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1426,19 +1426,16 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity lua_settable(state, -3); } + // freeze_table(state); // TODO: make pairs() work lua_settable(state, ix_fields); lua_pop(state, 1); // field name } + // lua_pushvalue(state, ix_fields); + // freeze_table(state); // TODO: figure out why this creates an __index cycle for nonexistent fields lua_pushvalue(state, ix_wrapper); lua_setfield(state, ftable_idx, "_fields"); - lua_pushvalue(state, ix_fieldinfo); - lua_setfield(state, ftable_idx, "_fieldsinfo"); - lua_pushvalue(state, ix_meta); - lua_setfield(state, ftable_idx, "_fieldsmeta"); - lua_pushvalue(state, ix_fielditer); - lua_setfield(state, ftable_idx, "_fieldsiter"); } void LuaWrapper::IndexStatics(lua_State *state, int meta_idx, int ftable_idx, struct_identity *pstruct) diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua new file mode 100644 index 000000000..e94c9fbc5 --- /dev/null +++ b/test/structures/struct_fields.lua @@ -0,0 +1,54 @@ +config.target = 'core' + +local COORD_FIELD_NAMES = {'x', 'y', 'z', 'isValid', 'clear'} +local COORD_FIELD_EXPECTED_DATA = { + x = {type_name='int16_t'}, + y = {type_name='int16_t'}, + z = {type_name='int16_t'}, + isValid = {type_name='function'}, + clear = {type_name='function'}, +} + +local READONLY_MSG = 'Attempt to change a read%-only table.' + +function test.access() + local fields = df.coord._fields + expect.true_(fields) + expect.eq(fields, df.coord._fields) + + for name, expected in pairs(COORD_FIELD_EXPECTED_DATA) do + expect.true_(fields[name], name) + expect.eq(fields[name].name, name, name) + expect.eq(fields[name].type_name, expected.type_name, name) + expect.eq(type(fields[name].offset), 'number', name) + end +end + +function test.order() + local i = 0 + for name in pairs(df.coord._fields) do + i = i + 1 + expect.eq(name, COORD_FIELD_NAMES[i], i) + end + expect.eq(i, #COORD_FIELD_NAMES) +end + +function test.nonexistent() + expect.nil_(df.coord._fields.nonexistent) +end + +function test.readonly() + expect.error_match(READONLY_MSG, function() + df.coord._fields.x = 'foo' + end) + expect.error_match(READONLY_MSG, function() + df.coord._fields.nonexistent = 'foo' + end) + -- see TODOs in LuaTypes.cpp + -- expect.error_match(READONLY_MSG, function() + -- df.coord._fields.x.name = 'foo' + -- end) + + expect.eq(df.coord._fields.x.name, 'x') + expect.nil_(df.coord._fields.nonexistent) +end From 4a9a83daa5e40cf8f1cd493ffcdc699434fe90f6 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 19:28:02 -0400 Subject: [PATCH 3/8] Expose more fields, refactor --- library/LuaTypes.cpp | 55 +++++++++++++++++++++---------- test/structures/struct_fields.lua | 8 +++++ 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 1cc83be31..0d81886db 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1365,6 +1365,42 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, in } } +static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *field) +{ + lua_newtable(state); // new field info + Lua::TableInsert(state, "mode", field->mode); + Lua::TableInsert(state, "name", field->name); + Lua::TableInsert(state, "offset", field->offset); + Lua::TableInsert(state, "count", field->count); + + if (field->type) { + Lua::TableInsert(state, "type_name", field->type->getFullName()); + + lua_pushlightuserdata(state, field->type); + lua_setfield(state, -2, "type_identity"); + + lua_pushstring(state, "type"); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPEID_TABLE_TOKEN); + lua_rawgetp(state, -1, field->type); + lua_remove(state, -2); // TYPEID_TABLE + lua_settable(state, -3); + } + + if (field->extra) { + // TODO: index_enum, ref_target + if (field->extra->union_tag_field) { + Lua::TableInsert(state, "union_tag_field", field->extra->union_tag_field); + } + if (field->extra->union_tag_attr) { + Lua::TableInsert(state, "union_tag_attr", field->extra->union_tag_attr); + } + if (field->extra->original_name) { + Lua::TableInsert(state, "original_name", field->extra->original_name); + } + } + // freeze_table(state); // TODO: make pairs() work +} + static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity *pstruct) { Lua::StackUnwinder base{state}; @@ -1412,24 +1448,9 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity while (lua_next(state, ix_fieldinfo)) { auto field = static_cast(lua_touserdata(state, -1)); lua_pushvalue(state, -2); // field name - lua_newtable(state); // new field info - Lua::TableInsert(state, "name", field->name); - Lua::TableInsert(state, "offset", field->offset); - - if (field->type) { - Lua::TableInsert(state, "type_name", field->type->getFullName()); - - lua_pushstring(state, "type"); - lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPEID_TABLE_TOKEN); - lua_rawgetp(state, -1, field->type); - lua_remove(state, -2); // TYPEID_TABLE - lua_settable(state, -3); - } - - // freeze_table(state); // TODO: make pairs() work + PushFieldInfoSubTable(state, field); lua_settable(state, ix_fields); - - lua_pop(state, 1); // field name + lua_pop(state, 1); // struct_field_info } // lua_pushvalue(state, ix_fields); diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index e94c9fbc5..5ad513dcc 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -21,6 +21,14 @@ function test.access() expect.eq(fields[name].name, name, name) expect.eq(fields[name].type_name, expected.type_name, name) expect.eq(type(fields[name].offset), 'number', name) + expect.eq(type(fields[name].mode), 'number', name) + expect.eq(type(fields[name].count), 'number', name) + end +end + +function test.globals_original_name() + for name, info in pairs(df.global._fields) do + expect.eq(type(info.original_name), 'string', name) end end From fc6d4caa8ea3c28579e28cf2a2e86fa446a32f21 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 20:18:22 -0400 Subject: [PATCH 4/8] Dynamically generate field info in __index Constructing the complete tables when the types were initialized made it impossible to populate the "type" field, because not all types had been added to the global type tables yet. --- library/LuaTypes.cpp | 58 +++++++++++++++++++++++++++++-- test/structures/struct_fields.lua | 12 +++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 0d81886db..4507cb718 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1367,6 +1367,11 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, in static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *field) { + if (!field) { + lua_pushnil(state); + return; + } + lua_newtable(state); // new field info Lua::TableInsert(state, "mode", field->mode); Lua::TableInsert(state, "name", field->name); @@ -1401,6 +1406,51 @@ static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *fie // freeze_table(state); // TODO: make pairs() work } +/** + * Metamethod: __index for struct._fields + * + * upvalue 1: name -> struct_field_info* table + */ +static int meta_fieldinfo_index(lua_State *state) +{ + luaL_checktype(state, -1, LUA_TSTRING); + + lua_gettable(state, lua_upvalueindex(1)); + auto field = static_cast(lua_touserdata(state, -1)); + lua_pop(state, 1); + PushFieldInfoSubTable(state, field); + + return 1; +} + +/** + * Metamethod: iterator for struct._fields + * + * upvalue 1: name -> struct_field_info* table + * upvalue 3: field table (int <-> name) + */ +static int meta_fieldinfo_next(lua_State *state) +{ + if (lua_gettop(state) < 2) lua_pushnil(state); + + int len = lua_rawlen(state, UPVAL_FIELDTABLE); + int idx = cur_iter_index(state, len+1, 2, 0); + if (idx == len) + return 0; + + lua_rawgeti(state, UPVAL_FIELDTABLE, idx+1); + + // modified from meta_struct_next: + // retrieve the struct_field_info* from the table and convert it + lua_dup(state); + lua_gettable(state, lua_upvalueindex(1)); + auto field = static_cast(lua_touserdata(state, -1)); + lua_pop(state, 1); + PushFieldInfoSubTable(state, field); + + return 2; +} + static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity *pstruct) { Lua::StackUnwinder base{state}; @@ -1418,7 +1468,10 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity int ix_fielditer = lua_gettop(state); IndexFields(state, base, pstruct, IndexFieldsFlags::RAW); - PushStructMethod(state, ix_meta, ix_fielditer, meta_struct_next); + PushStructMethod(state, ix_meta, ix_fielditer, meta_fieldinfo_next); + // change upvalue 1 to the field info table since we don't need the original + lua_pushvalue(state, ix_fieldinfo); + lua_setupvalue(state, -2, 1); SetPairsMethod(state, ix_meta, "__pairs"); // field table (name -> table representation of struct_field_info) @@ -1432,7 +1485,8 @@ static void AddFieldInfoTable(lua_State *state, int ftable_idx, struct_identity // set up metatable for the wrapper // use field table for __index lua_pushstring(state, "__index"); - lua_pushvalue(state, ix_fields); + lua_pushvalue(state, ix_fieldinfo); + lua_pushcclosure(state, meta_fieldinfo_index, 1); lua_settable(state, ix_meta); // use change_error() for __newindex diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index 5ad513dcc..f9b3c41fc 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -60,3 +60,15 @@ function test.readonly() expect.eq(df.coord._fields.x.name, 'x') expect.nil_(df.coord._fields.nonexistent) end + +function test.circular_refs_init() + expect.eq(df.job._fields.list_link.type, df.job_list_link) + expect.eq(df.job_list_link._fields.item.type, df.job) +end + +function test.subclass_match() + for f, parent in pairs(df.viewscreen._fields) do + local child = df.viewscreen_titlest._fields[f] + expect.table_eq(parent, child, f) + end +end From 396b2d7832746bc8a1ae6ba6d34164e25f4fe27f Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 21:59:14 -0400 Subject: [PATCH 5/8] Adapt tests to dynamically-generated field info No need to freeze the field info tables anymore --- library/LuaTypes.cpp | 1 - test/structures/struct_fields.lua | 15 ++++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 4507cb718..ac99600a1 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1403,7 +1403,6 @@ static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *fie Lua::TableInsert(state, "original_name", field->extra->original_name); } } - // freeze_table(state); // TODO: make pairs() work } /** diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index f9b3c41fc..53c9ebcc1 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -43,6 +43,13 @@ end function test.nonexistent() expect.nil_(df.coord._fields.nonexistent) + + expect.error_match('string expected', function() + expect.nil_(df.coord._fields[2]) + end) + expect.error_match('string expected', function() + expect.nil_(df.coord._fields[nil]) + end) end function test.readonly() @@ -52,13 +59,11 @@ function test.readonly() expect.error_match(READONLY_MSG, function() df.coord._fields.nonexistent = 'foo' end) - -- see TODOs in LuaTypes.cpp - -- expect.error_match(READONLY_MSG, function() - -- df.coord._fields.x.name = 'foo' - -- end) + expect.nil_(df.coord._fields.nonexistent) + -- should have no effect + df.coord._fields.x.name = 'foo' expect.eq(df.coord._fields.x.name, 'x') - expect.nil_(df.coord._fields.nonexistent) end function test.circular_refs_init() From 574fa08747a2809fe41d74f56e999b8684470e98 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 22:12:45 -0400 Subject: [PATCH 6/8] Add index_enum, ref_target --- library/LuaTypes.cpp | 23 +++++++++++++++++------ test/structures/struct_fields.lua | 12 ++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index ac99600a1..407dea265 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1365,6 +1365,13 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, in } } +static void PushTypeIdentity(lua_State *state, const type_identity *id) +{ + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPEID_TABLE_TOKEN); + lua_rawgetp(state, -1, id); + lua_remove(state, -2); // TYPEID_TABLE +} + static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *field) { if (!field) { @@ -1384,15 +1391,19 @@ static void PushFieldInfoSubTable(lua_State *state, const struct_field_info *fie lua_pushlightuserdata(state, field->type); lua_setfield(state, -2, "type_identity"); - lua_pushstring(state, "type"); - lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_TYPEID_TABLE_TOKEN); - lua_rawgetp(state, -1, field->type); - lua_remove(state, -2); // TYPEID_TABLE - lua_settable(state, -3); + PushTypeIdentity(state, field->type); + lua_setfield(state, -2, "type"); } if (field->extra) { - // TODO: index_enum, ref_target + if (field->extra->index_enum) { + PushTypeIdentity(state, field->extra->index_enum); + lua_setfield(state, -2, "index_enum"); + } + if (field->extra->ref_target) { + PushTypeIdentity(state, field->extra->ref_target); + lua_setfield(state, -2, "ref_target"); + } if (field->extra->union_tag_field) { Lua::TableInsert(state, "union_tag_field", field->extra->union_tag_field); } diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index 53c9ebcc1..d61c71846 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -52,6 +52,18 @@ function test.nonexistent() end) end +function test.count() + expect.eq(df.unit._fields.relationship_ids.count, 10) +end + +function test.index_enum() + expect.eq(df.unit._fields.relationship_ids.index_enum, df.unit_relationship_type) +end + +function test.ref_target() + expect.eq(df.unit._fields.hist_figure_id.ref_target, df.historical_figure) +end + function test.readonly() expect.error_match(READONLY_MSG, function() df.coord._fields.x = 'foo' From 2a82add030f74c80dc3c45a1703e58c2a06b236c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 12 Aug 2023 22:39:20 -0400 Subject: [PATCH 7/8] Test for order of subclass fields --- test/structures/struct_fields.lua | 33 +++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/test/structures/struct_fields.lua b/test/structures/struct_fields.lua index d61c71846..01e4175d4 100644 --- a/test/structures/struct_fields.lua +++ b/test/structures/struct_fields.lua @@ -11,6 +11,14 @@ local COORD_FIELD_EXPECTED_DATA = { local READONLY_MSG = 'Attempt to change a read%-only table.' +local function listFieldNames(t) + local names = {} + for name in pairs(t._fields) do + table.insert(names, name) + end + return names +end + function test.access() local fields = df.coord._fields expect.true_(fields) @@ -33,12 +41,7 @@ function test.globals_original_name() end function test.order() - local i = 0 - for name in pairs(df.coord._fields) do - i = i + 1 - expect.eq(name, COORD_FIELD_NAMES[i], i) - end - expect.eq(i, #COORD_FIELD_NAMES) + expect.table_eq(listFieldNames(df.coord), COORD_FIELD_NAMES) end function test.nonexistent() @@ -89,3 +92,21 @@ function test.subclass_match() expect.table_eq(parent, child, f) end end + +function test.subclass_order() + -- ensure that parent class fields come before subclass fields + local hierarchy = {df.item, df.item_actual, df.item_crafted, df.item_constructed, df.item_bedst} + local field_names = {} + for _, t in pairs(hierarchy) do + field_names[t] = listFieldNames(t) + end + for ic = 1, #hierarchy do + for ip = 1, ic - 1 do + local parent_fields = listFieldNames(hierarchy[ip]) + local child_fields = listFieldNames(hierarchy[ic]) + child_fields = table.pack(table.unpack(child_fields, 1, #parent_fields)) + child_fields.n = nil + expect.table_eq(child_fields, parent_fields, ('compare %s to %s'):format(hierarchy[ip], hierarchy[ic])) + end + end +end From b039f227c336b8fee9295a3f2eb5e599e99cca7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 13 Aug 2023 01:16:25 -0400 Subject: [PATCH 8/8] Document type._fields --- docs/dev/Lua API.rst | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index be0062696..08b20e1b3 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -306,7 +306,32 @@ All types and the global object have the following features: * ``type._identity`` Contains a lightuserdata pointing to the underlying - ``DFHack::type_instance`` object. + ``DFHack::type_identity`` object. + +All compound types (structs, classes, unions, and the global object) support: + +* ``type._fields`` + + Contains a table mapping field names to descriptions of the type's fields, + including data members and functions. Iterating with ``pairs()`` returns data + fields in the order they are defined in the type. Functions and globals may + appear in an arbitrary order. + + Each entry contains the following fields: + + * ``name``: the name of the field (matches the ``_fields`` table key) + * ``offset``: for data members, the position of the field relative to the start of the type, in bytes + * ``count``: for arrays, the number of elements + * ``mode``: implementation detail. See ``struct_field_info::Mode`` in ``DataDefs.h``. + + Each entry may also contain the following fields, depending on its type: + + * ``type_name``: present for most fields; a string representation of the field's type + * ``type``: the type object matching the field's type; present if such an object exists + (e.g. present for DF types, absent for primitive types) + * ``type_identity``: present for most fields; a lightuserdata pointing to the field's underlying ``DFHack::type_identity`` object + * ``index_enum``, ``ref_target``: the type object corresponding to the field's similarly-named XML attribute, if present + * ``union_tag_field``, ``union_tag_attr``, ``original_name``: the string value of the field's similarly-named XML attribute, if present Types excluding the global object also support: