1788 lines
37 KiB
Go
1788 lines
37 KiB
Go
package lua
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/yuin/gopher-lua/parse"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
const MultRet = -1
|
|
const RegistryIndex = -10000
|
|
const EnvironIndex = -10001
|
|
const GlobalsIndex = -10002
|
|
|
|
/* ApiError {{{ */
|
|
|
|
type ApiError struct {
|
|
Type ApiErrorType
|
|
Object LValue
|
|
StackTrace string
|
|
// Underlying error. This attribute is set only if the Type is ApiErrorFile or ApiErrorSyntax
|
|
Cause error
|
|
}
|
|
|
|
func newApiError(code ApiErrorType, object LValue) *ApiError {
|
|
return &ApiError{code, object, "", nil}
|
|
}
|
|
|
|
func newApiErrorS(code ApiErrorType, message string) *ApiError {
|
|
return newApiError(code, LString(message))
|
|
}
|
|
|
|
func newApiErrorE(code ApiErrorType, err error) *ApiError {
|
|
return &ApiError{code, LString(err.Error()), "", err}
|
|
}
|
|
|
|
func (e *ApiError) Error() string {
|
|
if len(e.StackTrace) > 0 {
|
|
return fmt.Sprintf("%s\n%s", e.Object.String(), e.StackTrace)
|
|
}
|
|
return e.Object.String()
|
|
}
|
|
|
|
type ApiErrorType int
|
|
|
|
const (
|
|
ApiErrorSyntax ApiErrorType = iota
|
|
ApiErrorFile
|
|
ApiErrorRun
|
|
ApiErrorError
|
|
ApiErrorPanic
|
|
)
|
|
|
|
/* }}} */
|
|
|
|
/* ResumeState {{{ */
|
|
|
|
type ResumeState int
|
|
|
|
const (
|
|
ResumeOK ResumeState = iota
|
|
ResumeYield
|
|
ResumeError
|
|
)
|
|
|
|
/* }}} */
|
|
|
|
/* P {{{ */
|
|
|
|
type P struct {
|
|
Fn LValue
|
|
NRet int
|
|
Protect bool
|
|
Handler *LFunction
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* Options {{{ */
|
|
|
|
// Options is a configuration that is used to create a new LState.
|
|
type Options struct {
|
|
// Call stack size. This defaults to `lua.CallStackSize`.
|
|
CallStackSize int
|
|
// Data stack size. This defaults to `lua.RegistrySize`.
|
|
RegistrySize int
|
|
// Controls whether or not libraries are opened by default
|
|
SkipOpenLibs bool
|
|
// Tells whether a Go stacktrace should be included in a Lua stacktrace when panics occur.
|
|
IncludeGoStackTrace bool
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* Debug {{{ */
|
|
|
|
type Debug struct {
|
|
frame *callFrame
|
|
Name string
|
|
What string
|
|
Source string
|
|
CurrentLine int
|
|
NUpvalues int
|
|
LineDefined int
|
|
LastLineDefined int
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* callFrame {{{ */
|
|
|
|
type callFrame struct {
|
|
Idx int
|
|
Fn *LFunction
|
|
Parent *callFrame
|
|
Pc int
|
|
Base int
|
|
LocalBase int
|
|
ReturnBase int
|
|
NArgs int
|
|
NRet int
|
|
TailCall int
|
|
}
|
|
|
|
type callFrameStack struct {
|
|
array []callFrame
|
|
sp int
|
|
}
|
|
|
|
func newCallFrameStack(size int) *callFrameStack {
|
|
return &callFrameStack{
|
|
array: make([]callFrame, size),
|
|
sp: 0,
|
|
}
|
|
}
|
|
|
|
func (cs *callFrameStack) IsEmpty() bool { return cs.sp == 0 }
|
|
|
|
func (cs *callFrameStack) Clear() {
|
|
cs.sp = 0
|
|
}
|
|
|
|
func (cs *callFrameStack) Push(v callFrame) { // +inline-start
|
|
cs.array[cs.sp] = v
|
|
cs.array[cs.sp].Idx = cs.sp
|
|
cs.sp++
|
|
} // +inline-end
|
|
|
|
func (cs *callFrameStack) Remove(sp int) {
|
|
psp := sp - 1
|
|
nsp := sp + 1
|
|
var pre *callFrame
|
|
var next *callFrame
|
|
if psp > 0 {
|
|
pre = &cs.array[psp]
|
|
}
|
|
if nsp < cs.sp {
|
|
next = &cs.array[nsp]
|
|
}
|
|
if next != nil {
|
|
next.Parent = pre
|
|
}
|
|
for i := sp; i+1 < cs.sp; i++ {
|
|
cs.array[i] = cs.array[i+1]
|
|
cs.array[i].Idx = i
|
|
cs.sp = i
|
|
}
|
|
cs.sp++
|
|
}
|
|
|
|
func (cs *callFrameStack) Sp() int {
|
|
return cs.sp
|
|
}
|
|
|
|
func (cs *callFrameStack) SetSp(sp int) {
|
|
cs.sp = sp
|
|
}
|
|
|
|
func (cs *callFrameStack) Last() *callFrame {
|
|
if cs.sp == 0 {
|
|
return nil
|
|
}
|
|
return &cs.array[cs.sp-1]
|
|
}
|
|
|
|
func (cs *callFrameStack) At(sp int) *callFrame {
|
|
return &cs.array[sp]
|
|
}
|
|
|
|
func (cs *callFrameStack) Pop() *callFrame {
|
|
cs.sp--
|
|
return &cs.array[cs.sp]
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* registry {{{ */
|
|
|
|
type registry struct {
|
|
array []LValue
|
|
top int
|
|
alloc *allocator
|
|
}
|
|
|
|
func newRegistry(size int, alloc *allocator) *registry {
|
|
return ®istry{make([]LValue, size), 0, alloc}
|
|
}
|
|
|
|
func (rg *registry) SetTop(top int) {
|
|
oldtop := rg.top
|
|
rg.top = top
|
|
for i := oldtop; i < rg.top; i++ {
|
|
rg.array[i] = LNil
|
|
}
|
|
for i := rg.top; i < oldtop; i++ {
|
|
rg.array[i] = LNil
|
|
}
|
|
}
|
|
|
|
func (rg *registry) Top() int {
|
|
return rg.top
|
|
}
|
|
|
|
func (rg *registry) Push(v LValue) {
|
|
rg.array[rg.top] = v
|
|
rg.top++
|
|
}
|
|
|
|
func (rg *registry) Pop() LValue {
|
|
v := rg.array[rg.top-1]
|
|
rg.array[rg.top-1] = LNil
|
|
rg.top--
|
|
return v
|
|
}
|
|
|
|
func (rg *registry) Get(reg int) LValue {
|
|
return rg.array[reg]
|
|
}
|
|
|
|
func (rg *registry) CopyRange(regv, start, limit, n int) { // +inline-start
|
|
for i := 0; i < n; i++ {
|
|
if tidx := start + i; tidx >= rg.top || limit > -1 && tidx >= limit || tidx < 0 {
|
|
rg.array[regv+i] = LNil
|
|
} else {
|
|
rg.array[regv+i] = rg.array[tidx]
|
|
}
|
|
}
|
|
rg.top = regv + n
|
|
} // +inline-end
|
|
|
|
func (rg *registry) FillNil(regm, n int) { // +inline-start
|
|
for i := 0; i < n; i++ {
|
|
rg.array[regm+i] = LNil
|
|
}
|
|
rg.top = regm + n
|
|
} // +inline-end
|
|
|
|
func (rg *registry) Insert(value LValue, reg int) {
|
|
top := rg.Top()
|
|
if reg >= top {
|
|
rg.Set(reg, value)
|
|
return
|
|
}
|
|
top--
|
|
for ; top >= reg; top-- {
|
|
rg.Set(top+1, rg.Get(top))
|
|
}
|
|
rg.Set(reg, value)
|
|
}
|
|
|
|
func (rg *registry) Set(reg int, val LValue) {
|
|
rg.array[reg] = val
|
|
if reg >= rg.top {
|
|
rg.top = reg + 1
|
|
}
|
|
}
|
|
|
|
func (rg *registry) SetNumber(reg int, val LNumber) {
|
|
rg.array[reg] = rg.alloc.LNumber2I(val)
|
|
if reg >= rg.top {
|
|
rg.top = reg + 1
|
|
}
|
|
} /* }}} */
|
|
|
|
/* Global {{{ */
|
|
|
|
func newGlobal() *Global {
|
|
return &Global{
|
|
MainThread: nil,
|
|
Registry: newLTable(0, 32),
|
|
Global: newLTable(0, 64),
|
|
builtinMts: make(map[int]LValue),
|
|
tempFiles: make([]*os.File, 0, 10),
|
|
}
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* package local methods {{{ */
|
|
|
|
func panicWithTraceback(L *LState) {
|
|
err := newApiError(ApiErrorRun, L.Get(-1))
|
|
err.StackTrace = L.stackTrace(0)
|
|
panic(err)
|
|
}
|
|
|
|
func panicWithoutTraceback(L *LState) {
|
|
err := newApiError(ApiErrorRun, L.Get(-1))
|
|
panic(err)
|
|
}
|
|
|
|
func newLState(options Options) *LState {
|
|
al := newAllocator(32)
|
|
ls := &LState{
|
|
G: newGlobal(),
|
|
Parent: nil,
|
|
Panic: panicWithTraceback,
|
|
Dead: false,
|
|
Options: options,
|
|
|
|
stop: 0,
|
|
reg: newRegistry(options.RegistrySize, al),
|
|
stack: newCallFrameStack(options.CallStackSize),
|
|
alloc: al,
|
|
currentFrame: nil,
|
|
wrapped: false,
|
|
uvcache: nil,
|
|
hasErrorFunc: false,
|
|
mainLoop: mainLoop,
|
|
ctx: nil,
|
|
}
|
|
ls.Env = ls.G.Global
|
|
return ls
|
|
}
|
|
|
|
func (ls *LState) printReg() {
|
|
println("-------------------------")
|
|
println("thread:", ls)
|
|
println("top:", ls.reg.Top())
|
|
if ls.currentFrame != nil {
|
|
println("function base:", ls.currentFrame.Base)
|
|
println("return base:", ls.currentFrame.ReturnBase)
|
|
} else {
|
|
println("(vm not started)")
|
|
}
|
|
println("local base:", ls.currentLocalBase())
|
|
for i := 0; i < ls.reg.Top(); i++ {
|
|
println(i, ls.reg.Get(i).String())
|
|
}
|
|
println("-------------------------")
|
|
}
|
|
|
|
func (ls *LState) printCallStack() {
|
|
println("-------------------------")
|
|
for i := 0; i < ls.stack.Sp(); i++ {
|
|
print(i)
|
|
print(" ")
|
|
frame := ls.stack.At(i)
|
|
if frame == nil {
|
|
break
|
|
}
|
|
if frame.Fn.IsG {
|
|
println("IsG:", true, "Frame:", frame, "Fn:", frame.Fn)
|
|
} else {
|
|
println("IsG:", false, "Frame:", frame, "Fn:", frame.Fn, "pc:", frame.Pc)
|
|
}
|
|
}
|
|
println("-------------------------")
|
|
}
|
|
|
|
func (ls *LState) closeAllUpvalues() { // +inline-start
|
|
for cf := ls.currentFrame; cf != nil; cf = cf.Parent {
|
|
if !cf.Fn.IsG {
|
|
ls.closeUpvalues(cf.LocalBase)
|
|
}
|
|
}
|
|
} // +inline-end
|
|
|
|
func (ls *LState) raiseError(level int, format string, args ...interface{}) {
|
|
if !ls.hasErrorFunc {
|
|
ls.closeAllUpvalues()
|
|
}
|
|
message := format
|
|
if len(args) > 0 {
|
|
message = fmt.Sprintf(format, args...)
|
|
}
|
|
if level > 0 {
|
|
message = fmt.Sprintf("%v %v", ls.where(level-1, true), message)
|
|
}
|
|
ls.reg.Push(LString(message))
|
|
ls.Panic(ls)
|
|
}
|
|
|
|
func (ls *LState) findLocal(frame *callFrame, no int) string {
|
|
fn := frame.Fn
|
|
if !fn.IsG {
|
|
if name, ok := fn.LocalName(no, frame.Pc-1); ok {
|
|
return name
|
|
}
|
|
}
|
|
var top int
|
|
if ls.currentFrame == frame {
|
|
top = ls.reg.Top()
|
|
} else if frame.Idx+1 < ls.stack.Sp() {
|
|
top = ls.stack.At(frame.Idx + 1).Base
|
|
} else {
|
|
return ""
|
|
}
|
|
if top-frame.LocalBase >= no {
|
|
return "(*temporary)"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (ls *LState) where(level int, skipg bool) string {
|
|
dbg, ok := ls.GetStack(level)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
cf := dbg.frame
|
|
proto := cf.Fn.Proto
|
|
sourcename := "[G]"
|
|
if proto != nil {
|
|
sourcename = proto.SourceName
|
|
} else if skipg {
|
|
return ls.where(level+1, skipg)
|
|
}
|
|
line := ""
|
|
if proto != nil {
|
|
line = fmt.Sprintf("%v:", proto.DbgSourcePositions[cf.Pc-1])
|
|
}
|
|
return fmt.Sprintf("%v:%v", sourcename, line)
|
|
}
|
|
|
|
func (ls *LState) stackTrace(level int) string {
|
|
buf := []string{}
|
|
header := "stack traceback:"
|
|
if ls.currentFrame != nil {
|
|
i := 0
|
|
for dbg, ok := ls.GetStack(i); ok; dbg, ok = ls.GetStack(i) {
|
|
cf := dbg.frame
|
|
buf = append(buf, fmt.Sprintf("\t%v in %v", ls.Where(i), ls.formattedFrameFuncName(cf)))
|
|
if !cf.Fn.IsG && cf.TailCall > 0 {
|
|
for tc := cf.TailCall; tc > 0; tc-- {
|
|
buf = append(buf, "\t(tailcall): ?")
|
|
i++
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
buf = append(buf, fmt.Sprintf("\t%v: %v", "[G]", "?"))
|
|
buf = buf[intMax(0, intMin(level, len(buf))):len(buf)]
|
|
if len(buf) > 20 {
|
|
newbuf := make([]string, 0, 20)
|
|
newbuf = append(newbuf, buf[0:7]...)
|
|
newbuf = append(newbuf, "\t...")
|
|
newbuf = append(newbuf, buf[len(buf)-7:len(buf)]...)
|
|
buf = newbuf
|
|
}
|
|
return fmt.Sprintf("%s\n%s", header, strings.Join(buf, "\n"))
|
|
}
|
|
|
|
func (ls *LState) formattedFrameFuncName(fr *callFrame) string {
|
|
name, ischunk := ls.frameFuncName(fr)
|
|
if ischunk {
|
|
return name
|
|
}
|
|
if name[0] != '(' && name[0] != '<' {
|
|
return fmt.Sprintf("function '%s'", name)
|
|
}
|
|
return fmt.Sprintf("function %s", name)
|
|
}
|
|
|
|
func (ls *LState) rawFrameFuncName(fr *callFrame) string {
|
|
name, _ := ls.frameFuncName(fr)
|
|
return name
|
|
}
|
|
|
|
func (ls *LState) frameFuncName(fr *callFrame) (string, bool) {
|
|
frame := fr.Parent
|
|
if frame == nil {
|
|
if ls.Parent == nil {
|
|
return "main chunk", true
|
|
} else {
|
|
return "corountine", true
|
|
}
|
|
}
|
|
if !frame.Fn.IsG {
|
|
pc := frame.Pc - 1
|
|
for _, call := range frame.Fn.Proto.DbgCalls {
|
|
if call.Pc == pc {
|
|
name := call.Name
|
|
if (name == "?" || fr.TailCall > 0) && !fr.Fn.IsG {
|
|
name = fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined)
|
|
}
|
|
return name, false
|
|
}
|
|
}
|
|
}
|
|
if !fr.Fn.IsG {
|
|
return fmt.Sprintf("<%v:%v>", fr.Fn.Proto.SourceName, fr.Fn.Proto.LineDefined), false
|
|
}
|
|
return "(anonymous)", false
|
|
}
|
|
|
|
func (ls *LState) isStarted() bool {
|
|
return ls.currentFrame != nil
|
|
}
|
|
|
|
func (ls *LState) kill() {
|
|
ls.Dead = true
|
|
}
|
|
|
|
func (ls *LState) indexToReg(idx int) int {
|
|
base := ls.currentLocalBase()
|
|
if idx > 0 {
|
|
return base + idx - 1
|
|
} else if idx == 0 {
|
|
return -1
|
|
} else {
|
|
tidx := ls.reg.Top() + idx
|
|
if tidx < base {
|
|
return -1
|
|
}
|
|
return tidx
|
|
}
|
|
}
|
|
|
|
func (ls *LState) currentLocalBase() int {
|
|
base := 0
|
|
if ls.currentFrame != nil {
|
|
base = ls.currentFrame.LocalBase
|
|
}
|
|
return base
|
|
}
|
|
|
|
func (ls *LState) currentEnv() *LTable {
|
|
return ls.Env
|
|
/*
|
|
if ls.currentFrame == nil {
|
|
return ls.Env
|
|
}
|
|
return ls.currentFrame.Fn.Env
|
|
*/
|
|
}
|
|
|
|
func (ls *LState) rkValue(idx int) LValue {
|
|
/*
|
|
if OpIsK(idx) {
|
|
return ls.currentFrame.Fn.Proto.Constants[opIndexK(idx)]
|
|
}
|
|
return ls.reg.Get(ls.currentFrame.LocalBase + idx)
|
|
*/
|
|
if (idx & opBitRk) != 0 {
|
|
return ls.currentFrame.Fn.Proto.Constants[idx & ^opBitRk]
|
|
}
|
|
return ls.reg.array[ls.currentFrame.LocalBase+idx]
|
|
}
|
|
|
|
func (ls *LState) rkString(idx int) string {
|
|
if (idx & opBitRk) != 0 {
|
|
return ls.currentFrame.Fn.Proto.stringConstants[idx & ^opBitRk]
|
|
}
|
|
return string(ls.reg.array[ls.currentFrame.LocalBase+idx].(LString))
|
|
}
|
|
|
|
func (ls *LState) closeUpvalues(idx int) { // +inline-start
|
|
if ls.uvcache != nil {
|
|
var prev *Upvalue
|
|
for uv := ls.uvcache; uv != nil; uv = uv.next {
|
|
if uv.index >= idx {
|
|
if prev != nil {
|
|
prev.next = nil
|
|
} else {
|
|
ls.uvcache = nil
|
|
}
|
|
uv.Close()
|
|
}
|
|
prev = uv
|
|
}
|
|
}
|
|
} // +inline-end
|
|
|
|
func (ls *LState) findUpvalue(idx int) *Upvalue {
|
|
var prev *Upvalue
|
|
var next *Upvalue
|
|
if ls.uvcache != nil {
|
|
for uv := ls.uvcache; uv != nil; uv = uv.next {
|
|
if uv.index == idx {
|
|
return uv
|
|
}
|
|
if uv.index > idx {
|
|
next = uv
|
|
break
|
|
}
|
|
prev = uv
|
|
}
|
|
}
|
|
uv := &Upvalue{reg: ls.reg, index: idx, closed: false}
|
|
if prev != nil {
|
|
prev.next = uv
|
|
} else {
|
|
ls.uvcache = uv
|
|
}
|
|
if next != nil {
|
|
uv.next = next
|
|
}
|
|
return uv
|
|
}
|
|
|
|
func (ls *LState) metatable(lvalue LValue, rawget bool) LValue {
|
|
var metatable LValue = LNil
|
|
switch obj := lvalue.(type) {
|
|
case *LTable:
|
|
metatable = obj.Metatable
|
|
case *LUserData:
|
|
metatable = obj.Metatable
|
|
default:
|
|
if table, ok := ls.G.builtinMts[int(obj.Type())]; ok {
|
|
metatable = table
|
|
}
|
|
}
|
|
|
|
if !rawget && metatable != LNil {
|
|
oldmt := metatable
|
|
if tb, ok := metatable.(*LTable); ok {
|
|
metatable = tb.RawGetString("__metatable")
|
|
if metatable == LNil {
|
|
metatable = oldmt
|
|
}
|
|
}
|
|
}
|
|
|
|
return metatable
|
|
}
|
|
|
|
func (ls *LState) metaOp1(lvalue LValue, event string) LValue {
|
|
if mt := ls.metatable(lvalue, true); mt != LNil {
|
|
if tb, ok := mt.(*LTable); ok {
|
|
return tb.RawGetString(event)
|
|
}
|
|
}
|
|
return LNil
|
|
}
|
|
|
|
func (ls *LState) metaOp2(value1, value2 LValue, event string) LValue {
|
|
if mt := ls.metatable(value1, true); mt != LNil {
|
|
if tb, ok := mt.(*LTable); ok {
|
|
if ret := tb.RawGetString(event); ret != LNil {
|
|
return ret
|
|
}
|
|
}
|
|
}
|
|
if mt := ls.metatable(value2, true); mt != LNil {
|
|
if tb, ok := mt.(*LTable); ok {
|
|
return tb.RawGetString(event)
|
|
}
|
|
}
|
|
return LNil
|
|
}
|
|
|
|
func (ls *LState) metaCall(lvalue LValue) (*LFunction, bool) {
|
|
if fn, ok := lvalue.(*LFunction); ok {
|
|
return fn, false
|
|
}
|
|
if fn, ok := ls.metaOp1(lvalue, "__call").(*LFunction); ok {
|
|
return fn, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (ls *LState) initCallFrame(cf *callFrame) { // +inline-start
|
|
if cf.Fn.IsG {
|
|
ls.reg.SetTop(cf.LocalBase + cf.NArgs)
|
|
} else {
|
|
proto := cf.Fn.Proto
|
|
nargs := cf.NArgs
|
|
np := int(proto.NumParameters)
|
|
for i := nargs; i < np; i++ {
|
|
ls.reg.array[cf.LocalBase+i] = LNil
|
|
nargs = np
|
|
}
|
|
|
|
if (proto.IsVarArg & VarArgIsVarArg) == 0 {
|
|
if nargs < int(proto.NumUsedRegisters) {
|
|
nargs = int(proto.NumUsedRegisters)
|
|
}
|
|
for i := np; i < nargs; i++ {
|
|
ls.reg.array[cf.LocalBase+i] = LNil
|
|
}
|
|
ls.reg.top = cf.LocalBase + int(proto.NumUsedRegisters)
|
|
} else {
|
|
/* swap vararg positions:
|
|
closure
|
|
namedparam1 <- lbase
|
|
namedparam2
|
|
vararg1
|
|
vararg2
|
|
|
|
TO
|
|
|
|
closure
|
|
nil
|
|
nil
|
|
vararg1
|
|
vararg2
|
|
namedparam1 <- lbase
|
|
namedparam2
|
|
*/
|
|
nvarargs := nargs - np
|
|
if nvarargs < 0 {
|
|
nvarargs = 0
|
|
}
|
|
|
|
ls.reg.SetTop(cf.LocalBase + nargs + np)
|
|
for i := 0; i < np; i++ {
|
|
//ls.reg.Set(cf.LocalBase+nargs+i, ls.reg.Get(cf.LocalBase+i))
|
|
ls.reg.array[cf.LocalBase+nargs+i] = ls.reg.array[cf.LocalBase+i]
|
|
//ls.reg.Set(cf.LocalBase+i, LNil)
|
|
ls.reg.array[cf.LocalBase+i] = LNil
|
|
}
|
|
|
|
if CompatVarArg {
|
|
ls.reg.SetTop(cf.LocalBase + nargs + np + 1)
|
|
if (proto.IsVarArg & VarArgNeedsArg) != 0 {
|
|
argtb := newLTable(nvarargs, 0)
|
|
for i := 0; i < nvarargs; i++ {
|
|
argtb.RawSetInt(i+1, ls.reg.Get(cf.LocalBase+np+i))
|
|
}
|
|
argtb.RawSetString("n", LNumber(nvarargs))
|
|
//ls.reg.Set(cf.LocalBase+nargs+np, argtb)
|
|
ls.reg.array[cf.LocalBase+nargs+np] = argtb
|
|
} else {
|
|
ls.reg.array[cf.LocalBase+nargs+np] = LNil
|
|
}
|
|
}
|
|
cf.LocalBase += nargs
|
|
maxreg := cf.LocalBase + int(proto.NumUsedRegisters)
|
|
ls.reg.SetTop(maxreg)
|
|
}
|
|
}
|
|
} // +inline-end
|
|
|
|
func (ls *LState) pushCallFrame(cf callFrame, fn LValue, meta bool) { // +inline-start
|
|
if meta {
|
|
cf.NArgs++
|
|
ls.reg.Insert(fn, cf.LocalBase)
|
|
}
|
|
if cf.Fn == nil {
|
|
ls.RaiseError("attempt to call a non-function object")
|
|
}
|
|
if ls.stack.sp == ls.Options.CallStackSize {
|
|
ls.RaiseError("stack overflow")
|
|
}
|
|
// +inline-call ls.stack.Push cf
|
|
newcf := ls.stack.Last()
|
|
// +inline-call ls.initCallFrame newcf
|
|
ls.currentFrame = newcf
|
|
} // +inline-end
|
|
|
|
func (ls *LState) callR(nargs, nret, rbase int) {
|
|
base := ls.reg.Top() - nargs - 1
|
|
if rbase < 0 {
|
|
rbase = base
|
|
}
|
|
lv := ls.reg.Get(base)
|
|
fn, meta := ls.metaCall(lv)
|
|
ls.pushCallFrame(callFrame{
|
|
Fn: fn,
|
|
Pc: 0,
|
|
Base: base,
|
|
LocalBase: base + 1,
|
|
ReturnBase: rbase,
|
|
NArgs: nargs,
|
|
NRet: nret,
|
|
Parent: ls.currentFrame,
|
|
TailCall: 0,
|
|
}, lv, meta)
|
|
if ls.G.MainThread == nil {
|
|
ls.G.MainThread = ls
|
|
ls.G.CurrentThread = ls
|
|
ls.mainLoop(ls, nil)
|
|
} else {
|
|
ls.mainLoop(ls, ls.currentFrame)
|
|
}
|
|
if nret != MultRet {
|
|
ls.reg.SetTop(rbase + nret)
|
|
}
|
|
}
|
|
|
|
func (ls *LState) getField(obj LValue, key LValue) LValue {
|
|
curobj := obj
|
|
for i := 0; i < MaxTableGetLoop; i++ {
|
|
tb, istable := curobj.(*LTable)
|
|
if istable {
|
|
ret := tb.RawGet(key)
|
|
if ret != LNil {
|
|
return ret
|
|
}
|
|
}
|
|
metaindex := ls.metaOp1(curobj, "__index")
|
|
if metaindex == LNil {
|
|
if !istable {
|
|
ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String())
|
|
}
|
|
return LNil
|
|
}
|
|
if metaindex.Type() == LTFunction {
|
|
ls.reg.Push(metaindex)
|
|
ls.reg.Push(curobj)
|
|
ls.reg.Push(key)
|
|
ls.Call(2, 1)
|
|
return ls.reg.Pop()
|
|
} else {
|
|
curobj = metaindex
|
|
}
|
|
}
|
|
ls.RaiseError("too many recursions in gettable")
|
|
return nil
|
|
}
|
|
|
|
func (ls *LState) getFieldString(obj LValue, key string) LValue {
|
|
curobj := obj
|
|
for i := 0; i < MaxTableGetLoop; i++ {
|
|
tb, istable := curobj.(*LTable)
|
|
if istable {
|
|
ret := tb.RawGetString(key)
|
|
if ret != LNil {
|
|
return ret
|
|
}
|
|
}
|
|
metaindex := ls.metaOp1(curobj, "__index")
|
|
if metaindex == LNil {
|
|
if !istable {
|
|
ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String())
|
|
}
|
|
return LNil
|
|
}
|
|
if metaindex.Type() == LTFunction {
|
|
ls.reg.Push(metaindex)
|
|
ls.reg.Push(curobj)
|
|
ls.reg.Push(LString(key))
|
|
ls.Call(2, 1)
|
|
return ls.reg.Pop()
|
|
} else {
|
|
curobj = metaindex
|
|
}
|
|
}
|
|
ls.RaiseError("too many recursions in gettable")
|
|
return nil
|
|
}
|
|
|
|
func (ls *LState) setField(obj LValue, key LValue, value LValue) {
|
|
curobj := obj
|
|
for i := 0; i < MaxTableGetLoop; i++ {
|
|
tb, istable := curobj.(*LTable)
|
|
if istable {
|
|
if tb.RawGet(key) != LNil {
|
|
ls.RawSet(tb, key, value)
|
|
return
|
|
}
|
|
}
|
|
metaindex := ls.metaOp1(curobj, "__newindex")
|
|
if metaindex == LNil {
|
|
if !istable {
|
|
ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String())
|
|
}
|
|
ls.RawSet(tb, key, value)
|
|
return
|
|
}
|
|
if metaindex.Type() == LTFunction {
|
|
ls.reg.Push(metaindex)
|
|
ls.reg.Push(curobj)
|
|
ls.reg.Push(key)
|
|
ls.reg.Push(value)
|
|
ls.Call(3, 0)
|
|
return
|
|
} else {
|
|
curobj = metaindex
|
|
}
|
|
}
|
|
ls.RaiseError("too many recursions in settable")
|
|
}
|
|
|
|
func (ls *LState) setFieldString(obj LValue, key string, value LValue) {
|
|
curobj := obj
|
|
for i := 0; i < MaxTableGetLoop; i++ {
|
|
tb, istable := curobj.(*LTable)
|
|
if istable {
|
|
if tb.RawGetString(key) != LNil {
|
|
tb.RawSetString(key, value)
|
|
return
|
|
}
|
|
}
|
|
metaindex := ls.metaOp1(curobj, "__newindex")
|
|
if metaindex == LNil {
|
|
if !istable {
|
|
ls.RaiseError("attempt to index a non-table object(%v)", curobj.Type().String())
|
|
}
|
|
tb.RawSetString(key, value)
|
|
return
|
|
}
|
|
if metaindex.Type() == LTFunction {
|
|
ls.reg.Push(metaindex)
|
|
ls.reg.Push(curobj)
|
|
ls.reg.Push(LString(key))
|
|
ls.reg.Push(value)
|
|
ls.Call(3, 0)
|
|
return
|
|
} else {
|
|
curobj = metaindex
|
|
}
|
|
}
|
|
ls.RaiseError("too many recursions in settable")
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* api methods {{{ */
|
|
|
|
func NewState(opts ...Options) *LState {
|
|
var ls *LState
|
|
if len(opts) == 0 {
|
|
ls = newLState(Options{
|
|
CallStackSize: CallStackSize,
|
|
RegistrySize: RegistrySize,
|
|
})
|
|
ls.OpenLibs()
|
|
} else {
|
|
if opts[0].CallStackSize < 1 {
|
|
opts[0].CallStackSize = CallStackSize
|
|
}
|
|
if opts[0].RegistrySize < 128 {
|
|
opts[0].RegistrySize = RegistrySize
|
|
}
|
|
ls = newLState(opts[0])
|
|
if !opts[0].SkipOpenLibs {
|
|
ls.OpenLibs()
|
|
}
|
|
}
|
|
return ls
|
|
}
|
|
|
|
func (ls *LState) Close() {
|
|
atomic.AddInt32(&ls.stop, 1)
|
|
for _, file := range ls.G.tempFiles {
|
|
// ignore errors in these operations
|
|
file.Close()
|
|
os.Remove(file.Name())
|
|
}
|
|
}
|
|
|
|
/* registry operations {{{ */
|
|
|
|
func (ls *LState) GetTop() int {
|
|
return ls.reg.Top() - ls.currentLocalBase()
|
|
}
|
|
|
|
func (ls *LState) SetTop(idx int) {
|
|
base := ls.currentLocalBase()
|
|
newtop := ls.indexToReg(idx) + 1
|
|
if newtop < base {
|
|
ls.reg.SetTop(base)
|
|
} else {
|
|
ls.reg.SetTop(newtop)
|
|
}
|
|
}
|
|
|
|
func (ls *LState) Replace(idx int, value LValue) {
|
|
base := ls.currentLocalBase()
|
|
if idx > 0 {
|
|
reg := base + idx - 1
|
|
if reg < ls.reg.Top() {
|
|
ls.reg.Set(reg, value)
|
|
}
|
|
} else if idx == 0 {
|
|
} else if idx > RegistryIndex {
|
|
if tidx := ls.reg.Top() + idx; tidx >= base {
|
|
ls.reg.Set(tidx, value)
|
|
}
|
|
} else {
|
|
switch idx {
|
|
case RegistryIndex:
|
|
if tb, ok := value.(*LTable); ok {
|
|
ls.G.Registry = tb
|
|
} else {
|
|
ls.RaiseError("registry must be a table(%v)", value.Type().String())
|
|
}
|
|
case EnvironIndex:
|
|
if ls.currentFrame == nil {
|
|
ls.RaiseError("no calling environment")
|
|
}
|
|
if tb, ok := value.(*LTable); ok {
|
|
ls.currentFrame.Fn.Env = tb
|
|
} else {
|
|
ls.RaiseError("environment must be a table(%v)", value.Type().String())
|
|
}
|
|
case GlobalsIndex:
|
|
if tb, ok := value.(*LTable); ok {
|
|
ls.G.Global = tb
|
|
} else {
|
|
ls.RaiseError("_G must be a table(%v)", value.Type().String())
|
|
}
|
|
default:
|
|
fn := ls.currentFrame.Fn
|
|
index := GlobalsIndex - idx - 1
|
|
if index < len(fn.Upvalues) {
|
|
fn.Upvalues[index].SetValue(value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (ls *LState) Get(idx int) LValue {
|
|
base := ls.currentLocalBase()
|
|
if idx > 0 {
|
|
reg := base + idx - 1
|
|
if reg < ls.reg.Top() {
|
|
return ls.reg.Get(reg)
|
|
}
|
|
return LNil
|
|
} else if idx == 0 {
|
|
return LNil
|
|
} else if idx > RegistryIndex {
|
|
tidx := ls.reg.Top() + idx
|
|
if tidx < base {
|
|
return LNil
|
|
}
|
|
return ls.reg.Get(tidx)
|
|
} else {
|
|
switch idx {
|
|
case RegistryIndex:
|
|
return ls.G.Registry
|
|
case EnvironIndex:
|
|
if ls.currentFrame == nil {
|
|
return ls.Env
|
|
}
|
|
return ls.currentFrame.Fn.Env
|
|
case GlobalsIndex:
|
|
return ls.G.Global
|
|
default:
|
|
fn := ls.currentFrame.Fn
|
|
index := GlobalsIndex - idx - 1
|
|
if index < len(fn.Upvalues) {
|
|
return fn.Upvalues[index].Value()
|
|
}
|
|
return LNil
|
|
}
|
|
}
|
|
return LNil
|
|
}
|
|
|
|
func (ls *LState) Push(value LValue) {
|
|
ls.reg.Push(value)
|
|
}
|
|
|
|
func (ls *LState) Pop(n int) {
|
|
for i := 0; i < n; i++ {
|
|
if ls.GetTop() == 0 {
|
|
ls.RaiseError("register underflow")
|
|
}
|
|
ls.reg.Pop()
|
|
}
|
|
}
|
|
|
|
func (ls *LState) Insert(value LValue, index int) {
|
|
reg := ls.indexToReg(index)
|
|
top := ls.reg.Top()
|
|
if reg >= top {
|
|
ls.reg.Set(reg, value)
|
|
return
|
|
}
|
|
if reg <= ls.currentLocalBase() {
|
|
reg = ls.currentLocalBase()
|
|
}
|
|
top--
|
|
for ; top >= reg; top-- {
|
|
ls.reg.Set(top+1, ls.reg.Get(top))
|
|
}
|
|
ls.reg.Set(reg, value)
|
|
}
|
|
|
|
func (ls *LState) Remove(index int) {
|
|
reg := ls.indexToReg(index)
|
|
top := ls.reg.Top()
|
|
switch {
|
|
case reg >= top:
|
|
return
|
|
case reg < ls.currentLocalBase():
|
|
return
|
|
case reg == top-1:
|
|
ls.Pop(1)
|
|
return
|
|
}
|
|
for i := reg; i < top-1; i++ {
|
|
ls.reg.Set(i, ls.reg.Get(i+1))
|
|
}
|
|
ls.reg.SetTop(top - 1)
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* object allocation {{{ */
|
|
|
|
func (ls *LState) NewTable() *LTable {
|
|
return newLTable(defaultArrayCap, defaultHashCap)
|
|
}
|
|
|
|
func (ls *LState) CreateTable(acap, hcap int) *LTable {
|
|
return newLTable(acap, hcap)
|
|
}
|
|
|
|
// NewThread returns a new LState that shares with the original state all global objects.
|
|
// If the original state has context.Context, the new state has a new child context of the original state and this function returns its cancel function.
|
|
func (ls *LState) NewThread() (*LState, context.CancelFunc) {
|
|
thread := newLState(ls.Options)
|
|
thread.G = ls.G
|
|
thread.Env = ls.Env
|
|
var f context.CancelFunc = nil
|
|
if ls.ctx != nil {
|
|
thread.mainLoop = mainLoopWithContext
|
|
thread.ctx, f = context.WithCancel(ls.ctx)
|
|
}
|
|
return thread, f
|
|
}
|
|
|
|
func (ls *LState) NewUserData() *LUserData {
|
|
return &LUserData{
|
|
Env: ls.currentEnv(),
|
|
Metatable: LNil,
|
|
}
|
|
}
|
|
|
|
func (ls *LState) NewFunction(fn LGFunction) *LFunction {
|
|
return newLFunctionG(fn, ls.currentEnv(), 0)
|
|
}
|
|
|
|
func (ls *LState) NewClosure(fn LGFunction, upvalues ...LValue) *LFunction {
|
|
cl := newLFunctionG(fn, ls.currentEnv(), len(upvalues))
|
|
for i, lv := range upvalues {
|
|
cl.Upvalues[i] = &Upvalue{}
|
|
cl.Upvalues[i].Close()
|
|
cl.Upvalues[i].SetValue(lv)
|
|
}
|
|
return cl
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* toType {{{ */
|
|
|
|
func (ls *LState) ToBool(n int) bool {
|
|
return LVAsBool(ls.Get(n))
|
|
}
|
|
|
|
func (ls *LState) ToInt(n int) int {
|
|
if lv, ok := ls.Get(n).(LNumber); ok {
|
|
return int(lv)
|
|
}
|
|
if lv, ok := ls.Get(n).(LString); ok {
|
|
if num, err := parseNumber(string(lv)); err == nil {
|
|
return int(num)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (ls *LState) ToInt64(n int) int64 {
|
|
if lv, ok := ls.Get(n).(LNumber); ok {
|
|
return int64(lv)
|
|
}
|
|
if lv, ok := ls.Get(n).(LString); ok {
|
|
if num, err := parseNumber(string(lv)); err == nil {
|
|
return int64(num)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (ls *LState) ToNumber(n int) LNumber {
|
|
return LVAsNumber(ls.Get(n))
|
|
}
|
|
|
|
func (ls *LState) ToString(n int) string {
|
|
return LVAsString(ls.Get(n))
|
|
}
|
|
|
|
func (ls *LState) ToTable(n int) *LTable {
|
|
if lv, ok := ls.Get(n).(*LTable); ok {
|
|
return lv
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *LState) ToFunction(n int) *LFunction {
|
|
if lv, ok := ls.Get(n).(*LFunction); ok {
|
|
return lv
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *LState) ToUserData(n int) *LUserData {
|
|
if lv, ok := ls.Get(n).(*LUserData); ok {
|
|
return lv
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ls *LState) ToThread(n int) *LState {
|
|
if lv, ok := ls.Get(n).(*LState); ok {
|
|
return lv
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* error & debug operations {{{ */
|
|
|
|
// This function is equivalent to luaL_error( http://www.lua.org/manual/5.1/manual.html#luaL_error ).
|
|
func (ls *LState) RaiseError(format string, args ...interface{}) {
|
|
ls.raiseError(1, format, args...)
|
|
}
|
|
|
|
// This function is equivalent to lua_error( http://www.lua.org/manual/5.1/manual.html#lua_error ).
|
|
func (ls *LState) Error(lv LValue, level int) {
|
|
if str, ok := lv.(LString); ok {
|
|
ls.raiseError(level, string(str))
|
|
} else {
|
|
if !ls.hasErrorFunc {
|
|
ls.closeAllUpvalues()
|
|
}
|
|
ls.Push(lv)
|
|
ls.Panic(ls)
|
|
}
|
|
}
|
|
|
|
func (ls *LState) GetInfo(what string, dbg *Debug, fn LValue) (LValue, error) {
|
|
if !strings.HasPrefix(what, ">") {
|
|
fn = dbg.frame.Fn
|
|
} else {
|
|
what = what[1:]
|
|
}
|
|
f, ok := fn.(*LFunction)
|
|
if !ok {
|
|
return LNil, newApiErrorS(ApiErrorRun, "can not get debug info(an object in not a function)")
|
|
}
|
|
|
|
retfn := false
|
|
for _, c := range what {
|
|
switch c {
|
|
case 'f':
|
|
retfn = true
|
|
case 'S':
|
|
if dbg.frame != nil && dbg.frame.Parent == nil {
|
|
dbg.What = "main"
|
|
} else if f.IsG {
|
|
dbg.What = "G"
|
|
} else if dbg.frame != nil && dbg.frame.TailCall > 0 {
|
|
dbg.What = "tail"
|
|
} else {
|
|
dbg.What = "Lua"
|
|
}
|
|
if !f.IsG {
|
|
dbg.Source = f.Proto.SourceName
|
|
dbg.LineDefined = f.Proto.LineDefined
|
|
dbg.LastLineDefined = f.Proto.LastLineDefined
|
|
}
|
|
case 'l':
|
|
if !f.IsG && dbg.frame != nil {
|
|
if dbg.frame.Pc > 0 {
|
|
dbg.CurrentLine = f.Proto.DbgSourcePositions[dbg.frame.Pc-1]
|
|
}
|
|
} else {
|
|
dbg.CurrentLine = -1
|
|
}
|
|
case 'u':
|
|
dbg.NUpvalues = len(f.Upvalues)
|
|
case 'n':
|
|
if dbg.frame != nil {
|
|
dbg.Name = ls.rawFrameFuncName(dbg.frame)
|
|
}
|
|
default:
|
|
return LNil, newApiErrorS(ApiErrorRun, "invalid what: "+string(c))
|
|
}
|
|
}
|
|
|
|
if retfn {
|
|
return f, nil
|
|
}
|
|
return LNil, nil
|
|
|
|
}
|
|
|
|
func (ls *LState) GetStack(level int) (*Debug, bool) {
|
|
frame := ls.currentFrame
|
|
for ; level > 0 && frame != nil; frame = frame.Parent {
|
|
level--
|
|
if !frame.Fn.IsG {
|
|
level -= frame.TailCall
|
|
}
|
|
}
|
|
|
|
if level == 0 && frame != nil {
|
|
return &Debug{frame: frame}, true
|
|
} else if level < 0 && ls.stack.Sp() > 0 {
|
|
return &Debug{frame: ls.stack.At(0)}, true
|
|
}
|
|
return &Debug{}, false
|
|
}
|
|
|
|
func (ls *LState) GetLocal(dbg *Debug, no int) (string, LValue) {
|
|
frame := dbg.frame
|
|
if name := ls.findLocal(frame, no); len(name) > 0 {
|
|
return name, ls.reg.Get(frame.LocalBase + no - 1)
|
|
}
|
|
return "", LNil
|
|
}
|
|
|
|
func (ls *LState) SetLocal(dbg *Debug, no int, lv LValue) string {
|
|
frame := dbg.frame
|
|
if name := ls.findLocal(frame, no); len(name) > 0 {
|
|
ls.reg.Set(frame.LocalBase+no-1, lv)
|
|
return name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (ls *LState) GetUpvalue(fn *LFunction, no int) (string, LValue) {
|
|
if fn.IsG {
|
|
return "", LNil
|
|
}
|
|
|
|
no--
|
|
if no >= 0 && no < len(fn.Upvalues) {
|
|
return fn.Proto.DbgUpvalues[no], fn.Upvalues[no].Value()
|
|
}
|
|
return "", LNil
|
|
}
|
|
|
|
func (ls *LState) SetUpvalue(fn *LFunction, no int, lv LValue) string {
|
|
if fn.IsG {
|
|
return ""
|
|
}
|
|
|
|
no--
|
|
if no >= 0 && no < len(fn.Upvalues) {
|
|
fn.Upvalues[no].SetValue(lv)
|
|
return fn.Proto.DbgUpvalues[no]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* env operations {{{ */
|
|
|
|
func (ls *LState) GetFEnv(obj LValue) LValue {
|
|
switch lv := obj.(type) {
|
|
case *LFunction:
|
|
return lv.Env
|
|
case *LUserData:
|
|
return lv.Env
|
|
case *LState:
|
|
return lv.Env
|
|
}
|
|
return LNil
|
|
}
|
|
|
|
func (ls *LState) SetFEnv(obj LValue, env LValue) {
|
|
tb, ok := env.(*LTable)
|
|
if !ok {
|
|
ls.RaiseError("cannot use %v as an environment", env.Type().String())
|
|
}
|
|
|
|
switch lv := obj.(type) {
|
|
case *LFunction:
|
|
lv.Env = tb
|
|
case *LUserData:
|
|
lv.Env = tb
|
|
case *LState:
|
|
lv.Env = tb
|
|
}
|
|
/* do nothing */
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* table operations {{{ */
|
|
|
|
func (ls *LState) RawGet(tb *LTable, key LValue) LValue {
|
|
return tb.RawGet(key)
|
|
}
|
|
|
|
func (ls *LState) RawGetInt(tb *LTable, key int) LValue {
|
|
return tb.RawGetInt(key)
|
|
}
|
|
|
|
func (ls *LState) GetField(obj LValue, skey string) LValue {
|
|
return ls.getFieldString(obj, skey)
|
|
}
|
|
|
|
func (ls *LState) GetTable(obj LValue, key LValue) LValue {
|
|
return ls.getField(obj, key)
|
|
}
|
|
|
|
func (ls *LState) RawSet(tb *LTable, key LValue, value LValue) {
|
|
if n, ok := key.(LNumber); ok && math.IsNaN(float64(n)) {
|
|
ls.RaiseError("table index is NaN")
|
|
} else if key == LNil {
|
|
ls.RaiseError("table index is nil")
|
|
}
|
|
tb.RawSet(key, value)
|
|
}
|
|
|
|
func (ls *LState) RawSetInt(tb *LTable, key int, value LValue) {
|
|
tb.RawSetInt(key, value)
|
|
}
|
|
|
|
func (ls *LState) SetField(obj LValue, key string, value LValue) {
|
|
ls.setFieldString(obj, key, value)
|
|
}
|
|
|
|
func (ls *LState) SetTable(obj LValue, key LValue, value LValue) {
|
|
ls.setField(obj, key, value)
|
|
}
|
|
|
|
func (ls *LState) ForEach(tb *LTable, cb func(LValue, LValue)) {
|
|
tb.ForEach(cb)
|
|
}
|
|
|
|
func (ls *LState) GetGlobal(name string) LValue {
|
|
return ls.GetField(ls.Get(GlobalsIndex), name)
|
|
}
|
|
|
|
func (ls *LState) SetGlobal(name string, value LValue) {
|
|
ls.SetField(ls.Get(GlobalsIndex), name, value)
|
|
}
|
|
|
|
func (ls *LState) Next(tb *LTable, key LValue) (LValue, LValue) {
|
|
return tb.Next(key)
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* unary operations {{{ */
|
|
|
|
func (ls *LState) ObjLen(v1 LValue) int {
|
|
if v1.Type() == LTString {
|
|
return len(string(v1.(LString)))
|
|
}
|
|
op := ls.metaOp1(v1, "__len")
|
|
if op.Type() == LTFunction {
|
|
ls.Push(op)
|
|
ls.Push(v1)
|
|
ls.Call(1, 1)
|
|
ret := ls.reg.Pop()
|
|
if ret.Type() == LTNumber {
|
|
return int(ret.(LNumber))
|
|
}
|
|
} else if v1.Type() == LTTable {
|
|
return v1.(*LTable).Len()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* binary operations {{{ */
|
|
|
|
func (ls *LState) Concat(values ...LValue) string {
|
|
top := ls.reg.Top()
|
|
for _, value := range values {
|
|
ls.reg.Push(value)
|
|
}
|
|
ret := stringConcat(ls, len(values), ls.reg.Top()-1)
|
|
ls.reg.SetTop(top)
|
|
return LVAsString(ret)
|
|
}
|
|
|
|
func (ls *LState) LessThan(lhs, rhs LValue) bool {
|
|
return lessThan(ls, lhs, rhs)
|
|
}
|
|
|
|
func (ls *LState) Equal(lhs, rhs LValue) bool {
|
|
return equals(ls, lhs, rhs, false)
|
|
}
|
|
|
|
func (ls *LState) RawEqual(lhs, rhs LValue) bool {
|
|
return equals(ls, lhs, rhs, true)
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* register operations {{{ */
|
|
|
|
func (ls *LState) Register(name string, fn LGFunction) {
|
|
ls.SetGlobal(name, ls.NewFunction(fn))
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* load and function call operations {{{ */
|
|
|
|
func (ls *LState) Load(reader io.Reader, name string) (*LFunction, error) {
|
|
chunk, err := parse.Parse(reader, name)
|
|
if err != nil {
|
|
return nil, newApiErrorE(ApiErrorSyntax, err)
|
|
}
|
|
proto, err := Compile(chunk, name)
|
|
if err != nil {
|
|
return nil, newApiErrorE(ApiErrorSyntax, err)
|
|
}
|
|
return newLFunctionL(proto, ls.currentEnv(), 0), nil
|
|
}
|
|
|
|
func (ls *LState) Call(nargs, nret int) {
|
|
ls.callR(nargs, nret, -1)
|
|
}
|
|
|
|
func (ls *LState) PCall(nargs, nret int, errfunc *LFunction) (err error) {
|
|
err = nil
|
|
sp := ls.stack.Sp()
|
|
base := ls.reg.Top() - nargs - 1
|
|
oldpanic := ls.Panic
|
|
ls.Panic = panicWithoutTraceback
|
|
if errfunc != nil {
|
|
ls.hasErrorFunc = true
|
|
}
|
|
defer func() {
|
|
ls.Panic = oldpanic
|
|
ls.hasErrorFunc = false
|
|
rcv := recover()
|
|
if rcv != nil {
|
|
if _, ok := rcv.(*ApiError); !ok {
|
|
err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv))
|
|
if ls.Options.IncludeGoStackTrace {
|
|
buf := make([]byte, 4096)
|
|
runtime.Stack(buf, false)
|
|
err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + "\n" + ls.stackTrace(0)
|
|
}
|
|
} else {
|
|
err = rcv.(*ApiError)
|
|
}
|
|
if errfunc != nil {
|
|
ls.Push(errfunc)
|
|
ls.Push(err.(*ApiError).Object)
|
|
ls.Panic = panicWithoutTraceback
|
|
defer func() {
|
|
ls.Panic = oldpanic
|
|
rcv := recover()
|
|
if rcv != nil {
|
|
if _, ok := rcv.(*ApiError); !ok {
|
|
err = newApiErrorS(ApiErrorPanic, fmt.Sprint(rcv))
|
|
if ls.Options.IncludeGoStackTrace {
|
|
buf := make([]byte, 4096)
|
|
runtime.Stack(buf, false)
|
|
err.(*ApiError).StackTrace = strings.Trim(string(buf), "\000") + ls.stackTrace(0)
|
|
}
|
|
} else {
|
|
err = rcv.(*ApiError)
|
|
err.(*ApiError).StackTrace = ls.stackTrace(0)
|
|
}
|
|
}
|
|
}()
|
|
ls.Call(1, 1)
|
|
err = newApiError(ApiErrorError, ls.Get(-1))
|
|
} else if len(err.(*ApiError).StackTrace) == 0 {
|
|
err.(*ApiError).StackTrace = ls.stackTrace(0)
|
|
}
|
|
ls.stack.SetSp(sp)
|
|
ls.currentFrame = ls.stack.Last()
|
|
ls.reg.SetTop(base)
|
|
}
|
|
ls.stack.SetSp(sp)
|
|
if sp == 0 {
|
|
ls.currentFrame = nil
|
|
}
|
|
}()
|
|
|
|
ls.Call(nargs, nret)
|
|
|
|
return
|
|
}
|
|
|
|
func (ls *LState) GPCall(fn LGFunction, data LValue) error {
|
|
ls.Push(newLFunctionG(fn, ls.currentEnv(), 0))
|
|
ls.Push(data)
|
|
return ls.PCall(1, MultRet, nil)
|
|
}
|
|
|
|
func (ls *LState) CallByParam(cp P, args ...LValue) error {
|
|
ls.Push(cp.Fn)
|
|
for _, arg := range args {
|
|
ls.Push(arg)
|
|
}
|
|
|
|
if cp.Protect {
|
|
return ls.PCall(len(args), cp.NRet, cp.Handler)
|
|
}
|
|
ls.Call(len(args), cp.NRet)
|
|
return nil
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* metatable operations {{{ */
|
|
|
|
func (ls *LState) GetMetatable(obj LValue) LValue {
|
|
return ls.metatable(obj, false)
|
|
}
|
|
|
|
func (ls *LState) SetMetatable(obj LValue, mt LValue) {
|
|
switch mt.(type) {
|
|
case *LNilType, *LTable:
|
|
default:
|
|
ls.RaiseError("metatable must be a table or nil, but got %v", mt.Type().String())
|
|
}
|
|
|
|
switch v := obj.(type) {
|
|
case *LTable:
|
|
v.Metatable = mt
|
|
case *LUserData:
|
|
v.Metatable = mt
|
|
default:
|
|
ls.G.builtinMts[int(obj.Type())] = mt
|
|
}
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* coroutine operations {{{ */
|
|
|
|
func (ls *LState) Status(th *LState) string {
|
|
status := "suspended"
|
|
if th.Dead {
|
|
status = "dead"
|
|
} else if ls.G.CurrentThread == th {
|
|
status = "running"
|
|
} else if ls.Parent == th {
|
|
status = "normal"
|
|
}
|
|
return status
|
|
}
|
|
|
|
func (ls *LState) Resume(th *LState, fn *LFunction, args ...LValue) (ResumeState, error, []LValue) {
|
|
isstarted := th.isStarted()
|
|
if !isstarted {
|
|
base := 0
|
|
th.stack.Push(callFrame{
|
|
Fn: fn,
|
|
Pc: 0,
|
|
Base: base,
|
|
LocalBase: base + 1,
|
|
ReturnBase: base,
|
|
NArgs: 0,
|
|
NRet: MultRet,
|
|
Parent: nil,
|
|
TailCall: 0,
|
|
})
|
|
}
|
|
|
|
if ls.G.CurrentThread == th {
|
|
return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a running thread"), nil
|
|
}
|
|
if th.Dead {
|
|
return ResumeError, newApiErrorS(ApiErrorRun, "can not resume a dead thread"), nil
|
|
}
|
|
th.Parent = ls
|
|
ls.G.CurrentThread = th
|
|
if !isstarted {
|
|
cf := th.stack.Last()
|
|
th.currentFrame = cf
|
|
th.SetTop(0)
|
|
for _, arg := range args {
|
|
th.Push(arg)
|
|
}
|
|
cf.NArgs = len(args)
|
|
th.initCallFrame(cf)
|
|
th.Panic = panicWithoutTraceback
|
|
} else {
|
|
for _, arg := range args {
|
|
th.Push(arg)
|
|
}
|
|
}
|
|
top := ls.GetTop()
|
|
threadRun(th)
|
|
haserror := LVIsFalse(ls.Get(top + 1))
|
|
ret := make([]LValue, 0, ls.GetTop())
|
|
for idx := top + 2; idx <= ls.GetTop(); idx++ {
|
|
ret = append(ret, ls.Get(idx))
|
|
}
|
|
if len(ret) == 0 {
|
|
ret = append(ret, LNil)
|
|
}
|
|
ls.SetTop(top)
|
|
|
|
if haserror {
|
|
return ResumeError, newApiError(ApiErrorRun, ret[0]), nil
|
|
} else if th.stack.IsEmpty() {
|
|
return ResumeOK, nil, ret
|
|
}
|
|
return ResumeYield, nil, ret
|
|
}
|
|
|
|
func (ls *LState) Yield(values ...LValue) int {
|
|
ls.SetTop(0)
|
|
for _, lv := range values {
|
|
ls.Push(lv)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (ls *LState) XMoveTo(other *LState, n int) {
|
|
if ls == other {
|
|
return
|
|
}
|
|
top := ls.GetTop()
|
|
n = intMin(n, top)
|
|
for i := n; i > 0; i-- {
|
|
other.Push(ls.Get(top - i + 1))
|
|
}
|
|
ls.SetTop(top - n)
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* GopherLua original APIs {{{ */
|
|
|
|
// Set maximum memory size. This function can only be called from the main thread.
|
|
func (ls *LState) SetMx(mx int) {
|
|
if ls.Parent != nil {
|
|
ls.RaiseError("sub threads are not allowed to set a memory limit")
|
|
}
|
|
go func() {
|
|
limit := uint64(mx * 1024 * 1024) //MB
|
|
var s runtime.MemStats
|
|
for ls.stop == 0 {
|
|
runtime.ReadMemStats(&s)
|
|
if s.Alloc >= limit {
|
|
fmt.Println("out of memory")
|
|
os.Exit(3)
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
}()
|
|
}
|
|
|
|
// SetContext set a context ctx to this LState. The provided ctx must be non-nil.
|
|
func (ls *LState) SetContext(ctx context.Context) {
|
|
ls.mainLoop = mainLoopWithContext
|
|
ls.ctx = ctx
|
|
}
|
|
|
|
// Context returns the LState's context. To change the context, use WithContext.
|
|
func (ls *LState) Context() context.Context {
|
|
return ls.ctx
|
|
}
|
|
|
|
// RemoveContext removes the context associated with this LState and returns this context.
|
|
func (ls *LState) RemoveContext() context.Context {
|
|
oldctx := ls.ctx
|
|
ls.mainLoop = mainLoop
|
|
ls.ctx = nil
|
|
return oldctx
|
|
}
|
|
|
|
// Converts the Lua value at the given acceptable index to the chan LValue.
|
|
func (ls *LState) ToChannel(n int) chan LValue {
|
|
if lv, ok := ls.Get(n).(LChannel); ok {
|
|
return (chan LValue)(lv)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* }}} */
|
|
|
|
//
|