diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 536bcd72a..1e33ed713 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -65,9 +65,22 @@ void constructed_identity::lua_read(lua_State *state, int fname_idx, void *ptr) push_object_internal(state, this, ptr); } +static void invoke_assign(lua_State *state, type_identity *id, void *ptr, int val_index) +{ + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + push_object_internal(state, id, ptr); + lua_pushvalue(state, val_index); + lua_call(state, 2, 0); +} + void constructed_identity::lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) { - field_error(state, fname_idx, "complex object", "write"); + if (lua_istable(state, val_index)) + { + invoke_assign(state, this, ptr, val_index); + } + else + field_error(state, fname_idx, "complex object", "write"); } void enum_identity::lua_read(lua_State *state, int fname_idx, void *ptr) @@ -150,6 +163,47 @@ void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr) lua_read(state, fname_idx, ptr, target); } +static void autovivify_ptr(lua_State *state, int fname_idx, void **pptr, + type_identity *target, int val_index) +{ + lua_getfield(state, val_index, "new"); + + if (!lua_isnil(state, -1)) + { + int top = lua_gettop(state); + + type_identity *suggested = get_object_identity(state, top, "autovivify", true, true); + + if (!is_type_compatible(state, target, 0, suggested, top+1, false)) + field_error(state, fname_idx, "incompatible suggested autovivify type", "write"); + + lua_pop(state, 1); + + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME); + lua_swap(state); + lua_call(state, 1, 1); + + void *nval = get_object_internal(state, target, top, false); + + if (!nval) + field_error(state, fname_idx, "inconsistent autovivify type", "write"); + + *pptr = nval; + } + else + { + if (!target) + field_error(state, fname_idx, "trying to autovivify void*", "write"); + + *pptr = target->allocate(); + + if (!*pptr) + field_error(state, fname_idx, "could not allocate in autovivify", "write"); + } + + lua_pop(state, 1); +} + void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, type_identity *target, int val_index) { @@ -157,6 +211,13 @@ void df::pointer_identity::lua_write(lua_State *state, int fname_idx, void *ptr, if (lua_isnil(state, val_index)) *pptr = NULL; + else if (lua_istable(state, val_index)) + { + if (!*pptr) + autovivify_ptr(state, fname_idx, pptr, target, val_index); + + invoke_assign(state, target, *pptr, val_index); + } else { void *nval = get_object_internal(state, target, val_index, false); @@ -435,10 +496,15 @@ static void write_field(lua_State *state, const struct_field_info *field, void * case struct_field_info::POINTER: df::pointer_identity::lua_write(state, 2, ptr, field->type, value_idx); + return; case struct_field_info::STATIC_ARRAY: case struct_field_info::STL_VECTOR_PTR: - field_error(state, 2, "complex object", "write"); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + read_field(state, field, ptr); + lua_pushvalue(state, value_idx); + lua_call(state, 2, 0); + return; case struct_field_info::END: return; diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 4758f04f1..11db14875 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -255,8 +255,8 @@ static void fetch_container_details(lua_State *state, int meta, type_identity ** /** * Check if type1 and type2 are compatible, possibly using additional metatable data. */ -static bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, - type_identity *type2, int meta2, bool exact_equal) +bool LuaWrapper::is_type_compatible(lua_State *state, type_identity *type1, int meta1, + type_identity *type2, int meta2, bool exact_equal) { if (type1 == type2) return true; @@ -417,9 +417,9 @@ static bool is_valid_metatable(lua_State *state, int objidx, int metaidx) /** * Given a DF object reference or type, safely retrieve its identity pointer. */ -static type_identity *get_object_identity(lua_State *state, int objidx, - const char *ctx, bool allow_type = false, - bool keep_metatable = false) +type_identity *LuaWrapper::get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type, + bool keep_metatable) { if (!lua_getmetatable(state, objidx)) luaL_error(state, "Invalid object in %s", ctx); @@ -608,6 +608,31 @@ static int meta_new(lua_State *state) return 1; } +static void invoke_resize(lua_State *state, int table, lua_Integer size) +{ + lua_getfield(state, table, "resize"); + lua_pushvalue(state, table); + lua_pushinteger(state, size); + lua_call(state, 2, 0); +} + +static void copy_table(lua_State *state, int dest, int src, int skipkey) +{ + lua_pushnil(state); + + while (lua_next(state, src)) + { + if (lua_equal(state, -2, skipkey)) + lua_pop(state, 1); + else + { + lua_pushvalue(state, -2); + lua_swap(state); + lua_settable(state, dest); + } + } +} + /** * Method: assign data between objects. */ @@ -618,11 +643,83 @@ static int meta_assign(lua_State *state) if (argc != 2) luaL_error(state, "Usage: target:assign(src) or df.assign(target,src)"); - type_identity *id1, *id2; - check_type_compatible(state, 1, 2, &id1, &id2, "df.assign()", false, false); + if (!lua_istable(state, 2)) + { + type_identity *id1, *id2; + check_type_compatible(state, 1, 2, &id1, &id2, "df.assign()", false, false); + + if (!id1->copy(get_object_ref(state, 1), get_object_ref(state, 2))) + luaL_error(state, "No copy support for %s", id1->getFullName().c_str()); + } + else + { + type_identity *id = get_object_identity(state, 1, "df.assign()", false); + + if (id->isContainer()) + { + lua_pushstring(state, "resize"); + int resize_str = lua_gettop(state); - if (!id1->copy(get_object_ref(state, 1), get_object_ref(state, 2))) - luaL_error(state, "No copy support for %s", id1->getFullName().c_str()); + lua_dup(state); + lua_rawget(state, 2); + + if (lua_isnil(state,-1)) + { + /* + * nil or missing resize field => 1-based lua array + */ + int size = lua_objlen(state, 2); + + lua_pop(state, 1); + invoke_resize(state, 1, size); + + for (int i = 1; i <= size; i++) + { + lua_pushinteger(state, i-1); + lua_rawgeti(state, 2, i); + lua_settable(state, 1); + } + } + else + { + if (lua_isboolean(state, -1)) + { + // resize=false => just assign + // resize=true => find the largest index + if (lua_toboolean(state, -1)) + { + lua_Integer size = 0; + + lua_pushnil(state); + while (lua_next(state, 2)) + { + lua_pop(state, 1); + if (lua_isnumber(state,-1)) + size = std::max(size, lua_tointeger(state,-1)+1); + } + + invoke_resize(state, 1, size); + } + } + else + { + // otherwise, must be an explicit number + if (!lua_isnumber(state,-1)) + luaL_error(state, "Invalid container.resize value in df.assign()"); + + invoke_resize(state, 1, lua_tointeger(state, -1)); + } + + lua_pop(state, 1); + copy_table(state, 1, 2, resize_str); + } + } + else + { + lua_pushstring(state, "new"); + copy_table(state, 1, 2, lua_gettop(state)); + } + } return 0; } diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index d043d96f7..64ebf4de6 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -149,6 +149,13 @@ namespace DFHack { namespace LuaWrapper { */ uint8_t *get_object_addr(lua_State *state, int obj, int field, const char *mode); + bool is_type_compatible(lua_State *state, type_identity *type1, int meta1, + type_identity *type2, int meta2, bool exact_equal); + + type_identity *get_object_identity(lua_State *state, int objidx, + const char *ctx, bool allow_type = false, + bool keep_metatable = false); + 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);