xgo/docs/interoperability.md

225 lines
8.3 KiB
Markdown
Raw Normal View History

2019-01-24 04:35:57 +03:00
# Interoperability
## Table of Contents
2019-01-24 04:35:57 +03:00
- [Using Scripts](#using-scripts)
- [Type Conversion Table](#type-conversion-table)
2019-01-31 08:50:15 +03:00
- [User Types](#user-types)
- [Sandbox Environments](#sandbox-environments)
- [Concurrency](#concurrency)
2019-01-25 00:56:41 +03:00
- [Compiler and VM](#compiler-and-vm)
2019-01-24 04:35:57 +03:00
## Using Scripts
Embedding and executing the Tengo code in Go is very easy. At a high level, this process is like:
- create a [Script](https://godoc.org/github.com/d5/tengo/script#Script) instance with your code,
- _optionally_ add some [Script Variables](https://godoc.org/github.com/d5/tengo/script#Variable) to Script,
- compile or directly run the script,
- retrieve _output_ values from the [Compiled](https://godoc.org/github.com/d5/tengo/script#Compiled) instance.
The following is an example where a Tengo script is compiled and run with no input/output variables.
2019-01-24 03:30:41 +03:00
```golang
import "github.com/d5/tengo/script"
var code = `
reduce := func(seq, fn) {
s := 0
for x in seq { fn(x, s) }
return s
}
print(reduce([1, 2, 3], func(x, s) { s += x }))
`
func main() {
s := script.New([]byte(code))
if _, err := s.Run(); err != nil {
panic(err)
}
}
```
2019-01-24 04:35:57 +03:00
Here's another example where an input variable is added to the script, and, an output variable is accessed through [Variable.Int](https://godoc.org/github.com/d5/tengo/script#Variable.Int) function:
2019-01-24 03:30:41 +03:00
```golang
import (
"fmt"
"github.com/d5/tengo/script"
)
func main() {
s := script.New([]byte(`a := b + 20`))
// define variable 'b'
_ = s.Add("b", 10)
// compile the source
c, err := s.Compile()
if err != nil {
panic(err)
}
// run the compiled bytecode
// a compiled bytecode 'c' can be executed multiple times without re-compiling it
2019-01-24 03:30:41 +03:00
if err := c.Run(); err != nil {
panic(err)
}
// retrieve value of 'a'
a := c.Get("a")
fmt.Println(a.Int()) // prints "30"
// re-run after replacing value of 'b'
if err := c.Set("b", 20); err != nil {
panic(err)
}
if err := c.Run(); err != nil {
panic(err)
}
fmt.Println(c.Get("a").Int()) // prints "40"
2019-01-24 03:30:41 +03:00
}
```
2019-01-24 04:35:57 +03:00
A variable `b` is defined by the user before compilation using [Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add) function. Then a compiled bytecode `c` is used to execute the bytecode and get the value of global variables. In this example, the value of global variable `a` is read using [Compiled.Get](https://godoc.org/github.com/d5/tengo/script#Compiled.Get) function. See [documentation](https://godoc.org/github.com/d5/tengo/script#Variable) for the full list of variable value functions.
2019-01-24 03:30:41 +03:00
Value of the global variables can be replaced using [Compiled.Set](https://godoc.org/github.com/d5/tengo/script#Compiled.Set) function. But it will return an error if you try to set the value of un-defined global variables _(e.g. trying to set the value of `x` in the example)_.
2019-01-24 04:35:57 +03:00
### Type Conversion Table
2019-01-24 03:30:41 +03:00
2019-01-24 04:35:57 +03:00
When adding a Variable _([Script.Add](https://godoc.org/github.com/d5/tengo/script#Script.Add))_, Script converts Go values into Tengo values based on the following conversion table.
2019-01-24 03:30:41 +03:00
2019-01-24 04:35:57 +03:00
| Go Type | Tengo Type | Note |
| :--- | :--- | :--- |
|`nil`|`Undefined`||
|`string`|`String`||
|`int64`|`Int`||
|`int`|`Int`||
|`bool`|`Bool`||
|`rune`|`Char`||
|`byte`|`Char`||
|`float64`|`Float`||
|`[]byte`|`Bytes`||
2019-01-31 08:50:15 +03:00
|`time.Time`|`Time`||
2019-01-24 04:35:57 +03:00
|`error`|`Error{String}`|use `error.Error()` as String value|
|`map[string]Object`|`Map`||
|`map[string]interface{}`|`Map`|individual elements converted to Tengo objects|
|`[]Object`|`Array`||
|`[]interface{}`|`Array`|individual elements converted to Tengo objects|
|`Object`|`Object`|_(no type conversion performed)_|
2019-01-24 03:30:41 +03:00
2019-01-31 08:50:15 +03:00
### User Types
2019-01-31 12:14:47 +03:00
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.
2019-01-25 00:56:41 +03:00
## Sandbox Environments
2019-01-25 00:56:41 +03:00
To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions.
2019-01-24 04:35:57 +03:00
#### Script.SetBuiltinFunctions(funcs []*objects.BuiltinFunction)
SetBuiltinFunctions resets all builtin functions in the compiler to the ones provided in the input parameter. Compiler will report a compile-time error if the a function not set is referenced. Passing `nil` will disable all builtin functions. All builtin functions **are included by default** unless `SetBuiltinFunctions` is called.
2019-01-25 00:56:41 +03:00
```golang
s := script.New([]byte(`print([1, 2, 3])`))
_, err := s.Run() // prints [1, 2, 3]
s.SetBuiltinFunctions(nil)
2019-01-25 00:56:41 +03:00
_, err := s.Run() // compile error
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]})
2019-01-25 00:56:41 +03:00
_, err := s.Run() // prints [1, 2, 3]
```
#### Script.SetBuiltinModules(modules map[string]*objects.ImmutableMap)
2019-01-25 00:56:41 +03:00
SetBuiltinModules adds builtin modules provided in the input parameter. This can be used to add [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) modules into the compiler and VM. Compiler will report a compile-time error if the code tries to import a module that hasn't been included. Passing `nil` will disable all builtin modules. No standard library modules are included by default unless `SetBuiltinModules` is called.
2019-01-25 00:56:41 +03:00
```golang
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
_, err := s.Run() // compile error
s.SetBuiltinModules(stdlib.Modules)
_, err := s.Run() // a = 19.84
s.SetBuiltinModules(nil)
_, err := s.Run() // compile error
s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": stdlib.Modules["math"]})
2019-01-25 00:56:41 +03:00
_, err := s.Run() // a = 19.84
```
2019-01-25 00:56:41 +03:00
#### Script.SetUserModuleLoader(loader compiler.ModuleLoader)
SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file.
```golang
s := script.New([]byte(`math := import("mod1"); a := math.foo()`))
s.SetUserModuleLoader(func(moduleName string) ([]byte, error) {
if moduleName == "mod1" {
return []byte(`foo := func() { return 5 }`), nil
}
return nil, errors.New("module not found")
})
```
Note that when a script is being added to another script as a module (via `Script.AddModule`), it does not inherit the module loader from the main script.
#### Script.SetMaxAllocs(n int64)
SetMaxAllocs sets the maximum number of object allocations. Note this is a cumulative metric that tracks only the object creations. Set this to a negative number (e.g. `-1`) if you don't need to limit the number of allocations.
#### tengo.MaxStringLen
Sets the maximum byte-length of string values. This limit applies to all running VM instances in the process. Also it's not recommended to set or update this value while any VM is executing.
#### tengo.MaxBytesLen
Sets the maximum length of bytes values. This limit applies to all running VM instances in the process. Also it's not recommended to set or update this value while any VM is executing.
## Concurrency
A compiled script (`script.Compiled`) can be used to run the code multiple times by a goroutine. If you want to run the compiled script by multiple goroutine, you should use `Compiled.Clone` function to make a copy of Compiled instances.
#### Compiled.Clone()
Clone creates a new copy of Compiled instance. Cloned copies are safe for concurrent use by multiple goroutines.
```golang
for i := 0; i < concurrency; i++ {
go func(compiled *script.Compiled) {
// inputs
_ = compiled.Set("a", rand.Intn(10))
_ = compiled.Set("b", rand.Intn(10))
_ = compiled.Set("c", rand.Intn(10))
if err := compiled.Run(); err != nil {
panic(err)
}
// outputs
d = compiled.Get("d").Int()
e = compiled.Get("e").Int()
}(compiled.Clone()) // Pass the cloned copy of Compiled
}
```
2019-01-25 00:56:41 +03:00
## Compiler and VM
Although it's not recommended, you can directly create and run the Tengo [Parser](https://godoc.org/github.com/d5/tengo/compiler/parser#Parser), [Compiler](https://godoc.org/github.com/d5/tengo/compiler#Compiler), and [VM](https://godoc.org/github.com/d5/tengo/runtime#VM) for yourself instead of using Scripts and Script Variables. It's a bit more involved as you have to manage the symbol tables and global variables between them, but, basically that's what Script and Script Variable is doing internally.
_TODO: add more information here_