diff --git a/limmutable.c b/limmutable.c index 2617701..2a547ca 100644 --- a/limmutable.c +++ b/limmutable.c @@ -19,7 +19,6 @@ * hash buckets: weak-keyed map from instance userdata -> table of values * __buckets: weak-keyed map from instance userdata -> hash bucket (used to manage hash bucket lifetimes) * __fields: list of named fields -* __indices: map from field names to the index in the instance table where the value is stored * metamethods, methods, class variables, etc. */ @@ -50,11 +49,11 @@ static int WEAK_VALUE_METATABLE; // constructed classes to have the same metatable: // {__new=Lcreate_instance, __tostring=function(cls) return cls.name or 'immutable(...)' end} static int SHARED_CLASS_METATABLE; -static int SUPER_METHODS; +static int SUPER_METHODS, NIL_SENTINEL; typedef struct { int hash; - size_t len; + size_t array_len; } immutable_info_t; #define IMMUTABLE_GETVALUE(i) if (from_table) {\ @@ -68,8 +67,12 @@ typedef struct { if (i <= num_args) {\ lua_pushvalue(L, 1+i);\ } else {\ - lua_pushnil(L);\ + lua_pushlightuserdata(L, &NIL_SENTINEL);\ }\ +}\ +if (lua_isnil(L, -1)) {\ + lua_pop(L, 1);\ + lua_pushlightuserdata(L, &NIL_SENTINEL);\ } static inline int _create_instance(lua_State *L, int from_table) @@ -79,6 +82,7 @@ static inline int _create_instance(lua_State *L, int from_table) int fields_index = lua_gettop(L); size_t num_fields = lua_objlen(L, -1); size_t num_values = from_table ? (num_fields + lua_objlen(L, 2)) : (num_args >= num_fields ? num_args : num_fields); + size_t array_len = num_values - num_fields; // Compute the hash and populate the values table: // Stack: [fields] @@ -107,11 +111,15 @@ static inline int _create_instance(lua_State *L, int from_table) // Arbitrarily chosen values item_hash = lua_toboolean(L, -1)? 0x82684f71 : 0x88d66f2a; break; + case LUA_TLIGHTUSERDATA: + if (lua_touserdata(L, -1) == &NIL_SENTINEL) { + item_hash = (1000003 * type) ^ (lua_Integer)lua_topointer(L, -1); + break; + } case LUA_TTABLE: case LUA_TFUNCTION: case LUA_TUSERDATA: case LUA_TTHREAD: - case LUA_TLIGHTUSERDATA: item_hash = (1000003 * type) ^ (lua_Integer)lua_topointer(L, -1); break; case LUA_TSTRING: @@ -163,12 +171,14 @@ static inline int _create_instance(lua_State *L, int from_table) // Stack: [fields, __instances, bucket, hash_collider_inst, collider_table] // Shallow equality check: immutable_info_t *collider_info = (immutable_info_t*)lua_touserdata(L, -2); - if (collider_info->len != num_values) { + if (collider_info->array_len != array_len) { lua_pop(L, 1); goto next_bucket_item; } - for (size_t i = 1; i <= num_values; i++) { - lua_rawgeti(L, -1, i); + // Stack: [fields, __instances, bucket, hash_collider_inst, collider_table] + for (size_t i = 1; i <= num_fields; i++) { + lua_rawgeti(L, fields_index, i); + lua_gettable(L, -2); IMMUTABLE_GETVALUE(i); // Stack: [fields, __instances, bucket, hash_collider_inst, collider_table, collider_val, inst_val] if (! lua_rawequal(L, -1, -2)) { // If the i'th entry doesn't match the i'th arg @@ -178,6 +188,17 @@ static inline int _create_instance(lua_State *L, int from_table) lua_pop(L, 2); } } + for (size_t i = 1; i <= array_len; i++) { + lua_rawgeti(L, -1, i); + IMMUTABLE_GETVALUE(i+num_fields); + // Stack: [fields, __instances, bucket, hash_collider_inst, collider_table, collider_val, inst_val] + if (! lua_rawequal(L, -1, -2)) { // If the i'th entry doesn't match the i'th arg + lua_pop(L, 3); + goto next_bucket_item; + } else { + lua_pop(L, 2); + } + } // bucket item matches // Stack: [fields, __instances, bucket, hash_collider_inst, collider_table] lua_pop(L, 1); @@ -194,7 +215,7 @@ static inline int _create_instance(lua_State *L, int from_table) // Stack: [fields, __instances, bucket, inst_userdata] int userdata_index = lua_gettop(L); userdata->hash = hash; - userdata->len = num_values; + userdata->array_len = array_len; lua_pushvalue(L, 1); // Stack: [fields, __instances, bucket, inst_userdata, cls] @@ -214,9 +235,14 @@ static inline int _create_instance(lua_State *L, int from_table) lua_pushvalue(L, userdata_index); // Stack: [fields, __instances, bucket, inst_userdata, inst_userdata] - lua_createtable(L, num_values, 0); - for (size_t i=1; i <= num_values; i++) { + lua_createtable(L, num_values-num_fields, num_fields); + for (size_t i=1; i <= num_fields; i++) { + lua_rawgeti(L, fields_index, i); IMMUTABLE_GETVALUE(i); + lua_settable(L, -3); + } + for (size_t i=1; i <= array_len; i++) { + IMMUTABLE_GETVALUE(i+num_fields); lua_rawseti(L, -2, i); } // Stack: [fields, __instances, bucket, inst_userdata, inst_userdata, inst_table] @@ -263,49 +289,24 @@ static int Llen(lua_State *L) luaL_error(L, "invalid type"); } immutable_info_t *info = (immutable_info_t *)lua_touserdata(L, 1); - lua_getfield(L, -1, "__fields"); - size_t len = info->len - lua_objlen(L, -1); - lua_pushinteger(L, len); + lua_pushinteger(L, info->array_len); return 1; } -static int Linst_index(lua_State *L) +static int Lindex(lua_State *L) { // Return inst[key], luaL_checktype(L, 1, LUA_TUSERDATA); if (! lua_getmetatable(L, 1)) { luaL_error(L, "invalid type"); } + int class_index = lua_gettop(L); // Stack: [mt] immutable_info_t *info = (immutable_info_t*)lua_touserdata(L, 1); if (! info) { luaL_error(L, "invalid type"); } - lua_getfield(L, -1, "__fields"); - size_t num_fields = lua_objlen(L, -1); - lua_pop(L, 1); - - // Stack: [mt] - lua_getfield(L, -1, "__indices"); - // Stack: [mt, indices] - lua_pushvalue(L, 2); - lua_gettable(L, -2); - // Stack: [mt, indices, index] - int index; - if (lua_isnil(L, -1)) { - if (! lua_isinteger(L, 2)) { - return 0; - } - index = lua_tointeger(L, 2) + num_fields; - } else { - index = lua_tointeger(L, -1); - } - lua_pop(L, 2); - // Stack: [mt] - if (! (0 < index && index <= (int)info->len)) { - return 0; - } // Stack: [mt] lua_getfield(L, -1, "__instances"); // Stack: [mt, buckets] @@ -321,28 +322,20 @@ static int Linst_index(lua_State *L) if (lua_isnil(L, -1)) { luaL_error(L, "Failed to find instance"); } - lua_rawgeti(L, -1, index); - lua_pushboolean(L, 1); - return 2; -} - -static int Lindex(lua_State *L) -{ - int ret = Linst_index(L); - if (ret > 0) { - return ret; - } - // Fall back to class: - lua_getmetatable(L, 1); - // Stack: [..., mt] lua_pushvalue(L, 2); - // Stack: [..., mt, key] lua_gettable(L, -2); - // Stack: [..., mt, value] - if (! lua_isnil(L, -1)) { + if (lua_isnil(L, -1)) { + // Fall back to class: + lua_pushvalue(L, 2); + lua_gettable(L, class_index); return 1; } - return 0; + if (lua_islightuserdata(L, -1) && lua_touserdata(L, -1) == &NIL_SENTINEL) { + lua_pop(L, 1); + lua_pushnil(L); + } + lua_pushboolean(L, 1); + return 2; } static int Ltostring(lua_State *L) @@ -382,10 +375,13 @@ static int Ltostring(lua_State *L) } int inst_table_index = lua_gettop(L); // Stack: [mt, buckets, bucket, inst_table] + lua_getfield(L, cls_index, "__fields"); + int fields_index = lua_gettop(L); lua_getglobal(L, "tostring"); int tostring_index = lua_gettop(L); - // Stack: [mt, buckets, bucket, inst_table, tostring] - int n = info->len, i = 1; + // Stack: [mt, buckets, bucket, inst_table, __fields, tostring] + int num_fields = lua_objlen(L, fields_index); + int n = num_fields + info->array_len, i = 1; if (i <= n) { goto first_list_item; } @@ -395,7 +391,16 @@ static int Ltostring(lua_State *L) first_list_item: lua_pushvalue(L, tostring_index); // Stack: [mt, buckets, bucket, inst_table, tostring, ???, tostring] - lua_rawgeti(L, inst_table_index, i); + if (i <= num_fields) { + lua_rawgeti(L, fields_index, i); + lua_gettable(L, inst_table_index); + } else { + lua_rawgeti(L, inst_table_index, i - num_fields); + } + if (lua_islightuserdata(L, -1) && lua_touserdata(L, -1) == &NIL_SENTINEL) { + lua_pop(L, 1); + lua_pushnil(L); + } // Stack: [mt, buckets, bucket, inst_table, tostring, ???, tostring, value] int quotes = lua_type(L, -1) == LUA_TSTRING; lua_call(L, 1, 1); @@ -420,7 +425,6 @@ static int Lnexti(lua_State *L) if (! lua_getmetatable(L, 1)) { luaL_error(L, "invalid type"); } - int cls_index = lua_gettop(L); // Stack: [mt] lua_getfield(L, -1, "__instances"); // Stack: [mt, buckets] @@ -428,11 +432,8 @@ static int Lnexti(lua_State *L) if (! info) { luaL_error(L, "invalid type"); } - int i = lua_isnil(L, 2) ? 1 : lua_tointeger(L, 2)+1; - lua_getfield(L, cls_index, "__fields"); - int num_fields = lua_objlen(L, -1); - lua_pop(L, 1); - if (i + num_fields > (int)info->len) { + int i = lua_tointeger(L, 2)+1; + if (i > (int)info->array_len) { return 0; } lua_rawgeti(L, -1, info->hash); @@ -446,8 +447,12 @@ static int Lnexti(lua_State *L) // Stack: [mt, buckets, bucket, inst_table] lua_pushinteger(L, i); // Stack: [mt, buckets, bucket, inst_table, i] - lua_rawgeti(L, -2, i + num_fields); + lua_rawgeti(L, -2, i); // Stack: [mt, buckets, bucket, inst_table, i, table[i]] + if (lua_islightuserdata(L, -1) && lua_touserdata(L, -1) == &NIL_SENTINEL) { + lua_pop(L, 1); + lua_pushnil(L); + } return 2; } @@ -457,8 +462,8 @@ static int Lipairs(lua_State *L) // Stack: [Lnexti] lua_pushvalue(L, 1); // Stack: [Lnexti, inst_udata] - lua_pushnil(L); - // Stack: [Lnexti, inst_udata, nil] + lua_pushinteger(L, 0); + // Stack: [Lnexti, inst_udata, 0] return 3; } @@ -467,57 +472,31 @@ static int Lnext(lua_State *L) if (! lua_getmetatable(L, 1)) { luaL_error(L, "invalid type"); } - int cls_index = lua_gettop(L); // Stack: [mt] - lua_getfield(L, cls_index, "__fields"); - int fields_index = lua_gettop(L); - lua_getfield(L, cls_index, "__indices"); - int indices_index = lua_gettop(L); - lua_getfield(L, cls_index, "__instances"); - // Stack: [..., buckets] + lua_getfield(L, -1, "__instances"); + // Stack: [mt, buckets] immutable_info_t *info = (immutable_info_t*)lua_touserdata(L, 1); if (! info) { luaL_error(L, "invalid type"); } lua_rawgeti(L, -1, info->hash); - // Stack: [..., buckets, bucket] + // Stack: [mt, buckets, bucket] if (lua_isnil(L, -1)) { luaL_error(L, "Failed to find hash bucket"); } lua_pushvalue(L, 1); - // Stack: [..., buckets, bucket, inst_udata] + // Stack: [mt, buckets, bucket, inst_udata] lua_rawget(L, -2); - // Stack: [..., buckets, bucket, inst_table] - size_t num_fields = lua_objlen(L, fields_index); - int index; - if (lua_isnil(L, 2)) { - index = 1; - } else { - lua_pushvalue(L, 2); - // Stack: [..., buckets, bucket, inst_table, k] - lua_gettable(L, indices_index); - // Stack: [..., buckets, bucket, inst_table, i] - if (! lua_isnil(L, -1)) { - index = lua_tointeger(L, -1) + 1; - } else { - index = lua_tointeger(L, 2) + num_fields + 1; - } - lua_pop(L, 1); - } - // Stack: [..., buckets, bucket, inst_table] - if (! (0 < index && index <= (int)info->len)) { + // Stack: [mt, buckets, bucket, inst_table] + lua_pushvalue(L, 2); + // TODO: this is in a random order, and it might be good to force it to be in the same order as __fields + if (lua_next(L, -2) == 0) { return 0; } - - // Now find field name - if (index <= (int)num_fields) { - lua_rawgeti(L, fields_index, index); - } else { - lua_pushinteger(L, index - num_fields); + if (lua_islightuserdata(L, -1) && lua_touserdata(L, -1) == &NIL_SENTINEL) { + lua_pop(L, 1); + lua_pushnil(L); } - // Stack: [..., buckets, bucket, inst_table, k2] - lua_rawgeti(L, -2, index); - // Stack: [..., buckets, bucket, inst_table, k2, value2] return 2; } @@ -605,39 +584,18 @@ static int Lmake_class(lua_State *L) // Stack: [CLS] switch (num_args == 0 ? LUA_TNIL : lua_type(L, 1)) { - case LUA_TTABLE: { - // CLS.__fields = arg1 + case LUA_TTABLE: lua_pushvalue(L, 1); - // Stack: [CLS, __fields] - lua_setfield(L, -2, "__fields"); - // Stack: [CLS] - - size_t n = lua_objlen(L, 1); - lua_createtable(L, 0, n); - // Stack: [CLS, __indices] - lua_pushnil(L); - while (lua_next(L, 1) != 0) { - // Stack: [CLS, __indices, i, fieldname] - lua_pushvalue(L, -2); - // Stack: [CLS, __indices, i, fieldname, i] - lua_settable(L, -4); - // Stack: [CLS, __indices, i] - } - lua_setfield(L, -2, "__indices"); break; - } - case LUA_TNIL: case LUA_TNONE: { - // If no fields were passed in, set __fields and __indices to empty tables + case LUA_TNIL: case LUA_TNONE: + // If no fields were passed in, set __fields to empty table lua_createtable(L, 0, 0); - lua_setfield(L, -2, "__fields"); - lua_createtable(L, 0, 0); - lua_setfield(L, -2, "__indices"); break; - } default: { luaL_error(L, "immutable expected the fields to be either table or nil"); } } + lua_setfield(L, -2, "__fields"); // Stack: [CLS] lua_pushlightuserdata(L, (void*)&SHARED_CLASS_METATABLE); lua_gettable(L, LUA_REGISTRYINDEX);