mirror of https://github.com/tidwall/tile38.git
593 lines
11 KiB
Go
593 lines
11 KiB
Go
package lua
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
/* basic functions {{{ */
|
|
|
|
func OpenBase(L *LState) int {
|
|
global := L.Get(GlobalsIndex).(*LTable)
|
|
L.SetGlobal("_G", global)
|
|
L.SetGlobal("_VERSION", LString(LuaVersion))
|
|
L.SetGlobal("_GOPHER_LUA_VERSION", LString(PackageName+" "+PackageVersion))
|
|
basemod := L.RegisterModule("_G", baseFuncs)
|
|
global.RawSetString("ipairs", L.NewClosure(baseIpairs, L.NewFunction(ipairsaux)))
|
|
global.RawSetString("pairs", L.NewClosure(basePairs, L.NewFunction(pairsaux)))
|
|
L.Push(basemod)
|
|
return 1
|
|
}
|
|
|
|
var baseFuncs = map[string]LGFunction{
|
|
"assert": baseAssert,
|
|
"collectgarbage": baseCollectGarbage,
|
|
"dofile": baseDoFile,
|
|
"error": baseError,
|
|
"getfenv": baseGetFEnv,
|
|
"getmetatable": baseGetMetatable,
|
|
"load": baseLoad,
|
|
"loadfile": baseLoadFile,
|
|
"loadstring": baseLoadString,
|
|
"next": baseNext,
|
|
"pcall": basePCall,
|
|
"print": basePrint,
|
|
"rawequal": baseRawEqual,
|
|
"rawget": baseRawGet,
|
|
"rawset": baseRawSet,
|
|
"select": baseSelect,
|
|
"_printregs": base_PrintRegs,
|
|
"setfenv": baseSetFEnv,
|
|
"setmetatable": baseSetMetatable,
|
|
"tonumber": baseToNumber,
|
|
"tostring": baseToString,
|
|
"type": baseType,
|
|
"unpack": baseUnpack,
|
|
"xpcall": baseXPCall,
|
|
// loadlib
|
|
"module": loModule,
|
|
"require": loRequire,
|
|
// hidden features
|
|
"newproxy": baseNewProxy,
|
|
}
|
|
|
|
func baseAssert(L *LState) int {
|
|
if !L.ToBool(1) {
|
|
L.RaiseError(L.OptString(2, "assertion failed!"))
|
|
return 0
|
|
}
|
|
return L.GetTop()
|
|
}
|
|
|
|
func baseCollectGarbage(L *LState) int {
|
|
runtime.GC()
|
|
return 0
|
|
}
|
|
|
|
func baseDoFile(L *LState) int {
|
|
src := L.ToString(1)
|
|
top := L.GetTop()
|
|
fn, err := L.LoadFile(src)
|
|
if err != nil {
|
|
L.Push(LString(err.Error()))
|
|
L.Panic(L)
|
|
}
|
|
L.Push(fn)
|
|
L.Call(0, MultRet)
|
|
return L.GetTop() - top
|
|
}
|
|
|
|
func baseError(L *LState) int {
|
|
obj := L.CheckAny(1)
|
|
level := L.OptInt(2, 1)
|
|
L.Error(obj, level)
|
|
return 0
|
|
}
|
|
|
|
func baseGetFEnv(L *LState) int {
|
|
var value LValue
|
|
if L.GetTop() == 0 {
|
|
value = LNumber(1)
|
|
} else {
|
|
value = L.Get(1)
|
|
}
|
|
|
|
if fn, ok := value.(*LFunction); ok {
|
|
if !fn.IsG {
|
|
L.Push(fn.Env)
|
|
} else {
|
|
L.Push(L.G.Global)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
if number, ok := value.(LNumber); ok {
|
|
level := int(float64(number))
|
|
if level <= 0 {
|
|
L.Push(L.Env)
|
|
} else {
|
|
cf := L.currentFrame
|
|
for i := 0; i < level && cf != nil; i++ {
|
|
cf = cf.Parent
|
|
}
|
|
if cf == nil || cf.Fn.IsG {
|
|
L.Push(L.G.Global)
|
|
} else {
|
|
L.Push(cf.Fn.Env)
|
|
}
|
|
}
|
|
return 1
|
|
}
|
|
|
|
L.Push(L.G.Global)
|
|
return 1
|
|
}
|
|
|
|
func baseGetMetatable(L *LState) int {
|
|
L.Push(L.GetMetatable(L.CheckAny(1)))
|
|
return 1
|
|
}
|
|
|
|
func ipairsaux(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
i := L.CheckInt(2)
|
|
i++
|
|
v := tb.RawGetInt(i)
|
|
if v == LNil {
|
|
return 0
|
|
} else {
|
|
L.Pop(1)
|
|
L.Push(LNumber(i))
|
|
L.Push(LNumber(i))
|
|
L.Push(v)
|
|
return 2
|
|
}
|
|
}
|
|
|
|
func baseIpairs(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
L.Push(L.Get(UpvalueIndex(1)))
|
|
L.Push(tb)
|
|
L.Push(LNumber(0))
|
|
return 3
|
|
}
|
|
|
|
func loadaux(L *LState, reader io.Reader, chunkname string) int {
|
|
if fn, err := L.Load(reader, chunkname); err != nil {
|
|
L.Push(LNil)
|
|
L.Push(LString(err.Error()))
|
|
return 2
|
|
} else {
|
|
L.Push(fn)
|
|
return 1
|
|
}
|
|
}
|
|
|
|
func baseLoad(L *LState) int {
|
|
fn := L.CheckFunction(1)
|
|
chunkname := L.OptString(2, "?")
|
|
top := L.GetTop()
|
|
buf := []string{}
|
|
for {
|
|
L.SetTop(top)
|
|
L.Push(fn)
|
|
L.Call(0, 1)
|
|
ret := L.reg.Pop()
|
|
if ret == LNil {
|
|
break
|
|
} else if LVCanConvToString(ret) {
|
|
str := ret.String()
|
|
if len(str) > 0 {
|
|
buf = append(buf, string(str))
|
|
} else {
|
|
break
|
|
}
|
|
} else {
|
|
L.Push(LNil)
|
|
L.Push(LString("reader function must return a string"))
|
|
return 2
|
|
}
|
|
}
|
|
return loadaux(L, strings.NewReader(strings.Join(buf, "")), chunkname)
|
|
}
|
|
|
|
func baseLoadFile(L *LState) int {
|
|
var reader io.Reader
|
|
var chunkname string
|
|
var err error
|
|
if L.GetTop() < 1 {
|
|
reader = os.Stdin
|
|
chunkname = "<stdin>"
|
|
} else {
|
|
chunkname = L.CheckString(1)
|
|
reader, err = os.Open(chunkname)
|
|
if err != nil {
|
|
L.Push(LNil)
|
|
L.Push(LString(fmt.Sprintf("can not open file: %v", chunkname)))
|
|
return 2
|
|
}
|
|
defer reader.(*os.File).Close()
|
|
}
|
|
return loadaux(L, reader, chunkname)
|
|
}
|
|
|
|
func baseLoadString(L *LState) int {
|
|
return loadaux(L, strings.NewReader(L.CheckString(1)), L.OptString(2, "<string>"))
|
|
}
|
|
|
|
func baseNext(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
index := LNil
|
|
if L.GetTop() >= 2 {
|
|
index = L.Get(2)
|
|
}
|
|
key, value := tb.Next(index)
|
|
if key == LNil {
|
|
L.Push(LNil)
|
|
return 1
|
|
}
|
|
L.Push(key)
|
|
L.Push(value)
|
|
return 2
|
|
}
|
|
|
|
func pairsaux(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
key, value := tb.Next(L.Get(2))
|
|
if key == LNil {
|
|
return 0
|
|
} else {
|
|
L.Pop(1)
|
|
L.Push(key)
|
|
L.Push(key)
|
|
L.Push(value)
|
|
return 2
|
|
}
|
|
}
|
|
|
|
func basePairs(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
L.Push(L.Get(UpvalueIndex(1)))
|
|
L.Push(tb)
|
|
L.Push(LNil)
|
|
return 3
|
|
}
|
|
|
|
func basePCall(L *LState) int {
|
|
L.CheckAny(1)
|
|
v := L.Get(1)
|
|
if v.Type() != LTFunction {
|
|
L.Push(LFalse)
|
|
L.Push(LString("attempt to call a " + v.Type().String() + " value"))
|
|
return 2
|
|
}
|
|
nargs := L.GetTop() - 1
|
|
if err := L.PCall(nargs, MultRet, nil); err != nil {
|
|
L.Push(LFalse)
|
|
if aerr, ok := err.(*ApiError); ok {
|
|
L.Push(aerr.Object)
|
|
} else {
|
|
L.Push(LString(err.Error()))
|
|
}
|
|
return 2
|
|
} else {
|
|
L.Insert(LTrue, 1)
|
|
return L.GetTop()
|
|
}
|
|
}
|
|
|
|
func basePrint(L *LState) int {
|
|
top := L.GetTop()
|
|
for i := 1; i <= top; i++ {
|
|
fmt.Print(L.ToStringMeta(L.Get(i)).String())
|
|
if i != top {
|
|
fmt.Print("\t")
|
|
}
|
|
}
|
|
fmt.Println("")
|
|
return 0
|
|
}
|
|
|
|
func base_PrintRegs(L *LState) int {
|
|
L.printReg()
|
|
return 0
|
|
}
|
|
|
|
func baseRawEqual(L *LState) int {
|
|
if L.CheckAny(1) == L.CheckAny(2) {
|
|
L.Push(LTrue)
|
|
} else {
|
|
L.Push(LFalse)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func baseRawGet(L *LState) int {
|
|
L.Push(L.RawGet(L.CheckTable(1), L.CheckAny(2)))
|
|
return 1
|
|
}
|
|
|
|
func baseRawSet(L *LState) int {
|
|
L.RawSet(L.CheckTable(1), L.CheckAny(2), L.CheckAny(3))
|
|
return 0
|
|
}
|
|
|
|
func baseSelect(L *LState) int {
|
|
L.CheckTypes(1, LTNumber, LTString)
|
|
switch lv := L.Get(1).(type) {
|
|
case LNumber:
|
|
idx := int(lv)
|
|
num := L.reg.Top() - L.indexToReg(int(lv)) - 1
|
|
if idx < 0 {
|
|
num++
|
|
}
|
|
return num
|
|
case LString:
|
|
if string(lv) != "#" {
|
|
L.ArgError(1, "invalid string '"+string(lv)+"'")
|
|
}
|
|
L.Push(LNumber(L.GetTop() - 1))
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func baseSetFEnv(L *LState) int {
|
|
var value LValue
|
|
if L.GetTop() == 0 {
|
|
value = LNumber(1)
|
|
} else {
|
|
value = L.Get(1)
|
|
}
|
|
env := L.CheckTable(2)
|
|
|
|
if fn, ok := value.(*LFunction); ok {
|
|
if fn.IsG {
|
|
L.RaiseError("cannot change the environment of given object")
|
|
} else {
|
|
fn.Env = env
|
|
L.Push(fn)
|
|
return 1
|
|
}
|
|
}
|
|
|
|
if number, ok := value.(LNumber); ok {
|
|
level := int(float64(number))
|
|
if level <= 0 {
|
|
L.Env = env
|
|
return 0
|
|
}
|
|
|
|
cf := L.currentFrame
|
|
for i := 0; i < level && cf != nil; i++ {
|
|
cf = cf.Parent
|
|
}
|
|
if cf == nil || cf.Fn.IsG {
|
|
L.RaiseError("cannot change the environment of given object")
|
|
} else {
|
|
cf.Fn.Env = env
|
|
L.Push(cf.Fn)
|
|
return 1
|
|
}
|
|
}
|
|
|
|
L.RaiseError("cannot change the environment of given object")
|
|
return 0
|
|
}
|
|
|
|
func baseSetMetatable(L *LState) int {
|
|
L.CheckTypes(2, LTNil, LTTable)
|
|
obj := L.Get(1)
|
|
if obj == LNil {
|
|
L.RaiseError("cannot set metatable to a nil object.")
|
|
}
|
|
mt := L.Get(2)
|
|
if m := L.metatable(obj, true); m != LNil {
|
|
if tb, ok := m.(*LTable); ok && tb.RawGetString("__metatable") != LNil {
|
|
L.RaiseError("cannot change a protected metatable")
|
|
}
|
|
}
|
|
L.SetMetatable(obj, mt)
|
|
L.SetTop(1)
|
|
return 1
|
|
}
|
|
|
|
func baseToNumber(L *LState) int {
|
|
base := L.OptInt(2, 10)
|
|
noBase := L.Get(2) == LNil
|
|
|
|
switch lv := L.CheckAny(1).(type) {
|
|
case LNumber:
|
|
L.Push(lv)
|
|
case LString:
|
|
str := strings.Trim(string(lv), " \n\t")
|
|
if strings.Index(str, ".") > -1 {
|
|
if v, err := strconv.ParseFloat(str, LNumberBit); err != nil {
|
|
L.Push(LNil)
|
|
} else {
|
|
L.Push(LNumber(v))
|
|
}
|
|
} else {
|
|
if noBase && strings.HasPrefix(strings.ToLower(str), "0x") {
|
|
base, str = 16, str[2:] // Hex number
|
|
}
|
|
if v, err := strconv.ParseInt(str, base, LNumberBit); err != nil {
|
|
L.Push(LNil)
|
|
} else {
|
|
L.Push(LNumber(v))
|
|
}
|
|
}
|
|
default:
|
|
L.Push(LNil)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func baseToString(L *LState) int {
|
|
v1 := L.CheckAny(1)
|
|
L.Push(L.ToStringMeta(v1))
|
|
return 1
|
|
}
|
|
|
|
func baseType(L *LState) int {
|
|
L.Push(LString(L.CheckAny(1).Type().String()))
|
|
return 1
|
|
}
|
|
|
|
func baseUnpack(L *LState) int {
|
|
tb := L.CheckTable(1)
|
|
start := L.OptInt(2, 1)
|
|
end := L.OptInt(3, tb.Len())
|
|
for i := start; i <= end; i++ {
|
|
L.Push(tb.RawGetInt(i))
|
|
}
|
|
ret := end - start + 1
|
|
if ret < 0 {
|
|
return 0
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func baseXPCall(L *LState) int {
|
|
fn := L.CheckFunction(1)
|
|
errfunc := L.CheckFunction(2)
|
|
|
|
top := L.GetTop()
|
|
L.Push(fn)
|
|
if err := L.PCall(0, MultRet, errfunc); err != nil {
|
|
L.Push(LFalse)
|
|
if aerr, ok := err.(*ApiError); ok {
|
|
L.Push(aerr.Object)
|
|
} else {
|
|
L.Push(LString(err.Error()))
|
|
}
|
|
return 2
|
|
} else {
|
|
L.Insert(LTrue, top+1)
|
|
return L.GetTop() - top
|
|
}
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* load lib {{{ */
|
|
|
|
func loModule(L *LState) int {
|
|
name := L.CheckString(1)
|
|
loaded := L.GetField(L.Get(RegistryIndex), "_LOADED")
|
|
tb := L.GetField(loaded, name)
|
|
if _, ok := tb.(*LTable); !ok {
|
|
tb = L.FindTable(L.Get(GlobalsIndex).(*LTable), name, 1)
|
|
if tb == LNil {
|
|
L.RaiseError("name conflict for module: %v", name)
|
|
}
|
|
L.SetField(loaded, name, tb)
|
|
}
|
|
if L.GetField(tb, "_NAME") == LNil {
|
|
L.SetField(tb, "_M", tb)
|
|
L.SetField(tb, "_NAME", LString(name))
|
|
names := strings.Split(name, ".")
|
|
pname := ""
|
|
if len(names) > 1 {
|
|
pname = strings.Join(names[:len(names)-1], ".") + "."
|
|
}
|
|
L.SetField(tb, "_PACKAGE", LString(pname))
|
|
}
|
|
|
|
caller := L.currentFrame.Parent
|
|
if caller == nil {
|
|
L.RaiseError("no calling stack.")
|
|
} else if caller.Fn.IsG {
|
|
L.RaiseError("module() can not be called from GFunctions.")
|
|
}
|
|
L.SetFEnv(caller.Fn, tb)
|
|
|
|
top := L.GetTop()
|
|
for i := 2; i <= top; i++ {
|
|
L.Push(L.Get(i))
|
|
L.Push(tb)
|
|
L.Call(1, 0)
|
|
}
|
|
L.Push(tb)
|
|
return 1
|
|
}
|
|
|
|
var loopdetection = &LUserData{}
|
|
|
|
func loRequire(L *LState) int {
|
|
name := L.CheckString(1)
|
|
loaded := L.GetField(L.Get(RegistryIndex), "_LOADED")
|
|
lv := L.GetField(loaded, name)
|
|
if LVAsBool(lv) {
|
|
if lv == loopdetection {
|
|
L.RaiseError("loop or previous error loading module: %s", name)
|
|
}
|
|
L.Push(lv)
|
|
return 1
|
|
}
|
|
loaders, ok := L.GetField(L.Get(RegistryIndex), "_LOADERS").(*LTable)
|
|
if !ok {
|
|
L.RaiseError("package.loaders must be a table")
|
|
}
|
|
messages := []string{}
|
|
var modasfunc LValue
|
|
for i := 1; ; i++ {
|
|
loader := L.RawGetInt(loaders, i)
|
|
if loader == LNil {
|
|
L.RaiseError("module %s not found:\n\t%s, ", name, strings.Join(messages, "\n\t"))
|
|
}
|
|
L.Push(loader)
|
|
L.Push(LString(name))
|
|
L.Call(1, 1)
|
|
ret := L.reg.Pop()
|
|
switch retv := ret.(type) {
|
|
case *LFunction:
|
|
modasfunc = retv
|
|
goto loopbreak
|
|
case LString:
|
|
messages = append(messages, string(retv))
|
|
}
|
|
}
|
|
loopbreak:
|
|
L.SetField(loaded, name, loopdetection)
|
|
L.Push(modasfunc)
|
|
L.Push(LString(name))
|
|
L.Call(1, 1)
|
|
ret := L.reg.Pop()
|
|
modv := L.GetField(loaded, name)
|
|
if ret != LNil && modv == loopdetection {
|
|
L.SetField(loaded, name, ret)
|
|
L.Push(ret)
|
|
} else if modv == loopdetection {
|
|
L.SetField(loaded, name, LTrue)
|
|
L.Push(LTrue)
|
|
} else {
|
|
L.Push(modv)
|
|
}
|
|
return 1
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* hidden features {{{ */
|
|
|
|
func baseNewProxy(L *LState) int {
|
|
ud := L.NewUserData()
|
|
L.SetTop(1)
|
|
if L.Get(1) == LTrue {
|
|
L.SetMetatable(ud, L.NewTable())
|
|
} else if d, ok := L.Get(1).(*LUserData); ok {
|
|
L.SetMetatable(ud, L.GetMetatable(d))
|
|
}
|
|
L.Push(ud)
|
|
return 1
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
//
|