working on script modules (WIP)
This commit is contained in:
parent
f7b0cd8299
commit
edc23cf2f1
3 changed files with 89 additions and 2 deletions
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
- [Using Scripts](#using-scripts)
|
- [Using Scripts](#using-scripts)
|
||||||
- [Type Conversion Table](#type-conversion-table)
|
- [Type Conversion Table](#type-conversion-table)
|
||||||
- [User Types](#user-types)
|
- [User Types](#user-types)
|
||||||
|
- [Importing Scripts](#importing-scripts)
|
||||||
- [Sandbox Environments](#sandbox-environments)
|
- [Sandbox Environments](#sandbox-environments)
|
||||||
- [Compiler and VM](#compiler-and-vm)
|
- [Compiler and VM](#compiler-and-vm)
|
||||||
|
|
||||||
|
@ -90,6 +91,7 @@ When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/scri
|
||||||
|`byte`|`Char`||
|
|`byte`|`Char`||
|
||||||
|`float64`|`Float`||
|
|`float64`|`Float`||
|
||||||
|`[]byte`|`Bytes`||
|
|`[]byte`|`Bytes`||
|
||||||
|
|`time.Time`|`Time`||
|
||||||
|`error`|`Error{String}`|use `error.Error()` as String value|
|
|`error`|`Error{String}`|use `error.Error()` as String value|
|
||||||
|`map[string]Object`|`Map`||
|
|`map[string]Object`|`Map`||
|
||||||
|`map[string]interface{}`|`Map`|individual elements converted to Tengo objects|
|
|`map[string]interface{}`|`Map`|individual elements converted to Tengo objects|
|
||||||
|
@ -98,10 +100,24 @@ When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/scri
|
||||||
|`Object`|`Object`|_(no type conversion performed)_|
|
|`Object`|`Object`|_(no type conversion performed)_|
|
||||||
|
|
||||||
|
|
||||||
## User Types
|
### User Types
|
||||||
|
|
||||||
One can easily add and use customized value types in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types exactly in the same way it does to the runtime types with no performance overhead. See [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.
|
One can easily add and use customized value types in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types exactly in the same way it does to the runtime types with no performance overhead. See [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.
|
||||||
|
|
||||||
|
### Importing Scripts
|
||||||
|
|
||||||
|
Using `Script.AddModule` function, a compiled script can be used _(imported)_ by another script as a module, in the same way the script can load the standard library or the user modules.
|
||||||
|
|
||||||
|
```golang
|
||||||
|
mod1, _ := script.New([]byte(`a := 5`)).Compile() // mod1 is a "compiled" script
|
||||||
|
|
||||||
|
s := script.New([]byte(`print(import("mod1").a)`)) // main script
|
||||||
|
_ = s.AddModule("mod1", mod1) // add mod1 using name "mod1"
|
||||||
|
_, _ = s.Run() // prints "5"
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice that the compiled script (`mod1` in this example code) does not have to be `Run()` before it's added to another script as module. Actually `Script.AddModule` function runs the given compiled script so it can populate values of the global variables.
|
||||||
|
|
||||||
## Sandbox Environments
|
## Sandbox Environments
|
||||||
|
|
||||||
To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions.
|
To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions.
|
||||||
|
|
|
@ -17,6 +17,7 @@ type Script struct {
|
||||||
variables map[string]*Variable
|
variables map[string]*Variable
|
||||||
removedBuiltins map[string]bool
|
removedBuiltins map[string]bool
|
||||||
removedStdModules map[string]bool
|
removedStdModules map[string]bool
|
||||||
|
userModules map[string]*objects.ImmutableMap
|
||||||
userModuleLoader compiler.ModuleLoader
|
userModuleLoader compiler.ModuleLoader
|
||||||
input []byte
|
input []byte
|
||||||
}
|
}
|
||||||
|
@ -79,6 +80,31 @@ func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
|
||||||
s.userModuleLoader = loader
|
s.userModuleLoader = loader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddModule adds the compiled script as an import module. Note that
|
||||||
|
// the compiled script must be run at least once before it is added
|
||||||
|
// to another script.
|
||||||
|
func (s *Script) AddModule(name string, compiled *Compiled) {
|
||||||
|
if s.userModules == nil {
|
||||||
|
s.userModules = make(map[string]*objects.ImmutableMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := &objects.ImmutableMap{
|
||||||
|
Value: make(map[string]objects.Object),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, symbolName := range compiled.symbolTable.Names() {
|
||||||
|
symbol, _, ok := compiled.symbolTable.Resolve(symbolName)
|
||||||
|
if ok && symbol.Scope == compiler.ScopeGlobal {
|
||||||
|
value := compiled.machine.Globals()[symbol.Index]
|
||||||
|
if value != nil {
|
||||||
|
mod.Value[symbolName] = *value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.userModules[name] = mod
|
||||||
|
}
|
||||||
|
|
||||||
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
||||||
func (s *Script) Compile() (*Compiled, error) {
|
func (s *Script) Compile() (*Compiled, error) {
|
||||||
symbolTable, stdModules, globals := s.prepCompile()
|
symbolTable, stdModules, globals := s.prepCompile()
|
||||||
|
@ -151,6 +177,9 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
|
||||||
stdModules[name] = mod
|
stdModules[name] = mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for name, mod := range s.userModules {
|
||||||
|
stdModules[name] = mod
|
||||||
|
}
|
||||||
|
|
||||||
globals = make([]*objects.Object, len(names), len(names))
|
globals = make([]*objects.Object, len(names), len(names))
|
||||||
|
|
||||||
|
|
42
script/script_module_test.go
Normal file
42
script/script_module_test.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package script_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/d5/tengo/assert"
|
||||||
|
"github.com/d5/tengo/script"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScript_AddModule(t *testing.T) {
|
||||||
|
// mod1 module
|
||||||
|
mod1, err := script.New([]byte(`a := 5`)).Compile()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// script1 imports "mod1"
|
||||||
|
scr1 := script.New([]byte(`mod1 := import("mod1"); out := mod1.a`))
|
||||||
|
scr1.AddModule("mod1", mod1) // added before mod1 was run
|
||||||
|
c, err := scr1.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, c.Get("out").Value()) // 'a' is undefined because mod1 was not yet run
|
||||||
|
err = mod1.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
scr1.AddModule("mod1", mod1) // this time, mod1 was run before it's added
|
||||||
|
c, err = scr1.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(5), c.Get("out").Value())
|
||||||
|
|
||||||
|
// mod2 module imports "mod1"
|
||||||
|
mod2Script := script.New([]byte(`mod1 := import("mod1"); b := mod1.a * 2`))
|
||||||
|
mod2Script.AddModule("mod1", mod1)
|
||||||
|
mod2, err := mod2Script.Compile()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = mod2.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// script2 imports "mod2" (which imports "mod1")
|
||||||
|
scr2 := script.New([]byte(`mod2 := import("mod2"); out := mod2.b`))
|
||||||
|
scr2.AddModule("mod2", mod2)
|
||||||
|
c, err = scr2.Run()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, int64(10), c.Get("out").Value())
|
||||||
|
}
|
Loading…
Reference in a new issue