commit
00168726d6
6 changed files with 156 additions and 96 deletions
24
README.md
24
README.md
|
@ -22,23 +22,25 @@ Tengo is [fast](#benchmark) as it's compiled to bytecode and executed on stack-b
|
|||
- Garbage collected _(thanks to Go runtime)_
|
||||
- Easily extensible using customizable types
|
||||
- Written in pure Go _(no CGO, no external dependencies)_
|
||||
- Executable as a [standalone language](https://github.com/d5/tengo#tengo-as-a-standalone-language) _(without writing any Go code)_
|
||||
- Executable as a standalone language
|
||||
|
||||
## Benchmark
|
||||
|
||||
| | fib(35) | fibt(35) | Type |
|
||||
| :--- | ---: | ---: | :---: |
|
||||
| Go | `59ms` | `4ms` | Go (native) |
|
||||
| [**Tengo**](https://github.com/d5/tengo) | `4,809ms` | `5ms` | VM on Go |
|
||||
| Lua | `1,752ms` | `3ms` | Lua (native) |
|
||||
| [go-lua](https://github.com/Shopify/go-lua) | `5,236ms` | `5ms` | Lua VM on Go |
|
||||
| [GopherLua](https://github.com/yuin/gopher-lua) | `5,558ms` | `5ms` | Lua VM on Go |
|
||||
| Python | `3,132ms` | `28ms` | Python (native) |
|
||||
| [starlark-go](https://github.com/google/starlark-go) | `16,789ms` | `5ms` | Python-like Interpreter on Go |
|
||||
| [otto](https://github.com/robertkrimen/otto) | `85,765ms` | `22ms` | JS Interpreter on Go |
|
||||
| [Anko](https://github.com/mattn/anko) | `99,235ms` | `24ms` | Interpreter on Go |
|
||||
| Go | `67ms` | `4ms` | Go (native) |
|
||||
| [**Tengo**](https://github.com/d5/tengo) | `4,390ms` | `5ms` | VM on Go |
|
||||
| Lua | `1,804ms` | `3ms` | Lua (native) |
|
||||
| [go-lua](https://github.com/Shopify/go-lua) | `5,114ms` | `4ms` | Lua VM on Go |
|
||||
| [GopherLua](https://github.com/yuin/gopher-lua) | `5,679ms` | `5ms` | Lua VM on Go |
|
||||
| Python | `2,853ms` | `25ms` | Python (native) |
|
||||
| [starlark-go](https://github.com/google/starlark-go) | `16,725ms` | `5ms` | Python-like Interpreter on Go |
|
||||
| [otto](https://github.com/robertkrimen/otto) | `88,148ms` | `21ms` | JS Interpreter on Go |
|
||||
| [Anko](https://github.com/mattn/anko) | `107,968ms` | `22ms` | Interpreter on Go |
|
||||
|
||||
[fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo) is a function to compute 35th Fibonacci number, and, [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo) is the [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of the same function. You can see all the code used for this test in [tengobench](https://github.com/d5/tengobench).
|
||||
[fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo) is a function to compute 35th Fibonacci number, and, [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo) is the [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of the same function.
|
||||
|
||||
_Please note that **Go** case does not read the source code from a local file, while all other cases do. All shell commands and the source code used in this benchmarking is available [here](https://github.com/d5/tengobench)._
|
||||
|
||||
## Tengo Syntax in 5 Minutes
|
||||
|
||||
|
|
|
@ -176,9 +176,9 @@ func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) {
|
|||
for _, width := range numOperands {
|
||||
switch width {
|
||||
case 1:
|
||||
operands = append(operands, int(ReadUint8(ins[offset:])))
|
||||
operands = append(operands, int(ins[offset]))
|
||||
case 2:
|
||||
operands = append(operands, int(ReadUint16(ins[offset:])))
|
||||
operands = append(operands, int(ins[offset+1])|int(ins[offset])<<8)
|
||||
}
|
||||
|
||||
offset += width
|
||||
|
@ -186,13 +186,3 @@ func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadUint16 reads uint16 from the byte slice.
|
||||
func ReadUint16(b []byte) uint16 {
|
||||
return uint16(b[1]) | uint16(b[0])<<8
|
||||
}
|
||||
|
||||
// ReadUint8 reads uint8 from the byte slice.
|
||||
func ReadUint8(b []byte) uint8 {
|
||||
return uint8(b[0])
|
||||
}
|
||||
|
|
153
runtime/vm.go
153
runtime/vm.go
|
@ -3,6 +3,7 @@ package runtime
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/d5/tengo/compiler"
|
||||
"github.com/d5/tengo/compiler/token"
|
||||
|
@ -38,7 +39,8 @@ type VM struct {
|
|||
curFrame *Frame
|
||||
curInsts []byte
|
||||
curIPLimit int
|
||||
aborting bool
|
||||
ip int
|
||||
aborting int64
|
||||
}
|
||||
|
||||
// NewVM creates a VM.
|
||||
|
@ -67,27 +69,24 @@ func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object) *VM {
|
|||
curFrame: &(frames[0]),
|
||||
curInsts: frames[0].fn.Instructions,
|
||||
curIPLimit: len(frames[0].fn.Instructions) - 1,
|
||||
ip: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Abort aborts the execution.
|
||||
func (v *VM) Abort() {
|
||||
v.aborting = true
|
||||
atomic.StoreInt64(&v.aborting, 1)
|
||||
}
|
||||
|
||||
// Run starts the execution.
|
||||
func (v *VM) Run() error {
|
||||
var ip int
|
||||
for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) {
|
||||
v.ip++
|
||||
|
||||
for v.curFrame.ip < v.curIPLimit && !v.aborting {
|
||||
v.curFrame.ip++
|
||||
|
||||
ip = v.curFrame.ip
|
||||
|
||||
switch compiler.Opcode(v.curInsts[ip]) {
|
||||
switch compiler.Opcode(v.curInsts[v.ip]) {
|
||||
case compiler.OpConstant:
|
||||
cidx := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
v.curFrame.ip += 2
|
||||
cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
if v.sp >= StackSize {
|
||||
return ErrStackOverflow
|
||||
|
@ -437,54 +436,54 @@ func (v *VM) Run() error {
|
|||
}
|
||||
|
||||
case compiler.OpJumpFalsy:
|
||||
pos := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip += 2
|
||||
pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
condition := v.stack[v.sp-1]
|
||||
v.sp--
|
||||
|
||||
if (*condition).IsFalsy() {
|
||||
v.curFrame.ip = pos - 1
|
||||
v.ip = pos - 1
|
||||
}
|
||||
|
||||
case compiler.OpAndJump:
|
||||
pos := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip += 2
|
||||
pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
condition := *v.stack[v.sp-1]
|
||||
if condition.IsFalsy() {
|
||||
v.curFrame.ip = pos - 1
|
||||
v.ip = pos - 1
|
||||
} else {
|
||||
v.sp--
|
||||
}
|
||||
|
||||
case compiler.OpOrJump:
|
||||
pos := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip += 2
|
||||
pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
condition := *v.stack[v.sp-1]
|
||||
if !condition.IsFalsy() {
|
||||
v.curFrame.ip = pos - 1
|
||||
v.ip = pos - 1
|
||||
} else {
|
||||
v.sp--
|
||||
}
|
||||
|
||||
case compiler.OpJump:
|
||||
pos := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip = pos - 1
|
||||
pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip = pos - 1
|
||||
|
||||
case compiler.OpSetGlobal:
|
||||
globalIndex := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
v.curFrame.ip += 2
|
||||
globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
v.sp--
|
||||
|
||||
v.globals[globalIndex] = v.stack[v.sp]
|
||||
|
||||
case compiler.OpSetSelGlobal:
|
||||
globalIndex := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
numSelectors := int(compiler.ReadUint8(v.curInsts[ip+3:]))
|
||||
v.curFrame.ip += 3
|
||||
globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
numSelectors := int(v.curInsts[v.ip+3])
|
||||
v.ip += 3
|
||||
|
||||
// pop selector outcomes (left to right)
|
||||
selectors := make([]interface{}, numSelectors, numSelectors)
|
||||
|
@ -511,8 +510,8 @@ func (v *VM) Run() error {
|
|||
}
|
||||
|
||||
case compiler.OpGetGlobal:
|
||||
globalIndex := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
v.curFrame.ip += 2
|
||||
globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
val := v.globals[globalIndex]
|
||||
|
||||
|
@ -524,8 +523,8 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpArray:
|
||||
numElements := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip += 2
|
||||
numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
var elements []objects.Object
|
||||
for i := v.sp - numElements; i < v.sp; i++ {
|
||||
|
@ -543,8 +542,8 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpMap:
|
||||
numElements := int(compiler.ReadUint16(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip += 2
|
||||
numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
kv := make(map[string]objects.Object)
|
||||
for i := v.sp - numElements; i < v.sp; i += 2 {
|
||||
|
@ -803,8 +802,8 @@ func (v *VM) Run() error {
|
|||
}
|
||||
|
||||
case compiler.OpCall:
|
||||
numArgs := int(compiler.ReadUint8(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip++
|
||||
numArgs := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
callee := *v.stack[v.sp-1-numArgs]
|
||||
|
||||
|
@ -847,9 +846,9 @@ func (v *VM) Run() error {
|
|||
}
|
||||
|
||||
case compiler.OpReturnValue:
|
||||
//numRets := int(compiler.ReadUint8(v.curInsts[ip+1:]))
|
||||
_ = int(compiler.ReadUint8(v.curInsts[ip+1:]))
|
||||
v.curFrame.ip++
|
||||
//numRets := int(compiler.ReadUint8(v.curInsts[v.ip+1:]))
|
||||
//_ = int64(compiler.ReadUint8(v.curInsts[v.ip+1:]))
|
||||
v.ip++
|
||||
|
||||
// TODO: multi-value return is not fully implemented yet
|
||||
//var rets []*objects.Object
|
||||
|
@ -865,6 +864,7 @@ func (v *VM) Run() error {
|
|||
v.curFrame = &v.frames[v.framesIndex-1]
|
||||
v.curInsts = v.curFrame.fn.Instructions
|
||||
v.curIPLimit = len(v.curInsts) - 1
|
||||
v.ip = v.curFrame.ip
|
||||
|
||||
//v.sp = lastFrame.basePointer - 1
|
||||
v.sp = lastFrame.basePointer
|
||||
|
@ -887,6 +887,7 @@ func (v *VM) Run() error {
|
|||
v.curFrame = &v.frames[v.framesIndex-1]
|
||||
v.curInsts = v.curFrame.fn.Instructions
|
||||
v.curIPLimit = len(v.curInsts) - 1
|
||||
v.ip = v.curFrame.ip
|
||||
|
||||
v.sp = lastFrame.basePointer - 1
|
||||
|
||||
|
@ -898,10 +899,10 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpDefineLocal:
|
||||
localIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
localIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
sp := v.curFrame.basePointer + int(localIndex)
|
||||
sp := v.curFrame.basePointer + localIndex
|
||||
|
||||
// local variables can be mutated by other actions
|
||||
// so always store the copy of popped value
|
||||
|
@ -911,10 +912,10 @@ func (v *VM) Run() error {
|
|||
v.stack[sp] = &val
|
||||
|
||||
case compiler.OpSetLocal:
|
||||
localIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
localIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
sp := v.curFrame.basePointer + int(localIndex)
|
||||
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.
|
||||
|
@ -924,10 +925,10 @@ func (v *VM) Run() error {
|
|||
*v.stack[sp] = *val // also use a copy of popped value
|
||||
|
||||
case compiler.OpSetSelLocal:
|
||||
localIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
numSelectors := int(compiler.ReadUint8(v.curInsts[ip+2:]))
|
||||
v.curFrame.ip += 2
|
||||
localIndex := int(v.curInsts[v.ip+1])
|
||||
numSelectors := int(v.curInsts[v.ip+2])
|
||||
|
||||
v.ip += 2
|
||||
// pop selector outcomes (left to right)
|
||||
selectors := make([]interface{}, numSelectors, numSelectors)
|
||||
for i := 0; i < numSelectors; i++ {
|
||||
|
@ -948,17 +949,17 @@ func (v *VM) Run() error {
|
|||
val := v.stack[v.sp-1] // no need to copy value here; selectorAssign uses copy of value
|
||||
v.sp--
|
||||
|
||||
sp := v.curFrame.basePointer + int(localIndex)
|
||||
sp := v.curFrame.basePointer + localIndex
|
||||
|
||||
if err := selectorAssign(v.stack[sp], val, selectors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case compiler.OpGetLocal:
|
||||
localIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
localIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
val := v.stack[v.curFrame.basePointer+int(localIndex)]
|
||||
val := v.stack[v.curFrame.basePointer+localIndex]
|
||||
|
||||
if v.sp >= StackSize {
|
||||
return ErrStackOverflow
|
||||
|
@ -968,8 +969,8 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpGetBuiltin:
|
||||
builtinIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
builtinIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
if v.sp >= StackSize {
|
||||
return ErrStackOverflow
|
||||
|
@ -979,17 +980,17 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpClosure:
|
||||
constIndex := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
numFree := compiler.ReadUint8(v.curInsts[ip+3:])
|
||||
v.curFrame.ip += 3
|
||||
constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
numFree := int(v.curInsts[v.ip+3])
|
||||
v.ip += 3
|
||||
|
||||
if err := v.pushClosure(int(constIndex), int(numFree)); err != nil {
|
||||
if err := v.pushClosure(constIndex, numFree); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case compiler.OpGetFree:
|
||||
freeIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
freeIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
val := v.curFrame.freeVars[freeIndex]
|
||||
|
||||
|
@ -1001,9 +1002,9 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpSetSelFree:
|
||||
freeIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
numSelectors := int(compiler.ReadUint8(v.curInsts[ip+2:]))
|
||||
v.curFrame.ip += 2
|
||||
freeIndex := int(v.curInsts[v.ip+1])
|
||||
numSelectors := int(v.curInsts[v.ip+2])
|
||||
v.ip += 2
|
||||
|
||||
// pop selector outcomes (left to right)
|
||||
selectors := make([]interface{}, numSelectors, numSelectors)
|
||||
|
@ -1030,8 +1031,8 @@ func (v *VM) Run() error {
|
|||
}
|
||||
|
||||
case compiler.OpSetFree:
|
||||
freeIndex := compiler.ReadUint8(v.curInsts[ip+1:])
|
||||
v.curFrame.ip++
|
||||
freeIndex := int(v.curInsts[v.ip+1])
|
||||
v.ip++
|
||||
|
||||
val := v.stack[v.sp-1]
|
||||
v.sp--
|
||||
|
@ -1107,20 +1108,20 @@ func (v *VM) Run() error {
|
|||
v.sp++
|
||||
|
||||
case compiler.OpModule:
|
||||
cidx := compiler.ReadUint16(v.curInsts[ip+1:])
|
||||
v.curFrame.ip += 2
|
||||
cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
|
||||
v.ip += 2
|
||||
|
||||
if err := v.importModule(v.constants[cidx].(*objects.CompiledModule)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown opcode: %d", v.curInsts[ip])
|
||||
return fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])
|
||||
}
|
||||
}
|
||||
|
||||
// check if stack still has some objects left
|
||||
if v.sp > 0 && !v.aborting {
|
||||
if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 {
|
||||
return fmt.Errorf("non empty stack after execution")
|
||||
}
|
||||
|
||||
|
@ -1133,7 +1134,7 @@ func (v *VM) Globals() []*objects.Object {
|
|||
}
|
||||
|
||||
// FrameInfo returns the current function call frame information.
|
||||
func (v *VM) FrameInfo() (frameIndex int, ip int) {
|
||||
func (v *VM) FrameInfo() (frameIndex, ip int) {
|
||||
return v.framesIndex - 1, v.frames[v.framesIndex-1].ip
|
||||
}
|
||||
|
||||
|
@ -1174,10 +1175,10 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
|
|||
|
||||
// check if this is a tail-call (recursive call right before return)
|
||||
if fn == v.curFrame.fn { // recursion
|
||||
nextOp := compiler.Opcode(v.curInsts[v.curFrame.ip+1])
|
||||
nextOp := compiler.Opcode(v.curInsts[v.ip+1])
|
||||
if nextOp == compiler.OpReturnValue || // tail call
|
||||
(nextOp == compiler.OpPop &&
|
||||
compiler.OpReturn == compiler.Opcode(v.curInsts[v.curFrame.ip+2])) {
|
||||
compiler.OpReturn == compiler.Opcode(v.curInsts[v.ip+2])) {
|
||||
|
||||
// stack before tail-call
|
||||
//
|
||||
|
@ -1203,7 +1204,8 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
|
|||
v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p]
|
||||
}
|
||||
v.sp -= numArgs + 1
|
||||
v.curFrame.ip = -1 // reset IP to beginning of the frame
|
||||
v.ip = -1
|
||||
//v.curFrame.ip = -1 // reset IP to beginning of the frame
|
||||
|
||||
// stack after tail-call
|
||||
//
|
||||
|
@ -1229,12 +1231,17 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje
|
|||
}
|
||||
}
|
||||
|
||||
// store current ip before call
|
||||
v.curFrame.ip = v.ip
|
||||
|
||||
// update call frame
|
||||
v.curFrame = &(v.frames[v.framesIndex])
|
||||
v.curFrame.fn = fn
|
||||
v.curFrame.freeVars = freeVars
|
||||
v.curFrame.ip = -1
|
||||
//v.curFrame.ip = -1
|
||||
v.curFrame.basePointer = v.sp - numArgs
|
||||
v.curInsts = fn.Instructions
|
||||
v.ip = -1
|
||||
v.curIPLimit = len(v.curInsts) - 1
|
||||
v.framesIndex++
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package script
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/d5/tengo/compiler"
|
||||
"github.com/d5/tengo/objects"
|
||||
"github.com/d5/tengo/runtime"
|
||||
|
@ -20,6 +22,25 @@ func (c *Compiled) Run() error {
|
|||
return c.machine.Run()
|
||||
}
|
||||
|
||||
// RunContext is like Run but includes a context.
|
||||
func (c *Compiled) RunContext(ctx context.Context) (err error) {
|
||||
ch := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
ch <- c.machine.Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
c.machine.Abort()
|
||||
<-ch
|
||||
err = ctx.Err()
|
||||
case err = <-ch:
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsDefined returns true if the variable name is defined (has value) before or after the execution.
|
||||
func (c *Compiled) IsDefined(name string) bool {
|
||||
symbol, _, ok := c.symbolTable.Resolve(name)
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package script_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/d5/tengo/assert"
|
||||
"github.com/d5/tengo/script"
|
||||
|
@ -45,6 +47,31 @@ func TestCompiled_IsDefined(t *testing.T) {
|
|||
compiledIsDefined(t, c, "b", false)
|
||||
}
|
||||
|
||||
func TestCompiled_RunContext(t *testing.T) {
|
||||
// machine completes normally
|
||||
c := compile(t, `a := 5`, nil)
|
||||
err := c.RunContext(context.Background())
|
||||
assert.NoError(t, err)
|
||||
compiledGet(t, c, "a", int64(5))
|
||||
|
||||
// cancelled
|
||||
c = compile(t, `for true {}`, nil)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
cancel()
|
||||
}()
|
||||
err = c.RunContext(ctx)
|
||||
assert.Equal(t, context.Canceled, err)
|
||||
|
||||
// timeout
|
||||
c = compile(t, `for true {}`, nil)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Millisecond)
|
||||
defer cancel()
|
||||
err = c.RunContext(ctx)
|
||||
assert.Equal(t, context.DeadlineExceeded, err)
|
||||
}
|
||||
|
||||
func compile(t *testing.T, input string, vars M) *script.Compiled {
|
||||
s := script.New([]byte(input))
|
||||
for vn, vv := range vars {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package script
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/d5/tengo/compiler"
|
||||
|
@ -87,6 +88,18 @@ func (s *Script) Run() (compiled *Compiled, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// RunContext is like Run but includes a context.
|
||||
func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) {
|
||||
compiled, err = s.Compile()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = compiled.RunContext(ctx)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*objects.Object) {
|
||||
var names []string
|
||||
for name := range s.variables {
|
||||
|
|
Loading…
Reference in a new issue