forked from lug/matterbridge
		
	
		
			
				
	
	
		
			884 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			884 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package tengo
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"sync/atomic"
 | 
						|
 | 
						|
	"github.com/d5/tengo/v2/parser"
 | 
						|
	"github.com/d5/tengo/v2/token"
 | 
						|
)
 | 
						|
 | 
						|
// frame represents a function call frame.
 | 
						|
type frame struct {
 | 
						|
	fn          *CompiledFunction
 | 
						|
	freeVars    []*ObjectPtr
 | 
						|
	ip          int
 | 
						|
	basePointer int
 | 
						|
}
 | 
						|
 | 
						|
// VM is a virtual machine that executes the bytecode compiled by Compiler.
 | 
						|
type VM struct {
 | 
						|
	constants   []Object
 | 
						|
	stack       [StackSize]Object
 | 
						|
	sp          int
 | 
						|
	globals     []Object
 | 
						|
	fileSet     *parser.SourceFileSet
 | 
						|
	frames      [MaxFrames]frame
 | 
						|
	framesIndex int
 | 
						|
	curFrame    *frame
 | 
						|
	curInsts    []byte
 | 
						|
	ip          int
 | 
						|
	aborting    int64
 | 
						|
	maxAllocs   int64
 | 
						|
	allocs      int64
 | 
						|
	err         error
 | 
						|
}
 | 
						|
 | 
						|
// NewVM creates a VM.
 | 
						|
func NewVM(
 | 
						|
	bytecode *Bytecode,
 | 
						|
	globals []Object,
 | 
						|
	maxAllocs int64,
 | 
						|
) *VM {
 | 
						|
	if globals == nil {
 | 
						|
		globals = make([]Object, GlobalsSize)
 | 
						|
	}
 | 
						|
	v := &VM{
 | 
						|
		constants:   bytecode.Constants,
 | 
						|
		sp:          0,
 | 
						|
		globals:     globals,
 | 
						|
		fileSet:     bytecode.FileSet,
 | 
						|
		framesIndex: 1,
 | 
						|
		ip:          -1,
 | 
						|
		maxAllocs:   maxAllocs,
 | 
						|
	}
 | 
						|
	v.frames[0].fn = bytecode.MainFunction
 | 
						|
	v.frames[0].ip = -1
 | 
						|
	v.curFrame = &v.frames[0]
 | 
						|
	v.curInsts = v.curFrame.fn.Instructions
 | 
						|
	return v
 | 
						|
}
 | 
						|
 | 
						|
// Abort aborts the execution.
 | 
						|
func (v *VM) Abort() {
 | 
						|
	atomic.StoreInt64(&v.aborting, 1)
 | 
						|
}
 | 
						|
 | 
						|
// Run starts the execution.
 | 
						|
