From 46c719010984553f720bac3391c59a8aec61b0d2 Mon Sep 17 00:00:00 2001 From: Daniel Kang Date: Wed, 30 Jan 2019 23:06:00 -0800 Subject: [PATCH] Script.AddModule() to take *Script not *Compiled --- docs/interoperability.md | 12 +++---- script/script.go | 67 ++++++++++++++++++++++-------------- script/script_module_test.go | 20 +++-------- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/docs/interoperability.md b/docs/interoperability.md index ea7484f1..81c1db25 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -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 diff --git a/script/script.go b/script/script.go index 53b38ba2..4bb14924 100644 --- a/script/script.go +++ b/script/script.go @@ -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 } @@ -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() @@ -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) @@ -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 } diff --git a/script/script_module_test.go b/script/script_module_test.go index 5b3290c1..3203a155 100644 --- a/script/script_module_test.go +++ b/script/script_module_test.go @@ -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`))