ledisdb/lua/lua.go

671 lines
16 KiB
Go
Raw Normal View History

2014-09-13 10:11:05 +04:00
// +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 <lua.h>
#include <stdlib.h>
#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)
}