Added better support for tuple-like immutable tables. Updated docs and
tests.
This commit is contained in:
parent
139e214b1f
commit
0f72014571
66
README.md
66
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).
|
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`
|
`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`).
|
||||||
or for LuaJIT: `make LUA=/path/to/luajit_dir LUASUFFIX=jit`
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
local immutable = require "immutable"
|
immutable = require "immutable"
|
||||||
local Vec2 = immutable({"x","y"}, {
|
Vec = immutable({"x","y"}, {
|
||||||
|
name='Vector',
|
||||||
len2=function(self)
|
len2=function(self)
|
||||||
return self.x*self.x + self.y*self.y
|
return self.x*self.x + self.y*self.y
|
||||||
end,
|
end,
|
||||||
is_a_vec2 = true,
|
class_variable = "classvar",
|
||||||
__add=function(self, other)
|
__add=function(self, other)
|
||||||
local cls = getmetatable(self)
|
local cls = getmetatable(self)
|
||||||
return cls(self.x+other.x, self.y+other.y)
|
return cls(self.x+other.x, self.y+other.y)
|
||||||
end,
|
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)
|
assert(v.x == 2 and v.y == 3)
|
||||||
local v2 = v + Vec2(0, 1)
|
also_v = Vec(2, 3)
|
||||||
assert(v2 == Vec2(2, 4))
|
assert(v == also_v)
|
||||||
local t = {[v2]='yep'}
|
t = {[v]='yep'}
|
||||||
assert(t[Vec2(2,4)] == 'yep')
|
assert(t[also_v] == 'yep')
|
||||||
assert(Vec2(2,0):len2() == 4)
|
assert(v + Vec(0,1) == Vec(2,4))
|
||||||
assert(Vec2(2,0).is_a_vec2)
|
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)")
|
||||||
```
|
```
|
||||||
|
107
limmutable.c
107
limmutable.c
@ -328,6 +328,7 @@ static int Ltostring(lua_State *L)
|
|||||||
|
|
||||||
lua_pushnil(L);
|
lua_pushnil(L);
|
||||||
int needs_comma = 0;
|
int needs_comma = 0;
|
||||||
|
int numeric_index = 1;
|
||||||
while (lua_next(L, -2) != 0) {
|
while (lua_next(L, -2) != 0) {
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname]
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname]
|
||||||
if (needs_comma) {
|
if (needs_comma) {
|
||||||
@ -336,15 +337,19 @@ static int Ltostring(lua_State *L)
|
|||||||
needs_comma = 1;
|
needs_comma = 1;
|
||||||
}
|
}
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname]
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname]
|
||||||
lua_pushvalue(L, -4);
|
if (lua_type(L, -1) == LUA_TNUMBER && lua_tointeger(L, -1) == numeric_index) {
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname, tostring]
|
lua_pop(L, 1);
|
||||||
lua_insert(L, -2);
|
} else {
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, tostring, fieldname]
|
lua_pushvalue(L, -4);
|
||||||
lua_call(L, 1, 1);
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, fieldname, tostring]
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, field string]
|
lua_insert(L, -2);
|
||||||
luaL_addvalue(&b);
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, tostring, fieldname]
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i]
|
lua_call(L, 1, 1);
|
||||||
luaL_addstring(&b, "=");
|
// 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));
|
lua_rawgeti(L, -4, lua_tonumber(L, -1));
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, value]
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, value]
|
||||||
lua_pushvalue(L, -4);
|
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]
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i, value string]
|
||||||
luaL_addvalue(&b);
|
luaL_addvalue(&b);
|
||||||
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i]
|
// Stack: [mt, buckets, bucket, inst_table, tostring, fields, i]
|
||||||
|
numeric_index++;
|
||||||
}
|
}
|
||||||
luaL_addstring(&b, ")");
|
luaL_addstring(&b, ")");
|
||||||
luaL_pushresult(&b);
|
luaL_pushresult(&b);
|
||||||
@ -431,9 +437,9 @@ static int Lnext(lua_State *L)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// Stack: [mt, buckets, bucket, inst_table, fields, k2, next_i]
|
// Stack: [mt, buckets, bucket, inst_table, fields, k2, next_i]
|
||||||
lua_rawgeti(L, -4, lua_tonumber(L, -1));
|
lua_gettable(L, -4);
|
||||||
// Stack: [mt, buckets, bucket, inst_table, fields, k2, next_i, value]
|
// Stack: [mt, buckets, bucket, inst_table, fields, k2, value]
|
||||||
return 3;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int Lpairs(lua_State *L)
|
static int Lpairs(lua_State *L)
|
||||||
@ -493,30 +499,65 @@ static int Lmake_class(lua_State *L)
|
|||||||
lua_setfield(L, -2, "__instances");
|
lua_setfield(L, -2, "__instances");
|
||||||
|
|
||||||
// Stack: [CLS]
|
// 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
|
size_t n = lua_objlen(L, 1);
|
||||||
lua_pushvalue(L, 1);
|
lua_createtable(L, 0, n);
|
||||||
// Stack: [CLS, __fields]
|
// Stack: [CLS, __indices]
|
||||||
lua_setfield(L, -2, "__fields");
|
lua_pushnil(L);
|
||||||
// Stack: [CLS]
|
while (lua_next(L, 1) != 0) {
|
||||||
|
// Stack: [CLS, __indices, i, fieldname]
|
||||||
if (lua_type(L, 1) != LUA_TTABLE) {
|
lua_pushvalue(L, -2);
|
||||||
// If no fields were passed in, make them empty (i.e. a singleton)
|
// Stack: [CLS, __indices, i, fieldname, i]
|
||||||
lua_createtable(L, 0, 0);
|
lua_settable(L, -4);
|
||||||
} else {
|
// Stack: [CLS, __indices, i]
|
||||||
size_t n = lua_objlen(L, 1);
|
}
|
||||||
lua_createtable(L, 0, n);
|
lua_setfield(L, -2, "__indices");
|
||||||
// Stack: [CLS, __indices]
|
break;
|
||||||
lua_pushnil(L);
|
}
|
||||||
while (lua_next(L, 1) != 0) {
|
case LUA_TNUMBER: {
|
||||||
// Stack: [CLS, __indices, i, fieldname]
|
// If no fields were passed in, make them empty (i.e. a singleton)
|
||||||
lua_pushvalue(L, -2);
|
lua_Integer n = lua_tointeger(L, 1);
|
||||||
// Stack: [CLS, __indices, i, fieldname, i]
|
if (n < 0) {
|
||||||
lua_settable(L, -4);
|
luaL_error(L, "immutable table size must be positive");
|
||||||
// Stack: [CLS, __indices, i]
|
}
|
||||||
|
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]
|
// Stack: [CLS]
|
||||||
|
|
||||||
// setmetatable(CLS, {__new=CLS.new})
|
// setmetatable(CLS, {__new=CLS.new})
|
||||||
|
21
tests.lua
21
tests.lua
@ -179,6 +179,27 @@ test("Testing unpacking", function()
|
|||||||
end
|
end
|
||||||
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
|
if num_errors == 0 then
|
||||||
print(green.."All tests passed!"..reset)
|
print(green.."All tests passed!"..reset)
|
||||||
else
|
else
|
||||||
|
Loading…
Reference in New Issue
Block a user