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)
|
||||
- [Type Conversion Table](#type-conversion-table)
|
||||
- [User Types](#user-types)
|
||||
- [User Types](#user-types)
|
||||
- [Importing Scripts](#importing-scripts)
|
||||
- [Sandbox Environments](#sandbox-environments)
|
||||
- [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`||
|
||||
|`float64`|`Float`||
|
||||
|`[]byte`|`Bytes`||
|
||||
|`time.Time`|`Time`||
|
||||
|`error`|`Error{String}`|use `error.Error()` as String value|
|
||||
|`map[string]Object`|`Map`||
|
||||
|`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)_|
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
### 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
|
||||
|
||||
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
|
||||
removedBuiltins map[string]bool
|
||||
removedStdModules map[string]bool
|
||||
userModules map[string]*objects.ImmutableMap
|
||||
userModuleLoader compiler.ModuleLoader
|
||||
input []byte
|
||||
}
|
||||
|
@ -79,6 +80,31 @@ func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
|
|||
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.
|
||||
func (s *Script) Compile() (*Compiled, error) {
|
||||
symbolTable, stdModules, globals := s.prepCompile()
|
||||
|
@ -151,6 +177,9 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
|
|||
stdModules[name] = mod
|
||||
}
|
||||
}
|
||||
for name, mod := range s.userModules {
|
||||
stdModules[name] = mod
|
||||
}
|
||||
|
||||
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