Skip to content

Commit

Permalink
Merge pull request #64 from d5/scriptmodule
Browse files Browse the repository at this point in the history
Script Imports
  • Loading branch information
d5 authored Jan 31, 2019
2 parents f7b0cd8 + 44248b8 commit 61e534e
Show file tree
Hide file tree
Showing 8 changed files with 370 additions and 185 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ _* See [here](https://github.com/d5/tengobench) for commands/codes used_
## References

- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md)
- [Tengo Objects](https://github.com/d5/tengo/blob/master/docs/objects.md)
- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md)
- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md)
- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md)
- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md)
Expand Down
22 changes: 19 additions & 3 deletions docs/interoperability.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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|
Expand All @@ -98,9 +100,23 @@ 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.
Users can add and use a custom user type in Tengo code by implementing [Object](https://godoc.org/github.com/d5/tengo/objects#Object) interface. Tengo runtime will treat the user types in the same way it does to the runtime types with no performance overhead. See [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md) for more details.

### Importing Scripts

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
mod1Script := script.New([]byte(`a := 5`)) // mod1 script

mainScript := script.New([]byte(`print(import("mod1").a)`)) // main script
mainScript.AddModule("mod1", mod1Script) // add mod1 using name "mod1"
mainScript.Run() // prints "5"
```

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
369 changes: 205 additions & 164 deletions docs/objects.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ b = "foo" // ok: this is not mutating the value of array
// but updating reference 'b' with different value
```

Not that, if you copy (using `copy` builtin function) an immutable value, it will return a "mutable" copy. Also, immutability is not applied to the individual elements of the array or map value, unless they are explicitly made immutable.
Note that, if you copy (using `copy` builtin function) an immutable value, it will return a "mutable" copy. Also, immutability is not applied to the individual elements of the array or map value, unless they are explicitly made immutable.

```golang
a := immutable({b: 4, c: [1, 2, 3]})
Expand Down Expand Up @@ -232,7 +232,7 @@ func1 := func(x) { print(x) }
foo := 2
```

Basically, `import` expression returns all the global variables defined in the module as a Map-like value. One can access the functions or variables defined in the module using `.` selector or `["key"]` indexer, but, module variables are immutable.
Basically, `import` expression returns all the global variables defined in the module as an ImmutableMap value.

Also, you can use `import` to load the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md).

Expand Down
12 changes: 0 additions & 12 deletions runtime/vm_call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,4 @@ func TestCall(t *testing.T) {
expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, 7)

// "this" binding
// expect(t, `
//a = {
// b: {
// c: func(x) {
// return x + this.d // this -> a.b
// },
// d: 5
// }
//}
//out = a["b"].c(2)`, 7)
}
66 changes: 65 additions & 1 deletion runtime/vm_indexable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,55 @@ func (o *StringCircle) IndexSet(index, value objects.Object) error {
}

type StringArray struct {
objectImpl
Value []string
}

func (o *StringArray) String() string {
return strings.Join(o.Value, ", ")
}

func (o *StringArray) BinaryOp(op token.Token, rhs objects.Object) (objects.Object, error) {
if rhs, ok := rhs.(*StringArray); ok {
switch op {
case token.Add:
if len(rhs.Value) == 0 {
return o, nil
}
return &StringArray{Value: append(o.Value, rhs.Value...)}, nil
}
}

return nil, objects.ErrInvalidOperator
}

func (o *StringArray) IsFalsy() bool {
return len(o.Value) == 0
}

func (o *StringArray) Equals(x objects.Object) bool {
if x, ok := x.(*StringArray); ok {
if len(o.Value) != len(x.Value) {
return false
}

for i, v := range o.Value {
if v != x.Value[i] {
return false
}
}

return true
}

return false
}

func (o *StringArray) Copy() objects.Object {
return &StringArray{
Value: append([]string{}, o.Value...),
}
}

func (o *StringArray) TypeName() string {
return "string-array"
}
Expand Down Expand Up @@ -155,6 +200,25 @@ func (o *StringArray) IndexSet(index, value objects.Object) error {
return objects.ErrInvalidIndexType
}

func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}

s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
}

for i, v := range o.Value {
if v == s1 {
return &objects.Int{Value: int64(i)}, nil
}
}

return objects.UndefinedValue, nil
}

func TestIndexable(t *testing.T) {
dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} }
expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()})
Expand Down
50 changes: 48 additions & 2 deletions script/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Script struct {
variables map[string]*Variable
removedBuiltins map[string]bool
removedStdModules map[string]bool
scriptModules map[string]*Script
userModuleLoader compiler.ModuleLoader
input []byte
}
Expand Down Expand Up @@ -79,9 +80,22 @@ func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
s.userModuleLoader = loader
}

// 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)
}

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 @@ -132,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 @@ -151,6 +165,38 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
stdModules[name] = mod
}
}
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
}

globals = make([]*objects.Object, len(names), len(names))

Expand Down
30 changes: 30 additions & 0 deletions script/script_module_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package script_test

import (
"testing"

"github.com/d5/tengo/assert"
"github.com/d5/tengo/script"
)

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

// script1 imports "mod1"
scr1 := script.New([]byte(`mod1 := import("mod1"); out := mod1.a`))
scr1.AddModule("mod1", mod1)
c, err := scr1.Run()
assert.Equal(t, int64(5), c.Get("out").Value())

// mod2 module imports "mod1"
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`))
scr2.AddModule("mod2", mod2)
c, err = scr2.Run()
assert.NoError(t, err)
assert.Equal(t, int64(10), c.Get("out").Value())
}

0 comments on commit 61e534e

Please sign in to comment.