diff --git a/docs/interoperability.md b/docs/interoperability.md index d5b90392..3cf70079 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -70,12 +70,23 @@ func main() { // retrieve value of 'a' a := c.Get("a") - fmt.Println(a.Int()) + fmt.Println(a.Int()) // prints "30" + + // re-run after replacing value of 'b' + if err := c.Set("b", 20); err != nil { + panic(err) + } + if err := c.Run(); err != nil { + panic(err) + } + fmt.Println(c.Get("a").Int()) // prints "40" } ``` A variable `b` is defined by the user before compilation using [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) function. Then a compiled bytecode `c` is used to execute the bytecode and get the value of global variables. In this example, the value of global variable `a` is read using [Compiled.Get](https://godoc.org/github.com/d5/tengo/script#Compiled.Get) function. See [documentation](https://godoc.org/github.com/d5/tengo/script#Variable) for the full list of variable value functions. +Value of the global variables can be replaced using [Compiled.Set](https://godoc.org/github.com/d5/tengo/script#Compiled.Set) function. But it will return an error if you try to set the value of un-defined global variables _(e.g. trying to set the value of `x` in the example)_. + ### Type Conversion Table When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add))_, Script converts Go values into Tengo values based on the following conversion table. diff --git a/runtime/vm.go b/runtime/vm.go index bbc230f0..ba0057ac 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -80,6 +80,15 @@ func (v *VM) Abort() { // Run starts the execution. func (v *VM) Run() error { + // reset VM states + v.sp = 0 + v.curFrame = &(v.frames[0]) + v.curInsts = v.curFrame.fn.Instructions + v.curIPLimit = len(v.curInsts) - 1 + v.framesIndex = 1 + v.ip = -1 + atomic.StoreInt64(&v.aborting, 0) + for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) { v.ip++ diff --git a/script/compiled.go b/script/compiled.go index c26b8f24..4acc46ee 100644 --- a/script/compiled.go +++ b/script/compiled.go @@ -2,6 +2,7 @@ package script import ( "context" + "fmt" "github.com/d5/tengo/compiler" "github.com/d5/tengo/objects" @@ -92,3 +93,21 @@ func (c *Compiled) GetAll() []*Variable { return vars } + +// Set replaces the value of a global variable identified by the name. +// An error will be returned if the name was not defined during compilation. +func (c *Compiled) Set(name string, value interface{}) error { + obj, err := objects.FromInterface(value) + if err != nil { + return err + } + + symbol, _, ok := c.symbolTable.Resolve(name) + if !ok || symbol.Scope != compiler.ScopeGlobal { + return fmt.Errorf("'%s' is not defined", name) + } + + c.machine.Globals()[symbol.Index] = &obj + + return nil +} diff --git a/script/compiled_test.go b/script/compiled_test.go index 117d4b14..e4f30849 100644 --- a/script/compiled_test.go +++ b/script/compiled_test.go @@ -47,6 +47,36 @@ func TestCompiled_IsDefined(t *testing.T) { compiledIsDefined(t, c, "b", false) } +func TestCompiled_Set(t *testing.T) { + c := compile(t, `a := b`, M{"b": "foo"}) + compiledRun(t, c) + compiledGet(t, c, "a", "foo") + + // replace value of 'b' + err := c.Set("b", "bar") + assert.NoError(t, err) + compiledRun(t, c) + compiledGet(t, c, "a", "bar") + + // try to replace undefined variable + err = c.Set("c", 1984) + assert.Error(t, err) // 'c' is not defined + + // case #2 + c = compile(t, ` +a := func() { + return func() { + return b + 5 + }() +}()`, M{"b": 5}) + compiledRun(t, c) + compiledGet(t, c, "a", int64(10)) + err = c.Set("b", 10) + assert.NoError(t, err) + compiledRun(t, c) + compiledGet(t, c, "a", int64(15)) +} + func TestCompiled_RunContext(t *testing.T) { // machine completes normally c := compile(t, `a := 5`, nil)