From 5f404a5240075b1d93a84415345d969c6e701af0 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 12 Sep 2014 14:25:31 +0800 Subject: [PATCH 1/6] remove select cmd because of conn pool --- client/ledis-py/ledis/client.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/client/ledis-py/ledis/client.py b/client/ledis-py/ledis/client.py index 75d19c5..0247d86 100644 --- a/client/ledis-py/ledis/client.py +++ b/client/ledis-py/ledis/client.py @@ -238,14 +238,6 @@ class Ledis(object): "Ping the Ledis server" return self.execute_command('PING') - def select(self, db): - """Select a Ledis db, ``db`` is integer type""" - try: - db = int(db) - except ValueError: - db = 0 - return self.execute_command('SELECT', db) - def info(self, section=None): """ Return From 8284c944530f2d4f69fcbeeae2ad11e8e2c2d8d1 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 12 Sep 2014 15:06:36 +0800 Subject: [PATCH 2/6] store add compact and string interface --- store/boltdb/db.go | 4 ++++ store/db.go | 9 +++++++++ store/driver/driver.go | 2 ++ store/goleveldb/db.go | 5 +++++ store/hyperleveldb/db.go | 5 +++++ store/leveldb/db.go | 5 +++++ store/mdb/mdb.go | 7 ++++--- store/rocksdb/db.go | 5 +++++ store/store.go | 2 +- store/store_test.go | 5 ++++- 10 files changed, 44 insertions(+), 5 deletions(-) diff --git a/store/boltdb/db.go b/store/boltdb/db.go index 8212d08..f2cb1f3 100644 --- a/store/boltdb/db.go +++ b/store/boltdb/db.go @@ -152,6 +152,10 @@ func (db *DB) BatchPut(writes []driver.Write) error { return err } +func (db *DB) Compact() error { + return nil +} + func init() { driver.Register(Store{}) } diff --git a/store/db.go b/store/db.go index ca57326..99e6ed2 100644 --- a/store/db.go +++ b/store/db.go @@ -6,6 +6,11 @@ import ( type DB struct { driver.IDB + name string +} + +func (db *DB) String() string { + return db.name } func (db *DB) NewIterator() *Iterator { @@ -29,6 +34,10 @@ func (db *DB) NewSnapshot() (*Snapshot, error) { return s, nil } +func (db *DB) Compact() error { + return db.IDB.Compact() +} + func (db *DB) RangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator { return NewRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1}) } diff --git a/store/driver/driver.go b/store/driver/driver.go index 6da67df..859b840 100644 --- a/store/driver/driver.go +++ b/store/driver/driver.go @@ -23,6 +23,8 @@ type IDB interface { NewSnapshot() (ISnapshot, error) Begin() (Tx, error) + + Compact() error } type ISnapshot interface { diff --git a/store/goleveldb/db.go b/store/goleveldb/db.go index e873feb..2a13f50 100644 --- a/store/goleveldb/db.go +++ b/store/goleveldb/db.go @@ -6,6 +6,7 @@ import ( "github.com/siddontang/goleveldb/leveldb/filter" "github.com/siddontang/goleveldb/leveldb/opt" "github.com/siddontang/goleveldb/leveldb/storage" + "github.com/siddontang/goleveldb/leveldb/util" "github.com/siddontang/ledisdb/config" "github.com/siddontang/ledisdb/store/driver" @@ -180,6 +181,10 @@ func (db *DB) NewSnapshot() (driver.ISnapshot, error) { return s, nil } +func (db *DB) Compact() error { + return db.db.CompactRange(util.Range{nil, nil}) +} + func init() { driver.Register(Store{}) driver.Register(MemStore{}) diff --git a/store/hyperleveldb/db.go b/store/hyperleveldb/db.go index 6d0e176..d6d7aeb 100644 --- a/store/hyperleveldb/db.go +++ b/store/hyperleveldb/db.go @@ -270,6 +270,11 @@ func (db *DB) Begin() (driver.Tx, error) { return nil, driver.ErrTxSupport } +func (db *DB) Compact() error { + C.leveldb_compact_range(db.db, nil, 0, nil, 0) + return nil +} + func init() { driver.Register(Store{}) } diff --git a/store/leveldb/db.go b/store/leveldb/db.go index 43ee0c2..92a2419 100644 --- a/store/leveldb/db.go +++ b/store/leveldb/db.go @@ -270,6 +270,11 @@ func (db *DB) Begin() (driver.Tx, error) { return nil, driver.ErrTxSupport } +func (db *DB) Compact() error { + C.leveldb_compact_range(db.db, nil, 0, nil, 0) + return nil +} + func init() { driver.Register(Store{}) } diff --git a/store/mdb/mdb.go b/store/mdb/mdb.go index d5c3987..ca79706 100644 --- a/store/mdb/mdb.go +++ b/store/mdb/mdb.go @@ -240,9 +240,6 @@ func (db MDB) Path() string { return db.path } -func (db MDB) Compact() { -} - func (db MDB) iterator(rdonly bool) *MDBIterator { flags := uint(0) if rdonly { @@ -286,6 +283,10 @@ func (db MDB) NewSnapshot() (driver.ISnapshot, error) { return newSnapshot(db) } +func (db MDB) Compact() error { + return nil +} + func init() { driver.Register(Store{}) } diff --git a/store/rocksdb/db.go b/store/rocksdb/db.go index cc1ab81..1c79229 100644 --- a/store/rocksdb/db.go +++ b/store/rocksdb/db.go @@ -296,6 +296,11 @@ func (db *DB) Begin() (driver.Tx, error) { return nil, driver.ErrTxSupport } +func (db *DB) Compact() error { + C.rocksdb_compact_range(db.db, nil, 0, nil, 0) + return nil +} + func init() { driver.Register(Store{}) } diff --git a/store/store.go b/store/store.go index e2a6b85..d620c03 100644 --- a/store/store.go +++ b/store/store.go @@ -36,7 +36,7 @@ func Open(cfg *config.Config) (*DB, error) { return nil, err } - db := &DB{idb} + db := &DB{idb, s.String()} return db, nil } diff --git a/store/store_test.go b/store/store_test.go index 5fdd95d..d2f2ca6 100644 --- a/store/store_test.go +++ b/store/store_test.go @@ -148,6 +148,9 @@ func checkIterator(it *RangeLimitIterator, cv ...int) error { v := make([]string, 0, len(cv)) for ; it.Valid(); it.Next() { k := it.Key() + if string(it.Value()) != "value" { + return fmt.Errorf("invalid value") + } v = append(v, string(k)) } @@ -175,7 +178,7 @@ func testIterator(db *DB, t *testing.T) { for i := 0; i < 10; i++ { key := []byte(fmt.Sprintf("key_%d", i)) - value := []byte("") + value := []byte("value") db.Put(key, value) } From a1096fd1d87d9a12bdbc0b35e403f1c7c5ef078d Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 12 Sep 2014 15:06:47 +0800 Subject: [PATCH 3/6] binlog purgeall bugfix --- ledis/binlog.go | 10 ++++++++++ ledis/binlog_test.go | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/ledis/binlog.go b/ledis/binlog.go index 6e6aa5b..077398a 100644 --- a/ledis/binlog.go +++ b/ledis/binlog.go @@ -256,6 +256,10 @@ func (l *BinLog) checkLogFileSize() bool { } func (l *BinLog) closeLog() { + if l.logFile == nil { + return + } + l.lastLogIndex++ l.logFile.Close() @@ -263,6 +267,9 @@ func (l *BinLog) closeLog() { } func (l *BinLog) purge(n int) { + if len(l.logNames) < n { + n = len(l.logNames) + } for i := 0; i < n; i++ { logPath := path.Join(l.path, l.logNames[i]) os.Remove(logPath) @@ -338,6 +345,9 @@ func (l *BinLog) PurgeAll() error { defer l.Unlock() l.closeLog() + + l.purge(len(l.logNames)) + return l.openNewLogFile() } diff --git a/ledis/binlog_test.go b/ledis/binlog_test.go index f7a8320..ea62bd9 100644 --- a/ledis/binlog_test.go +++ b/ledis/binlog_test.go @@ -34,4 +34,16 @@ func TestBinLog(t *testing.T) { } else if len(fs) != 2 { t.Fatal(len(fs)) } + + if err := b.PurgeAll(); err != nil { + t.Fatal(err) + } + + if fs, err := ioutil.ReadDir(b.LogPath()); err != nil { + t.Fatal(err) + } else if len(fs) != 2 { + t.Fatal(len(fs)) + } else if b.LogFilePos() != 0 { + t.Fatal(b.LogFilePos()) + } } From 6aec5d495207fead8bea82ea8e641e494f73a308 Mon Sep 17 00:00:00 2001 From: siddontang Date: Sat, 13 Sep 2014 14:11:05 +0800 Subject: [PATCH 4/6] use golua directly --- README.md | 16 +- bootstrap.sh | 2 - dev.sh | 5 +- lua/c-golua.c | 422 +++++++++++++ lua/golua.go | 222 +++++++ lua/golua.h | 39 ++ lua/golua_license | 21 + lua/lauxlib.go | 216 +++++++ lua/lua.go | 670 +++++++++++++++++++++ lua/lua_cjson.c | 1301 +++++++++++++++++++++++++++++++++++++++++ lua/lua_cmsgpack.c | 731 +++++++++++++++++++++++ lua/lua_defs.go | 73 +++ lua/lua_struct.c | 423 ++++++++++++++ lua/lua_test.go | 386 ++++++++++++ lua/strbuf.c | 253 ++++++++ lua/strbuf.h | 144 +++++ server/cmd_script.go | 2 +- server/script.go | 2 +- server/script_test.go | 2 +- tools/check_lua.go | 10 - 20 files changed, 4914 insertions(+), 26 deletions(-) create mode 100644 lua/c-golua.c create mode 100644 lua/golua.go create mode 100644 lua/golua.h create mode 100644 lua/golua_license create mode 100644 lua/lauxlib.go create mode 100644 lua/lua.go create mode 100644 lua/lua_cjson.c create mode 100644 lua/lua_cmsgpack.c create mode 100644 lua/lua_defs.go create mode 100644 lua/lua_struct.c create mode 100644 lua/lua_test.go create mode 100644 lua/strbuf.c create mode 100644 lua/strbuf.h delete mode 100644 tools/check_lua.go diff --git a/README.md b/README.md index e520031..6779b94 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,8 @@ Create a workspace and checkout ledisdb source LedisDB use the modified LevelDB for better performance, see [here](https://github.com/siddontang/ledisdb/wiki/leveldb-source-modification). -+ Set ```LEVELDB_DIR``` and ```SNAPPY_DIR``` to the actual install path in dev.sh. -+ ```make``` ++ Set `LEVELDB_DIR` and `SNAPPY_DIR` to the actual install path in dev.sh. ++ `make clean && make` ## RocksDB support @@ -57,8 +57,8 @@ Create a workspace and checkout ledisdb source LedisDB has not supplied a simple script to install, maybe later. -+ Set ```ROCKSDB_DIR``` and ```SNAPPY_DIR``` to the actual install path in `dev.sh`. -+ ```make``` ++ Set `ROCKSDB_DIR` and `SNAPPY_DIR` to the actual install path in `dev.sh`. ++ `make clean && make` **Because RocksDB API may change sometimes, LedisDB may not build successfully. Now LedisDB supports RocksDB version 3.5 or newest master branch. ** @@ -70,7 +70,7 @@ Create a workspace and checkout ledisdb source LedisDB has not supplied a simple script to install, maybe later. + Set `HYPERLEVELDB` and `SNAPPY_DIR` to the actual install path in `dev.sh`. -+ `make` ++ `make clean && make` ## Choose store database @@ -91,9 +91,9 @@ Choosing a store database to use is very simple, you have two ways: ## Lua support -+ You must install lua by yourself first and be sure that `golua` can find and link it. -+ `go get -u github.com/siddontang/golua/lua` -+ `make` ++ Compile and install lua ++ Set `LUA_DIR` to the actual path in `dev.sh` ++ `make clean && make` ## Configuration diff --git a/bootstrap.sh b/bootstrap.sh index ffb4c46..ee260b7 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -15,5 +15,3 @@ go get github.com/ugorji/go/codec go get github.com/BurntSushi/toml go get github.com/siddontang/go-bson/bson - -go get github.com/siddontang/golua/lua \ No newline at end of file diff --git a/dev.sh b/dev.sh index a9be046..72cde0f 100644 --- a/dev.sh +++ b/dev.sh @@ -14,6 +14,7 @@ SNAPPY_DIR=/usr/local/snappy LEVELDB_DIR=/usr/local/leveldb ROCKSDB_DIR=/usr/local/rocksdb HYPERLEVELDB_DIR=/usr/local/hyperleveldb +LUA_DIR=/usr/local/lua function add_path() { @@ -75,9 +76,7 @@ if [ -f $HYPERLEVELDB_DIR/include/hyperleveldb/c.h ]; then fi #check lua -CHECK_LUA_FILE="$LEDISTOP/tools/check_lua.go" -go run $CHECK_LUA_FILE >> /dev/null 2>&1 -if [ "$?" = 0 ]; then +if [ -f $LUA_DIR/include/lua.h ]; then GO_BUILD_TAGS="$GO_BUILD_TAGS lua" fi diff --git a/lua/c-golua.c b/lua/c-golua.c new file mode 100644 index 0000000..18dec08 --- /dev/null +++ b/lua/c-golua.c @@ -0,0 +1,422 @@ +// +build lua + +#include +#include +#include +#include +#include +#include "_cgo_export.h" + +#define MT_GOFUNCTION "GoLua.GoFunction" +#define MT_GOINTERFACE "GoLua.GoInterface" + +#define GOLUA_DEFAULT_MSGHANDLER "golua_default_msghandler" + +static const char GoStateRegistryKey = 'k'; //golua registry key +static const char PanicFIDRegistryKey = 'k'; + +/* taken from lua5.2 source */ +void *testudata(lua_State *L, int ud, const char *tname) +{ + void *p = lua_touserdata(L, ud); + if (p != NULL) + { /* value is a userdata? */ + if (lua_getmetatable(L, ud)) + { /* does it have a metatable? */ + luaL_getmetatable(L, tname); /* get correct metatable */ + if (!lua_rawequal(L, -1, -2)) /* not the same? */ + p = NULL; /* value is a userdata with wrong metatable */ + lua_pop(L, 2); /* remove both metatables */ + return p; + } + } + return NULL; /* value is not a userdata with a metatable */ +} + +int clua_isgofunction(lua_State *L, int n) +{ + return testudata(L, n, MT_GOFUNCTION) != NULL; +} + +int clua_isgostruct(lua_State *L, int n) +{ + return testudata(L, n, MT_GOINTERFACE) != NULL; +} + +unsigned int* clua_checkgosomething(lua_State* L, int index, const char *desired_metatable) +{ + if (desired_metatable != NULL) + { + return testudata(L, index, desired_metatable); + } + else + { + unsigned int *sid = testudata(L, index, MT_GOFUNCTION); + if (sid != NULL) return sid; + return testudata(L, index, MT_GOINTERFACE); + } +} + +GoInterface* clua_getgostate(lua_State* L) +{ + //get gostate from registry entry + lua_pushlightuserdata(L,(void*)&GoStateRegistryKey); + lua_gettable(L, LUA_REGISTRYINDEX); + GoInterface* gip = lua_touserdata(L,-1); + lua_pop(L,1); + return gip; +} + + +//wrapper for callgofunction +int callback_function(lua_State* L) +{ + int r; + unsigned int *fid = clua_checkgosomething(L, 1, MT_GOFUNCTION); + GoInterface* gi = clua_getgostate(L); + //remove the go function from the stack (to present same behavior as lua_CFunctions) + lua_remove(L,1); + return golua_callgofunction(*gi, fid!=NULL ? *fid : -1); +} + +//wrapper for gchook +int gchook_wrapper(lua_State* L) +{ + //printf("Garbage collection wrapper\n"); + unsigned int* fid = clua_checkgosomething(L, -1, NULL); + GoInterface* gi = clua_getgostate(L); + if (fid != NULL) + return golua_gchook(*gi,*fid); + return 0; +} + +unsigned int clua_togofunction(lua_State* L, int index) +{ + unsigned int *r = clua_checkgosomething(L, index, MT_GOFUNCTION); + return (r != NULL) ? *r : -1; +} + +unsigned int clua_togostruct(lua_State *L, int index) +{ + unsigned int *r = clua_checkgosomething(L, index, MT_GOINTERFACE); + return (r != NULL) ? *r : -1; +} + +void clua_pushgofunction(lua_State* L, unsigned int fid) +{ + unsigned int* fidptr = (unsigned int *)lua_newuserdata(L, sizeof(unsigned int)); + *fidptr = fid; + luaL_getmetatable(L, MT_GOFUNCTION); + lua_setmetatable(L, -2); +} + +static int callback_c (lua_State* L) +{ + int fid = clua_togofunction(L,lua_upvalueindex(1)); + GoInterface *gi = clua_getgostate(L); + return golua_callgofunction(*gi,fid); +} + +void clua_pushcallback(lua_State* L) +{ + lua_pushcclosure(L,callback_c,1); +} + +void clua_pushgostruct(lua_State* L, unsigned int iid) +{ + unsigned int* iidptr = (unsigned int *)lua_newuserdata(L, sizeof(unsigned int)); + *iidptr = iid; + luaL_getmetatable(L, MT_GOINTERFACE); + lua_setmetatable(L,-2); +} + +int default_panicf(lua_State *L) +{ + const char *s = lua_tostring(L, -1); + printf("Lua unprotected panic: %s\n", s); + abort(); +} + +void clua_setgostate(lua_State* L, GoInterface gi) +{ + lua_atpanic(L, default_panicf); + lua_pushlightuserdata(L,(void*)&GoStateRegistryKey); + GoInterface* gip = (GoInterface*)lua_newuserdata(L,sizeof(GoInterface)); + //copy interface value to userdata + gip->v = gi.v; + gip->t = gi.t; + //set into registry table + lua_settable(L, LUA_REGISTRYINDEX); +} + +/* called when lua code attempts to access a field of a published go object */ +int interface_index_callback(lua_State *L) +{ + unsigned int *iid = clua_checkgosomething(L, 1, MT_GOINTERFACE); + if (iid == NULL) + { + lua_pushnil(L); + return 1; + } + + char *field_name = (char *)lua_tostring(L, 2); + if (field_name == NULL) + { + lua_pushnil(L); + return 1; + } + + GoInterface* gi = clua_getgostate(L); + + int r = golua_interface_index_callback(*gi, *iid, field_name); + + if (r < 0) + { + lua_error(L); + return 0; + } + else + { + return r; + } +} + +/* called when lua code attempts to set a field of a published go object */ +int interface_newindex_callback(lua_State *L) +{ + unsigned int *iid = clua_checkgosomething(L, 1, MT_GOINTERFACE); + if (iid == NULL) + { + lua_pushnil(L); + return 1; + } + + char *field_name = (char *)lua_tostring(L, 2); + if (field_name == NULL) + { + lua_pushnil(L); + return 1; + } + + GoInterface* gi = clua_getgostate(L); + + int r = golua_interface_newindex_callback(*gi, *iid, field_name); + + if (r < 0) + { + lua_error(L); + return 0; + } + else + { + return r; + } +} + +int panic_msghandler(lua_State *L) +{ + GoInterface* gi = clua_getgostate(L); + go_panic_msghandler(*gi, (char *)lua_tolstring(L, -1, NULL)); + return 0; +} + +void clua_hide_pcall(lua_State *L) +{ + lua_getglobal(L, "pcall"); + lua_setglobal(L, "unsafe_pcall"); + lua_pushnil(L); + lua_setglobal(L, "pcall"); + + lua_getglobal(L, "xpcall"); + lua_setglobal(L, "unsafe_xpcall"); + lua_pushnil(L); + lua_setglobal(L, "xpcall"); +} + +void clua_initstate(lua_State* L) +{ + /* create the GoLua.GoFunction metatable */ + luaL_newmetatable(L, MT_GOFUNCTION); + + // gofunction_metatable[__call] = &callback_function + lua_pushliteral(L,"__call"); + lua_pushcfunction(L,&callback_function); + lua_settable(L,-3); + + // gofunction_metatable[__gc] = &gchook_wrapper + lua_pushliteral(L,"__gc"); + lua_pushcfunction(L,&gchook_wrapper); + lua_settable(L,-3); + lua_pop(L,1); + + luaL_newmetatable(L, MT_GOINTERFACE); + + // gointerface_metatable[__gc] = &gchook_wrapper + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, &gchook_wrapper); + lua_settable(L, -3); + + // gointerface_metatable[__index] = &interface_index_callback + lua_pushliteral(L, "__index"); + lua_pushcfunction(L, &interface_index_callback); + lua_settable(L, -3); + + // gointerface_metatable[__newindex] = &interface_newindex_callback + lua_pushliteral(L, "__newindex"); + lua_pushcfunction(L, &interface_newindex_callback); + lua_settable(L, -3); + + lua_register(L, GOLUA_DEFAULT_MSGHANDLER, &panic_msghandler); + lua_pop(L, 1); +} + + +int callback_panicf(lua_State* L) +{ + lua_pushlightuserdata(L,(void*)&PanicFIDRegistryKey); + lua_gettable(L,LUA_REGISTRYINDEX); + unsigned int fid = lua_tointeger(L,-1); + lua_pop(L,1); + GoInterface* gi = clua_getgostate(L); + return golua_callpanicfunction(*gi,fid); + +} + +//TODO: currently setting garbage when panicf set to null +GoInterface clua_atpanic(lua_State* L, unsigned int panicf_id) +{ + //get old panicfid + unsigned int old_id; + lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); + lua_gettable(L,LUA_REGISTRYINDEX); + if(lua_isnil(L, -1) == 0) + old_id = lua_tointeger(L,-1); + lua_pop(L, 1); + + //set registry key for function id of go panic function + lua_pushlightuserdata(L, (void*)&PanicFIDRegistryKey); + //push id value + lua_pushinteger(L, panicf_id); + //set into registry table + lua_settable(L, LUA_REGISTRYINDEX); + + //now set the panic function + lua_CFunction pf = lua_atpanic(L,&callback_panicf); + //make a GoInterface with a wrapped C panicf or the original go panicf + if(pf == &callback_panicf) + { + return golua_idtointerface(old_id); + } + else + { + //TODO: technically UB, function ptr -> non function ptr + return golua_cfunctiontointerface((GoUintptr *)pf); + } +} + +int clua_callluacfunc(lua_State* L, lua_CFunction f) +{ + return f(L); +} + +void* allocwrapper(void* ud, void *ptr, size_t osize, size_t nsize) +{ + return (void*)golua_callallocf((GoUintptr)ud,(GoUintptr)ptr,osize,nsize); +} + +lua_State* clua_newstate(void* goallocf) +{ + return lua_newstate(&allocwrapper,goallocf); +} + +void clua_setallocf(lua_State* L, void* goallocf) +{ + lua_setallocf(L,&allocwrapper,goallocf); +} + +void clua_openbase(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_base); + lua_pushstring(L,""); + lua_call(L, 1, 0); + clua_hide_pcall(L); +} + +void clua_openio(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_io); + lua_pushstring(L,"io"); + lua_call(L, 1, 0); +} + +void clua_openmath(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_math); + lua_pushstring(L,"math"); + lua_call(L, 1, 0); +} + +void clua_openpackage(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_package); + lua_pushstring(L,"package"); + lua_call(L, 1, 0); +} + +void clua_openstring(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_string); + lua_pushstring(L,"string"); + lua_call(L, 1, 0); +} + +void clua_opentable(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_table); + lua_pushstring(L,"table"); + lua_call(L, 1, 0); +} + +void clua_openos(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_os); + lua_pushstring(L,"os"); + lua_call(L, 1, 0); +} + +void clua_hook_function(lua_State *L, lua_Debug *ar) +{ + lua_checkstack(L, 2); + lua_pushstring(L, "Lua execution quantum exceeded"); + lua_error(L); +} + +void clua_setexecutionlimit(lua_State* L, int n) +{ + lua_sethook(L, &clua_hook_function, LUA_MASKCOUNT, n); +} + +LUALIB_API int (luaopen_cjson) (lua_State *L); +LUALIB_API int (luaopen_struct) (lua_State *L); +LUALIB_API int (luaopen_cmsgpack) (lua_State *L); + +void clua_opencjson(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_cjson); + lua_pushstring(L,"cjson"); + lua_call(L, 1, 0); +} + +void clua_openstruct(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_struct); + lua_pushstring(L,"struct"); + lua_call(L, 1, 0); +} + +void clua_opencmsgpack(lua_State* L) +{ + lua_pushcfunction(L,&luaopen_cmsgpack); + lua_pushstring(L,"cmsgpack"); + lua_call(L, 1, 0); +} diff --git a/lua/golua.go b/lua/golua.go new file mode 100644 index 0000000..5cbe494 --- /dev/null +++ b/lua/golua.go @@ -0,0 +1,222 @@ +// +build lua + +package lua + +/* +#include +#include +#include +*/ +import "C" + +import ( + "reflect" + "unsafe" +) + +// Type of allocation functions to use with NewStateAlloc +type Alloc func(ptr unsafe.Pointer, osize uint, nsize uint) unsafe.Pointer + +// This is the type of go function that can be registered as lua functions +type LuaGoFunction func(L *State) int + +// Wrapper to keep cgo from complaining about incomplete ptr type +//export State +type State struct { + // Wrapped lua_State object + s *C.lua_State + + // Registry of go object that have been pushed to Lua VM + registry []interface{} + + // Freelist for funcs indices, to allow for freeing + freeIndices []uint +} + +//export golua_callgofunction +func golua_callgofunction(L interface{}, fid uint) int { + L1 := L.(*State) + if fid < 0 { + panic(&LuaError{0, "Requested execution of an unknown function", L1.StackTrace()}) + } + f := L1.registry[fid].(LuaGoFunction) + return f(L1) +} + +//export golua_interface_newindex_callback +func golua_interface_newindex_callback(Li interface{}, iid uint, field_name_cstr *C.char) int { + L := Li.(*State) + iface := L.registry[iid] + ifacevalue := reflect.ValueOf(iface).Elem() + + field_name := C.GoString(field_name_cstr) + + fval := ifacevalue.FieldByName(field_name) + + if fval.Kind() == reflect.Ptr { + fval = fval.Elem() + } + + luatype := LuaValType(C.lua_type(L.s, 3)) + + switch fval.Kind() { + case reflect.Bool: + if luatype == LUA_TBOOLEAN { + fval.SetBool(int(C.lua_toboolean(L.s, 3)) != 0) + return 1 + } else { + L.PushString("Wrong assignment to field " + field_name) + return -1 + } + + case reflect.Int: + fallthrough + case reflect.Int8: + fallthrough + case reflect.Int16: + fallthrough + case reflect.Int32: + fallthrough + case reflect.Int64: + if luatype == LUA_TNUMBER { + fval.SetInt(int64(C.lua_tointeger(L.s, 3))) + return 1 + } else { + L.PushString("Wrong assignment to field " + field_name) + return -1 + } + + case reflect.Uint: + fallthrough + case reflect.Uint8: + fallthrough + case reflect.Uint16: + fallthrough + case reflect.Uint32: + fallthrough + case reflect.Uint64: + if luatype == LUA_TNUMBER { + fval.SetUint(uint64(C.lua_tointeger(L.s, 3))) + return 1 + } else { + L.PushString("Wrong assignment to field " + field_name) + return -1 + } + + case reflect.String: + if luatype == LUA_TSTRING { + fval.SetString(C.GoString(C.lua_tolstring(L.s, 3, nil))) + return 1 + } else { + L.PushString("Wrong assignment to field " + field_name) + return -1 + } + + case reflect.Float32: + fallthrough + case reflect.Float64: + if luatype == LUA_TNUMBER { + fval.SetFloat(float64(C.lua_tonumber(L.s, 3))) + return 1 + } else { + L.PushString("Wrong assignment to field " + field_name) + return -1 + } + } + + L.PushString("Unsupported type of field " + field_name + ": " + fval.Type().String()) + return -1 +} + +//export golua_interface_index_callback +func golua_interface_index_callback(Li interface{}, iid uint, field_name *C.char) int { + L := Li.(*State) + iface := L.registry[iid] + ifacevalue := reflect.ValueOf(iface).Elem() + + fval := ifacevalue.FieldByName(C.GoString(field_name)) + + if fval.Kind() == reflect.Ptr { + fval = fval.Elem() + } + + switch fval.Kind() { + case reflect.Bool: + L.PushBoolean(fval.Bool()) + return 1 + + case reflect.Int: + fallthrough + case reflect.Int8: + fallthrough + case reflect.Int16: + fallthrough + case reflect.Int32: + fallthrough + case reflect.Int64: + L.PushInteger(fval.Int()) + return 1 + + case reflect.Uint: + fallthrough + case reflect.Uint8: + fallthrough + case reflect.Uint16: + fallthrough + case reflect.Uint32: + fallthrough + case reflect.Uint64: + L.PushInteger(int64(fval.Uint())) + return 1 + + case reflect.String: + L.PushString(fval.String()) + return 1 + + case reflect.Float32: + fallthrough + case reflect.Float64: + L.PushNumber(fval.Float()) + return 1 + } + + L.PushString("Unsupported type of field: " + fval.Type().String()) + return -1 +} + +//export golua_gchook +func golua_gchook(L interface{}, id uint) int { + L1 := L.(*State) + L1.unregister(id) + return 0 +} + +//export golua_callpanicfunction +func golua_callpanicfunction(L interface{}, id uint) int { + L1 := L.(*State) + f := L1.registry[id].(LuaGoFunction) + return f(L1) +} + +//export golua_idtointerface +func golua_idtointerface(id uint) interface{} { + return id +} + +//export golua_cfunctiontointerface +func golua_cfunctiontointerface(f *uintptr) interface{} { + return f +} + +//export golua_callallocf +func golua_callallocf(fp uintptr, ptr uintptr, osize uint, nsize uint) uintptr { + return uintptr((*((*Alloc)(unsafe.Pointer(fp))))(unsafe.Pointer(ptr), osize, nsize)) +} + +//export go_panic_msghandler +func go_panic_msghandler(Li interface{}, z *C.char) { + L := Li.(*State) + s := C.GoString(z) + + panic(&LuaError{LUA_ERRERR, s, L.StackTrace()}) +} diff --git a/lua/golua.h b/lua/golua.h new file mode 100644 index 0000000..5d5b989 --- /dev/null +++ b/lua/golua.h @@ -0,0 +1,39 @@ +// +build lua + +#include + +typedef struct { void *t; void *v; } GoInterface; + +#define GOLUA_DEFAULT_MSGHANDLER "golua_default_msghandler" + +/* function to setup metatables, etc */ +void clua_initstate(lua_State* L); +void clua_hide_pcall(lua_State *L); + +unsigned int clua_togofunction(lua_State* L, int index); +unsigned int clua_togostruct(lua_State *L, int index); +void clua_pushcallback(lua_State* L); +void clua_pushgofunction(lua_State* L, unsigned int fid); +void clua_pushgostruct(lua_State *L, unsigned int fid); +void clua_setgostate(lua_State* L, GoInterface gostate); +GoInterface* clua_getgostate(lua_State* L); +GoInterface clua_atpanic(lua_State* L, unsigned int panicf_id); +int clua_callluacfunc(lua_State* L, lua_CFunction f); +lua_State* clua_newstate(void* goallocf); +void clua_setallocf(lua_State* L, void* goallocf); + +void clua_openbase(lua_State* L); +void clua_openio(lua_State* L); +void clua_openmath(lua_State* L); +void clua_openpackage(lua_State* L); +void clua_openstring(lua_State* L); +void clua_opentable(lua_State* L); +void clua_openos(lua_State* L); +void clua_setexecutionlimit(lua_State* L, int n); + +int clua_isgofunction(lua_State *L, int n); +int clua_isgostruct(lua_State *L, int n); + +void clua_opencjson(lua_State* L); +void clua_openstruct(lua_State* L); +void clua_opencmsgpack(lua_State* L); diff --git a/lua/golua_license b/lua/golua_license new file mode 100644 index 0000000..12a8196 --- /dev/null +++ b/lua/golua_license @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/lua/lauxlib.go b/lua/lauxlib.go new file mode 100644 index 0000000..4ce3b7c --- /dev/null +++ b/lua/lauxlib.go @@ -0,0 +1,216 @@ +// +build lua + +package lua + +//#include +//#include +//#include +//#include +//#include "golua.h" +import "C" +import "unsafe" + +type LuaError struct { + code int + message string + stackTrace []LuaStackEntry +} + +func (err *LuaError) Error() string { + return err.message +} + +func (err *LuaError) Code() int { + return err.code +} + +func (err *LuaError) StackTrace() []LuaStackEntry { + return err.stackTrace +} + +// luaL_argcheck +func (L *State) ArgCheck(cond bool, narg int, extramsg string) { + if cond { + Cextramsg := C.CString(extramsg) + defer C.free(unsafe.Pointer(Cextramsg)) + C.luaL_argerror(L.s, C.int(narg), Cextramsg) + } +} + +// luaL_argerror +func (L *State) ArgError(narg int, extramsg string) int { + Cextramsg := C.CString(extramsg) + defer C.free(unsafe.Pointer(Cextramsg)) + return int(C.luaL_argerror(L.s, C.int(narg), Cextramsg)) +} + +// luaL_callmeta +func (L *State) CallMeta(obj int, e string) int { + Ce := C.CString(e) + defer C.free(unsafe.Pointer(Ce)) + return int(C.luaL_callmeta(L.s, C.int(obj), Ce)) +} + +// luaL_checkany +func (L *State) CheckAny(narg int) { + C.luaL_checkany(L.s, C.int(narg)) +} + +// luaL_checkinteger +func (L *State) CheckInteger(narg int) int { + return int(C.luaL_checkinteger(L.s, C.int(narg))) +} + +// luaL_checknumber +func (L *State) CheckNumber(narg int) float64 { + return float64(C.luaL_checknumber(L.s, C.int(narg))) +} + +// luaL_checkstring +func (L *State) CheckString(narg int) string { + var length C.size_t + return C.GoString(C.luaL_checklstring(L.s, C.int(narg), &length)) +} + +// luaL_checkoption +// +// BUG(everyone_involved): not implemented +func (L *State) CheckOption(narg int, def string, lst []string) int { + //TODO: complication: lst conversion to const char* lst[] from string slice + return 0 +} + +// luaL_checktype +func (L *State) CheckType(narg int, t LuaValType) { + C.luaL_checktype(L.s, C.int(narg), C.int(t)) +} + +// luaL_checkudata +func (L *State) CheckUdata(narg int, tname string) unsafe.Pointer { + Ctname := C.CString(tname) + defer C.free(unsafe.Pointer(Ctname)) + return unsafe.Pointer(C.luaL_checkudata(L.s, C.int(narg), Ctname)) +} + +// Executes file, returns nil for no errors or the lua error string on failure +func (L *State) DoFile(filename string) error { + if r := L.LoadFile(filename); r != 0 { + return &LuaError{r, L.ToString(-1), L.StackTrace()} + } + return L.Call(0, LUA_MULTRET) +} + +// Executes the string, returns nil for no errors or the lua error string on failure +func (L *State) DoString(str string) error { + if r := L.LoadString(str); r != 0 { + return &LuaError{r, L.ToString(-1), L.StackTrace()} + } + return L.Call(0, LUA_MULTRET) +} + +// Like DoString but panics on error +func (L *State) MustDoString(str string) { + if err := L.DoString(str); err != nil { + panic(err) + } +} + +// luaL_getmetafield +func (L *State) GetMetaField(obj int, e string) bool { + Ce := C.CString(e) + defer C.free(unsafe.Pointer(Ce)) + return C.luaL_getmetafield(L.s, C.int(obj), Ce) != 0 +} + +// luaL_getmetatable +func (L *State) LGetMetaTable(tname string) { + Ctname := C.CString(tname) + defer C.free(unsafe.Pointer(Ctname)) + C.lua_getfield(L.s, LUA_REGISTRYINDEX, Ctname) +} + +// luaL_gsub +func (L *State) GSub(s string, p string, r string) string { + Cs := C.CString(s) + Cp := C.CString(p) + Cr := C.CString(r) + defer func() { + C.free(unsafe.Pointer(Cs)) + C.free(unsafe.Pointer(Cp)) + C.free(unsafe.Pointer(Cr)) + }() + + return C.GoString(C.luaL_gsub(L.s, Cs, Cp, Cr)) +} + +// luaL_loadfile +func (L *State) LoadFile(filename string) int { + Cfilename := C.CString(filename) + defer C.free(unsafe.Pointer(Cfilename)) + return int(C.luaL_loadfile(L.s, Cfilename)) +} + +// luaL_loadstring +func (L *State) LoadString(s string) int { + Cs := C.CString(s) + defer C.free(unsafe.Pointer(Cs)) + return int(C.luaL_loadstring(L.s, Cs)) +} + +// luaL_newmetatable +func (L *State) NewMetaTable(tname string) bool { + Ctname := C.CString(tname) + defer C.free(unsafe.Pointer(Ctname)) + return C.luaL_newmetatable(L.s, Ctname) != 0 +} + +// luaL_newstate +func NewState() *State { + ls := (C.luaL_newstate()) + L := newState(ls) + return L +} + +// luaL_openlibs +func (L *State) OpenLibs() { + C.luaL_openlibs(L.s) + C.clua_hide_pcall(L.s) +} + +// luaL_optinteger +func (L *State) OptInteger(narg int, d int) int { + return int(C.luaL_optinteger(L.s, C.int(narg), C.lua_Integer(d))) +} + +// luaL_optnumber +func (L *State) OptNumber(narg int, d float64) float64 { + return float64(C.luaL_optnumber(L.s, C.int(narg), C.lua_Number(d))) +} + +// luaL_optstring +func (L *State) OptString(narg int, d string) string { + var length C.size_t + Cd := C.CString(d) + defer C.free(unsafe.Pointer(Cd)) + return C.GoString(C.luaL_optlstring(L.s, C.int(narg), Cd, &length)) +} + +// luaL_ref +func (L *State) Ref(t int) int { + return int(C.luaL_ref(L.s, C.int(t))) +} + +// luaL_typename +func (L *State) LTypename(index int) string { + return C.GoString(C.lua_typename(L.s, C.lua_type(L.s, C.int(index)))) +} + +// luaL_unref +func (L *State) Unref(t int, ref int) { + C.luaL_unref(L.s, C.int(t), C.int(ref)) +} + +// luaL_where +func (L *State) Where(lvl int) { + C.luaL_where(L.s, C.int(lvl)) +} diff --git a/lua/lua.go b/lua/lua.go new file mode 100644 index 0000000..ecef2bc --- /dev/null +++ b/lua/lua.go @@ -0,0 +1,670 @@ +// +build lua + +// This package provides access to the excellent lua language interpreter from go code. +// +// Access to most of the functions in lua.h and lauxlib.h is provided as well as additional convenience functions to publish Go objects and functions to lua code. +// +// The documentation of this package is no substitute for the official lua documentation and in many instances methods are described only with the name of their C equivalent +package lua + +/* +#cgo LDFLAGS: -llua +#cgo linux LDFLAGS: -lm -ldl + +#include +#include + +#include "golua.h" + +*/ +import "C" +import "unsafe" + +import "fmt" + +type LuaStackEntry struct { + Name string + Source string + ShortSource string + CurrentLine int +} + +func newState(L *C.lua_State) *State { + var newstatei interface{} + newstate := &State{L, make([]interface{}, 0, 8), make([]uint, 0, 8)} + newstatei = newstate + ns1 := unsafe.Pointer(&newstatei) + ns2 := (*C.GoInterface)(ns1) + C.clua_setgostate(L, *ns2) //hacky.... + C.clua_initstate(L) + return newstate +} + +func (L *State) addFreeIndex(i uint) { + freelen := len(L.freeIndices) + //reallocate if necessary + if freelen+1 > cap(L.freeIndices) { + newSlice := make([]uint, freelen, cap(L.freeIndices)*2) + copy(newSlice, L.freeIndices) + L.freeIndices = newSlice + } + //reslice + L.freeIndices = L.freeIndices[0 : freelen+1] + L.freeIndices[freelen] = i +} + +func (L *State) getFreeIndex() (index uint, ok bool) { + freelen := len(L.freeIndices) + //if there exist entries in the freelist + if freelen > 0 { + i := L.freeIndices[freelen-1] //get index + //fmt.Printf("Free indices before: %v\n", L.freeIndices) + L.freeIndices = L.freeIndices[0 : freelen-1] //'pop' index from list + //fmt.Printf("Free indices after: %v\n", L.freeIndices) + return i, true + } + return 0, false +} + +//returns the registered function id +func (L *State) register(f interface{}) uint { + //fmt.Printf("Registering %v\n") + index, ok := L.getFreeIndex() + //fmt.Printf("\tfreeindex: index = %v, ok = %v\n", index, ok) + //if not ok, then we need to add new index by extending the slice + if !ok { + index = uint(len(L.registry)) + //reallocate backing array if necessary + if index+1 > uint(cap(L.registry)) { + newSlice := make([]interface{}, index, cap(L.registry)*2) + copy(newSlice, L.registry) + L.registry = newSlice + } + //reslice + L.registry = L.registry[0 : index+1] + } + //fmt.Printf("\tregistering %d %v\n", index, f) + L.registry[index] = f + return index +} + +func (L *State) unregister(fid uint) { + //fmt.Printf("Unregistering %d (len: %d, value: %v)\n", fid, len(L.registry), L.registry[fid]) + if (fid < uint(len(L.registry))) && (L.registry[fid] != nil) { + L.registry[fid] = nil + L.addFreeIndex(fid) + } +} + +// Like lua_pushcfunction pushes onto the stack a go function as user data +func (L *State) PushGoFunction(f LuaGoFunction) { + fid := L.register(f) + C.clua_pushgofunction(L.s, C.uint(fid)) +} + +// Sets a metamethod to execute a go function +// +// The code: +// +// L.LGetMetaTable(tableName) +// L.SetMetaMethod(methodName, function) +// +// is the logical equivalent of: +// +// L.LGetMetaTable(tableName) +// L.PushGoFunction(function) +// L.SetField(-2, methodName) +// +// except this wouldn't work because pushing a go function results in user data not a cfunction +func (L *State) SetMetaMethod(methodName string, f LuaGoFunction) { + L.PushGoFunction(f) // leaves Go function userdata on stack + C.clua_pushcallback(L.s) // wraps the userdata object with a closure making it into a function + L.SetField(-2, methodName) +} + +// Pushes a Go struct onto the stack as user data. +// +// The user data will be rigged so that lua code can access and change to public members of simple types directly +func (L *State) PushGoStruct(iface interface{}) { + iid := L.register(iface) + C.clua_pushgostruct(L.s, C.uint(iid)) +} + +// Push a pointer onto the stack as user data. +// +// This function doesn't save a reference to the interface, it is the responsibility of the caller of this function to insure that the interface outlasts the lifetime of the lua object that this function creates. +func (L *State) PushLightUserdata(ud *interface{}) { + //push + C.lua_pushlightuserdata(L.s, unsafe.Pointer(ud)) +} + +// Creates a new user data object of specified size and returns it +func (L *State) NewUserdata(size uintptr) unsafe.Pointer { + return unsafe.Pointer(C.lua_newuserdata(L.s, C.size_t(size))) +} + +// Sets the AtPanic function, returns the old one +// +// BUG(everyone_involved): passing nil causes serious problems +func (L *State) AtPanic(panicf LuaGoFunction) (oldpanicf LuaGoFunction) { + fid := uint(0) + if panicf != nil { + fid = L.register(panicf) + } + oldres := interface{}(C.clua_atpanic(L.s, C.uint(fid))) + switch i := oldres.(type) { + case C.uint: + f := L.registry[uint(i)].(LuaGoFunction) + //free registry entry + L.unregister(uint(i)) + return f + case C.lua_CFunction: + return func(L1 *State) int { + return int(C.clua_callluacfunc(L1.s, i)) + } + } + //generally we only get here if the panicf got set to something like nil + //potentially dangerous because we may silently fail + return nil +} + +func (L *State) pcall(nargs, nresults, errfunc int) int { + return int(C.lua_pcall(L.s, C.int(nargs), C.int(nresults), C.int(errfunc))) +} + +func (L *State) callEx(nargs, nresults int, catch bool) (err error) { + if catch { + defer func() { + if err2 := recover(); err2 != nil { + if _, ok := err2.(error); ok { + err = err2.(error) + } + return + } + }() + } + + L.GetGlobal(C.GOLUA_DEFAULT_MSGHANDLER) + // We must record where we put the error handler in the stack otherwise it will be impossible to remove after the pcall when nresults == LUA_MULTRET + erridx := L.GetTop() - nargs - 1 + L.Insert(erridx) + r := L.pcall(nargs, nresults, erridx) + L.Remove(erridx) + if r != 0 { + err = &LuaError{r, L.ToString(-1), L.StackTrace()} + if !catch { + panic(err) + } + } + return +} + +// lua_call +func (L *State) Call(nargs, nresults int) (err error) { + return L.callEx(nargs, nresults, true) +} + +// Like lua_call but panics on errors +func (L *State) MustCall(nargs, nresults int) { + L.callEx(nargs, nresults, false) +} + +// lua_checkstack +func (L *State) CheckStack(extra int) bool { + return C.lua_checkstack(L.s, C.int(extra)) != 0 +} + +// lua_close +func (L *State) Close() { + C.lua_close(L.s) +} + +// lua_concat +func (L *State) Concat(n int) { + C.lua_concat(L.s, C.int(n)) +} + +// lua_createtable +func (L *State) CreateTable(narr int, nrec int) { + C.lua_createtable(L.s, C.int(narr), C.int(nrec)) +} + +// lua_equal +func (L *State) Equal(index1, index2 int) bool { + return C.lua_equal(L.s, C.int(index1), C.int(index2)) == 1 +} + +// lua_gc +func (L *State) GC(what, data int) int { return int(C.lua_gc(L.s, C.int(what), C.int(data))) } + +// lua_getfenv +func (L *State) GetfEnv(index int) { C.lua_getfenv(L.s, C.int(index)) } + +// lua_getfield +func (L *State) GetField(index int, k string) { + Ck := C.CString(k) + defer C.free(unsafe.Pointer(Ck)) + C.lua_getfield(L.s, C.int(index), Ck) +} + +// Pushes on the stack the value of a global variable (lua_getglobal) +func (L *State) GetGlobal(name string) { L.GetField(LUA_GLOBALSINDEX, name) } + +// lua_getmetatable +func (L *State) GetMetaTable(index int) bool { + return C.lua_getmetatable(L.s, C.int(index)) != 0 +} + +// lua_gettable +func (L *State) GetTable(index int) { C.lua_gettable(L.s, C.int(index)) } + +// lua_gettop +func (L *State) GetTop() int { return int(C.lua_gettop(L.s)) } + +// lua_insert +func (L *State) Insert(index int) { C.lua_insert(L.s, C.int(index)) } + +// Returns true if lua_type == LUA_TBOOLEAN +func (L *State) IsBoolean(index int) bool { + return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TBOOLEAN +} + +// Returns true if the value at index is a LuaGoFunction +func (L *State) IsGoFunction(index int) bool { + return C.clua_isgofunction(L.s, C.int(index)) != 0 +} + +// Returns true if the value at index is user data pushed with PushGoStruct +func (L *State) IsGoStruct(index int) bool { + return C.clua_isgostruct(L.s, C.int(index)) != 0 +} + +// Returns true if the value at index is user data pushed with PushGoFunction +func (L *State) IsFunction(index int) bool { + return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TFUNCTION +} + +// Returns true if the value at index is light user data +func (L *State) IsLightUserdata(index int) bool { + return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TLIGHTUSERDATA +} + +// lua_isnil +func (L *State) IsNil(index int) bool { return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TNIL } + +// lua_isnone +func (L *State) IsNone(index int) bool { return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TNONE } + +// lua_isnoneornil +func (L *State) IsNoneOrNil(index int) bool { return int(C.lua_type(L.s, C.int(index))) <= 0 } + +// lua_isnumber +func (L *State) IsNumber(index int) bool { return C.lua_isnumber(L.s, C.int(index)) == 1 } + +// lua_isstring +func (L *State) IsString(index int) bool { return C.lua_isstring(L.s, C.int(index)) == 1 } + +// lua_istable +func (L *State) IsTable(index int) bool { + return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TTABLE +} + +// lua_isthread +func (L *State) IsThread(index int) bool { + return LuaValType(C.lua_type(L.s, C.int(index))) == LUA_TTHREAD +} + +// lua_isuserdata +func (L *State) IsUserdata(index int) bool { return C.lua_isuserdata(L.s, C.int(index)) == 1 } + +// lua_lessthan +func (L *State) LessThan(index1, index2 int) bool { + return C.lua_lessthan(L.s, C.int(index1), C.int(index2)) == 1 +} + +// Creates a new lua interpreter state with the given allocation function +func NewStateAlloc(f Alloc) *State { + ls := C.clua_newstate(unsafe.Pointer(&f)) + return newState(ls) +} + +// lua_newtable +func (L *State) NewTable() { + C.lua_createtable(L.s, 0, 0) +} + +// lua_newthread +func (L *State) NewThread() *State { + //TODO: call newState with result from C.lua_newthread and return it + //TODO: should have same lists as parent + // but may complicate gc + s := C.lua_newthread(L.s) + return &State{s, nil, nil} +} + +// lua_next +func (L *State) Next(index int) int { + return int(C.lua_next(L.s, C.int(index))) +} + +// lua_objlen +func (L *State) ObjLen(index int) uint { + return uint(C.lua_objlen(L.s, C.int(index))) +} + +// lua_pop +func (L *State) Pop(n int) { + //Why is this implemented this way? I don't get it... + //C.lua_pop(L.s, C.int(n)); + C.lua_settop(L.s, C.int(-n-1)) +} + +// lua_pushboolean +func (L *State) PushBoolean(b bool) { + var bint int + if b { + bint = 1 + } else { + bint = 0 + } + C.lua_pushboolean(L.s, C.int(bint)) +} + +// lua_pushstring +func (L *State) PushString(str string) { + Cstr := C.CString(str) + defer C.free(unsafe.Pointer(Cstr)) + C.lua_pushlstring(L.s, Cstr, C.size_t(len(str))) +} + +// lua_pushinteger +func (L *State) PushInteger(n int64) { + C.lua_pushinteger(L.s, C.lua_Integer(n)) +} + +// lua_pushnil +func (L *State) PushNil() { + C.lua_pushnil(L.s) +} + +// lua_pushnumber +func (L *State) PushNumber(n float64) { + C.lua_pushnumber(L.s, C.lua_Number(n)) +} + +// lua_pushthread +func (L *State) PushThread() (isMain bool) { + return C.lua_pushthread(L.s) != 0 +} + +// lua_pushvalue +func (L *State) PushValue(index int) { + C.lua_pushvalue(L.s, C.int(index)) +} + +// lua_rawequal +func (L *State) RawEqual(index1 int, index2 int) bool { + return C.lua_rawequal(L.s, C.int(index1), C.int(index2)) != 0 +} + +// lua_rawget +func (L *State) RawGet(index int) { + C.lua_rawget(L.s, C.int(index)) +} + +// lua_rawgeti +func (L *State) RawGeti(index int, n int) { + C.lua_rawgeti(L.s, C.int(index), C.int(n)) +} + +// lua_rawset +func (L *State) RawSet(index int) { + C.lua_rawset(L.s, C.int(index)) +} + +// lua_rawseti +func (L *State) RawSeti(index int, n int) { + C.lua_rawseti(L.s, C.int(index), C.int(n)) +} + +// Registers a Go function as a global variable +func (L *State) Register(name string, f LuaGoFunction) { + L.PushGoFunction(f) + L.SetGlobal(name) +} + +// lua_remove +func (L *State) Remove(index int) { + C.lua_remove(L.s, C.int(index)) +} + +// lua_replace +func (L *State) Replace(index int) { + C.lua_replace(L.s, C.int(index)) +} + +// lua_resume +func (L *State) Resume(narg int) int { + return int(C.lua_resume(L.s, C.int(narg))) +} + +// lua_setallocf +func (L *State) SetAllocf(f Alloc) { + C.clua_setallocf(L.s, unsafe.Pointer(&f)) +} + +// lua_setfenv +func (L *State) SetfEnv(index int) { + C.lua_setfenv(L.s, C.int(index)) +} + +// lua_setfield +func (L *State) SetField(index int, k string) { + Ck := C.CString(k) + defer C.free(unsafe.Pointer(Ck)) + C.lua_setfield(L.s, C.int(index), Ck) +} + +// lua_setglobal +func (L *State) SetGlobal(name string) { + Cname := C.CString(name) + defer C.free(unsafe.Pointer(Cname)) + C.lua_setfield(L.s, C.int(LUA_GLOBALSINDEX), Cname) +} + +// lua_setmetatable +func (L *State) SetMetaTable(index int) { + C.lua_setmetatable(L.s, C.int(index)) +} + +// lua_settable +func (L *State) SetTable(index int) { + C.lua_settable(L.s, C.int(index)) +} + +// lua_settop +func (L *State) SetTop(index int) { + C.lua_settop(L.s, C.int(index)) +} + +// lua_status +func (L *State) Status() int { + return int(C.lua_status(L.s)) +} + +// lua_toboolean +func (L *State) ToBoolean(index int) bool { + return C.lua_toboolean(L.s, C.int(index)) != 0 +} + +// Returns the value at index as a Go function (it must be something pushed with PushGoFunction) +func (L *State) ToGoFunction(index int) (f LuaGoFunction) { + if !L.IsGoFunction(index) { + return nil + } + fid := C.clua_togofunction(L.s, C.int(index)) + if fid < 0 { + return nil + } + return L.registry[fid].(LuaGoFunction) +} + +// Returns the value at index as a Go Struct (it must be something pushed with PushGoStruct) +func (L *State) ToGoStruct(index int) (f interface{}) { + if !L.IsGoStruct(index) { + return nil + } + fid := C.clua_togostruct(L.s, C.int(index)) + if fid < 0 { + return nil + } + return L.registry[fid] +} + +// lua_tostring +func (L *State) ToString(index int) string { + var size C.size_t + r := C.lua_tolstring(L.s, C.int(index), &size) + return C.GoStringN(r, C.int(size)) +} + +// lua_tointeger +func (L *State) ToInteger(index int) int { + return int(C.lua_tointeger(L.s, C.int(index))) +} + +// lua_tonumber +func (L *State) ToNumber(index int) float64 { + return float64(C.lua_tonumber(L.s, C.int(index))) +} + +// lua_topointer +func (L *State) ToPointer(index int) uintptr { + return uintptr(C.lua_topointer(L.s, C.int(index))) +} + +// lua_tothread +func (L *State) ToThread(index int) *State { + //TODO: find a way to link lua_State* to existing *State, return that + return &State{} +} + +// lua_touserdata +func (L *State) ToUserdata(index int) unsafe.Pointer { + return unsafe.Pointer(C.lua_touserdata(L.s, C.int(index))) +} + +// lua_type +func (L *State) Type(index int) LuaValType { + return LuaValType(C.lua_type(L.s, C.int(index))) +} + +// lua_typename +func (L *State) Typename(tp int) string { + return C.GoString(C.lua_typename(L.s, C.int(tp))) +} + +// lua_xmove +func XMove(from *State, to *State, n int) { + C.lua_xmove(from.s, to.s, C.int(n)) +} + +// lua_yield +func (L *State) Yield(nresults int) int { + return int(C.lua_yield(L.s, C.int(nresults))) +} + +// Restricted library opens + +// Calls luaopen_base +func (L *State) OpenBase() { + C.clua_openbase(L.s) +} + +// Calls luaopen_io +func (L *State) OpenIO() { + C.clua_openio(L.s) +} + +// Calls luaopen_math +func (L *State) OpenMath() { + C.clua_openmath(L.s) +} + +// Calls luaopen_package +func (L *State) OpenPackage() { + C.clua_openpackage(L.s) +} + +// Calls luaopen_string +func (L *State) OpenString() { + C.clua_openstring(L.s) +} + +// Calls luaopen_table +func (L *State) OpenTable() { + C.clua_opentable(L.s) +} + +// Calls luaopen_os +func (L *State) OpenOS() { + C.clua_openos(L.s) +} + +// Sets the maximum number of operations to execute at instrNumber, after this the execution ends +func (L *State) SetExecutionLimit(instrNumber int) { + C.clua_setexecutionlimit(L.s, C.int(instrNumber)) +} + +// Returns the current stack trace +func (L *State) StackTrace() []LuaStackEntry { + r := []LuaStackEntry{} + var d C.lua_Debug + Sln := C.CString("Sln") + defer C.free(unsafe.Pointer(Sln)) + + for depth := 0; C.lua_getstack(L.s, C.int(depth), &d) > 0; depth++ { + C.lua_getinfo(L.s, Sln, &d) + ssb := make([]byte, C.LUA_IDSIZE) + for i := 0; i < C.LUA_IDSIZE; i++ { + ssb[i] = byte(d.short_src[i]) + if ssb[i] == 0 { + ssb = ssb[:i] + break + } + } + ss := string(ssb) + + r = append(r, LuaStackEntry{C.GoString(d.name), C.GoString(d.source), ss, int(d.currentline)}) + } + + return r +} + +func (L *State) RaiseError(msg string) { + st := L.StackTrace() + prefix := "" + if len(st) >= 1 { + prefix = fmt.Sprintf("%s:%d: ", st[1].ShortSource, st[1].CurrentLine) + } + panic(&LuaError{0, prefix + msg, st}) +} + +func (L *State) NewError(msg string) *LuaError { + return &LuaError{0, msg, L.StackTrace()} +} + +// Calls luaopen_cjson +func (L *State) OpenCJson() { + C.clua_opencjson(L.s) +} + +// Calls luaopen_struct +func (L *State) OpenStruct() { + C.clua_openstruct(L.s) +} + +// Calls luaopen_cmsgpack +func (L *State) OpenCMsgpack() { + C.clua_opencmsgpack(L.s) +} diff --git a/lua/lua_cjson.c b/lua/lua_cjson.c new file mode 100644 index 0000000..cd3c19f --- /dev/null +++ b/lua/lua_cjson.c @@ -0,0 +1,1301 @@ +// +build lua + +#define VERSION "1.0.3" + +/* CJSON - JSON support for Lua + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* Caveats: + * - JSON "null" values are represented as lightuserdata since Lua + * tables cannot contain "nil". Compare with cjson.null. + * - Invalid UTF-8 characters are not detected and will be passed + * untouched. If required, UTF-8 error checking should be done + * outside this library. + * - Javascript comments are not part of the JSON spec, and are not + * currently supported. + * + * Note: Decoding is slower than encoding. Lua spends significant + * time (30%) managing tables when parsing JSON since it is + * difficult to know object/array sizes ahead of time. + */ + +#include +#include +#include +#include +#include + +#include "strbuf.h" + +#ifdef MISSING_ISINF +#define isinf(x) (!isnan(x) && isnan((x) - (x))) +#endif + +#define DEFAULT_SPARSE_CONVERT 0 +#define DEFAULT_SPARSE_RATIO 2 +#define DEFAULT_SPARSE_SAFE 10 +#define DEFAULT_MAX_DEPTH 20 +#define DEFAULT_ENCODE_REFUSE_BADNUM 1 +#define DEFAULT_DECODE_REFUSE_BADNUM 0 +#define DEFAULT_ENCODE_KEEP_BUFFER 1 + +typedef enum { + T_OBJ_BEGIN, + T_OBJ_END, + T_ARR_BEGIN, + T_ARR_END, + T_STRING, + T_NUMBER, + T_BOOLEAN, + T_NULL, + T_COLON, + T_COMMA, + T_END, + T_WHITESPACE, + T_ERROR, + T_UNKNOWN +} json_token_type_t; + +static const char *json_token_type_name[] = { + "T_OBJ_BEGIN", + "T_OBJ_END", + "T_ARR_BEGIN", + "T_ARR_END", + "T_STRING", + "T_NUMBER", + "T_BOOLEAN", + "T_NULL", + "T_COLON", + "T_COMMA", + "T_END", + "T_WHITESPACE", + "T_ERROR", + "T_UNKNOWN", + NULL +}; + +typedef struct { + json_token_type_t ch2token[256]; + char escape2char[256]; /* Decoding */ +#if 0 + char escapes[35][8]; /* Pre-generated escape string buffer */ + char *char2escape[256]; /* Encoding */ +#endif + strbuf_t encode_buf; + char number_fmt[8]; /* "%.XXg\0" */ + int current_depth; + + int encode_sparse_convert; + int encode_sparse_ratio; + int encode_sparse_safe; + int encode_max_depth; + int encode_refuse_badnum; + int decode_refuse_badnum; + int encode_keep_buffer; + int encode_number_precision; +} json_config_t; + +typedef struct { + const char *data; + int index; + strbuf_t *tmp; /* Temporary storage for strings */ + json_config_t *cfg; +} json_parse_t; + +typedef struct { + json_token_type_t type; + int index; + union { + const char *string; + double number; + int boolean; + } value; + int string_len; +} json_token_t; + +static const char *char2escape[256] = { + "\\u0000", "\\u0001", "\\u0002", "\\u0003", + "\\u0004", "\\u0005", "\\u0006", "\\u0007", + "\\b", "\\t", "\\n", "\\u000b", + "\\f", "\\r", "\\u000e", "\\u000f", + "\\u0010", "\\u0011", "\\u0012", "\\u0013", + "\\u0014", "\\u0015", "\\u0016", "\\u0017", + "\\u0018", "\\u0019", "\\u001a", "\\u001b", + "\\u001c", "\\u001d", "\\u001e", "\\u001f", + NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, "\\\\", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\u007f", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +}; + +static int json_config_key; + +/* ===== CONFIGURATION ===== */ + +static json_config_t *json_fetch_config(lua_State *l) +{ + json_config_t *cfg; + + lua_pushlightuserdata(l, &json_config_key); + lua_gettable(l, LUA_REGISTRYINDEX); + cfg = lua_touserdata(l, -1); + if (!cfg) + luaL_error(l, "BUG: Unable to fetch CJSON configuration"); + + lua_pop(l, 1); + + return cfg; +} + +static void json_verify_arg_count(lua_State *l, int args) +{ + luaL_argcheck(l, lua_gettop(l) <= args, args + 1, + "found too many arguments"); +} + +/* Configures handling of extremely sparse arrays: + * convert: Convert extremely sparse arrays into objects? Otherwise error. + * ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio + * safe: Always use an array when the max index <= safe */ +static int json_cfg_encode_sparse_array(lua_State *l) +{ + json_config_t *cfg; + int val; + + json_verify_arg_count(l, 3); + cfg = json_fetch_config(l); + + switch (lua_gettop(l)) { + case 3: + val = luaL_checkinteger(l, 3); + luaL_argcheck(l, val >= 0, 3, "expected integer >= 0"); + cfg->encode_sparse_safe = val; + case 2: + val = luaL_checkinteger(l, 2); + luaL_argcheck(l, val >= 0, 2, "expected integer >= 0"); + cfg->encode_sparse_ratio = val; + case 1: + luaL_argcheck(l, lua_isboolean(l, 1), 1, "expected boolean"); + cfg->encode_sparse_convert = lua_toboolean(l, 1); + } + + lua_pushboolean(l, cfg->encode_sparse_convert); + lua_pushinteger(l, cfg->encode_sparse_ratio); + lua_pushinteger(l, cfg->encode_sparse_safe); + + return 3; +} + +/* Configures the maximum number of nested arrays/objects allowed when + * encoding */ +static int json_cfg_encode_max_depth(lua_State *l) +{ + json_config_t *cfg; + int depth; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + depth = luaL_checkinteger(l, 1); + luaL_argcheck(l, depth > 0, 1, "expected positive integer"); + cfg->encode_max_depth = depth; + } + + lua_pushinteger(l, cfg->encode_max_depth); + + return 1; +} + +static void json_set_number_precision(json_config_t *cfg, int prec) +{ + cfg->encode_number_precision = prec; + sprintf(cfg->number_fmt, "%%.%dg", prec); +} + +/* Configures number precision when converting doubles to text */ +static int json_cfg_encode_number_precision(lua_State *l) +{ + json_config_t *cfg; + int precision; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + precision = luaL_checkinteger(l, 1); + luaL_argcheck(l, 1 <= precision && precision <= 14, 1, + "expected integer between 1 and 14"); + json_set_number_precision(cfg, precision); + } + + lua_pushinteger(l, cfg->encode_number_precision); + + return 1; +} + +/* Configures JSON encoding buffer persistence */ +static int json_cfg_encode_keep_buffer(lua_State *l) +{ + json_config_t *cfg; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + if (lua_gettop(l)) { + luaL_checktype(l, 1, LUA_TBOOLEAN); + cfg->encode_keep_buffer = lua_toboolean(l, 1); + } + + lua_pushboolean(l, cfg->encode_keep_buffer); + + return 1; +} + +/* On argument: decode enum and set config variables + * **options must point to a NULL terminated array of 4 enums + * Returns: current enum value */ +static void json_enum_option(lua_State *l, const char **options, + int *opt1, int *opt2) +{ + int setting; + + if (lua_gettop(l)) { + if (lua_isboolean(l, 1)) + setting = lua_toboolean(l, 1) * 3; + else + setting = luaL_checkoption(l, 1, NULL, options); + + *opt1 = setting & 1 ? 1 : 0; + *opt2 = setting & 2 ? 1 : 0; + } else { + setting = *opt1 | (*opt2 << 1); + } + + if (setting) + lua_pushstring(l, options[setting]); + else + lua_pushboolean(l, 0); +} + + +/* When enabled, rejects: NaN, Infinity, hexidecimal numbers */ +static int json_cfg_refuse_invalid_numbers(lua_State *l) +{ + static const char *options_enc_dec[] = { "none", "encode", "decode", + "both", NULL }; + json_config_t *cfg; + + json_verify_arg_count(l, 1); + cfg = json_fetch_config(l); + + json_enum_option(l, options_enc_dec, + &cfg->encode_refuse_badnum, + &cfg->decode_refuse_badnum); + + return 1; +} + +static int json_destroy_config(lua_State *l) +{ + json_config_t *cfg; + + cfg = lua_touserdata(l, 1); + if (cfg) + strbuf_free(&cfg->encode_buf); + cfg = NULL; + + return 0; +} + +static void json_create_config(lua_State *l) +{ + json_config_t *cfg; + int i; + + cfg = lua_newuserdata(l, sizeof(*cfg)); + + /* Create GC method to clean up strbuf */ + lua_newtable(l); + lua_pushcfunction(l, json_destroy_config); + lua_setfield(l, -2, "__gc"); + lua_setmetatable(l, -2); + + strbuf_init(&cfg->encode_buf, 0); + + cfg->encode_sparse_convert = DEFAULT_SPARSE_CONVERT; + cfg->encode_sparse_ratio = DEFAULT_SPARSE_RATIO; + cfg->encode_sparse_safe = DEFAULT_SPARSE_SAFE; + cfg->encode_max_depth = DEFAULT_MAX_DEPTH; + cfg->encode_refuse_badnum = DEFAULT_ENCODE_REFUSE_BADNUM; + cfg->decode_refuse_badnum = DEFAULT_DECODE_REFUSE_BADNUM; + cfg->encode_keep_buffer = DEFAULT_ENCODE_KEEP_BUFFER; + json_set_number_precision(cfg, 14); + + /* Decoding init */ + + /* Tag all characters as an error */ + for (i = 0; i < 256; i++) + cfg->ch2token[i] = T_ERROR; + + /* Set tokens that require no further processing */ + cfg->ch2token['{'] = T_OBJ_BEGIN; + cfg->ch2token['}'] = T_OBJ_END; + cfg->ch2token['['] = T_ARR_BEGIN; + cfg->ch2token[']'] = T_ARR_END; + cfg->ch2token[','] = T_COMMA; + cfg->ch2token[':'] = T_COLON; + cfg->ch2token['\0'] = T_END; + cfg->ch2token[' '] = T_WHITESPACE; + cfg->ch2token['\t'] = T_WHITESPACE; + cfg->ch2token['\n'] = T_WHITESPACE; + cfg->ch2token['\r'] = T_WHITESPACE; + + /* Update characters that require further processing */ + cfg->ch2token['f'] = T_UNKNOWN; /* false? */ + cfg->ch2token['i'] = T_UNKNOWN; /* inf, ininity? */ + cfg->ch2token['I'] = T_UNKNOWN; + cfg->ch2token['n'] = T_UNKNOWN; /* null, nan? */ + cfg->ch2token['N'] = T_UNKNOWN; + cfg->ch2token['t'] = T_UNKNOWN; /* true? */ + cfg->ch2token['"'] = T_UNKNOWN; /* string? */ + cfg->ch2token['+'] = T_UNKNOWN; /* number? */ + cfg->ch2token['-'] = T_UNKNOWN; + for (i = 0; i < 10; i++) + cfg->ch2token['0' + i] = T_UNKNOWN; + + /* Lookup table for parsing escape characters */ + for (i = 0; i < 256; i++) + cfg->escape2char[i] = 0; /* String error */ + cfg->escape2char['"'] = '"'; + cfg->escape2char['\\'] = '\\'; + cfg->escape2char['/'] = '/'; + cfg->escape2char['b'] = '\b'; + cfg->escape2char['t'] = '\t'; + cfg->escape2char['n'] = '\n'; + cfg->escape2char['f'] = '\f'; + cfg->escape2char['r'] = '\r'; + cfg->escape2char['u'] = 'u'; /* Unicode parsing required */ + + +#if 0 + /* Initialise separate storage for pre-generated escape codes. + * Escapes 0-31 map directly, 34, 92, 127 follow afterwards to + * save memory. */ + for (i = 0 ; i < 32; i++) + sprintf(cfg->escapes[i], "\\u%04x", i); + strcpy(cfg->escapes[8], "\b"); /* Override simpler escapes */ + strcpy(cfg->escapes[9], "\t"); + strcpy(cfg->escapes[10], "\n"); + strcpy(cfg->escapes[12], "\f"); + strcpy(cfg->escapes[13], "\r"); + strcpy(cfg->escapes[32], "\\\""); /* chr(34) */ + strcpy(cfg->escapes[33], "\\\\"); /* chr(92) */ + sprintf(cfg->escapes[34], "\\u%04x", 127); /* char(127) */ + + /* Initialise encoding escape lookup table */ + for (i = 0; i < 32; i++) + cfg->char2escape[i] = cfg->escapes[i]; + for (i = 32; i < 256; i++) + cfg->char2escape[i] = NULL; + cfg->char2escape[34] = cfg->escapes[32]; + cfg->char2escape[92] = cfg->escapes[33]; + cfg->char2escape[127] = cfg->escapes[34]; +#endif +} + +/* ===== ENCODING ===== */ + +static void json_encode_exception(lua_State *l, json_config_t *cfg, int lindex, + const char *reason) +{ + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + luaL_error(l, "Cannot serialise %s: %s", + lua_typename(l, lua_type(l, lindex)), reason); +} + +/* json_append_string args: + * - lua_State + * - JSON strbuf + * - String (Lua stack index) + * + * Returns nothing. Doesn't remove string from Lua stack */ +static void json_append_string(lua_State *l, strbuf_t *json, int lindex) +{ + const char *escstr; + int i; + const char *str; + size_t len; + + str = lua_tolstring(l, lindex, &len); + + /* Worst case is len * 6 (all unicode escapes). + * This buffer is reused constantly for small strings + * If there are any excess pages, they won't be hit anyway. + * This gains ~5% speedup. */ + strbuf_ensure_empty_length(json, len * 6 + 2); + + strbuf_append_char_unsafe(json, '\"'); + for (i = 0; i < len; i++) { + escstr = char2escape[(unsigned char)str[i]]; + if (escstr) + strbuf_append_string(json, escstr); + else + strbuf_append_char_unsafe(json, str[i]); + } + strbuf_append_char_unsafe(json, '\"'); +} + +/* Find the size of the array on the top of the Lua stack + * -1 object (not a pure array) + * >=0 elements in array + */ +static int lua_array_length(lua_State *l, json_config_t *cfg) +{ + double k; + int max; + int items; + + max = 0; + items = 0; + + lua_pushnil(l); + /* table, startkey */ + while (lua_next(l, -2) != 0) { + /* table, key, value */ + if (lua_type(l, -2) == LUA_TNUMBER && + (k = lua_tonumber(l, -2))) { + /* Integer >= 1 ? */ + if (floor(k) == k && k >= 1) { + if (k > max) + max = k; + items++; + lua_pop(l, 1); + continue; + } + } + + /* Must not be an array (non integer key) */ + lua_pop(l, 2); + return -1; + } + + /* Encode excessively sparse arrays as objects (if enabled) */ + if (cfg->encode_sparse_ratio > 0 && + max > items * cfg->encode_sparse_ratio && + max > cfg->encode_sparse_safe) { + if (!cfg->encode_sparse_convert) + json_encode_exception(l, cfg, -1, "excessively sparse array"); + + return -1; + } + + return max; +} + +static void json_encode_descend(lua_State *l, json_config_t *cfg) +{ + cfg->current_depth++; + + if (cfg->current_depth > cfg->encode_max_depth) { + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + luaL_error(l, "Cannot serialise, excessive nesting (%d)", + cfg->current_depth); + } +} + +static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json); + +/* json_append_array args: + * - lua_State + * - JSON strbuf + * - Size of passwd Lua array (top of stack) */ +static void json_append_array(lua_State *l, json_config_t *cfg, strbuf_t *json, + int array_length) +{ + int comma, i; + + json_encode_descend(l, cfg); + + strbuf_append_char(json, '['); + + comma = 0; + for (i = 1; i <= array_length; i++) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + lua_rawgeti(l, -1, i); + json_append_data(l, cfg, json); + lua_pop(l, 1); + } + + strbuf_append_char(json, ']'); + + cfg->current_depth--; +} + +static void json_append_number(lua_State *l, strbuf_t *json, int index, + json_config_t *cfg) +{ + double num = lua_tonumber(l, index); + + if (cfg->encode_refuse_badnum && (isinf(num) || isnan(num))) + json_encode_exception(l, cfg, index, "must not be NaN or Inf"); + + /* Lowest double printed with %.14g is 21 characters long: + * -1.7976931348623e+308 + * + * Use 32 to include the \0, and a few extra just in case.. + */ + strbuf_append_fmt(json, 32, cfg->number_fmt, num); +} + +static void json_append_object(lua_State *l, json_config_t *cfg, + strbuf_t *json) +{ + int comma, keytype; + + json_encode_descend(l, cfg); + + /* Object */ + strbuf_append_char(json, '{'); + + lua_pushnil(l); + /* table, startkey */ + comma = 0; + while (lua_next(l, -2) != 0) { + if (comma) + strbuf_append_char(json, ','); + else + comma = 1; + + /* table, key, value */ + keytype = lua_type(l, -2); + if (keytype == LUA_TNUMBER) { + strbuf_append_char(json, '"'); + json_append_number(l, json, -2, cfg); + strbuf_append_mem(json, "\":", 2); + } else if (keytype == LUA_TSTRING) { + json_append_string(l, json, -2); + strbuf_append_char(json, ':'); + } else { + json_encode_exception(l, cfg, -2, + "table key must be a number or string"); + /* never returns */ + } + + /* table, key, value */ + json_append_data(l, cfg, json); + lua_pop(l, 1); + /* table, key */ + } + + strbuf_append_char(json, '}'); + + cfg->current_depth--; +} + +/* Serialise Lua data into JSON string. */ +static void json_append_data(lua_State *l, json_config_t *cfg, strbuf_t *json) +{ + int len; + + switch (lua_type(l, -1)) { + case LUA_TSTRING: + json_append_string(l, json, -1); + break; + case LUA_TNUMBER: + json_append_number(l, json, -1, cfg); + break; + case LUA_TBOOLEAN: + if (lua_toboolean(l, -1)) + strbuf_append_mem(json, "true", 4); + else + strbuf_append_mem(json, "false", 5); + break; + case LUA_TTABLE: + len = lua_array_length(l, cfg); + if (len > 0) + json_append_array(l, cfg, json, len); + else + json_append_object(l, cfg, json); + break; + case LUA_TNIL: + strbuf_append_mem(json, "null", 4); + break; + case LUA_TLIGHTUSERDATA: + if (lua_touserdata(l, -1) == NULL) { + strbuf_append_mem(json, "null", 4); + break; + } + default: + /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, + * and LUA_TLIGHTUSERDATA) cannot be serialised */ + json_encode_exception(l, cfg, -1, "type not supported"); + /* never returns */ + } +} + +static int json_encode(lua_State *l) +{ + json_config_t *cfg; + char *json; + int len; + + /* Can't use json_verify_arg_count() since we need to ensure + * there is only 1 argument */ + luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); + + cfg = json_fetch_config(l); + cfg->current_depth = 0; + + /* Reset the persistent buffer if it exists. + * Otherwise allocate a new buffer. */ + if (strbuf_allocated(&cfg->encode_buf)) + strbuf_reset(&cfg->encode_buf); + else + strbuf_init(&cfg->encode_buf, 0); + + json_append_data(l, cfg, &cfg->encode_buf); + json = strbuf_string(&cfg->encode_buf, &len); + + lua_pushlstring(l, json, len); + + if (!cfg->encode_keep_buffer) + strbuf_free(&cfg->encode_buf); + + return 1; +} + +/* ===== DECODING ===== */ + +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token); + +static int hexdigit2int(char hex) +{ + if ('0' <= hex && hex <= '9') + return hex - '0'; + + /* Force lowercase */ + hex |= 0x20; + if ('a' <= hex && hex <= 'f') + return 10 + hex - 'a'; + + return -1; +} + +static int decode_hex4(const char *hex) +{ + int digit[4]; + int i; + + /* Convert ASCII hex digit to numeric digit + * Note: this returns an error for invalid hex digits, including + * NULL */ + for (i = 0; i < 4; i++) { + digit[i] = hexdigit2int(hex[i]); + if (digit[i] < 0) { + return -1; + } + } + + return (digit[0] << 12) + + (digit[1] << 8) + + (digit[2] << 4) + + digit[3]; +} + +/* Converts a Unicode codepoint to UTF-8. + * Returns UTF-8 string length, and up to 4 bytes in *utf8 */ +static int codepoint_to_utf8(char *utf8, int codepoint) +{ + /* 0xxxxxxx */ + if (codepoint <= 0x7F) { + utf8[0] = codepoint; + return 1; + } + + /* 110xxxxx 10xxxxxx */ + if (codepoint <= 0x7FF) { + utf8[0] = (codepoint >> 6) | 0xC0; + utf8[1] = (codepoint & 0x3F) | 0x80; + return 2; + } + + /* 1110xxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0xFFFF) { + utf8[0] = (codepoint >> 12) | 0xE0; + utf8[1] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[2] = (codepoint & 0x3F) | 0x80; + return 3; + } + + /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint <= 0x1FFFFF) { + utf8[0] = (codepoint >> 18) | 0xF0; + utf8[1] = ((codepoint >> 12) & 0x3F) | 0x80; + utf8[2] = ((codepoint >> 6) & 0x3F) | 0x80; + utf8[3] = (codepoint & 0x3F) | 0x80; + return 4; + } + + return 0; +} + + +/* Called when index pointing to beginning of UTF-16 code escape: \uXXXX + * \u is guaranteed to exist, but the remaining hex characters may be + * missing. + * Translate to UTF-8 and append to temporary token string. + * Must advance index to the next character to be processed. + * Returns: 0 success + * -1 error + */ +static int json_append_unicode_escape(json_parse_t *json) +{ + char utf8[4]; /* Surrogate pairs require 4 UTF-8 bytes */ + int codepoint; + int surrogate_low; + int len; + int escape_len = 6; + + /* Fetch UTF-16 code unit */ + codepoint = decode_hex4(&json->data[json->index + 2]); + if (codepoint < 0) + return -1; + + /* UTF-16 surrogate pairs take the following 2 byte form: + * 11011 x yyyyyyyyyy + * When x = 0: y is the high 10 bits of the codepoint + * x = 1: y is the low 10 bits of the codepoint + * + * Check for a surrogate pair (high or low) */ + if ((codepoint & 0xF800) == 0xD800) { + /* Error if the 1st surrogate is not high */ + if (codepoint & 0x400) + return -1; + + /* Ensure the next code is a unicode escape */ + if (json->data[json->index + escape_len] != '\\' || + json->data[json->index + escape_len + 1] != 'u') { + return -1; + } + + /* Fetch the next codepoint */ + surrogate_low = decode_hex4(&json->data[json->index + 2 + escape_len]); + if (surrogate_low < 0) + return -1; + + /* Error if the 2nd code is not a low surrogate */ + if ((surrogate_low & 0xFC00) != 0xDC00) + return -1; + + /* Calculate Unicode codepoint */ + codepoint = (codepoint & 0x3FF) << 10; + surrogate_low &= 0x3FF; + codepoint = (codepoint | surrogate_low) + 0x10000; + escape_len = 12; + } + + /* Convert codepoint to UTF-8 */ + len = codepoint_to_utf8(utf8, codepoint); + if (!len) + return -1; + + /* Append bytes and advance parse index */ + strbuf_append_mem_unsafe(json->tmp, utf8, len); + json->index += escape_len; + + return 0; +} + +static void json_set_token_error(json_token_t *token, json_parse_t *json, + const char *errtype) +{ + token->type = T_ERROR; + token->index = json->index; + token->value.string = errtype; +} + +static void json_next_string_token(json_parse_t *json, json_token_t *token) +{ + char *escape2char = json->cfg->escape2char; + char ch; + + /* Caller must ensure a string is next */ + assert(json->data[json->index] == '"'); + + /* Skip " */ + json->index++; + + /* json->tmp is the temporary strbuf used to accumulate the + * decoded string value. */ + strbuf_reset(json->tmp); + while ((ch = json->data[json->index]) != '"') { + if (!ch) { + /* Premature end of the string */ + json_set_token_error(token, json, "unexpected end of string"); + return; + } + + /* Handle escapes */ + if (ch == '\\') { + /* Fetch escape character */ + ch = json->data[json->index + 1]; + + /* Translate escape code and append to tmp string */ + ch = escape2char[(unsigned char)ch]; + if (ch == 'u') { + if (json_append_unicode_escape(json) == 0) + continue; + + json_set_token_error(token, json, + "invalid unicode escape code"); + return; + } + if (!ch) { + json_set_token_error(token, json, "invalid escape code"); + return; + } + + /* Skip '\' */ + json->index++; + } + /* Append normal character or translated single character + * Unicode escapes are handled above */ + strbuf_append_char_unsafe(json->tmp, ch); + json->index++; + } + json->index++; /* Eat final quote (") */ + + strbuf_ensure_null(json->tmp); + + token->type = T_STRING; + token->value.string = strbuf_string(json->tmp, &token->string_len); +} + +/* JSON numbers should take the following form: + * -?(0|[1-9]|[1-9][0-9]+)(.[0-9]+)?([eE][-+]?[0-9]+)? + * + * json_next_number_token() uses strtod() which allows other forms: + * - numbers starting with '+' + * - NaN, -NaN, infinity, -infinity + * - hexidecimal numbers + * - numbers with leading zeros + * + * json_is_invalid_number() detects "numbers" which may pass strtod()'s + * error checking, but should not be allowed with strict JSON. + * + * json_is_invalid_number() may pass numbers which cause strtod() + * to generate an error. + */ +static int json_is_invalid_number(json_parse_t *json) +{ + int i = json->index; + + /* Reject numbers starting with + */ + if (json->data[i] == '+') + return 1; + + /* Skip minus sign if it exists */ + if (json->data[i] == '-') + i++; + + /* Reject numbers starting with 0x, or leading zeros */ + if (json->data[i] == '0') { + int ch2 = json->data[i + 1]; + + if ((ch2 | 0x20) == 'x' || /* Hex */ + ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ + return 1; + + return 0; + } else if (json->data[i] <= '9') { + return 0; /* Ordinary number */ + } + + + /* Reject inf/nan */ + if (!strncasecmp(&json->data[i], "inf", 3)) + return 1; + if (!strncasecmp(&json->data[i], "nan", 3)) + return 1; + + /* Pass all other numbers which may still be invalid, but + * strtod() will catch them. */ + return 0; +} + +static void json_next_number_token(json_parse_t *json, json_token_t *token) +{ + const char *startptr; + char *endptr; + + token->type = T_NUMBER; + startptr = &json->data[json->index]; + token->value.number = strtod(&json->data[json->index], &endptr); + if (startptr == endptr) + json_set_token_error(token, json, "invalid number"); + else + json->index += endptr - startptr; /* Skip the processed number */ + + return; +} + +/* Fills in the token struct. + * T_STRING will return a pointer to the json_parse_t temporary string + * T_ERROR will leave the json->index pointer at the error. + */ +static void json_next_token(json_parse_t *json, json_token_t *token) +{ + json_token_type_t *ch2token = json->cfg->ch2token; + int ch; + + /* Eat whitespace. FIXME: UGLY */ + token->type = ch2token[(unsigned char)json->data[json->index]]; + while (token->type == T_WHITESPACE) + token->type = ch2token[(unsigned char)json->data[++json->index]]; + + token->index = json->index; + + /* Don't advance the pointer for an error or the end */ + if (token->type == T_ERROR) { + json_set_token_error(token, json, "invalid token"); + return; + } + + if (token->type == T_END) { + return; + } + + /* Found a known single character token, advance index and return */ + if (token->type != T_UNKNOWN) { + json->index++; + return; + } + + /* Process characters which triggered T_UNKNOWN */ + ch = json->data[json->index]; + + /* Must use strncmp() to match the front of the JSON string. + * JSON identifier must be lowercase. + * When strict_numbers if disabled, either case is allowed for + * Infinity/NaN (since we are no longer following the spec..) */ + if (ch == '"') { + json_next_string_token(json, token); + return; + } else if (ch == '-' || ('0' <= ch && ch <= '9')) { + if (json->cfg->decode_refuse_badnum && json_is_invalid_number(json)) { + json_set_token_error(token, json, "invalid number"); + return; + } + json_next_number_token(json, token); + return; + } else if (!strncmp(&json->data[json->index], "true", 4)) { + token->type = T_BOOLEAN; + token->value.boolean = 1; + json->index += 4; + return; + } else if (!strncmp(&json->data[json->index], "false", 5)) { + token->type = T_BOOLEAN; + token->value.boolean = 0; + json->index += 5; + return; + } else if (!strncmp(&json->data[json->index], "null", 4)) { + token->type = T_NULL; + json->index += 4; + return; + } else if (!json->cfg->decode_refuse_badnum && + json_is_invalid_number(json)) { + /* When refuse_badnum is disabled, only attempt to process + * numbers we know are invalid JSON (Inf, NaN, hex) + * This is required to generate an appropriate token error, + * otherwise all bad tokens will register as "invalid number" + */ + json_next_number_token(json, token); + return; + } + + /* Token starts with t/f/n but isn't recognised above. */ + json_set_token_error(token, json, "invalid token"); +} + +/* This function does not return. + * DO NOT CALL WITH DYNAMIC MEMORY ALLOCATED. + * The only supported exception is the temporary parser string + * json->tmp struct. + * json and token should exist on the stack somewhere. + * luaL_error() will long_jmp and release the stack */ +static void json_throw_parse_error(lua_State *l, json_parse_t *json, + const char *exp, json_token_t *token) +{ + const char *found; + + strbuf_free(json->tmp); + + if (token->type == T_ERROR) + found = token->value.string; + else + found = json_token_type_name[token->type]; + + /* Note: token->index is 0 based, display starting from 1 */ + luaL_error(l, "Expected %s but found %s at character %d", + exp, found, token->index + 1); +} + +static void json_decode_checkstack(lua_State *l, json_parse_t *json, int n) +{ + if (lua_checkstack(l, n)) + return; + + strbuf_free(json->tmp); + luaL_error(l, "Too many nested data structures"); +} + +static void json_parse_object_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + + /* 3 slots required: + * .., table, key, value */ + json_decode_checkstack(l, json, 3); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty objects */ + if (token.type == T_OBJ_END) { + return; + } + + while (1) { + if (token.type != T_STRING) + json_throw_parse_error(l, json, "object key string", &token); + + /* Push key */ + lua_pushlstring(l, token.value.string, token.string_len); + + json_next_token(json, &token); + if (token.type != T_COLON) + json_throw_parse_error(l, json, "colon", &token); + + /* Fetch value */ + json_next_token(json, &token); + json_process_value(l, json, &token); + + /* Set key = value */ + lua_rawset(l, -3); + + json_next_token(json, &token); + + if (token.type == T_OBJ_END) + return; + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or object end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the array context */ +static void json_parse_array_context(lua_State *l, json_parse_t *json) +{ + json_token_t token; + int i; + + /* 2 slots required: + * .., table, value */ + json_decode_checkstack(l, json, 2); + + lua_newtable(l); + + json_next_token(json, &token); + + /* Handle empty arrays */ + if (token.type == T_ARR_END) + return; + + for (i = 1; ; i++) { + json_process_value(l, json, &token); + lua_rawseti(l, -2, i); /* arr[i] = value */ + + json_next_token(json, &token); + + if (token.type == T_ARR_END) + return; + + if (token.type != T_COMMA) + json_throw_parse_error(l, json, "comma or array end", &token); + + json_next_token(json, &token); + } +} + +/* Handle the "value" context */ +static void json_process_value(lua_State *l, json_parse_t *json, + json_token_t *token) +{ + switch (token->type) { + case T_STRING: + lua_pushlstring(l, token->value.string, token->string_len); + break;; + case T_NUMBER: + lua_pushnumber(l, token->value.number); + break;; + case T_BOOLEAN: + lua_pushboolean(l, token->value.boolean); + break;; + case T_OBJ_BEGIN: + json_parse_object_context(l, json); + break;; + case T_ARR_BEGIN: + json_parse_array_context(l, json); + break;; + case T_NULL: + /* In Lua, setting "t[k] = nil" will delete k from the table. + * Hence a NULL pointer lightuserdata object is used instead */ + lua_pushlightuserdata(l, NULL); + break;; + default: + json_throw_parse_error(l, json, "value", token); + } +} + +/* json_text must be null terminated string */ +static void lua_json_decode(lua_State *l, const char *json_text, int json_len) +{ + json_parse_t json; + json_token_t token; + + json.cfg = json_fetch_config(l); + json.data = json_text; + json.index = 0; + + /* Ensure the temporary buffer can hold the entire string. + * This means we no longer need to do length checks since the decoded + * string must be smaller than the entire json string */ + json.tmp = strbuf_new(json_len); + + json_next_token(&json, &token); + json_process_value(l, &json, &token); + + /* Ensure there is no more input left */ + json_next_token(&json, &token); + + if (token.type != T_END) + json_throw_parse_error(l, &json, "the end", &token); + + strbuf_free(json.tmp); +} + +static int json_decode(lua_State *l) +{ + const char *json; + size_t len; + + json_verify_arg_count(l, 1); + + json = luaL_checklstring(l, 1, &len); + + /* Detect Unicode other than UTF-8 (see RFC 4627, Sec 3) + * + * CJSON can support any simple data type, hence only the first + * character is guaranteed to be ASCII (at worst: '"'). This is + * still enough to detect whether the wrong encoding is in use. */ + if (len >= 2 && (!json[0] || !json[1])) + luaL_error(l, "JSON parser does not support UTF-16 or UTF-32"); + + lua_json_decode(l, json, len); + + return 1; +} + +/* ===== INITIALISATION ===== */ + +int luaopen_cjson(lua_State *l) +{ + luaL_Reg reg[] = { + { "encode", json_encode }, + { "decode", json_decode }, + { "encode_sparse_array", json_cfg_encode_sparse_array }, + { "encode_max_depth", json_cfg_encode_max_depth }, + { "encode_number_precision", json_cfg_encode_number_precision }, + { "encode_keep_buffer", json_cfg_encode_keep_buffer }, + { "refuse_invalid_numbers", json_cfg_refuse_invalid_numbers }, + { NULL, NULL } + }; + + /* Use json_fetch_config as a pointer. + * It's faster than using a config string, and more unique */ + lua_pushlightuserdata(l, &json_config_key); + json_create_config(l); + lua_settable(l, LUA_REGISTRYINDEX); + + luaL_register(l, "cjson", reg); + + /* Set cjson.null */ + lua_pushlightuserdata(l, NULL); + lua_setfield(l, -2, "null"); + + /* Set cjson.version */ + lua_pushliteral(l, VERSION); + lua_setfield(l, -2, "version"); + + /* Return cjson table */ + return 1; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/lua/lua_cmsgpack.c b/lua/lua_cmsgpack.c new file mode 100644 index 0000000..1589f16 --- /dev/null +++ b/lua/lua_cmsgpack.c @@ -0,0 +1,731 @@ +// +build lua + +#include +#include +#include +#include +#include + +#include +#include + +#define LUACMSGPACK_VERSION "lua-cmsgpack 0.3.0" +#define LUACMSGPACK_COPYRIGHT "Copyright (C) 2012, Salvatore Sanfilippo" +#define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua" + +#define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ + +/* ============================================================================== + * MessagePack implementation and bindings for Lua 5.1. + * Copyright(C) 2012 Salvatore Sanfilippo + * + * http://github.com/antirez/lua-cmsgpack + * + * For MessagePack specification check the following web site: + * http://wiki.msgpack.org/display/MSGPACK/Format+specification + * + * See Copyright Notice at the end of this file. + * + * CHANGELOG: + * 19-Feb-2012 (ver 0.1.0): Initial release. + * 20-Feb-2012 (ver 0.2.0): Tables encoding improved. + * 20-Feb-2012 (ver 0.2.1): Minor bug fixing. + * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). + * ============================================================================ */ + +/* --------------------------- Endian conversion -------------------------------- + * We use it only for floats and doubles, all the other conversions are performed + * in an endian independent fashion. So the only thing we need is a function + * that swaps a binary string if the arch is little endian (and left it untouched + * otherwise). */ + +/* Reverse memory bytes if arch is little endian. Given the conceptual + * simplicity of the Lua build system we prefer to check for endianess at runtime. + * The performance difference should be acceptable. */ +static void memrevifle(void *ptr, size_t len) { + unsigned char *p = ptr, *e = p+len-1, aux; + int test = 1; + unsigned char *testp = (unsigned char*) &test; + + if (testp[0] == 0) return; /* Big endian, nothign to do. */ + len /= 2; + while(len--) { + aux = *p; + *p = *e; + *e = aux; + p++; + e--; + } +} + +/* ----------------------------- String buffer ---------------------------------- + * This is a simple implementation of string buffers. The only opereation + * supported is creating empty buffers and appending bytes to it. + * The string buffer uses 2x preallocation on every realloc for O(N) append + * behavior. */ + +typedef struct mp_buf { + unsigned char *b; + size_t len, free; +} mp_buf; + +static mp_buf *mp_buf_new(void) { + mp_buf *buf = malloc(sizeof(*buf)); + + buf->b = NULL; + buf->len = buf->free = 0; + return buf; +} + +void mp_buf_append(mp_buf *buf, const unsigned char *s, size_t len) { + if (buf->free < len) { + size_t newlen = buf->len+len; + + buf->b = realloc(buf->b,newlen*2); + buf->free = newlen; + } + memcpy(buf->b+buf->len,s,len); + buf->len += len; + buf->free -= len; +} + +void mp_buf_free(mp_buf *buf) { + free(buf->b); + free(buf); +} + +/* ------------------------------ String cursor ---------------------------------- + * This simple data structure is used for parsing. Basically you create a cursor + * using a string pointer and a length, then it is possible to access the + * current string position with cursor->p, check the remaining length + * in cursor->left, and finally consume more string using + * mp_cur_consume(cursor,len), to advance 'p' and subtract 'left'. + * An additional field cursor->error is set to zero on initialization and can + * be used to report errors. */ + +#define MP_CUR_ERROR_NONE 0 +#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete the opereation. */ +#define MP_CUR_ERROR_BADFMT 2 /* Bad data format */ + +typedef struct mp_cur { + const unsigned char *p; + size_t left; + int err; +} mp_cur; + +static mp_cur *mp_cur_new(const unsigned char *s, size_t len) { + mp_cur *cursor = malloc(sizeof(*cursor)); + + cursor->p = s; + cursor->left = len; + cursor->err = MP_CUR_ERROR_NONE; + return cursor; +} + +static void mp_cur_free(mp_cur *cursor) { + free(cursor); +} + +#define mp_cur_consume(_c,_len) do { _c->p += _len; _c->left -= _len; } while(0) + +/* When there is not enough room we set an error in the cursor and return, this + * is very common across the code so we have a macro to make the code look + * a bit simpler. */ +#define mp_cur_need(_c,_len) do { \ + if (_c->left < _len) { \ + _c->err = MP_CUR_ERROR_EOF; \ + return; \ + } \ +} while(0) + +/* --------------------------- Low level MP encoding -------------------------- */ + +static void mp_encode_bytes(mp_buf *buf, const unsigned char *s, size_t len) { + unsigned char hdr[5]; + int hdrlen; + + if (len < 32) { + hdr[0] = 0xa0 | (len&0xff); /* fix raw */ + hdrlen = 1; + } else if (len <= 0xffff) { + hdr[0] = 0xda; + hdr[1] = (len&0xff00)>>8; + hdr[2] = len&0xff; + hdrlen = 3; + } else { + hdr[0] = 0xdb; + hdr[1] = (len&0xff000000)>>24; + hdr[2] = (len&0xff0000)>>16; + hdr[3] = (len&0xff00)>>8; + hdr[4] = len&0xff; + hdrlen = 5; + } + mp_buf_append(buf,hdr,hdrlen); + mp_buf_append(buf,s,len); +} + +/* we assume IEEE 754 internal format for single and double precision floats. */ +static void mp_encode_double(mp_buf *buf, double d) { + unsigned char b[9]; + float f = d; + + assert(sizeof(f) == 4 && sizeof(d) == 8); + if (d == (double)f) { + b[0] = 0xca; /* float IEEE 754 */ + memcpy(b+1,&f,4); + memrevifle(b+1,4); + mp_buf_append(buf,b,5); + } else if (sizeof(d) == 8) { + b[0] = 0xcb; /* double IEEE 754 */ + memcpy(b+1,&d,8); + memrevifle(b+1,8); + mp_buf_append(buf,b,9); + } +} + +static void mp_encode_int(mp_buf *buf, int64_t n) { + unsigned char b[9]; + int enclen; + + if (n >= 0) { + if (n <= 127) { + b[0] = n & 0x7f; /* positive fixnum */ + enclen = 1; + } else if (n <= 0xff) { + b[0] = 0xcc; /* uint 8 */ + b[1] = n & 0xff; + enclen = 2; + } else if (n <= 0xffff) { + b[0] = 0xcd; /* uint 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else if (n <= 0xffffffffLL) { + b[0] = 0xce; /* uint 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } else { + b[0] = 0xcf; /* uint 64 */ + b[1] = (n & 0xff00000000000000LL) >> 56; + b[2] = (n & 0xff000000000000LL) >> 48; + b[3] = (n & 0xff0000000000LL) >> 40; + b[4] = (n & 0xff00000000LL) >> 32; + b[5] = (n & 0xff000000) >> 24; + b[6] = (n & 0xff0000) >> 16; + b[7] = (n & 0xff00) >> 8; + b[8] = n & 0xff; + enclen = 9; + } + } else { + if (n >= -32) { + b[0] = ((char)n); /* negative fixnum */ + enclen = 1; + } else if (n >= -128) { + b[0] = 0xd0; /* int 8 */ + b[1] = n & 0xff; + enclen = 2; + } else if (n >= -32768) { + b[0] = 0xd1; /* int 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else if (n >= -2147483648LL) { + b[0] = 0xd2; /* int 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } else { + b[0] = 0xd3; /* int 64 */ + b[1] = (n & 0xff00000000000000LL) >> 56; + b[2] = (n & 0xff000000000000LL) >> 48; + b[3] = (n & 0xff0000000000LL) >> 40; + b[4] = (n & 0xff00000000LL) >> 32; + b[5] = (n & 0xff000000) >> 24; + b[6] = (n & 0xff0000) >> 16; + b[7] = (n & 0xff00) >> 8; + b[8] = n & 0xff; + enclen = 9; + } + } + mp_buf_append(buf,b,enclen); +} + +static void mp_encode_array(mp_buf *buf, int64_t n) { + unsigned char b[5]; + int enclen; + + if (n <= 15) { + b[0] = 0x90 | (n & 0xf); /* fix array */ + enclen = 1; + } else if (n <= 65535) { + b[0] = 0xdc; /* array 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else { + b[0] = 0xdd; /* array 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } + mp_buf_append(buf,b,enclen); +} + +static void mp_encode_map(mp_buf *buf, int64_t n) { + unsigned char b[5]; + int enclen; + + if (n <= 15) { + b[0] = 0x80 | (n & 0xf); /* fix map */ + enclen = 1; + } else if (n <= 65535) { + b[0] = 0xde; /* map 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else { + b[0] = 0xdf; /* map 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } + mp_buf_append(buf,b,enclen); +} + +/* ----------------------------- Lua types encoding --------------------------- */ + +static void mp_encode_lua_string(lua_State *L, mp_buf *buf) { + size_t len; + const char *s; + + s = lua_tolstring(L,-1,&len); + mp_encode_bytes(buf,(const unsigned char*)s,len); +} + +static void mp_encode_lua_bool(lua_State *L, mp_buf *buf) { + unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2; + mp_buf_append(buf,&b,1); +} + +static void mp_encode_lua_number(lua_State *L, mp_buf *buf) { + lua_Number n = lua_tonumber(L,-1); + + if (floor(n) != n) { + mp_encode_double(buf,(double)n); + } else { + mp_encode_int(buf,(int64_t)n); + } +} + +static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level); + +/* Convert a lua table into a message pack list. */ +static void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) { + size_t len = lua_objlen(L,-1), j; + + mp_encode_array(buf,len); + for (j = 1; j <= len; j++) { + lua_pushnumber(L,j); + lua_gettable(L,-2); + mp_encode_lua_type(L,buf,level+1); + } +} + +/* Convert a lua table into a message pack key-value map. */ +static void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) { + size_t len = 0; + + /* First step: count keys into table. No other way to do it with the + * Lua API, we need to iterate a first time. Note that an alternative + * would be to do a single run, and then hack the buffer to insert the + * map opcodes for message pack. Too hachish for this lib. */ + lua_pushnil(L); + while(lua_next(L,-2)) { + lua_pop(L,1); /* remove value, keep key for next iteration. */ + len++; + } + + /* Step two: actually encoding of the map. */ + mp_encode_map(buf,len); + lua_pushnil(L); + while(lua_next(L,-2)) { + /* Stack: ... key value */ + lua_pushvalue(L,-2); /* Stack: ... key value key */ + mp_encode_lua_type(L,buf,level+1); /* encode key */ + mp_encode_lua_type(L,buf,level+1); /* encode val */ + } +} + +/* Returns true if the Lua table on top of the stack is exclusively composed + * of keys from numerical keys from 1 up to N, with N being the total number + * of elements, without any hole in the middle. */ +static int table_is_an_array(lua_State *L) { + long count = 0, max = 0, idx = 0; + lua_Number n; + + lua_pushnil(L); + while(lua_next(L,-2)) { + /* Stack: ... key value */ + lua_pop(L,1); /* Stack: ... key */ + if (lua_type(L,-1) != LUA_TNUMBER) goto not_array; + n = lua_tonumber(L,-1); + idx = n; + if (idx != n || idx < 1) goto not_array; + count++; + max = idx; + } + /* We have the total number of elements in "count". Also we have + * the max index encountered in "idx". We can't reach this code + * if there are indexes <= 0. If you also note that there can not be + * repeated keys into a table, you have that if idx==count you are sure + * that there are all the keys form 1 to count (both included). */ + return idx == count; + +not_array: + lua_pop(L,1); + return 0; +} + +/* If the length operator returns non-zero, that is, there is at least + * an object at key '1', we serialize to message pack list. Otherwise + * we use a map. */ +static void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) { + if (table_is_an_array(L)) + mp_encode_lua_table_as_array(L,buf,level); + else + mp_encode_lua_table_as_map(L,buf,level); +} + +static void mp_encode_lua_null(lua_State *L, mp_buf *buf) { + unsigned char b[1]; + + b[0] = 0xc0; + mp_buf_append(buf,b,1); +} + +static void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) { + int t = lua_type(L,-1); + + /* Limit the encoding of nested tables to a specfiied maximum depth, so that + * we survive when called against circular references in tables. */ + if (t == LUA_TTABLE && level == LUACMSGPACK_MAX_NESTING) t = LUA_TNIL; + switch(t) { + case LUA_TSTRING: mp_encode_lua_string(L,buf); break; + case LUA_TBOOLEAN: mp_encode_lua_bool(L,buf); break; + case LUA_TNUMBER: mp_encode_lua_number(L,buf); break; + case LUA_TTABLE: mp_encode_lua_table(L,buf,level); break; + default: mp_encode_lua_null(L,buf); break; + } + lua_pop(L,1); +} + +static int mp_pack(lua_State *L) { + mp_buf *buf = mp_buf_new(); + + mp_encode_lua_type(L,buf,0); + lua_pushlstring(L,(char*)buf->b,buf->len); + mp_buf_free(buf); + return 1; +} + +/* --------------------------------- Decoding --------------------------------- */ + +void mp_decode_to_lua_type(lua_State *L, mp_cur *c); + +void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) { + int index = 1; + + lua_newtable(L); + while(len--) { + lua_pushnumber(L,index++); + mp_decode_to_lua_type(L,c); + if (c->err) return; + lua_settable(L,-3); + } +} + +void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) { + lua_newtable(L); + while(len--) { + mp_decode_to_lua_type(L,c); /* key */ + if (c->err) return; + mp_decode_to_lua_type(L,c); /* value */ + if (c->err) return; + lua_settable(L,-3); + } +} + +/* Decode a Message Pack raw object pointed by the string cursor 'c' to + * a Lua type, that is left as the only result on the stack. */ +void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { + mp_cur_need(c,1); + switch(c->p[0]) { + case 0xcc: /* uint 8 */ + mp_cur_need(c,2); + lua_pushnumber(L,c->p[1]); + mp_cur_consume(c,2); + break; + case 0xd0: /* int 8 */ + mp_cur_need(c,2); + lua_pushnumber(L,(char)c->p[1]); + mp_cur_consume(c,2); + break; + case 0xcd: /* uint 16 */ + mp_cur_need(c,3); + lua_pushnumber(L, + (c->p[1] << 8) | + c->p[2]); + mp_cur_consume(c,3); + break; + case 0xd1: /* int 16 */ + mp_cur_need(c,3); + lua_pushnumber(L,(int16_t) + (c->p[1] << 8) | + c->p[2]); + mp_cur_consume(c,3); + break; + case 0xce: /* uint 32 */ + mp_cur_need(c,5); + lua_pushnumber(L, + ((uint32_t)c->p[1] << 24) | + ((uint32_t)c->p[2] << 16) | + ((uint32_t)c->p[3] << 8) | + (uint32_t)c->p[4]); + mp_cur_consume(c,5); + break; + case 0xd2: /* int 32 */ + mp_cur_need(c,5); + lua_pushnumber(L, + ((int32_t)c->p[1] << 24) | + ((int32_t)c->p[2] << 16) | + ((int32_t)c->p[3] << 8) | + (int32_t)c->p[4]); + mp_cur_consume(c,5); + break; + case 0xcf: /* uint 64 */ + mp_cur_need(c,9); + lua_pushnumber(L, + ((uint64_t)c->p[1] << 56) | + ((uint64_t)c->p[2] << 48) | + ((uint64_t)c->p[3] << 40) | + ((uint64_t)c->p[4] << 32) | + ((uint64_t)c->p[5] << 24) | + ((uint64_t)c->p[6] << 16) | + ((uint64_t)c->p[7] << 8) | + (uint64_t)c->p[8]); + mp_cur_consume(c,9); + break; + case 0xd3: /* int 64 */ + mp_cur_need(c,9); + lua_pushnumber(L, + ((int64_t)c->p[1] << 56) | + ((int64_t)c->p[2] << 48) | + ((int64_t)c->p[3] << 40) | + ((int64_t)c->p[4] << 32) | + ((int64_t)c->p[5] << 24) | + ((int64_t)c->p[6] << 16) | + ((int64_t)c->p[7] << 8) | + (int64_t)c->p[8]); + mp_cur_consume(c,9); + break; + case 0xc0: /* nil */ + lua_pushnil(L); + mp_cur_consume(c,1); + break; + case 0xc3: /* true */ + lua_pushboolean(L,1); + mp_cur_consume(c,1); + break; + case 0xc2: /* false */ + lua_pushboolean(L,0); + mp_cur_consume(c,1); + break; + case 0xca: /* float */ + mp_cur_need(c,5); + assert(sizeof(float) == 4); + { + float f; + memcpy(&f,c->p+1,4); + memrevifle(&f,4); + lua_pushnumber(L,f); + mp_cur_consume(c,5); + } + break; + case 0xcb: /* double */ + mp_cur_need(c,9); + assert(sizeof(double) == 8); + { + double d; + memcpy(&d,c->p+1,8); + memrevifle(&d,8); + lua_pushnumber(L,d); + mp_cur_consume(c,9); + } + break; + case 0xda: /* raw 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_need(c,3+l); + lua_pushlstring(L,(char*)c->p+3,l); + mp_cur_consume(c,3+l); + } + break; + case 0xdb: /* raw 32 */ + mp_cur_need(c,5); + { + size_t l = (c->p[1] << 24) | + (c->p[2] << 16) | + (c->p[3] << 8) | + c->p[4]; + mp_cur_need(c,5+l); + lua_pushlstring(L,(char*)c->p+5,l); + mp_cur_consume(c,5+l); + } + break; + case 0xdc: /* array 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_consume(c,3); + mp_decode_to_lua_array(L,c,l); + } + break; + case 0xdd: /* array 32 */ + mp_cur_need(c,5); + { + size_t l = (c->p[1] << 24) | + (c->p[2] << 16) | + (c->p[3] << 8) | + c->p[4]; + mp_cur_consume(c,5); + mp_decode_to_lua_array(L,c,l); + } + break; + case 0xde: /* map 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_consume(c,3); + mp_decode_to_lua_hash(L,c,l); + } + break; + case 0xdf: /* map 32 */ + mp_cur_need(c,5); + { + size_t l = (c->p[1] << 24) | + (c->p[2] << 16) | + (c->p[3] << 8) | + c->p[4]; + mp_cur_consume(c,5); + mp_decode_to_lua_hash(L,c,l); + } + break; + default: /* types that can't be idenitified by first byte value. */ + if ((c->p[0] & 0x80) == 0) { /* positive fixnum */ + lua_pushnumber(L,c->p[0]); + mp_cur_consume(c,1); + } else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */ + lua_pushnumber(L,(signed char)c->p[0]); + mp_cur_consume(c,1); + } else if ((c->p[0] & 0xe0) == 0xa0) { /* fix raw */ + size_t l = c->p[0] & 0x1f; + mp_cur_need(c,1+l); + lua_pushlstring(L,(char*)c->p+1,l); + mp_cur_consume(c,1+l); + } else if ((c->p[0] & 0xf0) == 0x90) { /* fix map */ + size_t l = c->p[0] & 0xf; + mp_cur_consume(c,1); + mp_decode_to_lua_array(L,c,l); + } else if ((c->p[0] & 0xf0) == 0x80) { /* fix map */ + size_t l = c->p[0] & 0xf; + mp_cur_consume(c,1); + mp_decode_to_lua_hash(L,c,l); + } else { + c->err = MP_CUR_ERROR_BADFMT; + } + } +} + +static int mp_unpack(lua_State *L) { + size_t len; + const unsigned char *s; + mp_cur *c; + + if (!lua_isstring(L,-1)) { + lua_pushstring(L,"MessagePack decoding needs a string as input."); + lua_error(L); + } + + s = (const unsigned char*) lua_tolstring(L,-1,&len); + c = mp_cur_new(s,len); + mp_decode_to_lua_type(L,c); + + if (c->err == MP_CUR_ERROR_EOF) { + mp_cur_free(c); + lua_pushstring(L,"Missing bytes in input."); + lua_error(L); + } else if (c->err == MP_CUR_ERROR_BADFMT) { + mp_cur_free(c); + lua_pushstring(L,"Bad data format in input."); + lua_error(L); + } else if (c->left != 0) { + mp_cur_free(c); + lua_pushstring(L,"Extra bytes in input."); + lua_error(L); + } + mp_cur_free(c); + return 1; +} + +/* ---------------------------------------------------------------------------- */ + +static const struct luaL_reg thislib[] = { + {"pack", mp_pack}, + {"unpack", mp_unpack}, + {NULL, NULL} +}; + +LUALIB_API int luaopen_cmsgpack (lua_State *L) { + luaL_register(L, "cmsgpack", thislib); + + lua_pushliteral(L, LUACMSGPACK_VERSION); + lua_setfield(L, -2, "_VERSION"); + lua_pushliteral(L, LUACMSGPACK_COPYRIGHT); + lua_setfield(L, -2, "_COPYRIGHT"); + lua_pushliteral(L, LUACMSGPACK_DESCRIPTION); + lua_setfield(L, -2, "_DESCRIPTION"); + return 1; +} + +/****************************************************************************** +* Copyright (C) 2012 Salvatore Sanfilippo. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ diff --git a/lua/lua_defs.go b/lua/lua_defs.go new file mode 100644 index 0000000..2f1c288 --- /dev/null +++ b/lua/lua_defs.go @@ -0,0 +1,73 @@ +// +build lua + +package lua + +/* +#include +#include +#include + +*/ +import "C" + +type LuaValType int + +const ( + LUA_TNIL = LuaValType(C.LUA_TNIL) + LUA_TNUMBER = LuaValType(C.LUA_TNUMBER) + LUA_TBOOLEAN = LuaValType(C.LUA_TBOOLEAN) + LUA_TSTRING = LuaValType(C.LUA_TSTRING) + LUA_TTABLE = LuaValType(C.LUA_TTABLE) + LUA_TFUNCTION = LuaValType(C.LUA_TFUNCTION) + LUA_TUSERDATA = LuaValType(C.LUA_TUSERDATA) + LUA_TTHREAD = LuaValType(C.LUA_TTHREAD) + LUA_TLIGHTUSERDATA = LuaValType(C.LUA_TLIGHTUSERDATA) +) + +const ( + LUA_VERSION = C.LUA_VERSION + LUA_RELEASE = C.LUA_RELEASE + LUA_VERSION_NUM = C.LUA_VERSION_NUM + LUA_COPYRIGHT = C.LUA_COPYRIGHT + LUA_AUTHORS = C.LUA_AUTHORS + LUA_MULTRET = C.LUA_MULTRET + LUA_REGISTRYINDEX = C.LUA_REGISTRYINDEX + LUA_ENVIRONINDEX = C.LUA_ENVIRONINDEX + LUA_GLOBALSINDEX = C.LUA_GLOBALSINDEX + LUA_YIELD = C.LUA_YIELD + LUA_ERRRUN = C.LUA_ERRRUN + LUA_ERRSYNTAX = C.LUA_ERRSYNTAX + LUA_ERRMEM = C.LUA_ERRMEM + LUA_ERRERR = C.LUA_ERRERR + LUA_TNONE = C.LUA_TNONE + LUA_MINSTACK = C.LUA_MINSTACK + LUA_GCSTOP = C.LUA_GCSTOP + LUA_GCRESTART = C.LUA_GCRESTART + LUA_GCCOLLECT = C.LUA_GCCOLLECT + LUA_GCCOUNT = C.LUA_GCCOUNT + LUA_GCCOUNTB = C.LUA_GCCOUNTB + LUA_GCSTEP = C.LUA_GCSTEP + LUA_GCSETPAUSE = C.LUA_GCSETPAUSE + LUA_GCSETSTEPMUL = C.LUA_GCSETSTEPMUL + LUA_HOOKCALL = C.LUA_HOOKCALL + LUA_HOOKRET = C.LUA_HOOKRET + LUA_HOOKLINE = C.LUA_HOOKLINE + LUA_HOOKCOUNT = C.LUA_HOOKCOUNT + LUA_HOOKTAILRET = C.LUA_HOOKTAILRET + LUA_MASKCALL = C.LUA_MASKCALL + LUA_MASKRET = C.LUA_MASKRET + LUA_MASKLINE = C.LUA_MASKLINE + LUA_MASKCOUNT = C.LUA_MASKCOUNT + LUA_ERRFILE = C.LUA_ERRFILE + LUA_NOREF = C.LUA_NOREF + LUA_REFNIL = C.LUA_REFNIL + LUA_FILEHANDLE = C.LUA_FILEHANDLE + LUA_COLIBNAME = C.LUA_COLIBNAME + LUA_TABLIBNAME = C.LUA_TABLIBNAME + LUA_IOLIBNAME = C.LUA_IOLIBNAME + LUA_OSLIBNAME = C.LUA_OSLIBNAME + LUA_STRLIBNAME = C.LUA_STRLIBNAME + LUA_MATHLIBNAME = C.LUA_MATHLIBNAME + LUA_DBLIBNAME = C.LUA_DBLIBNAME + LUA_LOADLIBNAME = C.LUA_LOADLIBNAME +) diff --git a/lua/lua_struct.c b/lua/lua_struct.c new file mode 100644 index 0000000..9d391a0 --- /dev/null +++ b/lua/lua_struct.c @@ -0,0 +1,423 @@ +// +build lua + +/* +** {====================================================== +** Library for packing/unpacking structures. +** $Id: struct.c,v 1.4 2012/07/04 18:54:29 roberto Exp $ +** See Copyright Notice at the end of this file +** ======================================================= +*/ +/* +** Valid formats: +** > - big endian +** < - little endian +** ![num] - alignment +** x - pading +** b/B - signed/unsigned byte +** h/H - signed/unsigned short +** l/L - signed/unsigned long +** T - size_t +** i/In - signed/unsigned integer with size `n' (default is size of int) +** cn - sequence of `n' chars (from/to a string); when packing, n==0 means + the whole string; when unpacking, n==0 means use the previous + read number as the string length +** s - zero-terminated string +** f - float +** d - double +** ' ' - ignored +*/ + + +#include +#include +#include +#include +#include + + +#include +#include + + +#if (LUA_VERSION_NUM >= 502) + +#define luaL_register(L,n,f) luaL_newlib(L,f) + +#endif + + +/* basic integer type */ +#if !defined(STRUCT_INT) +#define STRUCT_INT long +#endif + +typedef STRUCT_INT Inttype; + +/* corresponding unsigned version */ +typedef unsigned STRUCT_INT Uinttype; + + +/* maximum size (in bytes) for integral types */ +#define MAXINTSIZE 32 + +/* is 'x' a power of 2? */ +#define isp2(x) ((x) > 0 && ((x) & ((x) - 1)) == 0) + +/* dummy structure to get alignment requirements */ +struct cD { + char c; + double d; +}; + + +#define PADDING (sizeof(struct cD) - sizeof(double)) +#define MAXALIGN (PADDING > sizeof(int) ? PADDING : sizeof(int)) + + +/* endian options */ +#define BIG 0 +#define LITTLE 1 + + +static union { + int dummy; + char endian; +} const native = {1}; + + +typedef struct Header { + int endian; + int align; +} Header; + + +static int getnum (const char **fmt, int df) { + if (!isdigit(**fmt)) /* no number? */ + return df; /* return default value */ + else { + int a = 0; + do { + a = a*10 + *((*fmt)++) - '0'; + } while (isdigit(**fmt)); + return a; + } +} + + +#define defaultoptions(h) ((h)->endian = native.endian, (h)->align = 1) + + + +static size_t optsize (lua_State *L, char opt, const char **fmt) { + switch (opt) { + case 'B': case 'b': return sizeof(char); + case 'H': case 'h': return sizeof(short); + case 'L': case 'l': return sizeof(long); + case 'T': return sizeof(size_t); + case 'f': return sizeof(float); + case 'd': return sizeof(double); + case 'x': return 1; + case 'c': return getnum(fmt, 1); + case 'i': case 'I': { + int sz = getnum(fmt, sizeof(int)); + if (sz > MAXINTSIZE) + luaL_error(L, "integral size %d is larger than limit of %d", + sz, MAXINTSIZE); + return sz; + } + default: return 0; /* other cases do not need alignment */ + } +} + + +/* +** return number of bytes needed to align an element of size 'size' +** at current position 'len' +*/ +static int gettoalign (size_t len, Header *h, int opt, size_t size) { + if (size == 0 || opt == 'c') return 0; + if (size > (size_t)h->align) + size = h->align; /* respect max. alignment */ + return (size - (len & (size - 1))) & (size - 1); +} + + +/* +** options to control endianess and alignment +*/ +static void controloptions (lua_State *L, int opt, const char **fmt, + Header *h) { + switch (opt) { + case ' ': return; /* ignore white spaces */ + case '>': h->endian = BIG; return; + case '<': h->endian = LITTLE; return; + case '!': { + int a = getnum(fmt, MAXALIGN); + if (!isp2(a)) + luaL_error(L, "alignment %d is not a power of 2", a); + h->align = a; + return; + } + default: { + const char *msg = lua_pushfstring(L, "invalid format option '%c'", opt); + luaL_argerror(L, 1, msg); + } + } +} + + +static void putinteger (lua_State *L, luaL_Buffer *b, int arg, int endian, + int size) { + lua_Number n = luaL_checknumber(L, arg); + Uinttype value; + char buff[MAXINTSIZE]; + if (n < 0) + value = (Uinttype)(Inttype)n; + else + value = (Uinttype)n; + if (endian == LITTLE) { + int i; + for (i = 0; i < size; i++) { + buff[i] = (value & 0xff); + value >>= 8; + } + } + else { + int i; + for (i = size - 1; i >= 0; i--) { + buff[i] = (value & 0xff); + value >>= 8; + } + } + luaL_addlstring(b, buff, size); +} + + +static void correctbytes (char *b, int size, int endian) { + if (endian != native.endian) { + int i = 0; + while (i < --size) { + char temp = b[i]; + b[i++] = b[size]; + b[size] = temp; + } + } +} + + +static int b_pack (lua_State *L) { + luaL_Buffer b; + const char *fmt = luaL_checkstring(L, 1); + Header h; + int arg = 2; + size_t totalsize = 0; + defaultoptions(&h); + lua_pushnil(L); /* mark to separate arguments from string buffer */ + luaL_buffinit(L, &b); + while (*fmt != '\0') { + int opt = *fmt++; + size_t size = optsize(L, opt, &fmt); + int toalign = gettoalign(totalsize, &h, opt, size); + totalsize += toalign; + while (toalign-- > 0) luaL_addchar(&b, '\0'); + switch (opt) { + case 'b': case 'B': case 'h': case 'H': + case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ + putinteger(L, &b, arg++, h.endian, size); + break; + } + case 'x': { + luaL_addchar(&b, '\0'); + break; + } + case 'f': { + float f = (float)luaL_checknumber(L, arg++); + correctbytes((char *)&f, size, h.endian); + luaL_addlstring(&b, (char *)&f, size); + break; + } + case 'd': { + double d = luaL_checknumber(L, arg++); + correctbytes((char *)&d, size, h.endian); + luaL_addlstring(&b, (char *)&d, size); + break; + } + case 'c': case 's': { + size_t l; + const char *s = luaL_checklstring(L, arg++, &l); + if (size == 0) size = l; + luaL_argcheck(L, l >= (size_t)size, arg, "string too short"); + luaL_addlstring(&b, s, size); + if (opt == 's') { + luaL_addchar(&b, '\0'); /* add zero at the end */ + size++; + } + break; + } + default: controloptions(L, opt, &fmt, &h); + } + totalsize += size; + } + luaL_pushresult(&b); + return 1; +} + + +static lua_Number getinteger (const char *buff, int endian, + int issigned, int size) { + Uinttype l = 0; + int i; + if (endian == BIG) { + for (i = 0; i < size; i++) { + l <<= 8; + l |= (Uinttype)(unsigned char)buff[i]; + } + } + else { + for (i = size - 1; i >= 0; i--) { + l <<= 8; + l |= (Uinttype)(unsigned char)buff[i]; + } + } + if (!issigned) + return (lua_Number)l; + else { /* signed format */ + Uinttype mask = (Uinttype)(~((Uinttype)0)) << (size*8 - 1); + if (l & mask) /* negative value? */ + l |= mask; /* signal extension */ + return (lua_Number)(Inttype)l; + } +} + + +static int b_unpack (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t ld; + const char *data = luaL_checklstring(L, 2, &ld); + size_t pos = luaL_optinteger(L, 3, 1) - 1; + defaultoptions(&h); + lua_settop(L, 2); + while (*fmt) { + int opt = *fmt++; + size_t size = optsize(L, opt, &fmt); + pos += gettoalign(pos, &h, opt, size); + luaL_argcheck(L, pos+size <= ld, 2, "data string too short"); + luaL_checkstack(L, 1, "too many results"); + switch (opt) { + case 'b': case 'B': case 'h': case 'H': + case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */ + int issigned = islower(opt); + lua_Number res = getinteger(data+pos, h.endian, issigned, size); + lua_pushnumber(L, res); + break; + } + case 'x': { + break; + } + case 'f': { + float f; + memcpy(&f, data+pos, size); + correctbytes((char *)&f, sizeof(f), h.endian); + lua_pushnumber(L, f); + break; + } + case 'd': { + double d; + memcpy(&d, data+pos, size); + correctbytes((char *)&d, sizeof(d), h.endian); + lua_pushnumber(L, d); + break; + } + case 'c': { + if (size == 0) { + if (!lua_isnumber(L, -1)) + luaL_error(L, "format `c0' needs a previous size"); + size = lua_tonumber(L, -1); + lua_pop(L, 1); + luaL_argcheck(L, pos+size <= ld, 2, "data string too short"); + } + lua_pushlstring(L, data+pos, size); + break; + } + case 's': { + const char *e = (const char *)memchr(data+pos, '\0', ld - pos); + if (e == NULL) + luaL_error(L, "unfinished string in data"); + size = (e - (data+pos)) + 1; + lua_pushlstring(L, data+pos, size - 1); + break; + } + default: controloptions(L, opt, &fmt, &h); + } + pos += size; + } + lua_pushinteger(L, pos + 1); + return lua_gettop(L) - 2; +} + + +static int b_size (lua_State *L) { + Header h; + const char *fmt = luaL_checkstring(L, 1); + size_t pos = 0; + defaultoptions(&h); + while (*fmt) { + int opt = *fmt++; + size_t size = optsize(L, opt, &fmt); + pos += gettoalign(pos, &h, opt, size); + if (opt == 's') + luaL_argerror(L, 1, "option 's' has no fixed size"); + else if (opt == 'c' && size == 0) + luaL_argerror(L, 1, "option 'c0' has no fixed size"); + if (!isalnum(opt)) + controloptions(L, opt, &fmt, &h); + pos += size; + } + lua_pushinteger(L, pos); + return 1; +} + +/* }====================================================== */ + + + +static const struct luaL_Reg thislib[] = { + {"pack", b_pack}, + {"unpack", b_unpack}, + {"size", b_size}, + {NULL, NULL} +}; + + +LUALIB_API int luaopen_struct (lua_State *L); + +LUALIB_API int luaopen_struct (lua_State *L) { + luaL_register(L, "struct", thislib); + return 1; +} + + +/****************************************************************************** +* Copyright (C) 2010-2012 Lua.org, PUC-Rio. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ + diff --git a/lua/lua_test.go b/lua/lua_test.go new file mode 100644 index 0000000..b37bf75 --- /dev/null +++ b/lua/lua_test.go @@ -0,0 +1,386 @@ +// +build lua + +package lua + +import ( + "testing" + "unsafe" +) + +type TestStruct struct { + IntField int + StringField string + FloatField float64 +} + +func TestGoStruct(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + ts := &TestStruct{10, "test", 2.3} + + L.CheckStack(1) + + L.PushGoStruct(ts) + L.SetGlobal("t") + + L.GetGlobal("t") + if !L.IsGoStruct(-1) { + t.Fatal("Not go struct") + } + + tsr := L.ToGoStruct(-1).(*TestStruct) + if tsr != ts { + t.Fatal("Retrieved something different from what we inserted") + } + + L.Pop(1) + + L.PushString("This is not a struct") + if L.ToGoStruct(-1) != nil { + t.Fatal("Non-GoStruct value attempted to convert into GoStruct should result in nil") + } + + L.Pop(1) +} + +func TestCheckStringSuccess(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + Test := func(L *State) int { + L.PushString("this is a test") + L.CheckString(-1) + return 0 + } + + L.Register("test", Test) + err := L.DoString("test()") + if err != nil { + t.Fatalf("DoString did return an error: %v\n", err.Error()) + } +} + +func TestCheckStringFail(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + Test := func(L *State) int { + L.CheckString(-1) + return 0 + } + + L.Register("test", Test) + err := L.DoString("test();") + if err == nil { + t.Fatal("DoString did not return an error\n") + } +} + +func TestPCallHidden(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + err := L.DoString("pcall(print, \"ciao\")") + if err == nil { + t.Fatal("Can use pcall\n") + } + + err = L.DoString("unsafe_pcall(print, \"ciao\")") + if err != nil { + t.Fatal("Can not use unsafe_pcall\n") + } +} + +func TestCall(t *testing.T) { + L := NewState() + L.OpenLibs() + defer L.Close() + + test := func(L *State) int { + arg1 := L.ToString(1) + arg2 := L.ToString(2) + arg3 := L.ToString(3) + + if arg1 != "Argument1" { + t.Fatal("Got wrong argument (1)") + } + + if arg2 != "Argument2" { + t.Fatal("Got wrong argument (2)") + } + + if arg3 != "Argument3" { + t.Fatal("Got wrong argument (3)") + } + + L.PushString("Return1") + L.PushString("Return2") + + return 2 + } + + L.Register("test", test) + + L.PushString("Dummy") + L.GetGlobal("test") + L.PushString("Argument1") + L.PushString("Argument2") + L.PushString("Argument3") + err := L.Call(3, 2) + + if err != nil { + t.Fatalf("Error executing call: %v\n", err) + } + + dummy := L.ToString(1) + ret1 := L.ToString(2) + ret2 := L.ToString(3) + + if dummy != "Dummy" { + t.Fatal("The stack was disturbed") + } + + if ret1 != "Return1" { + t.Fatalf("Wrong return value (1) got: <%s>", ret1) + } + + if ret2 != "Return2" { + t.Fatalf("Wrong return value (2) got: <%s>", ret2) + } +} + +// equivalent to basic.go +func TestLikeBasic(t *testing.T) { + L := NewState() + defer L.Close() + L.OpenLibs() + + testCalled := 0 + + test := func(L *State) int { + testCalled++ + return 0 + } + + test2Arg := -1 + test2Argfrombottom := -1 + test2 := func(L *State) int { + test2Arg = L.CheckInteger(-1) + test2Argfrombottom = L.CheckInteger(1) + return 0 + } + + L.GetField(LUA_GLOBALSINDEX, "print") + L.PushString("Hello World!") + if err := L.Call(1, 0); err != nil { + t.Fatalf("Call to print returned error") + } + + L.PushGoFunction(test) + L.PushGoFunction(test) + L.PushGoFunction(test) + L.PushGoFunction(test2) + L.PushInteger(42) + if err := L.Call(1, 0); err != nil { + t.Fatalf("Call to print returned error") + } + if (test2Arg != 42) || (test2Argfrombottom != 42) { + t.Fatalf("Call to test2 didn't work") + } + + if err := L.Call(0, 0); err != nil { + t.Fatalf("Call to print returned error") + } + if err := L.Call(0, 0); err != nil { + t.Fatalf("Call to print returned error") + } + if err := L.Call(0, 0); err != nil { + t.Fatalf("Call to print returned error") + } + if testCalled != 3 { + t.Fatalf("Test function not called the correct number of times: %d\n", testCalled) + } + + // this will fail as we didn't register test2 function + if err := L.DoString("test2(42)"); err == nil { + t.Fatal("No error when calling unregistered function") + } +} + +// equivalent to quickstart.go +func TestLikeQuickstart(t *testing.T) { + adder := func(L *State) int { + a := L.ToInteger(1) + b := L.ToInteger(2) + L.PushInteger(int64(a + b)) + return 1 + } + + L := NewState() + defer L.Close() + L.OpenLibs() + + L.Register("adder", adder) + + if err := L.DoString("return adder(2, 2)"); err != nil { + t.Fatalf("Error during call to adder: %v\n", err) + } + if r := L.ToInteger(1); r != 4 { + t.Fatalf("Wrong return value from adder (was: %d)\n", r) + } +} + +// equivalent to userdata.go +func TestLikeUserdata(t *testing.T) { + type Userdata struct { + a, b int + } + + userDataProper := func(L *State) { + rawptr := L.NewUserdata(uintptr(unsafe.Sizeof(Userdata{}))) + var ptr *Userdata + ptr = (*Userdata)(rawptr) + ptr.a = 2 + ptr.b = 3 + + rawptr2 := L.ToUserdata(-1) + ptr2 := (*Userdata)(rawptr2) + + if ptr != ptr2 { + t.Fatalf("Failed to create userdata\n") + } + } + + testCalled := 0 + test := func(L *State) int { + testCalled++ + return 0 + } + + goDefinedFunctions := func(L *State) { + // example_function is registered inside Lua VM + L.Register("test", test) + + // This code demonstrates checking that a value on the stack is a go function + L.CheckStack(1) + L.GetGlobal("test") + if !L.IsGoFunction(-1) { + t.Fatalf("IsGoFunction failed to recognize a Go function object") + } + L.Pop(1) + + // We call example_function from inside Lua VM + testCalled = 0 + if err := L.DoString("test()"); err != nil { + t.Fatalf("Error executing test function: %v\n", err) + } + if testCalled != 1 { + t.Fatalf("It appears the test function wasn't actually called\n") + } + } + + type TestObject struct { + AField int + } + + goDefinedObjects := func(L *State) { + z := &TestObject{42} + + L.PushGoStruct(z) + L.SetGlobal("z") + + // This code demonstrates checking that a value on the stack is a go object + L.CheckStack(1) + L.GetGlobal("z") + if !L.IsGoStruct(-1) { + t.Fatal("IsGoStruct failed to recognize a Go struct\n") + } + L.Pop(1) + + // This code demonstrates access and assignment to a field of a go object + if err := L.DoString("return z.AField"); err != nil { + t.Fatal("Couldn't execute code") + } + before := L.ToInteger(-1) + L.Pop(1) + if before != 42 { + t.Fatalf("Wrong value of z.AField before change (%d)\n", before) + } + if err := L.DoString("z.AField = 10;"); err != nil { + t.Fatal("Couldn't execute code") + } + if err := L.DoString("return z.AField"); err != nil { + t.Fatal("Couldn't execute code") + } + after := L.ToInteger(-1) + L.Pop(1) + if after != 10 { + t.Fatalf("Wrong value of z.AField after change (%d)\n", after) + } + } + + L := NewState() + defer L.Close() + L.OpenLibs() + + userDataProper(L) + goDefinedFunctions(L) + goDefinedObjects(L) +} + +func TestStackTrace(t *testing.T) { + L := NewState() + defer L.Close() + L.OpenLibs() + + err := L.DoFile("../example/calls.lua") + if err == nil { + t.Fatal("No error returned from the execution of calls.lua") + } + + le := err.(*LuaError) + + if le.Code() != LUA_ERRERR { + t.Fatalf("Wrong kind of error encountered running calls.lua: %v (%d %d)\n", le, le.Code(), LUA_ERRERR) + } + + if len(le.StackTrace()) != 6 { + t.Fatalf("Wrong size of stack trace (%v)\n", le.StackTrace()) + } +} + +func TestConv(t *testing.T) { + L := NewState() + defer L.Close() + L.OpenLibs() + + L.PushString("10") + n := L.ToNumber(-1) + if n != 10 { + t.Fatalf("Wrong conversion (str -> int)") + } + if L.Type(-1) != LUA_TSTRING { + t.Fatalf("Wrong type (str)") + } + + L.Pop(1) + + L.PushInteger(10) + s := L.ToString(-1) + if s != "10" { + t.Fatalf("Wrong conversion (int -> str)") + } + + L.Pop(1) + + L.PushString("a\000test") + s = L.ToString(-1) + if s != "a\000test" { + t.Fatalf("Wrong conversion (str -> str): <%s>", s) + } +} diff --git a/lua/strbuf.c b/lua/strbuf.c new file mode 100644 index 0000000..7f85165 --- /dev/null +++ b/lua/strbuf.c @@ -0,0 +1,253 @@ +// +build lua + +/* strbuf - string buffer routines + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "strbuf.h" + +void die(const char *fmt, ...) +{ + va_list arg; + + va_start(arg, fmt); + vfprintf(stderr, fmt, arg); + va_end(arg); + fprintf(stderr, "\n"); + + exit(-1); +} + +void strbuf_init(strbuf_t *s, int len) +{ + int size; + + if (len <= 0) + size = STRBUF_DEFAULT_SIZE; + else + size = len + 1; /* \0 terminator */ + + s->buf = NULL; + s->size = size; + s->length = 0; + s->increment = STRBUF_DEFAULT_INCREMENT; + s->dynamic = 0; + s->reallocs = 0; + s->debug = 0; + + s->buf = malloc(size); + if (!s->buf) + die("Out of memory"); + + strbuf_ensure_null(s); +} + +strbuf_t *strbuf_new(int len) +{ + strbuf_t *s; + + s = malloc(sizeof(strbuf_t)); + if (!s) + die("Out of memory"); + + strbuf_init(s, len); + + /* Dynamic strbuf allocation / deallocation */ + s->dynamic = 1; + + return s; +} + +void strbuf_set_increment(strbuf_t *s, int increment) +{ + /* Increment > 0: Linear buffer growth rate + * Increment < -1: Exponential buffer growth rate */ + if (increment == 0 || increment == -1) + die("BUG: Invalid string increment"); + + s->increment = increment; +} + +static inline void debug_stats(strbuf_t *s) +{ + if (s->debug) { + fprintf(stderr, "strbuf(%lx) reallocs: %d, length: %d, size: %d\n", + (long)s, s->reallocs, s->length, s->size); + } +} + +/* If strbuf_t has not been dynamically allocated, strbuf_free() can + * be called any number of times strbuf_init() */ +void strbuf_free(strbuf_t *s) +{ + debug_stats(s); + + if (s->buf) { + free(s->buf); + s->buf = NULL; + } + if (s->dynamic) + free(s); +} + +char *strbuf_free_to_string(strbuf_t *s, int *len) +{ + char *buf; + + debug_stats(s); + + strbuf_ensure_null(s); + + buf = s->buf; + if (len) + *len = s->length; + + if (s->dynamic) + free(s); + + return buf; +} + +static int calculate_new_size(strbuf_t *s, int len) +{ + int reqsize, newsize; + + if (len <= 0) + die("BUG: Invalid strbuf length requested"); + + /* Ensure there is room for optional NULL termination */ + reqsize = len + 1; + + /* If the user has requested to shrink the buffer, do it exactly */ + if (s->size > reqsize) + return reqsize; + + newsize = s->size; + if (s->increment < 0) { + /* Exponential sizing */ + while (newsize < reqsize) + newsize *= -s->increment; + } else { + /* Linear sizing */ + newsize = ((newsize + s->increment - 1) / s->increment) * s->increment; + } + + return newsize; +} + + +/* Ensure strbuf can handle a string length bytes long (ignoring NULL + * optional termination). */ +void strbuf_resize(strbuf_t *s, int len) +{ + int newsize; + + newsize = calculate_new_size(s, len); + + if (s->debug > 1) { + fprintf(stderr, "strbuf(%lx) resize: %d => %d\n", + (long)s, s->size, newsize); + } + + s->size = newsize; + s->buf = realloc(s->buf, s->size); + if (!s->buf) + die("Out of memory"); + s->reallocs++; +} + +void strbuf_append_string(strbuf_t *s, const char *str) +{ + int space, i; + + space = strbuf_empty_length(s); + + for (i = 0; str[i]; i++) { + if (space < 1) { + strbuf_resize(s, s->length + 1); + space = strbuf_empty_length(s); + } + + s->buf[s->length] = str[i]; + s->length++; + space--; + } +} + +/* strbuf_append_fmt() should only be used when an upper bound + * is known for the output string. */ +void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...) +{ + va_list arg; + int fmt_len; + + strbuf_ensure_empty_length(s, len); + + va_start(arg, fmt); + fmt_len = vsnprintf(s->buf + s->length, len, fmt, arg); + va_end(arg); + + if (fmt_len < 0) + die("BUG: Unable to convert number"); /* This should never happen.. */ + + s->length += fmt_len; +} + +/* strbuf_append_fmt_retry() can be used when the there is no known + * upper bound for the output string. */ +void strbuf_append_fmt_retry(strbuf_t *s, const char *fmt, ...) +{ + va_list arg; + int fmt_len, try; + int empty_len; + + /* If the first attempt to append fails, resize the buffer appropriately + * and try again */ + for (try = 0; ; try++) { + va_start(arg, fmt); + /* Append the new formatted string */ + /* fmt_len is the length of the string required, excluding the + * trailing NULL */ + empty_len = strbuf_empty_length(s); + /* Add 1 since there is also space to store the terminating NULL. */ + fmt_len = vsnprintf(s->buf + s->length, empty_len + 1, fmt, arg); + va_end(arg); + + if (fmt_len <= empty_len) + break; /* SUCCESS */ + if (try > 0) + die("BUG: length of formatted string changed"); + + strbuf_resize(s, s->length + fmt_len); + } + + s->length += fmt_len; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/lua/strbuf.h b/lua/strbuf.h new file mode 100644 index 0000000..3039a62 --- /dev/null +++ b/lua/strbuf.h @@ -0,0 +1,144 @@ +// +build lua + +/* strbuf - String buffer routines + * + * Copyright (c) 2010-2011 Mark Pulford + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +/* Size: Total bytes allocated to *buf + * Length: String length, excluding optional NULL terminator. + * Increment: Allocation increments when resizing the string buffer. + * Dynamic: True if created via strbuf_new() + */ + +typedef struct { + char *buf; + int size; + int length; + int increment; + int dynamic; + int reallocs; + int debug; +} strbuf_t; + +#ifndef STRBUF_DEFAULT_SIZE +#define STRBUF_DEFAULT_SIZE 1023 +#endif +#ifndef STRBUF_DEFAULT_INCREMENT +#define STRBUF_DEFAULT_INCREMENT -2 +#endif + +/* Initialise */ +extern strbuf_t *strbuf_new(int len); +extern void strbuf_init(strbuf_t *s, int len); +extern void strbuf_set_increment(strbuf_t *s, int increment); + +/* Release */ +extern void strbuf_free(strbuf_t *s); +extern char *strbuf_free_to_string(strbuf_t *s, int *len); + +/* Management */ +extern void strbuf_resize(strbuf_t *s, int len); +static int strbuf_empty_length(strbuf_t *s); +static int strbuf_length(strbuf_t *s); +static char *strbuf_string(strbuf_t *s, int *len); +static void strbuf_ensure_empty_length(strbuf_t *s, int len); + +/* Update */ +extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...); +extern void strbuf_append_fmt_retry(strbuf_t *s, const char *format, ...); +static void strbuf_append_mem(strbuf_t *s, const char *c, int len); +extern void strbuf_append_string(strbuf_t *s, const char *str); +static void strbuf_append_char(strbuf_t *s, const char c); +static void strbuf_ensure_null(strbuf_t *s); + +/* Reset string for before use */ +static inline void strbuf_reset(strbuf_t *s) +{ + s->length = 0; +} + +static inline int strbuf_allocated(strbuf_t *s) +{ + return s->buf != NULL; +} + +/* Return bytes remaining in the string buffer + * Ensure there is space for a NULL terminator. */ +static inline int strbuf_empty_length(strbuf_t *s) +{ + return s->size - s->length - 1; +} + +static inline void strbuf_ensure_empty_length(strbuf_t *s, int len) +{ + if (len > strbuf_empty_length(s)) + strbuf_resize(s, s->length + len); +} + +static inline int strbuf_length(strbuf_t *s) +{ + return s->length; +} + +static inline void strbuf_append_char(strbuf_t *s, const char c) +{ + strbuf_ensure_empty_length(s, 1); + s->buf[s->length++] = c; +} + +static inline void strbuf_append_char_unsafe(strbuf_t *s, const char c) +{ + s->buf[s->length++] = c; +} + +static inline void strbuf_append_mem(strbuf_t *s, const char *c, int len) +{ + strbuf_ensure_empty_length(s, len); + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_append_mem_unsafe(strbuf_t *s, const char *c, int len) +{ + memcpy(s->buf + s->length, c, len); + s->length += len; +} + +static inline void strbuf_ensure_null(strbuf_t *s) +{ + s->buf[s->length] = 0; +} + +static inline char *strbuf_string(strbuf_t *s, int *len) +{ + if (len) + *len = s->length; + + return s->buf; +} + +/* vi:ai et sw=4 ts=4: + */ diff --git a/server/cmd_script.go b/server/cmd_script.go index de1844f..e7d62a4 100644 --- a/server/cmd_script.go +++ b/server/cmd_script.go @@ -6,8 +6,8 @@ import ( "crypto/sha1" "encoding/hex" "fmt" - "github.com/siddontang/golua/lua" "github.com/siddontang/ledisdb/ledis" + "github.com/siddontang/ledisdb/lua" "strconv" "strings" ) diff --git a/server/script.go b/server/script.go index 4f230fa..f8222c4 100644 --- a/server/script.go +++ b/server/script.go @@ -5,8 +5,8 @@ package server import ( "encoding/hex" "fmt" - "github.com/siddontang/golua/lua" "github.com/siddontang/ledisdb/ledis" + "github.com/siddontang/ledisdb/lua" "io" "sync" ) diff --git a/server/script_test.go b/server/script_test.go index 74160d3..a422a82 100644 --- a/server/script_test.go +++ b/server/script_test.go @@ -4,8 +4,8 @@ package server import ( "fmt" - "github.com/siddontang/golua/lua" "github.com/siddontang/ledisdb/config" + "github.com/siddontang/ledisdb/lua" "testing" ) diff --git a/tools/check_lua.go b/tools/check_lua.go deleted file mode 100644 index bc82c04..0000000 --- a/tools/check_lua.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build ignore - -package main - -import "github.com/siddontang/golua/lua" - -func main() { - L := lua.NewState() - L.Close() -} From 9d00aa229d1c66afb8f57683de49cdf9b5f8c9ad Mon Sep 17 00:00:00 2001 From: siddontang Date: Sat, 13 Sep 2014 14:34:15 +0800 Subject: [PATCH 5/6] update lua bash --- dev.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev.sh b/dev.sh index 72cde0f..4a68e7a 100644 --- a/dev.sh +++ b/dev.sh @@ -77,6 +77,10 @@ fi #check lua if [ -f $LUA_DIR/include/lua.h ]; then + CGO_CFLAGS="$CGO_CFLAGS -I$LUA_DIR/include" + CGO_LDFLAGS="$CGO_LDFLAGS -L$LUA_DIR/lib -llua" + LD_LIBRARY_PATH=$(add_path $LD_LIBRARY_PATH $LUA_DIR/lib) + DYLD_LIBRARY_PATH=$(add_path $DYLD_LIBRARY_PATH $LUA_DIR/lib) GO_BUILD_TAGS="$GO_BUILD_TAGS lua" fi From f22bbb97df3693b5e0700a8afb6dbcbdb4b835af Mon Sep 17 00:00:00 2001 From: siddontang Date: Thu, 18 Sep 2014 09:08:18 +0800 Subject: [PATCH 6/6] bugfix scan count 0 --- server/cmd_kv.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/cmd_kv.go b/server/cmd_kv.go index 44eccf1..c170601 100644 --- a/server/cmd_kv.go +++ b/server/cmd_kv.go @@ -295,10 +295,8 @@ func parseScanArgs(c *client) (key []byte, match string, count int, err error) { switch strings.ToLower(ledis.String(args[1])) { case "match": match = ledis.String(args[2]) - return case "count": count, err = strconv.Atoi(ledis.String(args[2])) - return default: err = ErrCmdParams return @@ -314,7 +312,10 @@ func parseScanArgs(c *client) (key []byte, match string, count int, err error) { match = ledis.String(args[2]) count, err = strconv.Atoi(ledis.String(args[4])) - return + } + + if count <= 0 { + err = ErrCmdParams } return