func (v *VM) Run() (err error) {
 | 
						|
	// reset VM states
 | 
						|
	v.sp = 0
 | 
						|
	v.curFrame = &(v.frames[0])
 | 
						|
	v.curInsts = v.curFrame.fn.Instructions
 | 
						|
	v.framesIndex = 1
 | 
						|
	v.ip = -1
 | 
						|
	v.allocs = v.maxAllocs + 1
 | 
						|
 | 
						|
	v.run()
 | 
						|
	atomic.StoreInt64(&v.aborting, 0)
 | 
						|
	err = v.err
 | 
						|
	if err != nil {
 | 
						|
		filePos := v.fileSet.Position(
 | 
						|
			v.curFrame.fn.SourcePos(v.ip - 1))
 | 
						|
		err = fmt.Errorf("Runtime Error: %w\n\tat %s",
 | 
						|
			err, filePos)
 | 
						|
		for v.framesIndex > 1 {
 | 
						|
			v.framesIndex--
 | 
						|
			v.curFrame = &v.frames[v.framesIndex-1]
 | 
						|
			filePos = v.fileSet.Position(
 | 
						|
				v.curFrame.fn.SourcePos(v.curFrame.ip - 1))
 | 
						|
			err = fmt.Errorf("%w\n\tat %s", err, filePos)
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (v *VM) run() {
 | 
						|
	for atomic.LoadInt64(&v.aborting) == 0 {
 | 
						|
		v.ip++
 | 
						|
 | 
						|
		switch v.curInsts[v.ip] {
 | 
						|
		case parser.OpConstant:
 | 
						|
			v.ip += 2
 | 
						|
			cidx := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
 | 
						|
			v.stack[v.sp] = v.constants[cidx]
 | 
						|
			v.sp++
 | 
						|
		case parser.OpNull:
 | 
						|
			v.stack[v.sp] = UndefinedValue
 | 
						|
			v.sp++
 | 
						|
		case parser.OpBinaryOp:
 | 
						|
			v.ip++
 | 
						|
			right := v.stack[v.sp-1]
 | 
						|
			left := v.stack[v.sp-2]
 | 
						|
			tok := token.Token(v.curInsts[v.ip])
 | 
						|
			res, e := left.BinaryOp(tok, right)
 | 
						|
			if e != nil {
 | 
						|
				v.sp -= 2
 | 
						|
				if e == ErrInvalidOperator {
 | 
						|
					v.err = fmt.Errorf("invalid operation: %s %s %s",
 | 
						|
						left.TypeName(), tok.String(), right.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.err = e
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			v.stack[v.sp-2] = res
 | 
						|
			v.sp--
 | 
						|
		case parser.OpEqual:
 | 
						|
			right := v.stack[v.sp-1]
 | 
						|
			left := v.stack[v.sp-2]
 | 
						|
			v.sp -= 2
 | 
						|
			if left.Equals(right) {
 | 
						|
				v.stack[v.sp] = TrueValue
 | 
						|
			} else {
 | 
						|
				v.stack[v.sp] = FalseValue
 | 
						|
			}
 | 
						|
			v.sp++
 | 
						|
		case parser.OpNotEqual:
 | 
						|
			right := v.stack[v.sp-1]
 | 
						|
			left := v.stack[v.sp-2]
 | 
						|
			v.sp -= 2
 | 
						|
			if left.Equals(right) {
 | 
						|
				v.stack[v.sp] = FalseValue
 | 
						|
			} else {
 | 
						|
				v.stack[v.sp] = TrueValue
 | 
						|
			}
 | 
						|
			v.sp++
 | 
						|
		case parser.OpPop:
 | 
						|
			v.sp--
 | 
						|
		case parser.OpTrue:
 | 
						|
			v.stack[v.sp] = TrueValue
 | 
						|
			v.sp++
 | 
						|
		case parser.OpFalse:
 | 
						|
			v.stack[v.sp] = FalseValue
 | 
						|
			v.sp++
 | 
						|
		case parser.OpLNot:
 | 
						|
			operand := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			if operand.IsFalsy() {
 | 
						|
				v.stack[v.sp] = TrueValue
 | 
						|
			} else {
 | 
						|
				v.stack[v.sp] = FalseValue
 | 
						|
			}
 | 
						|
			v.sp++
 | 
						|
		case parser.OpBComplement:
 | 
						|
			operand := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
 | 
						|
			switch x := operand.(type) {
 | 
						|
			case *Int:
 | 
						|
				var res Object = &Int{Value: ^x.Value}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = res
 | 
						|
				v.sp++
 | 
						|
			default:
 | 
						|
				v.err = fmt.Errorf("invalid operation: ^%s",
 | 
						|
					operand.TypeName())
 | 
						|
				return
 | 
						|
			}
 | 
						|
		case parser.OpMinus:
 | 
						|
			operand := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
 | 
						|
			switch x := operand.(type) {
 | 
						|
			case *Int:
 | 
						|
				var res Object = &Int{Value: -x.Value}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = res
 | 
						|
				v.sp++
 | 
						|
			case *Float:
 | 
						|
				var res Object = &Float{Value: -x.Value}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = res
 | 
						|
				v.sp++
 | 
						|
			default:
 | 
						|
				v.err = fmt.Errorf("invalid operation: -%s",
 | 
						|
					operand.TypeName())
 | 
						|
				return
 | 
						|
			}
 | 
						|
		case parser.OpJumpFalsy:
 | 
						|
			v.ip += 2
 | 
						|
			v.sp--
 | 
						|
			if v.stack[v.sp].IsFalsy() {
 | 
						|
				pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
				v.ip = pos - 1
 | 
						|
			}
 | 
						|
		case parser.OpAndJump:
 | 
						|
			v.ip += 2
 | 
						|
			if v.stack[v.sp-1].IsFalsy() {
 | 
						|
				pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
				v.ip = pos - 1
 | 
						|
			} else {
 | 
						|
				v.sp--
 | 
						|
			}
 | 
						|
		case parser.OpOrJump:
 | 
						|
			v.ip += 2
 | 
						|
			if v.stack[v.sp-1].IsFalsy() {
 | 
						|
				v.sp--
 | 
						|
			} else {
 | 
						|
				pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
				v.ip = pos - 1
 | 
						|
			}
 | 
						|
		case parser.OpJump:
 | 
						|
			pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
 | 
						|
			v.ip = pos - 1
 | 
						|
		case parser.OpSetGlobal:
 | 
						|
			v.ip += 2
 | 
						|
			v.sp--
 | 
						|
			globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
			v.globals[globalIndex] = v.stack[v.sp]
 | 
						|
		case parser.OpSetSelGlobal:
 | 
						|
			v.ip += 3
 | 
						|
			globalIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8
 | 
						|
			numSelectors := int(v.curInsts[v.ip])
 | 
						|
 | 
						|
			// selectors and RHS value
 | 
						|
			selectors := make([]Object, numSelectors)
 | 
						|
			for i := 0; i < numSelectors; i++ {
 | 
						|
				selectors[i] = v.stack[v.sp-numSelectors+i]
 | 
						|
			}
 | 
						|
			val := v.stack[v.sp-numSelectors-1]
 | 
						|
			v.sp -= numSelectors + 1
 | 
						|
			e := indexAssign(v.globals[globalIndex], val, selectors)
 | 
						|
			if e != nil {
 | 
						|
				v.err = e
 | 
						|
				return
 | 
						|
			}
 | 
						|
		case parser.OpGetGlobal:
 | 
						|
			v.ip += 2
 | 
						|
			globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
			val := v.globals[globalIndex]
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpArray:
 | 
						|
			v.ip += 2
 | 
						|
			numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
 | 
						|
			var elements []Object
 | 
						|
			for i := v.sp - numElements; i < v.sp; i++ {
 | 
						|
				elements = append(elements, v.stack[i])
 | 
						|
			}
 | 
						|
			v.sp -= numElements
 | 
						|
 | 
						|
			var arr Object = &Array{Value: elements}
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			v.stack[v.sp] = arr
 | 
						|
			v.sp++
 | 
						|
		case parser.OpMap:
 | 
						|
			v.ip += 2
 | 
						|
			numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8
 | 
						|
			kv := make(map[string]Object)
 | 
						|
			for i := v.sp - numElements; i < v.sp; i += 2 {
 | 
						|
				key := v.stack[i]
 | 
						|
				value := v.stack[i+1]
 | 
						|
				kv[key.(*String).Value] = value
 | 
						|
			}
 | 
						|
			v.sp -= numElements
 | 
						|
 | 
						|
			var m Object = &Map{Value: kv}
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = m
 | 
						|
			v.sp++
 | 
						|
		case parser.OpError:
 | 
						|
			value := v.stack[v.sp-1]
 | 
						|
			var e Object = &Error{
 | 
						|
				Value: value,
 | 
						|
			}
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
			v.stack[v.sp-1] = e
 | 
						|
		case parser.OpImmutable:
 | 
						|
			value := v.stack[v.sp-1]
 | 
						|
			switch value := value.(type) {
 | 
						|
			case *Array:
 | 
						|
				var immutableArray Object = &ImmutableArray{
 | 
						|
					Value: value.Value,
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp-1] = immutableArray
 | 
						|
			case *Map:
 | 
						|
				var immutableMap Object = &ImmutableMap{
 | 
						|
					Value: value.Value,
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp-1] = immutableMap
 | 
						|
			}
 | 
						|
		case parser.OpIndex:
 | 
						|
			index := v.stack[v.sp-1]
 | 
						|
			left := v.stack[v.sp-2]
 | 
						|
			v.sp -= 2
 | 
						|
 | 
						|
			val, err := left.IndexGet(index)
 | 
						|
			if err != nil {
 | 
						|
				if err == ErrNotIndexable {
 | 
						|
					v.err = fmt.Errorf("not indexable: %s", index.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if err == ErrInvalidIndexType {
 | 
						|
					v.err = fmt.Errorf("invalid index type: %s",
 | 
						|
						index.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.err = err
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if val == nil {
 | 
						|
				val = UndefinedValue
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpSliceIndex:
 | 
						|
			high := v.stack[v.sp-1]
 | 
						|
			low := v.stack[v.sp-2]
 | 
						|
			left := v.stack[v.sp-3]
 | 
						|
			v.sp -= 3
 | 
						|
 | 
						|
			var lowIdx int64
 | 
						|
			if low != UndefinedValue {
 | 
						|
				if low, ok := low.(*Int); ok {
 | 
						|
					lowIdx = low.Value
 | 
						|
				} else {
 | 
						|
					v.err = fmt.Errorf("invalid slice index type: %s",
 | 
						|
						low.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			switch left := left.(type) {
 | 
						|
			case *Array:
 | 
						|
				numElements := int64(len(left.Value))
 | 
						|
				var highIdx int64
 | 
						|
				if high == UndefinedValue {
 | 
						|
					highIdx = numElements
 | 
						|
				} else if high, ok := high.(*Int); ok {
 | 
						|
					highIdx = high.Value
 | 
						|
				} else {
 | 
						|
					v.err = fmt.Errorf("invalid slice index type: %s",
 | 
						|
						high.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx > highIdx {
 | 
						|
					v.err = fmt.Errorf("invalid slice index: %d > %d",
 | 
						|
						lowIdx, highIdx)
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx < 0 {
 | 
						|
					lowIdx = 0
 | 
						|
				} else if lowIdx > numElements {
 | 
						|
					lowIdx = numElements
 | 
						|
				}
 | 
						|
				if highIdx < 0 {
 | 
						|
					highIdx = 0
 | 
						|
				} else if highIdx > numElements {
 | 
						|
					highIdx = numElements
 | 
						|
				}
 | 
						|
				var val Object = &Array{
 | 
						|
					Value: left.Value[lowIdx:highIdx],
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = val
 | 
						|
				v.sp++
 | 
						|
			case *ImmutableArray:
 | 
						|
				numElements := int64(len(left.Value))
 | 
						|
				var highIdx int64
 | 
						|
				if high == UndefinedValue {
 | 
						|
					highIdx = numElements
 | 
						|
				} else if high, ok := high.(*Int); ok {
 | 
						|
					highIdx = high.Value
 | 
						|
				} else {
 | 
						|
					v.err = fmt.Errorf("invalid slice index type: %s",
 | 
						|
						high.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx > highIdx {
 | 
						|
					v.err = fmt.Errorf("invalid slice index: %d > %d",
 | 
						|
						lowIdx, highIdx)
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx < 0 {
 | 
						|
					lowIdx = 0
 | 
						|
				} else if lowIdx > numElements {
 | 
						|
					lowIdx = numElements
 | 
						|
				}
 | 
						|
				if highIdx < 0 {
 | 
						|
					highIdx = 0
 | 
						|
				} else if highIdx > numElements {
 | 
						|
					highIdx = numElements
 | 
						|
				}
 | 
						|
				var val Object = &Array{
 | 
						|
					Value: left.Value[lowIdx:highIdx],
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = val
 | 
						|
				v.sp++
 | 
						|
			case *String:
 | 
						|
				numElements := int64(len(left.Value))
 | 
						|
				var highIdx int64
 | 
						|
				if high == UndefinedValue {
 | 
						|
					highIdx = numElements
 | 
						|
				} else if high, ok := high.(*Int); ok {
 | 
						|
					highIdx = high.Value
 | 
						|
				} else {
 | 
						|
					v.err = fmt.Errorf("invalid slice index type: %s",
 | 
						|
						high.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx > highIdx {
 | 
						|
					v.err = fmt.Errorf("invalid slice index: %d > %d",
 | 
						|
						lowIdx, highIdx)
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx < 0 {
 | 
						|
					lowIdx = 0
 | 
						|
				} else if lowIdx > numElements {
 | 
						|
					lowIdx = numElements
 | 
						|
				}
 | 
						|
				if highIdx < 0 {
 | 
						|
					highIdx = 0
 | 
						|
				} else if highIdx > numElements {
 | 
						|
					highIdx = numElements
 | 
						|
				}
 | 
						|
				var val Object = &String{
 | 
						|
					Value: left.Value[lowIdx:highIdx],
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = val
 | 
						|
				v.sp++
 | 
						|
			case *Bytes:
 | 
						|
				numElements := int64(len(left.Value))
 | 
						|
				var highIdx int64
 | 
						|
				if high == UndefinedValue {
 | 
						|
					highIdx = numElements
 | 
						|
				} else if high, ok := high.(*Int); ok {
 | 
						|
					highIdx = high.Value
 | 
						|
				} else {
 | 
						|
					v.err = fmt.Errorf("invalid slice index type: %s",
 | 
						|
						high.TypeName())
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx > highIdx {
 | 
						|
					v.err = fmt.Errorf("invalid slice index: %d > %d",
 | 
						|
						lowIdx, highIdx)
 | 
						|
					return
 | 
						|
				}
 | 
						|
				if lowIdx < 0 {
 | 
						|
					lowIdx = 0
 | 
						|
				} else if lowIdx > numElements {
 | 
						|
					lowIdx = numElements
 | 
						|
				}
 | 
						|
				if highIdx < 0 {
 | 
						|
					highIdx = 0
 | 
						|
				} else if highIdx > numElements {
 | 
						|
					highIdx = numElements
 | 
						|
				}
 | 
						|
				var val Object = &Bytes{
 | 
						|
					Value: left.Value[lowIdx:highIdx],
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = val
 | 
						|
				v.sp++
 | 
						|
			}
 | 
						|
		case parser.OpCall:
 | 
						|
			numArgs := int(v.curInsts[v.ip+1])
 | 
						|
			v.ip++
 | 
						|
			value := v.stack[v.sp-1-numArgs]
 | 
						|
			if !value.CanCall() {
 | 
						|
				v.err = fmt.Errorf("not callable: %s", value.TypeName())
 | 
						|
				return
 | 
						|
			}
 | 
						|
			if callee, ok := value.(*CompiledFunction); ok {
 | 
						|
				if callee.VarArgs {
 | 
						|
					// if the closure is variadic,
 | 
						|
					// roll up all variadic parameters into an array
 | 
						|
					realArgs := callee.NumParameters - 1
 | 
						|
					varArgs := numArgs - realArgs
 | 
						|
					if varArgs >= 0 {
 | 
						|
						numArgs = realArgs + 1
 | 
						|
						args := make([]Object, varArgs)
 | 
						|
						spStart := v.sp - varArgs
 | 
						|
						for i := spStart; i < v.sp; i++ {
 | 
						|
							args[i-spStart] = v.stack[i]
 | 
						|
						}
 | 
						|
						v.stack[spStart] = &Array{Value: args}
 | 
						|
						v.sp = spStart + 1
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if numArgs != callee.NumParameters {
 | 
						|
					if callee.VarArgs {
 | 
						|
						v.err = fmt.Errorf(
 | 
						|
							"wrong number of arguments: want>=%d, got=%d",
 | 
						|
							callee.NumParameters-1, numArgs)
 | 
						|
					} else {
 | 
						|
						v.err = fmt.Errorf(
 | 
						|
							"wrong number of arguments: want=%d, got=%d",
 | 
						|
							callee.NumParameters, numArgs)
 | 
						|
					}
 | 
						|
					return
 | 
						|
				}
 | 
						|
 | 
						|
				// test if it's tail-call
 | 
						|
				if callee == v.curFrame.fn { // recursion
 | 
						|
					nextOp := v.curInsts[v.ip+1]
 | 
						|
					if nextOp == parser.OpReturn ||
 | 
						|
						(nextOp == parser.OpPop &&
 | 
						|
							parser.OpReturn == v.curInsts[v.ip+2]) {
 | 
						|
						for p := 0; p < numArgs; p++ {
 | 
						|
							v.stack[v.curFrame.basePointer+p] =
 | 
						|
								v.stack[v.sp-numArgs+p]
 | 
						|
						}
 | 
						|
						v.sp -= numArgs + 1
 | 
						|
						v.ip = -1 // reset IP to beginning of the frame
 | 
						|
						continue
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if v.framesIndex >= MaxFrames {
 | 
						|
					v.err = ErrStackOverflow
 | 
						|
					return
 | 
						|
				}
 | 
						|
 | 
						|
				// update call frame
 | 
						|
				v.curFrame.ip = v.ip // store current ip before call
 | 
						|
				v.curFrame = &(v.frames[v.framesIndex])
 | 
						|
				v.curFrame.fn = callee
 | 
						|
				v.curFrame.freeVars = callee.Free
 | 
						|
				v.curFrame.basePointer = v.sp - numArgs
 | 
						|
				v.curInsts = callee.Instructions
 | 
						|
				v.ip = -1
 | 
						|
				v.framesIndex++
 | 
						|
				v.sp = v.sp - numArgs + callee.NumLocals
 | 
						|
			} else {
 | 
						|
				var args []Object
 | 
						|
				args = append(args, v.stack[v.sp-numArgs:v.sp]...)
 | 
						|
				ret, e := value.Call(args...)
 | 
						|
				v.sp -= numArgs + 1
 | 
						|
 | 
						|
				// runtime error
 | 
						|
				if e != nil {
 | 
						|
					if e == ErrWrongNumArguments {
 | 
						|
						v.err = fmt.Errorf(
 | 
						|
							"wrong number of arguments in call to '%s'",
 | 
						|
							value.TypeName())
 | 
						|
						return
 | 
						|
					}
 | 
						|
					if e, ok := e.(ErrInvalidArgumentType); ok {
 | 
						|
						v.err = fmt.Errorf(
 | 
						|
							"invalid type for argument '%s' in call to '%s': "+
 | 
						|
								"expected %s, found %s",
 | 
						|
							e.Name, value.TypeName(), e.Expected, e.Found)
 | 
						|
						return
 | 
						|
					}
 | 
						|
					v.err = e
 | 
						|
					return
 | 
						|
				}
 | 
						|
 | 
						|
				// nil return -> undefined
 | 
						|
				if ret == nil {
 | 
						|
					ret = UndefinedValue
 | 
						|
				}
 | 
						|
				v.allocs--
 | 
						|
				if v.allocs == 0 {
 | 
						|
					v.err = ErrObjectAllocLimit
 | 
						|
					return
 | 
						|
				}
 | 
						|
				v.stack[v.sp] = ret
 | 
						|
				v.sp++
 | 
						|
			}
 | 
						|
		case parser.OpReturn:
 | 
						|
			v.ip++
 | 
						|
			var retVal Object
 | 
						|
			if int(v.curInsts[v.ip]) == 1 {
 | 
						|
				retVal = v.stack[v.sp-1]
 | 
						|
			} else {
 | 
						|
				retVal = UndefinedValue
 | 
						|
			}
 | 
						|
			//v.sp--
 | 
						|
			v.framesIndex--
 | 
						|
			v.curFrame = &v.frames[v.framesIndex-1]
 | 
						|
			v.curInsts = v.curFrame.fn.Instructions
 | 
						|
			v.ip = v.curFrame.ip
 | 
						|
			//v.sp = lastFrame.basePointer - 1
 | 
						|
			v.sp = v.frames[v.framesIndex].basePointer
 | 
						|
			// skip stack overflow check because (newSP) <= (oldSP)
 | 
						|
			v.stack[v.sp-1] = retVal
 | 
						|
			//v.sp++
 | 
						|
		case parser.OpDefineLocal:
 | 
						|
			v.ip++
 | 
						|
			localIndex := int(v.curInsts[v.ip])
 | 
						|
			sp := v.curFrame.basePointer + localIndex
 | 
						|
 | 
						|
			// local variables can be mutated by other actions
 | 
						|
			// so always store the copy of popped value
 | 
						|
			val := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			v.stack[sp] = val
 | 
						|
		case parser.OpSetLocal:
 | 
						|
			localIndex := int(v.curInsts[v.ip+1])
 | 
						|
			v.ip++
 | 
						|
			sp := v.curFrame.basePointer + localIndex
 | 
						|
 | 
						|
			// update pointee of v.stack[sp] instead of replacing the pointer
 | 
						|
			// itself. this is needed because there can be free variables
 | 
						|
			// referencing the same local variables.
 | 
						|
			val := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			if obj, ok := v.stack[sp].(*ObjectPtr); ok {
 | 
						|
				*obj.Value = val
 | 
						|
				val = obj
 | 
						|
			}
 | 
						|
			v.stack[sp] = val // also use a copy of popped value
 | 
						|
		case parser.OpSetSelLocal:
 | 
						|
			localIndex := int(v.curInsts[v.ip+1])
 | 
						|
			numSelectors := int(v.curInsts[v.ip+2])
 | 
						|
			v.ip += 2
 | 
						|
 | 
						|
			// selectors and RHS value
 | 
						|
			selectors := make([]Object, numSelectors)
 | 
						|
			for i := 0; i < numSelectors; i++ {
 | 
						|
				selectors[i] = v.stack[v.sp-numSelectors+i]
 | 
						|
			}
 | 
						|
			val := v.stack[v.sp-numSelectors-1]
 | 
						|
			v.sp -= numSelectors + 1
 | 
						|
			dst := v.stack[v.curFrame.basePointer+localIndex]
 | 
						|
			if obj, ok := dst.(*ObjectPtr); ok {
 | 
						|
				dst = *obj.Value
 | 
						|
			}
 | 
						|
			if e := indexAssign(dst, val, selectors); e != nil {
 | 
						|
				v.err = e
 | 
						|
				return
 | 
						|
			}
 | 
						|
		case parser.OpGetLocal:
 | 
						|
			v.ip++
 | 
						|
			localIndex := int(v.curInsts[v.ip])
 | 
						|
			val := v.stack[v.curFrame.basePointer+localIndex]
 | 
						|
			if obj, ok := val.(*ObjectPtr); ok {
 | 
						|
				val = *obj.Value
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpGetBuiltin:
 | 
						|
			v.ip++
 | 
						|
			builtinIndex := int(v.curInsts[v.ip])
 | 
						|
			v.stack[v.sp] = builtinFuncs[builtinIndex]
 | 
						|
			v.sp++
 | 
						|
		case parser.OpClosure:
 | 
						|
			v.ip += 3
 | 
						|
			constIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8
 | 
						|
			numFree := int(v.curInsts[v.ip])
 | 
						|
			fn, ok := v.constants[constIndex].(*CompiledFunction)
 | 
						|
			if !ok {
 | 
						|
				v.err = fmt.Errorf("not function: %s", fn.TypeName())
 | 
						|
				return
 | 
						|
			}
 | 
						|
			free := make([]*ObjectPtr, numFree)
 | 
						|
			for i := 0; i < numFree; i++ {
 | 
						|
				switch freeVar := (v.stack[v.sp-numFree+i]).(type) {
 | 
						|
				case *ObjectPtr:
 | 
						|
					free[i] = freeVar
 | 
						|
				default:
 | 
						|
					free[i] = &ObjectPtr{
 | 
						|
						Value: &v.stack[v.sp-numFree+i],
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			v.sp -= numFree
 | 
						|
			cl := &CompiledFunction{
 | 
						|
				Instructions:  fn.Instructions,
 | 
						|
				NumLocals:     fn.NumLocals,
 | 
						|
				NumParameters: fn.NumParameters,
 | 
						|
				VarArgs:       fn.VarArgs,
 | 
						|
				Free:          free,
 | 
						|
			}
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = cl
 | 
						|
			v.sp++
 | 
						|
		case parser.OpGetFreePtr:
 | 
						|
			v.ip++
 | 
						|
			freeIndex := int(v.curInsts[v.ip])
 | 
						|
			val := v.curFrame.freeVars[freeIndex]
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpGetFree:
 | 
						|
			v.ip++
 | 
						|
			freeIndex := int(v.curInsts[v.ip])
 | 
						|
			val := *v.curFrame.freeVars[freeIndex].Value
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpSetFree:
 | 
						|
			v.ip++
 | 
						|
			freeIndex := int(v.curInsts[v.ip])
 | 
						|
			*v.curFrame.freeVars[freeIndex].Value = v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
		case parser.OpGetLocalPtr:
 | 
						|
			v.ip++
 | 
						|
			localIndex := int(v.curInsts[v.ip])
 | 
						|
			sp := v.curFrame.basePointer + localIndex
 | 
						|
			val := v.stack[sp]
 | 
						|
			var freeVar *ObjectPtr
 | 
						|
			if obj, ok := val.(*ObjectPtr); ok {
 | 
						|
				freeVar = obj
 | 
						|
			} else {
 | 
						|
				freeVar = &ObjectPtr{Value: &val}
 | 
						|
				v.stack[sp] = freeVar
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = freeVar
 | 
						|
			v.sp++
 | 
						|
		case parser.OpSetSelFree:
 | 
						|
			v.ip += 2
 | 
						|
			freeIndex := int(v.curInsts[v.ip-1])
 | 
						|
			numSelectors := int(v.curInsts[v.ip])
 | 
						|
 | 
						|
			// selectors and RHS value
 | 
						|
			selectors := make([]Object, numSelectors)
 | 
						|
			for i := 0; i < numSelectors; i++ {
 | 
						|
				selectors[i] = v.stack[v.sp-numSelectors+i]
 | 
						|
			}
 | 
						|
			val := v.stack[v.sp-numSelectors-1]
 | 
						|
			v.sp -= numSelectors + 1
 | 
						|
			e := indexAssign(*v.curFrame.freeVars[freeIndex].Value,
 | 
						|
				val, selectors)
 | 
						|
			if e != nil {
 | 
						|
				v.err = e
 | 
						|
				return
 | 
						|
			}
 | 
						|
		case parser.OpIteratorInit:
 | 
						|
			var iterator Object
 | 
						|
			dst := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			if !dst.CanIterate() {
 | 
						|
				v.err = fmt.Errorf("not iterable: %s", dst.TypeName())
 | 
						|
				return
 | 
						|
			}
 | 
						|
			iterator = dst.Iterate()
 | 
						|
			v.allocs--
 | 
						|
			if v.allocs == 0 {
 | 
						|
				v.err = ErrObjectAllocLimit
 | 
						|
				return
 | 
						|
			}
 | 
						|
			v.stack[v.sp] = iterator
 | 
						|
			v.sp++
 | 
						|
		case parser.OpIteratorNext:
 | 
						|
			iterator := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			hasMore := iterator.(Iterator).Next()
 | 
						|
			if hasMore {
 | 
						|
				v.stack[v.sp] = TrueValue
 | 
						|
			} else {
 | 
						|
				v.stack[v.sp] = FalseValue
 | 
						|
			}
 | 
						|
			v.sp++
 | 
						|
		case parser.OpIteratorKey:
 | 
						|
			iterator := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			val := iterator.(Iterator).Key()
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpIteratorValue:
 | 
						|
			iterator := v.stack[v.sp-1]
 | 
						|
			v.sp--
 | 
						|
			val := iterator.(Iterator).Value()
 | 
						|
			v.stack[v.sp] = val
 | 
						|
			v.sp++
 | 
						|
		case parser.OpSuspend:
 | 
						|
			return
 | 
						|
		default:
 | 
						|
			v.err = fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// IsStackEmpty tests if the stack is empty or not.
 | 
						|
func (v *VM) IsStackEmpty() bool {
 | 
						|
	return v.sp == 0
 | 
						|
}
 | 
						|
 | 
						|
func indexAssign(dst, src Object, selectors []Object) error {
 | 
						|
	numSel := len(selectors)
 | 
						|
	for sidx := numSel - 1; sidx > 0; sidx-- {
 | 
						|
		next, err := dst.IndexGet(selectors[sidx])
 | 
						|
		if err != nil {
 | 
						|
			if err == ErrNotIndexable {
 | 
						|
				return fmt.Errorf("not indexable: %s", dst.TypeName())
 | 
						|
			}
 | 
						|
			if err == ErrInvalidIndexType {
 | 
						|
				return fmt.Errorf("invalid index type: %s",
 | 
						|
					selectors[sidx].TypeName())
 | 
						|
			}
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		dst = next
 | 
						|
	}
 | 
						|
 | 
						|
	if err := dst.IndexSet(selectors[0], src); err != nil {
 | 
						|
		if err == ErrNotIndexAssignable {
 | 
						|
			return fmt.Errorf("not index-assignable: %s", dst.TypeName())
 | 
						|
		}
 | 
						|
		if err == ErrInvalidIndexValueType {
 | 
						|
			return fmt.Errorf("invaid index value type: %s", src.TypeName())
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |