xgo/script/script.go

223 lines
5.1 KiB
Go
Raw Normal View History

2019-01-09 23:39:40 +03:00
package script
2019-01-09 10:17:42 +03:00
import (
2019-01-20 15:59:43 +03:00
"context"
2019-01-09 10:17:42 +03:00
"fmt"
"github.com/d5/tengo/compiler"
2019-01-11 13:27:28 +03:00
"github.com/d5/tengo/compiler/parser"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/stdlib"
2019-01-09 10:17:42 +03:00
"github.com/d5/tengo/objects"
2019-01-11 13:27:28 +03:00
"github.com/d5/tengo/runtime"
2019-01-09 10:17:42 +03:00
)
2019-01-09 23:39:40 +03:00
// Script can simplify compilation and execution of embedded scripts.
2019-01-09 10:17:42 +03:00
type Script struct {
variables map[string]*Variable
removedBuiltins map[string]bool
removedStdModules map[string]bool
scriptModules map[string]*Script
userModuleLoader compiler.ModuleLoader
input []byte
2019-01-09 10:17:42 +03:00
}
2019-01-09 23:39:40 +03:00
// New creates a Script instance with an input script.
func New(input []byte) *Script {
2019-01-09 10:17:42 +03:00
return &Script{
2019-01-09 23:39:40 +03:00
variables: make(map[string]*Variable),
2019-01-09 10:17:42 +03:00
input: input,
}
}
2019-01-09 23:39:40 +03:00
// Add adds a new variable or updates an existing variable to the script.
2019-01-09 10:17:42 +03:00
func (s *Script) Add(name string, value interface{}) error {
obj, err := objects.FromInterface(value)
2019-01-09 10:17:42 +03:00
if err != nil {
return err
}
2019-01-09 23:39:40 +03:00
s.variables[name] = &Variable{
name: name,
value: &obj,
}
2019-01-09 10:17:42 +03:00
return nil
}
2019-01-09 23:39:40 +03:00
// Remove removes (undefines) an existing variable for the script.
// It returns false if the variable name is not defined.
2019-01-09 10:17:42 +03:00
func (s *Script) Remove(name string) bool {
if _, ok := s.variables[name]; !ok {
return false
}
delete(s.variables, name)
return true
}
// DisableBuiltinFunction disables a builtin function.
func (s *Script) DisableBuiltinFunction(name string) {
if s.removedBuiltins == nil {
s.removedBuiltins = make(map[string]bool)
}
s.removedBuiltins[name] = true
}
// DisableStdModule disables a standard library module.
func (s *Script) DisableStdModule(name string) {
if s.removedStdModules == nil {
s.removedStdModules = make(map[string]bool)
}
s.removedStdModules[name] = true
}
// SetUserModuleLoader sets the user module loader for the compiler.
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)
2019-01-31 08:50:15 +03:00
}
s.scriptModules[name] = scriptModule
2019-01-31 08:50:15 +03:00
}
2019-01-09 23:39:40 +03:00
// Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) {
symbolTable, stdModules, globals, err := s.prepCompile()
if err != nil {
return nil, err
}
2019-01-09 10:17:42 +03:00
fileSet := source.NewFileSet()
2019-01-09 10:17:42 +03:00
p := parser.NewParser(fileSet.AddFile("", -1, len(s.input)), s.input, nil)
file, err := p.ParseFile()
if err != nil {
return nil, fmt.Errorf("parse error: %s", err.Error())
}
c := compiler.NewCompiler(symbolTable, stdModules, nil)
if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader)
}
2019-01-09 10:17:42 +03:00
if err := c.Compile(file); err != nil {
return nil, err
}
2019-01-09 23:39:40 +03:00
return &Compiled{
2019-01-09 10:17:42 +03:00
symbolTable: symbolTable,
2019-01-11 13:27:28 +03:00
machine: runtime.NewVM(c.Bytecode(), globals),
2019-01-09 10:17:42 +03:00
}, nil
}
2019-01-13 05:25:45 +03:00
// 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
}
2019-01-20 15:59:43 +03:00
// 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
}
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]*objects.ImmutableMap, globals []*objects.Object, err error) {
2019-01-09 10:17:42 +03:00
var names []string
for name := range s.variables {
names = append(names, name)
}
symbolTable = compiler.NewSymbolTable()
2019-01-18 20:19:45 +03:00
for idx, fn := range objects.Builtins {
if !s.removedBuiltins[fn.Name] {
symbolTable.DefineBuiltin(idx, fn.Name)
}
}
stdModules = make(map[string]*objects.ImmutableMap)
for name, mod := range stdlib.Modules {
if !s.removedStdModules[name] {
stdModules[name] = mod
}
2019-01-18 20:19:45 +03:00
}
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
}
}
}
2019-01-31 08:50:15 +03:00
stdModules[name] = mod
}
2019-01-18 20:19:45 +03:00
2019-01-09 10:17:42 +03:00
globals = make([]*objects.Object, len(names), len(names))
for idx, name := range names {
symbol := symbolTable.Define(name)
if symbol.Index != idx {
panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index))
}
2019-01-09 23:39:40 +03:00
globals[symbol.Index] = s.variables[name].value
2019-01-09 10:17:42 +03:00
}
return
}
2019-01-09 23:39:40 +03:00
func (s *Script) copyVariables() map[string]*Variable {
vars := make(map[string]*Variable)
for n, v := range s.variables {
vars[n] = v
2019-01-09 10:17:42 +03:00
}
2019-01-09 23:39:40 +03:00
return vars
2019-01-09 10:17:42 +03:00
}