diff --git a/README.md b/README.md index 8567f1f..bfa39c4 100644 --- a/README.md +++ b/README.md @@ -6,32 +6,68 @@ This is a native Lua library that allows the creation of immutable tables. Lua 5.1/5.2/5.3+ or LuaJIT 2.0+ built from source code is a prerequisite. Lua can be downloaded from the [lua.org downloads page](https://www.lua.org/ftp/), and LuaJIT can be downloaded from the [LuaJIT.org downloads page](http://luajit.org/download.html). -`make LUA=/path/to/lua_dir` -or for LuaJIT: `make LUA=/path/to/luajit_dir LUASUFFIX=jit` +`make` or for LuaJIT: `make LUA=luajit` (you can also optionally specify the path to the directory containing lua.h and lauxlib.h with `LUA_INC` and the path to the directory containing the lua binary with `LUA_BIN`). ## Usage ``` -local immutable = require "immutable" -local Vec2 = immutable({"x","y"}, { +immutable = require "immutable" +Vec = immutable({"x","y"}, { + name='Vector', len2=function(self) return self.x*self.x + self.y*self.y end, - is_a_vec2 = true, + class_variable = "classvar", __add=function(self, other) local cls = getmetatable(self) return cls(self.x+other.x, self.y+other.y) end, - __tostring=function(self) - return "Vec2("..tostring(self.x)..", "..tostring(self.y)..")" - end, }) -local v = Vec2(2, 3) +v = Vec(2, 3) assert(v.x == 2 and v.y == 3) -local v2 = v + Vec2(0, 1) -assert(v2 == Vec2(2, 4)) -local t = {[v2]='yep'} -assert(t[Vec2(2,4)] == 'yep') -assert(Vec2(2,0):len2() == 4) -assert(Vec2(2,0).is_a_vec2) +also_v = Vec(2, 3) +assert(v == also_v) +t = {[v]='yep'} +assert(t[also_v] == 'yep') +assert(v + Vec(0,1) == Vec(2,4)) +assert(#v == 2) +assert(v:len2() == 13) +assert(v.class_variable == "classvar") +assert(tostring(v) == 'Vector(x=2, y=3)') +assert(Vec:is_instance(v) and not Vec:is_instance({x=2,y=3})) +for k, v in pairs(v) do + assert((k == 'x' and v == 2) or (k == 'y' and v == 3)) +end +for i, v in ipairs(v) do + assert((i == 1 and v == 2) or (i == 2 and v == 3)) +end +NotVec = immutable({"x","y"}) +assert(NotVec(1,2) ~= Vec(1,2)) +``` + +## Singleton recipe +Singletons are pretty straightforward: +``` +Singleton = immutable() +assert(Singleton() == Singleton()) +``` +Or if you want methods/class variables: +``` +DogSingleton = immutable(0, {name="DogSingleton", bark=function(self) print("woof") end}) +DogSingleton():bark() +``` + +## Tuple recipe +With immutable tables, it's pretty simple to emulate Python-like tuples: +``` +local tuple_classes = {} +Tuple = function(...) + local n = select('#', ...) + if not tuple_classes[n] then + tuple_classes[n] = immutable(n) + end + return tuple_classes[n](...) +end +assert(Tuple(5,6,7) == Tuple(5,6,7)) +assert(tostring(Tuple(8,9)) == "(8, 9)") ``` diff --git a/limmutable.c b/limmutable.c index 3deb32b..a742cd5 100644 --- a/limmutable.c +++ b/limmutable.c @@ -328,6 +328,7 @@ static int Ltostring(lua_State *L) lua_pushnil(L); int needs_comma = 0; + int numeric_index = 1; while (lua_next(L, -2) != 0) { // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname] if (needs_comma) { @@ -336,15 +337,19 @@ static int Ltostring(lua_State *L) needs_comma = 1; } // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname] - lua_pushvalue(L, -4); - // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname, tostring] - lua_insert(L, -2); - // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, tostring, fieldname] - lua_call(L, 1, 1); - // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, field string] - luaL_addvalue(&b); - // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i] - luaL_addstring(&b, "="); + if (lua_type(L, -1) == LUA_TNUMBER && lua_tointeger(L, -1) == numeric_index) { + lua_pop(L, 1); + } else { + lua_pushvalue(L, -4); + // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname, tostring] + lua_insert(L, -2); + // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, tostring, fieldname] + lua_call(L, 1, 1); + // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, field string] + luaL_addvalue(&b); + // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i] + luaL_addstring(&b, "="); + } lua_rawgeti(L, -4, lua_tonumber(L, -1)); // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, value] lua_pushvalue(L, -4); @@ -355,6 +360,7 @@ static int Ltostring(lua_State *L) // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, value string] luaL_addvalue(&b); // Stack: [mt, buckets, bucket, inst_table, tostring, fields, i] + numeric_index++; } luaL_addstring(&b, ")"); luaL_pushresult(&b); @@ -431,9 +437,9 @@ static int Lnext(lua_State *L) return 0; } // Stack: [mt, buckets, bucket, inst_table, fields, k2, next_i] - lua_rawgeti(L, -4, lua_tonumber(L, -1)); - // Stack: [mt, buckets, bucket, inst_table, fields, k2, next_i, value] - return 3; + lua_gettable(L, -4); + // Stack: [mt, buckets, bucket, inst_table, fields, k2, value] + return 2; } static int Lpairs(lua_State *L) @@ -493,30 +499,65 @@ static int Lmake_class(lua_State *L) lua_setfield(L, -2, "__instances"); // Stack: [CLS] + switch (lua_type(L, 1)) { + case LUA_TTABLE: { + // CLS.__fields = arg1 + lua_pushvalue(L, 1); + // Stack: [CLS, __fields] + lua_setfield(L, -2, "__fields"); + // Stack: [CLS] - // CLS.__fields = arg1 - lua_pushvalue(L, 1); - // Stack: [CLS, __fields] - lua_setfield(L, -2, "__fields"); - // Stack: [CLS] - - if (lua_type(L, 1) != LUA_TTABLE) { - // If no fields were passed in, make them empty (i.e. a singleton) - lua_createtable(L, 0, 0); - } else { - 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] + 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_TNUMBER: { + // If no fields were passed in, make them empty (i.e. a singleton) + lua_Integer n = lua_tointeger(L, 1); + if (n < 0) { + luaL_error(L, "immutable table size must be positive"); + } + lua_createtable(L, n, 0); + lua_createtable(L, n, 0); + // Stack: [CLS, __fields, __indices] + for (lua_Integer i = 1; i <= n; i++) { + lua_pushinteger(L, i); + // Stack: [CLS, __fields, __indices, i] + lua_rawseti(L, -2, i); + // Stack: [CLS, __fields, __indices] + lua_pushinteger(L, i); + // Stack: [CLS, __fields, __indices, i] + lua_rawseti(L, -3, i); + // Stack: [CLS, __fields, __indices] + } + // Stack: [CLS, __fields, __indices] + lua_setfield(L, -3, "__indices"); + // Stack: [CLS, __fields] + lua_setfield(L, -2, "__fields"); + break; + } + case LUA_TNIL: case LUA_TNONE: { + // If no fields were passed in, make them empty (i.e. a singleton) + 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, "expected number, table, or nil"); } } - lua_setfield(L, -2, "__indices"); // Stack: [CLS] // setmetatable(CLS, {__new=CLS.new}) diff --git a/tests.lua b/tests.lua index 65eee6e..097ed16 100644 --- a/tests.lua +++ b/tests.lua @@ -179,6 +179,27 @@ test("Testing unpacking", function() end end) +test("Testing pairs()", function() + local copy = {} + for k,v in pairs(Vec(3,4)) do + copy[k] = v + end + assert(copy.x == 3 and copy.y == 4) +end) + +test("Testing ipairs()", function() + local copy = {} + for k,v in ipairs(Vec(3,4)) do + copy[k] = v + end + assert(copy[1] == 3 and copy[2] == 4) +end) + +test("Testing immutable(n)", function() + local Tup3 = immutable(3, {name="Tuple"}) + assert(tostring(Tup3(1,2,3)) == "Tuple(1, 2, 3)") +end) + if num_errors == 0 then print(green.."All tests passed!"..reset) else