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