2019-02-23 16:39:44 +01:00
|
|
|
package script
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/d5/tengo/compiler"
|
|
|
|
"github.com/d5/tengo/compiler/parser"
|
|
|
|
"github.com/d5/tengo/compiler/source"
|
|
|
|
"github.com/d5/tengo/objects"
|
|
|
|
"github.com/d5/tengo/runtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Script can simplify compilation and execution of embedded scripts.
|
|
|
|
type Script struct {
|
2019-03-05 23:08:54 +01:00
|
|
|
variables map[string]*Variable
|
|
|
|
builtinFuncs []objects.Object
|
|
|
|
builtinModules map[string]*objects.Object
|
|
|
|
userModuleLoader compiler.ModuleLoader
|
|
|
|
input []byte
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a Script instance with an input script.
|
|
|
|
func New(input []byte) *Script {
|
|
|
|
return &Script{
|
|
|
|
variables: make(map[string]*Variable),
|
|
|
|
input: input,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add adds a new variable or updates an existing variable to the script.
|
|
|
|
func (s *Script) Add(name string, value interface{}) error {
|
|
|
|
obj, err := objects.FromInterface(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.variables[name] = &Variable{
|
|
|
|
name: name,
|
|
|
|
value: &obj,
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove removes (undefines) an existing variable for the script.
|
|
|
|
// It returns false if the variable name is not defined.
|
|
|
|
func (s *Script) Remove(name string) bool {
|
|
|
|
if _, ok := s.variables[name]; !ok {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(s.variables, name)
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-03-05 23:08:54 +01:00
|
|
|
// SetBuiltinFunctions allows to define builtin functions.
|
|
|
|
func (s *Script) SetBuiltinFunctions(funcs []*objects.BuiltinFunction) {
|
|
|
|
if funcs != nil {
|
|
|
|
s.builtinFuncs = make([]objects.Object, len(funcs))
|
|
|
|
for idx, fn := range funcs {
|
|
|
|
s.builtinFuncs[idx] = fn
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s.builtinFuncs = []objects.Object{}
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 23:08:54 +01:00
|
|
|
// SetBuiltinModules allows to define builtin modules.
|
|
|
|
func (s *Script) SetBuiltinModules(modules map[string]*objects.ImmutableMap) {
|
|
|
|
if modules != nil {
|
|
|
|
s.builtinModules = make(map[string]*objects.Object, len(modules))
|
|
|
|
for k, mod := range modules {
|
|
|
|
s.builtinModules[k] = objectPtr(mod)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s.builtinModules = map[string]*objects.Object{}
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetUserModuleLoader sets the user module loader for the compiler.
|
|
|
|
func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
|
|
|
|
s.userModuleLoader = loader
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compile compiles the script with all the defined variables, and, returns Compiled object.
|
|
|
|
func (s *Script) Compile() (*Compiled, error) {
|
2019-03-05 23:08:54 +01:00
|
|
|
symbolTable, builtinModules, globals, err := s.prepCompile()
|
2019-02-23 16:39:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fileSet := source.NewFileSet()
|
|
|
|
srcFile := fileSet.AddFile("(main)", -1, len(s.input))
|
|
|
|
|
|
|
|
p := parser.NewParser(srcFile, s.input, nil)
|
|
|
|
file, err := p.ParseFile()
|
|
|
|
if err != nil {
|
2019-03-05 23:08:54 +01:00
|
|
|
return nil, err
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
|
2019-03-05 23:08:54 +01:00
|
|
|
c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil)
|
2019-02-23 16:39:44 +01:00
|
|
|
|
|
|
|
if s.userModuleLoader != nil {
|
|
|
|
c.SetModuleLoader(s.userModuleLoader)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Compile(file); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Compiled{
|
|
|
|
symbolTable: symbolTable,
|
2019-03-05 23:08:54 +01:00
|
|
|
machine: runtime.NewVM(c.Bytecode(), globals, s.builtinFuncs, s.builtinModules),
|
2019-02-23 16:39:44 +01:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run compiles and runs the scripts.
|
|
|
|
// Use returned compiled object to access global variables.
|
|
|
|
func (s *Script) Run() (compiled *Compiled, err error) {
|
|
|
|
compiled, err = s.Compile()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = compiled.Run()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RunContext is like Run but includes a context.
|
|
|
|
func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) {
|
|
|
|
compiled, err = s.Compile()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = compiled.RunContext(ctx)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-05 23:08:54 +01:00
|
|
|
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModules map[string]bool, globals []*objects.Object, err error) {
|
2019-02-23 16:39:44 +01:00
|
|
|
var names []string
|
|
|
|
for name := range s.variables {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
symbolTable = compiler.NewSymbolTable()
|
2019-03-05 23:08:54 +01:00
|
|
|
|
|
|
|
if s.builtinFuncs == nil {
|
|
|
|
s.builtinFuncs = make([]objects.Object, len(objects.Builtins))
|
|
|
|
for idx, fn := range objects.Builtins {
|
|
|
|
s.builtinFuncs[idx] = &objects.BuiltinFunction{
|
|
|
|
Name: fn.Name,
|
|
|
|
Value: fn.Value,
|
|
|
|
}
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 23:08:54 +01:00
|
|
|
if s.builtinModules == nil {
|
|
|
|
s.builtinModules = make(map[string]*objects.Object)
|
|
|
|
}
|
|
|
|
|
|
|
|
for idx, fn := range s.builtinFuncs {
|
|
|
|
f := fn.(*objects.BuiltinFunction)
|
|
|
|
symbolTable.DefineBuiltin(idx, f.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
builtinModules = make(map[string]bool)
|
|
|
|
for name := range s.builtinModules {
|
|
|
|
builtinModules[name] = true
|
2019-02-23 16:39:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize)
|
|
|
|
|
|
|
|
for idx, name := range names {
|
|
|
|
symbol := symbolTable.Define(name)
|
|
|
|
if symbol.Index != idx {
|
|
|
|
panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index))
|
|
|
|
}
|
|
|
|
|
|
|
|
globals[symbol.Index] = s.variables[name].value
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Script) copyVariables() map[string]*Variable {
|
|
|
|
vars := make(map[string]*Variable)
|
|
|
|
for n, v := range s.variables {
|
|
|
|
vars[n] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
return vars
|
|
|
|
}
|
2019-03-05 23:08:54 +01:00
|
|
|
|
|
|
|
func objectPtr(o objects.Object) *objects.Object {
|
|
|
|
return &o
|
|
|
|
}
|