Initial commit.
This commit is contained in:
commit
9db58c6b8a
50
Makefile
Normal file
50
Makefile
Normal file
@ -0,0 +1,50 @@
|
||||
# makefile for immutable table library for Lua
|
||||
|
||||
# change these to reflect your Lua installation
|
||||
LUA= /users/Bruce/Sandbox/Lua/lua-5.1.5
|
||||
LUAINC= $(LUA)/src
|
||||
LUALIB= $(LUA)/src
|
||||
LUABIN= $(LUA)/src
|
||||
|
||||
# these will probably work if Lua has been installed globally
|
||||
#LUA= /usr/local
|
||||
#LUAINC= $(LUA)/include
|
||||
#LUALIB= $(LUA)/lib
|
||||
#LUABIN= $(LUA)/bin
|
||||
|
||||
# probably no need to change anything below here
|
||||
CC= gcc
|
||||
CFLAGS= $(INCS) $(WARN) -O2 $G
|
||||
WARN= -std=c99 -pedantic -Wall -Wextra
|
||||
INCS= -I$(LUAINC)
|
||||
#MAKESO= $(CC) -shared
|
||||
MAKESO= $(CC) -bundle -undefined dynamic_lookup
|
||||
|
||||
MYNAME= immutable
|
||||
MYLIB= l$(MYNAME)
|
||||
T= $(MYNAME).so
|
||||
OBJS= $(MYLIB).o
|
||||
TEST= test.lua
|
||||
|
||||
all: test
|
||||
|
||||
test: $T
|
||||
$(LUABIN)/lua $(TEST)
|
||||
|
||||
o: $(MYLIB).o
|
||||
|
||||
so: $T
|
||||
|
||||
$T: $(OBJS)
|
||||
$(MAKESO) -o $@ $(OBJS)
|
||||
|
||||
$(OBJS): limmutable.c
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $T core core.*
|
||||
|
||||
doc:
|
||||
@echo "$(MYNAME) library:"
|
||||
@fgrep '/**' $(MYLIB).c | cut -f2 -d/ | tr -d '*' | sort | column
|
||||
|
||||
# eof
|
300
limmutable.c
Normal file
300
limmutable.c
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
* immutable.c
|
||||
* An immutable table library.
|
||||
*/
|
||||
|
||||
#include "lua.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lobject.h"
|
||||
#include "ltable.h"
|
||||
#include "lstate.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define MYNAME "immutable"
|
||||
#define MYTYPE MYNAME
|
||||
|
||||
// Class:new(...)
|
||||
static int Lcreate_instance(lua_State *L)
|
||||
{
|
||||
int n_args = lua_gettop(L);
|
||||
// arg 1: class table, ...
|
||||
|
||||
lua_getfield(L, 1, "__fields");
|
||||
// Stack: [__fields]
|
||||
size_t n = lua_objlen(L,-1);
|
||||
if ((size_t)n_args-1 != n) {
|
||||
lua_pushstring(L, "incorrect number of arguments");
|
||||
lua_error(L);
|
||||
}
|
||||
lua_pop(L,1);
|
||||
// Stack: []
|
||||
|
||||
lua_getfield(L, 1, "__instances");
|
||||
// Stack: [__instances]
|
||||
lua_createtable(L, n, 0);
|
||||
// Stack [__instances, inst]
|
||||
|
||||
// Copy in all the values, and simultaneously compute the hash:
|
||||
lua_Integer hash = 0;
|
||||
for (lua_Integer i=1; i <=(lua_Integer)n; i++) {
|
||||
lua_pushinteger(L, i);
|
||||
lua_pushvalue(L, i+1);
|
||||
// Stack [__instances, inst, i, args[i+1]]
|
||||
lua_Integer item_hash;
|
||||
switch (lua_type(L, -1)) {
|
||||
case LUA_TNIL:
|
||||
item_hash = 0;
|
||||
break;
|
||||
case LUA_TNUMBER:
|
||||
item_hash = (lua_Integer)lua_tonumber(L, -1);
|
||||
break;
|
||||
case LUA_TBOOLEAN:
|
||||
item_hash = (lua_Integer)lua_toboolean(L, -1);
|
||||
break;
|
||||
case LUA_TTABLE:
|
||||
case LUA_TFUNCTION:
|
||||
case LUA_TUSERDATA:
|
||||
case LUA_TTHREAD:
|
||||
case LUA_TLIGHTUSERDATA:
|
||||
item_hash = (lua_Integer)lua_topointer(L, -1);
|
||||
break;
|
||||
case LUA_TSTRING:
|
||||
{
|
||||
size_t strlen;
|
||||
const char *str = lua_tolstring(L, -1, &strlen);
|
||||
item_hash = *str << 7;
|
||||
for (const char *end = &str[strlen]; str < end; str++)
|
||||
item_hash = (1000003*item_hash) ^ *str;
|
||||
item_hash ^= strlen;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
item_hash = 0;
|
||||
}
|
||||
hash = (1000003 * hash) ^ item_hash;
|
||||
lua_settable(L, -3);
|
||||
// Stack [__instances, inst]
|
||||
}
|
||||
|
||||
// Stack: [__instances, inst]
|
||||
// Find bucket
|
||||
lua_pushinteger(L, hash);
|
||||
// Stack: [__instances, inst, hash]
|
||||
lua_gettable(L, -3);
|
||||
// Stack: [__instances, inst, bucket]
|
||||
if (lua_isnil(L, -1)) {
|
||||
// Make a new bucket
|
||||
// Stack: [__instances, inst, nil]
|
||||
lua_pop(L, 1);
|
||||
// Stack: [__instances, inst]
|
||||
lua_createtable(L, 1, 0);
|
||||
// Stack: [__instances, inst, bucket]
|
||||
lua_createtable(L, 0, 1);
|
||||
// Stack: [__instances, inst, bucket, bucket_mt]
|
||||
lua_pushstring(L, "__mode");
|
||||
lua_pushstring(L, "k");
|
||||
// Stack: [__instances, inst, bucket, bucket_mt, '__mode', 'k']
|
||||
lua_settable(L, -3);
|
||||
// Stack: [__instances, inst, bucket, {'__mode'='k'}]
|
||||
lua_setmetatable(L, -2);
|
||||
// Stack: [__instances, inst, bucket]
|
||||
|
||||
lua_pushinteger(L, hash);
|
||||
// Stack: [__instances, inst, bucket, hash]
|
||||
lua_pushvalue(L, -2);
|
||||
// Stack: [__instances, inst, bucket, hash, bucket]
|
||||
lua_settable(L, -5);
|
||||
// Stack: [__instances, inst, bucket]
|
||||
}
|
||||
// Stack: [__instances, inst, bucket]
|
||||
// scan bucket
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider]
|
||||
int bucket_item_matches = 1;
|
||||
// Perform a full equality check
|
||||
lua_pushnil(L);
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider, nil]
|
||||
while (lua_next(L, -2) != 0) {
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider, collider_key, collider_value]
|
||||
lua_pushvalue(L, -2);
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider, collider_key, collider_value, collider_key]
|
||||
lua_gettable(L, -7); // inst[collider_key]
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider, collider_key, collider_value, inst_value]
|
||||
if (!lua_equal(L, -1, -2)) {
|
||||
// go to next item in the bucket
|
||||
bucket_item_matches = 0;
|
||||
lua_pop(L, 4);
|
||||
break;
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst]
|
||||
} else {
|
||||
lua_pop(L, 2);
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider, collider_key]
|
||||
}
|
||||
}
|
||||
if (bucket_item_matches) {
|
||||
// Stack: [__instances, inst, bucket, hash_collider_inst, hash_collider]
|
||||
lua_pop(L, 1);
|
||||
// Found matching singleton
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// failed to find a singleton
|
||||
// Stack: [__instances, inst, bucket]
|
||||
|
||||
const void *data = lua_topointer(L, -2);
|
||||
void** userdata = (void**)lua_newuserdata(L, sizeof(void*));
|
||||
*userdata = (void*)data;
|
||||
|
||||
// Stack [__instances, inst, bucket, inst_userdata]
|
||||
lua_pushvalue(L, 1);
|
||||
// Stack [__instances, inst, bucket, inst_userdata, metatable]
|
||||
lua_setmetatable(L, -2);
|
||||
// Stack [__instances, inst, bucket, inst_userdata]
|
||||
|
||||
lua_pushvalue(L, -1);
|
||||
// Stack [__instances, inst, bucket, inst_userdata, inst_userdata]
|
||||
lua_pushvalue(L, -4);
|
||||
// Stack [__instances, inst, bucket, inst_userdata, inst_userdata, inst]
|
||||
lua_settable(L, -4);
|
||||
// Stack [__instances, inst, bucket, inst_userdata]
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int Llen(lua_State *L)
|
||||
{
|
||||
lua_pushvalue(L, 1);
|
||||
luaL_getmetatable(L, MYTYPE);
|
||||
lua_getfield(L, -1, "fields");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int Lindex(lua_State *L)
|
||||
{
|
||||
lua_getmetatable(L, 1);
|
||||
// Stack: [mt]
|
||||
lua_getfield(L, -1, "__fields");
|
||||
// Stack: [mt, fields]
|
||||
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_equal(L, -1, 2)) {
|
||||
Table **inst_table = (Table**)lua_touserdata(L, 1);
|
||||
lua_Integer i = lua_tointeger(L, -2);
|
||||
const TValue* value = luaH_getnum(*inst_table, i);
|
||||
setobj2s(L, L->top - 1, value);
|
||||
return 1;
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
// Stack: [mt]
|
||||
lua_getfield(L, -1, "__methods");
|
||||
// Stack: [mt, __methods]
|
||||
lua_pushvalue(L, 2);
|
||||
lua_gettable(L, -2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
static int Lgc(lua_State *L)
|
||||
{
|
||||
lua_getglobal(L, "print");
|
||||
lua_pushstring(L, "===========> Garbage collection of ");
|
||||
lua_getglobal(L, "tostring");
|
||||
lua_pushvalue(L, 1);
|
||||
lua_call(L, 1, 1);
|
||||
lua_concat(L, 2);
|
||||
lua_call(L, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
*/
|
||||
|
||||
static const luaL_Reg R[] =
|
||||
{
|
||||
{ "__len", Llen},
|
||||
//{ "__gc", Lgc},
|
||||
//{ "__eq", Leq},
|
||||
{ "__index", Lindex},
|
||||
//{ "__hash", Lhash},
|
||||
{ "new", Lcreate_instance},
|
||||
{ NULL, NULL}
|
||||
};
|
||||
|
||||
static int Lmake_class(lua_State *L)
|
||||
{
|
||||
// args: fields, [methods, [metamethods]]
|
||||
int n_args = lua_gettop(L);
|
||||
|
||||
// CLS = {}
|
||||
lua_newtable(L);
|
||||
// Stack: [CLS]
|
||||
|
||||
// If metamethods were passed in, copy them over
|
||||
if (n_args > 2) {
|
||||
lua_pushnil(L);
|
||||
// Stack: [CLS, nil]
|
||||
while (lua_next(L, 3) != 0) {
|
||||
lua_pushvalue(L, -2);
|
||||
lua_pushvalue(L, -2);
|
||||
// Stack: [CLS, method_name, method_value, method_name, method_value]
|
||||
lua_settable(L, -5);
|
||||
// Stack: [CLS, method_name, method_value]
|
||||
lua_pop(L, 1);
|
||||
// Stack: [CLS, method_name]
|
||||
}
|
||||
}
|
||||
|
||||
// CLS.__instances = {}
|
||||
lua_newtable(L);
|
||||
// Stack: [CLS, CLS.__instances]
|
||||
lua_setfield(L, -2, "__instances");
|
||||
// Stack: [CLS]
|
||||
|
||||
// CLS.__fields = arg1
|
||||
lua_pushvalue(L, 1);
|
||||
// Stack: [CLS, __fields]
|
||||
lua_setfield(L, -2, "__fields");
|
||||
// Stack: [CLS]
|
||||
|
||||
lua_newtable(L);
|
||||
// Stack: [CLS, __methods]
|
||||
// If methods were passed in, copy them over
|
||||
if (n_args > 1) {
|
||||
// Stack: [
|
||||
lua_pushnil(L);
|
||||
// Stack: [CLS, __methods, nil]
|
||||
while (lua_next(L, 2) != 0) {
|
||||
lua_pushvalue(L, -2);
|
||||
lua_pushvalue(L, -2);
|
||||
// Stack: [CLS, __methods, method_name, method_value, method_name, method_value]
|
||||
lua_settable(L, -5);
|
||||
// Stack: [CLS, __methods, method_name, method_value]
|
||||
lua_pop(L, 1);
|
||||
// Stack: [CLS, __methods, method_name]
|
||||
}
|
||||
// Stack: [CLS, __methods]
|
||||
}
|
||||
lua_setfield(L, -2, "__methods");
|
||||
// Stack: [CLS]
|
||||
|
||||
// Populate __len, __eq, __index, new, etc.
|
||||
luaL_register(L,NULL,R);
|
||||
|
||||
// setmetatable(CLS, {__new=CLS.new})
|
||||
lua_createtable(L, 0, 1);
|
||||
lua_pushcfunction(L, Lcreate_instance);
|
||||
lua_setfield(L, -2, "__call");
|
||||
lua_setmetatable(L, -2);
|
||||
// Stack [CLS]
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
LUALIB_API int luaopen_immutable(lua_State *L)
|
||||
{
|
||||
lua_pushcfunction(L, Lmake_class);
|
||||
return 1;
|
||||
}
|
71
test.lua
Normal file
71
test.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local immutable = require"immutable"
|
||||
|
||||
local bright = string.char(27).."[1m"
|
||||
local blue = string.char(27).."[34m"
|
||||
local reset = string.char(27).."[0m"
|
||||
local function say(...) io.write(bright..blue); print(...); io.write(reset); end
|
||||
local function repr(t)
|
||||
local tmp = {}
|
||||
for k,v in pairs(t) do tmp[#tmp+1] = ("%s=%s"):format(k,v) end
|
||||
return "{"..table.concat(tmp, ", ").."}"
|
||||
end
|
||||
|
||||
local Vec = immutable({"x","y"}, {
|
||||
len2=function(self)
|
||||
return self.x*self.x + self.y*self.y
|
||||
end,
|
||||
}, {
|
||||
__add=function(self, other)
|
||||
local cls = getmetatable(self)
|
||||
return cls(self.x+other.x, self.y+other.y)
|
||||
end,
|
||||
__tostring=function(self)
|
||||
return "Vec("..tostring(self.x)..", "..tostring(self.y)..")"
|
||||
end,
|
||||
})
|
||||
|
||||
local v = Vec(1,3)
|
||||
say(v)
|
||||
local also_v = Vec(1,3)
|
||||
say(also_v)
|
||||
-- Hash collision right now
|
||||
local not_v = Vec(true,3)
|
||||
say(not_v)
|
||||
local also_not_v = Vec(true,3)
|
||||
say(also_not_v)
|
||||
|
||||
say(v == also_v)
|
||||
say(v + Vec(5,6))
|
||||
collectgarbage()
|
||||
say(Vec("hello", "world"))
|
||||
say(Vec("hello", "world"))
|
||||
collectgarbage()
|
||||
collectgarbage()
|
||||
|
||||
say("Let's look at the instances")
|
||||
for h,b in pairs(Vec.__instances) do
|
||||
say(("Hash bucket: 0x%x"):format(h))
|
||||
for k,v in pairs(b) do
|
||||
local tmp = {}
|
||||
for kk,vv in pairs(v) do tmp[#tmp+1] = tostring(vv) end
|
||||
say(' <'..table.concat(tmp, ", ")..'>')
|
||||
end
|
||||
end
|
||||
|
||||
local FooVec = immutable({"x","y"}, {
|
||||
len2=function(self)
|
||||
return self.x*self.x + self.y*self.y
|
||||
end,
|
||||
}, {
|
||||
__add=function(self, other)
|
||||
local cls = getmetatable(self)
|
||||
return cls(self.x+other.x, self.y+other.y)
|
||||
end,
|
||||
__tostring=function(self)
|
||||
return "Vec("..tostring(self.x)..", "..tostring(self.y)..")"
|
||||
end,
|
||||
})
|
||||
|
||||
say("FooVec: "..tostring(FooVec(1,3)))
|
||||
say("FooVec == Vec: "..tostring(FooVec(1,3) == Vec(1,3)))
|
||||
say("FooVec:len2() = "..tostring(FooVec(1,3):len2()))
|
Loading…
Reference in New Issue
Block a user