Skip to content

Commit

Permalink
Script.AddModule() to take *Script not *Compiled
Browse files Browse the repository at this point in the history
  • Loading branch information
d5 committed Jan 31, 2019
1 parent edc23cf commit 46c7190
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 47 deletions.
12 changes: 6 additions & 6 deletions docs/interoperability.md
Expand Up @@ -106,17 +106,17 @@ One can easily add and use customized value types in Tengo code by implementing

### 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.
A script can import and use another script in the same way it can load the standard library or the user module. `Script.AddModule` function adds another script as a named module.

```golang
mod1, _ := script.New([]byte(`a := 5`)).Compile() // mod1 is a "compiled" script
mod1Script := script.New([]byte(`a := 5`)) // mod1 script

s := script.New([]byte(`print(import("mod1").a)`)) // main script
_ = s.AddModule("mod1", mod1) // add mod1 using name "mod1"
_, _ = s.Run() // prints "5"
mainScript := script.New([]byte(`print(import("mod1").a)`)) // main script
mainScript.AddModule("mod1", mod1Script) // add mod1 using name "mod1"
mainScript.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.
Note that the script modules added using `Script.AddModule` will be compiled and run right before the main script is compiled.

## Sandbox Environments

Expand Down
67 changes: 42 additions & 25 deletions script/script.go
Expand Up @@ -17,7 +17,7 @@ type Script struct {
variables map[string]*Variable
removedBuiltins map[string]bool
removedStdModules map[string]bool
userModules map[string]*objects.ImmutableMap
scriptModules map[string]*Script
userModuleLoader compiler.ModuleLoader
input []byte
}
Expand Down Expand Up @@ -80,34 +80,22 @@ 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)
// AddModule adds another script as a module. Script module will be
// compiled and run right before the main script s is compiled.
func (s *Script) AddModule(name string, scriptModule *Script) {
if s.scriptModules == nil {
s.scriptModules = make(map[string]*Script)
}

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
s.scriptModules[name] = scriptModule
}

// Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) {
symbolTable, stdModules, globals := s.prepCompile()
symbolTable, stdModules, globals, err := s.prepCompile()
if err != nil {
return nil, err
}

fileSet := source.NewFileSet()

Expand Down Expand Up @@ -158,7 +146,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
return
}

func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]*objects.ImmutableMap, globals []*objects.Object) {
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]*objects.ImmutableMap, globals []*objects.Object, err error) {
var names []string
for name := range s.variables {
names = append(names, name)
Expand All @@ -177,7 +165,36 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
stdModules[name] = mod
}
}
for name, mod := range s.userModules {
for name, scriptModule := range s.scriptModules {
if scriptModule == nil {
err = fmt.Errorf("script module must not be nil: %s", name)
}

var compiledModule *Compiled
compiledModule, err = scriptModule.Compile()
if err != nil {
return
}

err = compiledModule.Run()
if err != nil {
return
}

mod := &objects.ImmutableMap{
Value: make(map[string]objects.Object),
}

for _, symbolName := range compiledModule.symbolTable.Names() {
symbol, _, ok := compiledModule.symbolTable.Resolve(symbolName)
if ok && symbol.Scope == compiler.ScopeGlobal {
value := compiledModule.machine.Globals()[symbol.Index]
if value != nil {
mod.Value[symbolName] = *value
}
}
}

stdModules[name] = mod
}

Expand Down
20 changes: 4 additions & 16 deletions script/script_module_test.go
Expand Up @@ -9,29 +9,17 @@ import (

func TestScript_AddModule(t *testing.T) {
// mod1 module
mod1, err := script.New([]byte(`a := 5`)).Compile()
assert.NoError(t, err)
mod1 := script.New([]byte(`a := 5`))

// script1 imports "mod1"
scr1 := script.New([]byte(`mod1 := import("mod1"); out := mod1.a`))
scr1.AddModule("mod1", mod1) // added before mod1 was run
scr1.AddModule("mod1", mod1)
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)
mod2 := script.New([]byte(`mod1 := import("mod1"); b := mod1.a * 2`))
mod2.AddModule("mod1", mod1)

// script2 imports "mod2" (which imports "mod1")
scr2 := script.New([]byte(`mod2 := import("mod2"); out := mod2.b`))
Expand Down

0 comments on commit 46c7190

Please sign in to comment.