diff options
Diffstat (limited to 'vendor/github.com')
59 files changed, 7000 insertions, 1288 deletions
diff --git a/vendor/github.com/d5/tengo/Makefile b/vendor/github.com/d5/tengo/Makefile index e4613390..99daafd1 100644 --- a/vendor/github.com/d5/tengo/Makefile +++ b/vendor/github.com/d5/tengo/Makefile @@ -1,10 +1,13 @@ vet: go vet ./... +generate: + go generate ./... + lint: golint -set_exit_status ./... -test: vet lint +test: generate vet lint go test -race -cover ./... fmt: diff --git a/vendor/github.com/d5/tengo/README.md b/vendor/github.com/d5/tengo/README.md index 7aa05966..e68b0f86 100644 --- a/vendor/github.com/d5/tengo/README.md +++ b/vendor/github.com/d5/tengo/README.md @@ -7,7 +7,6 @@ [![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo/script) [![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo) [![Build Status](https://travis-ci.org/d5/tengo.svg?branch=master)](https://travis-ci.org/d5/tengo) -[![](https://img.shields.io/badge/Support%20Tengo-%241-brightgreen.svg)](https://www.patreon.com/tengolang) **Tengo is a small, dynamic, fast, secure script language for Go.** @@ -16,6 +15,8 @@ Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as byt ```golang /* The Tengo Language */ +fmt := import("fmt") + each := func(seq, fn) { for x in seq { fn(x) } } @@ -25,11 +26,11 @@ sum := func(init, seq) { return init } -n := sum(0, [1, 2, 3]) // == 6 -s := sum("", [1, 2, 3]) // == "123" +fmt.println(sum(0, [1, 2, 3])) // "6" +fmt.println(sum("", [1, 2, 3])) // "123" ``` -> Run this code in the [Playground](https://tengolang.com/?s=d01cf9ed81daba939e26618530eb171f7397d9c9) +> Run this code in the [Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3) ## Features @@ -41,22 +42,23 @@ s := sum("", [1, 2, 3]) // == "123" - [Securely Embeddable](https://github.com/d5/tengo/blob/master/docs/interoperability.md) and [Extensible](https://github.com/d5/tengo/blob/master/docs/objects.md) - Compiler/runtime written in native Go _(no external deps or cgo)_ - Executable as a [standalone](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) language / REPL +- Use cases: rules engine, [state machine](https://github.com/d5/go-fsm), [gaming](https://github.com/d5/pbr), data pipeline, [transpiler](https://github.com/d5/tengo2lua) ## Benchmark | | fib(35) | fibt(35) | Type | | :--- | ---: | ---: | :---: | -| Go | `58ms` | `4ms` | Go (native) | -| [**Tengo**](https://github.com/d5/tengo) | `4,180ms` | `5ms` | VM on Go | -| Lua | `1,695ms` | `3ms` | Lua (native) | -| [go-lua](https://github.com/Shopify/go-lua) | `5,163ms` | `5ms` | Lua VM on Go | -| [GopherLua](https://github.com/yuin/gopher-lua) | `5,525ms` | `5ms` | Lua VM on Go | -| Python | `3,097ms` | `27ms` | Python (native) | -| [starlark-go](https://github.com/google/starlark-go) | `15,307ms` | `5ms` | Python-like Interpreter on Go | -| [gpython](https://github.com/go-python/gpython) | `17,656ms` | `5ms` | Python Interpreter on Go | -| [goja](https://github.com/dop251/goja) | `6,876ms` | `5ms` | JS VM on Go | -| [otto](https://github.com/robertkrimen/otto) | `81,886ms` | `12ms` | JS Interpreter on Go | -| [Anko](https://github.com/mattn/anko) | `97,517ms` | `14ms` | Interpreter on Go | +| Go | `48ms` | `3ms` | Go (native) | +| [**Tengo**](https://github.com/d5/tengo) | `2,349ms` | `5ms` | VM on Go | +| Lua | `1,416ms` | `3ms` | Lua (native) | +| [go-lua](https://github.com/Shopify/go-lua) | `4,402ms` | `5ms` | Lua VM on Go | +| [GopherLua](https://github.com/yuin/gopher-lua) | `4,023ms` | `5ms` | Lua VM on Go | +| Python | `2,588ms` | `26ms` | Python (native) | +| [starlark-go](https://github.com/google/starlark-go) | `11,126ms` | `6ms` | Python-like Interpreter on Go | +| [gpython](https://github.com/go-python/gpython) | `15,035ms` | `4ms` | Python Interpreter on Go | +| [goja](https://github.com/dop251/goja) | `5,089ms` | `5ms` | JS VM on Go | +| [otto](https://github.com/robertkrimen/otto) | `68,377ms` | `11ms` | JS Interpreter on Go | +| [Anko](https://github.com/mattn/anko) | `92,579ms` | `18ms` | Interpreter on Go | _* [fib(35)](https://github.com/d5/tengobench/blob/master/code/fib.tengo): Fibonacci(35)_ _* [fibt(35)](https://github.com/d5/tengobench/blob/master/code/fibtc.tengo): [tail-call](https://en.wikipedia.org/wiki/Tail_call) version of Fibonacci(35)_ diff --git a/vendor/github.com/d5/tengo/compiler/bytecode.go b/vendor/github.com/d5/tengo/compiler/bytecode.go index 42527731..35f36c0a 100644 --- a/vendor/github.com/d5/tengo/compiler/bytecode.go +++ b/vendor/github.com/d5/tengo/compiler/bytecode.go @@ -17,32 +17,6 @@ type Bytecode struct { Constants []objects.Object } -// Decode reads Bytecode data from the reader. -func (b *Bytecode) Decode(r io.Reader) error { - dec := gob.NewDecoder(r) - - if err := dec.Decode(&b.FileSet); err != nil { - return err - } - // TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet - // as it's private field and not serialized by gob encoder/decoder. - - if err := dec.Decode(&b.MainFunction); err != nil { - return err - } - - if err := dec.Decode(&b.Constants); err != nil { - return err - } - - // replace Bool and Undefined with known value - for i, v := range b.Constants { - b.Constants[i] = cleanupObjects(v) - } - - return nil -} - // Encode writes Bytecode data to the writer. func (b *Bytecode) Encode(w io.Writer) error { enc := gob.NewEncoder(w) @@ -59,6 +33,17 @@ func (b *Bytecode) Encode(w io.Writer) error { return enc.Encode(b.Constants) } +// CountObjects returns the number of objects found in Constants. +func (b *Bytecode) CountObjects() int { + n := 0 + + for _, c := range b.Constants { + n += objects.CountObjects(c) + } + + return n +} + // FormatInstructions returns human readable string representations of // compiled instructions. func (b *Bytecode) FormatInstructions() []string { @@ -83,51 +68,22 @@ func (b *Bytecode) FormatConstants() (output []string) { return } -func cleanupObjects(o objects.Object) objects.Object { - switch o := o.(type) { - case *objects.Bool: - if o.IsFalsy() { - return objects.FalseValue - } - return objects.TrueValue - case *objects.Undefined: - return objects.UndefinedValue - case *objects.Array: - for i, v := range o.Value { - o.Value[i] = cleanupObjects(v) - } - case *objects.Map: - for k, v := range o.Value { - o.Value[k] = cleanupObjects(v) - } - } - - return o -} - func init() { gob.Register(&source.FileSet{}) gob.Register(&source.File{}) gob.Register(&objects.Array{}) - gob.Register(&objects.ArrayIterator{}) gob.Register(&objects.Bool{}) - gob.Register(&objects.Break{}) - gob.Register(&objects.BuiltinFunction{}) gob.Register(&objects.Bytes{}) gob.Register(&objects.Char{}) gob.Register(&objects.Closure{}) gob.Register(&objects.CompiledFunction{}) - gob.Register(&objects.Continue{}) gob.Register(&objects.Error{}) gob.Register(&objects.Float{}) gob.Register(&objects.ImmutableArray{}) gob.Register(&objects.ImmutableMap{}) gob.Register(&objects.Int{}) gob.Register(&objects.Map{}) - gob.Register(&objects.MapIterator{}) - gob.Register(&objects.ReturnValue{}) gob.Register(&objects.String{}) - gob.Register(&objects.StringIterator{}) gob.Register(&objects.Time{}) gob.Register(&objects.Undefined{}) gob.Register(&objects.UserFunction{}) diff --git a/vendor/github.com/d5/tengo/compiler/bytecode_decode.go b/vendor/github.com/d5/tengo/compiler/bytecode_decode.go new file mode 100644 index 00000000..b3b32a64 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/bytecode_decode.go @@ -0,0 +1,97 @@ +package compiler + +import ( + "encoding/gob" + "fmt" + "io" + + "github.com/d5/tengo/objects" +) + +// Decode reads Bytecode data from the reader. +func (b *Bytecode) Decode(r io.Reader, modules *objects.ModuleMap) error { + if modules == nil { + modules = objects.NewModuleMap() + } + + dec := gob.NewDecoder(r) + + if err := dec.Decode(&b.FileSet); err != nil { + return err + } + // TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet + // as it's private field and not serialized by gob encoder/decoder. + + if err := dec.Decode(&b.MainFunction); err != nil { + return err + } + + if err := dec.Decode(&b.Constants); err != nil { + return err + } + for i, v := range b.Constants { + fv, err := fixDecoded(v, modules) + if err != nil { + return err + } + b.Constants[i] = fv + } + + return nil +} + +func fixDecoded(o objects.Object, modules *objects.ModuleMap) (objects.Object, error) { + switch o := o.(type) { + case *objects.Bool: + if o.IsFalsy() { + return objects.FalseValue, nil + } + return objects.TrueValue, nil + case *objects.Undefined: + return objects.UndefinedValue, nil + case *objects.Array: + for i, v := range o.Value { + fv, err := fixDecoded(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *objects.ImmutableArray: + for i, v := range o.Value { + fv, err := fixDecoded(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *objects.Map: + for k, v := range o.Value { + fv, err := fixDecoded(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + case *objects.ImmutableMap: + modName := moduleName(o) + if mod := modules.GetBuiltinModule(modName); mod != nil { + return mod.AsImmutableMap(modName), nil + } + + for k, v := range o.Value { + // encoding of user function not supported + if _, isUserFunction := v.(*objects.UserFunction); isUserFunction { + return nil, fmt.Errorf("user function not decodable") + } + + fv, err := fixDecoded(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + } + + return o, nil +} diff --git a/vendor/github.com/d5/tengo/compiler/bytecode_optimize.go b/vendor/github.com/d5/tengo/compiler/bytecode_optimize.go new file mode 100644 index 00000000..9526de2e --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/bytecode_optimize.go @@ -0,0 +1,129 @@ +package compiler + +import ( + "fmt" + + "github.com/d5/tengo/objects" +) + +// RemoveDuplicates finds and remove the duplicate values in Constants. +// Note this function mutates Bytecode. +func (b *Bytecode) RemoveDuplicates() { + var deduped []objects.Object + + indexMap := make(map[int]int) // mapping from old constant index to new index + ints := make(map[int64]int) + strings := make(map[string]int) + floats := make(map[float64]int) + chars := make(map[rune]int) + immutableMaps := make(map[string]int) // for modules + + for curIdx, c := range b.Constants { + switch c := c.(type) { + case *objects.CompiledFunction: + // add to deduped list + indexMap[curIdx] = len(deduped) + deduped = append(deduped, c) + case *objects.ImmutableMap: + modName := moduleName(c) + newIdx, ok := immutableMaps[modName] + if modName != "" && ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + immutableMaps[modName] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *objects.Int: + if newIdx, ok := ints[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + ints[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *objects.String: + if newIdx, ok := strings[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + strings[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *objects.Float: + if newIdx, ok := floats[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + floats[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + case *objects.Char: + if newIdx, ok := chars[c.Value]; ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + chars[c.Value] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } + default: + panic(fmt.Errorf("unsupported top-level constant type: %s", c.TypeName())) + } + } + + // replace with de-duplicated constants + b.Constants = deduped + + // update CONST instructions with new indexes + // main function + updateConstIndexes(b.MainFunction.Instructions, indexMap) + // other compiled functions in constants + for _, c := range b.Constants { + switch c := c.(type) { + case *objects.CompiledFunction: + updateConstIndexes(c.Instructions, indexMap) + } + } +} + +func updateConstIndexes(insts []byte, indexMap map[int]int) { + i := 0 + for i < len(insts) { + op := insts[i] + numOperands := OpcodeOperands[op] + _, read := ReadOperands(numOperands, insts[i+1:]) + + switch op { + case OpConstant: + curIdx := int(insts[i+2]) | int(insts[i+1])<<8 + newIdx, ok := indexMap[curIdx] + if !ok { + panic(fmt.Errorf("constant index not found: %d", curIdx)) + } + copy(insts[i:], MakeInstruction(op, newIdx)) + case OpClosure: + curIdx := int(insts[i+2]) | int(insts[i+1])<<8 + numFree := int(insts[i+3]) + newIdx, ok := indexMap[curIdx] + if !ok { + panic(fmt.Errorf("constant index not found: %d", curIdx)) + } + copy(insts[i:], MakeInstruction(op, newIdx, numFree)) + } + + i += 1 + read + } +} + +func moduleName(mod *objects.ImmutableMap) string { + if modName, ok := mod.Value["__module_name__"].(*objects.String); ok { + return modName.Value + } + + return "" +} diff --git a/vendor/github.com/d5/tengo/compiler/compilation_scope.go b/vendor/github.com/d5/tengo/compiler/compilation_scope.go index dd198ae9..41e7876b 100644 --- a/vendor/github.com/d5/tengo/compiler/compilation_scope.go +++ b/vendor/github.com/d5/tengo/compiler/compilation_scope.go @@ -5,8 +5,7 @@ import "github.com/d5/tengo/compiler/source" // CompilationScope represents a compiled instructions // and the last two instructions that were emitted. type CompilationScope struct { - instructions []byte - lastInstructions [2]EmittedInstruction - symbolInit map[string]bool - sourceMap map[int]source.Pos + instructions []byte + symbolInit map[string]bool + sourceMap map[int]source.Pos } diff --git a/vendor/github.com/d5/tengo/compiler/compiler.go b/vendor/github.com/d5/tengo/compiler/compiler.go index d8bc05fd..4a3ec3ad 100644 --- a/vendor/github.com/d5/tengo/compiler/compiler.go +++ b/vendor/github.com/d5/tengo/compiler/compiler.go @@ -3,7 +3,10 @@ package compiler import ( "fmt" "io" + "io/ioutil" + "path/filepath" "reflect" + "strings" "github.com/d5/tengo" "github.com/d5/tengo/compiler/ast" @@ -16,14 +19,14 @@ import ( type Compiler struct { file *source.File parent *Compiler - moduleName string + modulePath string constants []objects.Object symbolTable *SymbolTable scopes []CompilationScope scopeIndex int - moduleLoader ModuleLoader - builtinModules map[string]bool + modules *objects.ModuleMap compiledModules map[string]*objects.CompiledFunction + allowFileImport bool loops []*Loop loopIndex int trace io.Writer @@ -31,12 +34,7 @@ type Compiler struct { } // NewCompiler creates a Compiler. -// User can optionally provide the symbol table if one wants to add or remove -// some global- or builtin- scope symbols. If not (nil), Compile will create -// a new symbol table and use the default builtin functions. Likewise, standard -// modules can be explicitly provided if user wants to add or remove some modules. -// By default, Compile will use all the standard modules otherwise. -func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, builtinModules map[string]bool, trace io.Writer) *Compiler { +func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler { mainScope := CompilationScope{ symbolInit: make(map[string]bool), sourceMap: make(map[int]source.Pos), @@ -45,15 +43,16 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object // symbol table if symbolTable == nil { symbolTable = NewSymbolTable() + } - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) - } + // add builtin functions to the symbol table + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) } // builtin modules - if builtinModules == nil { - builtinModules = make(map[string]bool) + if modules == nil { + modules = objects.NewModuleMap() } return &Compiler{ @@ -64,7 +63,7 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object scopeIndex: 0, loopIndex: -1, trace: trace, - builtinModules: builtinModules, + modules: modules, compiledModules: make(map[string]*objects.CompiledFunction), } } @@ -120,7 +119,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(node, OpGreaterThan) + c.emit(node, OpBinaryOp, int(token.Greater)) return nil } else if node.Token == token.LessEq { @@ -131,7 +130,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(node, OpGreaterThanEqual) + c.emit(node, OpBinaryOp, int(token.GreaterEq)) return nil } @@ -145,35 +144,35 @@ func (c *Compiler) Compile(node ast.Node) error { switch node.Token { case token.Add: - c.emit(node, OpAdd) + c.emit(node, OpBinaryOp, int(token.Add)) case token.Sub: - c.emit(node, OpSub) + c.emit(node, OpBinaryOp, int(token.Sub)) case token.Mul: - c.emit(node, OpMul) + c.emit(node, OpBinaryOp, int(token.Mul)) case token.Quo: - c.emit(node, OpDiv) + c.emit(node, OpBinaryOp, int(token.Quo)) case token.Rem: - c.emit(node, OpRem) + c.emit(node, OpBinaryOp, int(token.Rem)) case token.Greater: - c.emit(node, OpGreaterThan) + c.emit(node, OpBinaryOp, int(token.Greater)) case token.GreaterEq: - c.emit(node, OpGreaterThanEqual) + c.emit(node, OpBinaryOp, int(token.GreaterEq)) case token.Equal: c.emit(node, OpEqual) case token.NotEqual: c.emit(node, OpNotEqual) case token.And: - c.emit(node, OpBAnd) + c.emit(node, OpBinaryOp, int(token.And)) case token.Or: - c.emit(node, OpBOr) + c.emit(node, OpBinaryOp, int(token.Or)) case token.Xor: - c.emit(node, OpBXor) + c.emit(node, OpBinaryOp, int(token.Xor)) case token.AndNot: - c.emit(node, OpBAndNot) + c.emit(node, OpBinaryOp, int(token.AndNot)) case token.Shl: - c.emit(node, OpBShiftLeft) + c.emit(node, OpBinaryOp, int(token.Shl)) case token.Shr: - c.emit(node, OpBShiftRight) + c.emit(node, OpBinaryOp, int(token.Shr)) default: return c.errorf(node, "invalid binary operator: %s", node.Token.String()) } @@ -293,6 +292,15 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.BlockStmt: + if len(node.Stmts) == 0 { + return nil + } + + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + for _, stmt := range node.Stmts { if err := c.Compile(stmt); err != nil { return err @@ -405,10 +413,8 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - // add OpReturn if function returns nothing - if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) { - c.emit(node, OpReturn) - } + // code optimization + c.optimizeFunc(node) freeSymbols := c.symbolTable.FreeSymbols() numLocals := c.symbolTable.MaxSymbols() @@ -461,9 +467,9 @@ func (c *Compiler) Compile(node ast.Node) error { s.LocalAssigned = true } - c.emit(node, OpGetLocal, s.Index) + c.emit(node, OpGetLocalPtr, s.Index) case ScopeFree: - c.emit(node, OpGetFree, s.Index) + c.emit(node, OpGetFreePtr, s.Index) } } @@ -487,13 +493,13 @@ func (c *Compiler) Compile(node ast.Node) error { } if node.Result == nil { - c.emit(node, OpReturn) + c.emit(node, OpReturn, 0) } else { if err := c.Compile(node.Result); err != nil { return err } - c.emit(node, OpReturnValue) + c.emit(node, OpReturn, 1) } case *ast.CallExpr: @@ -510,21 +516,57 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(node, OpCall, len(node.Args)) case *ast.ImportExpr: - if c.builtinModules[node.ModuleName] { - if len(node.ModuleName) > tengo.MaxStringLen { - return c.error(node, objects.ErrStringLimit) + if node.ModuleName == "" { + return c.errorf(node, "empty module name") + } + + if mod := c.modules.Get(node.ModuleName); mod != nil { + v, err := mod.Import(node.ModuleName) + if err != nil { + return err } - c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName})) - c.emit(node, OpGetBuiltinModule) - } else { - userMod, err := c.compileModule(node) + switch v := v.(type) { + case []byte: // module written in Tengo + compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v) + if err != nil { + return err + } + c.emit(node, OpConstant, c.addConstant(compiled)) + c.emit(node, OpCall, 0) + case objects.Object: // builtin module + c.emit(node, OpConstant, c.addConstant(v)) + default: + panic(fmt.Errorf("invalid import value type: %T", v)) + } + } else if c.allowFileImport { + moduleName := node.ModuleName + if !strings.HasSuffix(moduleName, ".tengo") { + moduleName += ".tengo" + } + + modulePath, err := filepath.Abs(moduleName) if err != nil { + return c.errorf(node, "module file path error: %s", err.Error()) + } + + if err := c.checkCyclicImports(node, modulePath); err != nil { return err } - c.emit(node, OpConstant, c.addConstant(userMod)) + moduleSrc, err := ioutil.ReadFile(moduleName) + if err != nil { + return c.errorf(node, "module file read error: %s", err.Error()) + } + + compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc) + if err != nil { + return err + } + c.emit(node, OpConstant, c.addConstant(compiled)) c.emit(node, OpCall, 0) + } else { + return c.errorf(node, "module '%s' not found", node.ModuleName) } case *ast.ExportStmt: @@ -543,7 +585,7 @@ func (c *Compiler) Compile(node ast.Node) error { } c.emit(node, OpImmutable) - c.emit(node, OpReturnValue) + c.emit(node, OpReturn, 1) case *ast.ErrorExpr: if err := c.Compile(node.Expr); err != nil { @@ -602,18 +644,16 @@ func (c *Compiler) Bytecode() *Bytecode { } } -// SetModuleLoader sets or replaces the current module loader. -// Note that the module loader is used for user modules, -// not for the standard modules. -func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { - c.moduleLoader = moduleLoader +// EnableFileImport enables or disables module loading from local files. +// Local file modules are disabled by default. +func (c *Compiler) EnableFileImport(enable bool) { + c.allowFileImport = enable } -func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) - child.moduleName = moduleName // name of the module to compile - child.parent = c // parent to set to current compiler - child.moduleLoader = c.moduleLoader // share module loader +func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler { + child := NewCompiler(file, symbolTable, nil, c.modules, c.trace) + child.modulePath = modulePath // module file path + child.parent = c // parent to set to current compiler return child } @@ -657,33 +697,6 @@ func (c *Compiler) addInstruction(b []byte) int { return posNewIns } -func (c *Compiler) setLastInstruction(op Opcode, pos int) { - c.scopes[c.scopeIndex].lastInstructions[1] = c.scopes[c.scopeIndex].lastInstructions[0] - - c.scopes[c.scopeIndex].lastInstructions[0].Opcode = op - c.scopes[c.scopeIndex].lastInstructions[0].Position = pos -} - -func (c *Compiler) lastInstructionIs(op Opcode) bool { - if len(c.currentInstructions()) == 0 { - return false - } - - return c.scopes[c.scopeIndex].lastInstructions[0].Opcode == op -} - -func (c *Compiler) removeLastInstruction() { - lastPos := c.scopes[c.scopeIndex].lastInstructions[0].Position - - if c.trace != nil { - c.printTrace(fmt.Sprintf("DELET %s", - FormatInstructions(c.scopes[c.scopeIndex].instructions[lastPos:], lastPos)[0])) - } - - c.scopes[c.scopeIndex].instructions = c.currentInstructions()[:lastPos] - c.scopes[c.scopeIndex].lastInstructions[0] = c.scopes[c.scopeIndex].lastInstructions[1] -} - func (c *Compiler) replaceInstruction(pos int, inst []byte) { copy(c.currentInstructions()[pos:], inst) @@ -700,6 +713,92 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) { c.replaceInstruction(opPos, inst) } +// optimizeFunc performs some code-level optimization for the current function instructions +// it removes unreachable (dead code) instructions and adds "returns" instruction if needed. +func (c *Compiler) optimizeFunc(node ast.Node) { + // any instructions between RETURN and the function end + // or instructions between RETURN and jump target position + // are considered as unreachable. + + // pass 1. identify all jump destinations + dsts := make(map[int]bool) + iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool { + switch opcode { + case OpJump, OpJumpFalsy, OpAndJump, OpOrJump: + dsts[operands[0]] = true + } + + return true + }) + + var newInsts []byte + + // pass 2. eliminate dead code + posMap := make(map[int]int) // old position to new position + var dstIdx int + var deadCode bool + iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool { + switch { + case opcode == OpReturn: + if deadCode { + return true + } + deadCode = true + case dsts[pos]: + dstIdx++ + deadCode = false + case deadCode: + return true + } + + posMap[pos] = len(newInsts) + newInsts = append(newInsts, MakeInstruction(opcode, operands...)...) + return true + }) + + // pass 3. update jump positions + var lastOp Opcode + var appendReturn bool + endPos := len(c.scopes[c.scopeIndex].instructions) + iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool { + switch opcode { + case OpJump, OpJumpFalsy, OpAndJump, OpOrJump: + newDst, ok := posMap[operands[0]] + if ok { + copy(newInsts[pos:], MakeInstruction(opcode, newDst)) + } else if endPos == operands[0] { + // there's a jump instruction that jumps to the end of function + // compiler should append "return". + appendReturn = true + } else { + panic(fmt.Errorf("invalid jump position: %d", newDst)) + } + } + lastOp = opcode + return true + }) + if lastOp != OpReturn { + appendReturn = true + } + + // pass 4. update source map + newSourceMap := make(map[int]source.Pos) + for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap { + newPos, ok := posMap[pos] + if ok { + newSourceMap[newPos] = srcPos + } + } + + c.scopes[c.scopeIndex].instructions = newInsts + c.scopes[c.scopeIndex].sourceMap = newSourceMap + + // append "return" + if appendReturn { + c.emit(node, OpReturn, 0) + } +} + func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { filePos := source.NoPos if node != nil { @@ -709,7 +808,6 @@ func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { inst := MakeInstruction(opcode, operands...) pos := c.addInstruction(inst) c.scopes[c.scopeIndex].sourceMap[pos] = filePos - c.setLastInstruction(opcode, pos) if c.trace != nil { c.printTrace(fmt.Sprintf("EMIT %s", diff --git a/vendor/github.com/d5/tengo/compiler/compiler_assign.go b/vendor/github.com/d5/tengo/compiler/compiler_assign.go index 0e086c83..59296f6f 100644 --- a/vendor/github.com/d5/tengo/compiler/compiler_assign.go +++ b/vendor/github.com/d5/tengo/compiler/compiler_assign.go @@ -51,27 +51,27 @@ func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.To switch op { case token.AddAssign: - c.emit(node, OpAdd) + c.emit(node, OpBinaryOp, int(token.Add)) case token.SubAssign: - c.emit(node, OpSub) + c.emit(node, OpBinaryOp, int(token.Sub)) case token.MulAssign: - c.emit(node, OpMul) + c.emit(node, OpBinaryOp, int(token.Mul)) case token.QuoAssign: - c.emit(node, OpDiv) + c.emit(node, OpBinaryOp, int(token.Quo)) case token.RemAssign: - c.emit(node, OpRem) + c.emit(node, OpBinaryOp, int(token.Rem)) case token.AndAssign: - c.emit(node, OpBAnd) + c.emit(node, OpBinaryOp, int(token.And)) case token.OrAssign: - c.emit(node, OpBOr) + c.emit(node, OpBinaryOp, int(token.Or)) case token.AndNotAssign: - c.emit(node, OpBAndNot) + c.emit(node, OpBinaryOp, int(token.AndNot)) case token.XorAssign: - c.emit(node, OpBXor) + c.emit(node, OpBinaryOp, int(token.Xor)) case token.ShlAssign: - c.emit(node, OpBShiftLeft) + c.emit(node, OpBinaryOp, int(token.Shl)) case token.ShrAssign: - c.emit(node, OpBShiftRight) + c.emit(node, OpBinaryOp, int(token.Shr)) } // compile selector expressions (right to left) diff --git a/vendor/github.com/d5/tengo/compiler/compiler_module.go b/vendor/github.com/d5/tengo/compiler/compiler_module.go index d069bfab..8a3671ce 100644 --- a/vendor/github.com/d5/tengo/compiler/compiler_module.go +++ b/vendor/github.com/d5/tengo/compiler/compiler_module.go @@ -1,72 +1,31 @@ package compiler import ( - "io/ioutil" - "strings" - "github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/objects" ) -func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { - compiledModule, exists := c.loadCompiledModule(expr.ModuleName) - if exists { - return compiledModule, nil +func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error { + if c.modulePath == modulePath { + return c.errorf(node, "cyclic module import: %s", modulePath) + } else if c.parent != nil { + return c.parent.checkCyclicImports(node, modulePath) } - moduleName := expr.ModuleName - - // read module source from loader - var moduleSrc []byte - if c.moduleLoader == nil { - // default loader: read from local file - if !strings.HasSuffix(moduleName, ".tengo") { - moduleName += ".tengo" - } - - if err := c.checkCyclicImports(expr, moduleName); err != nil { - return nil, err - } - - var err error - moduleSrc, err = ioutil.ReadFile(moduleName) - if err != nil { - return nil, c.errorf(expr, "module file read error: %s", err.Error()) - } - } else { - if err := c.checkCyclicImports(expr, moduleName); err != nil { - return nil, err - } - - var err error - moduleSrc, err = c.moduleLoader(moduleName) - if err != nil { - return nil, err - } - } + return nil +} - compiledModule, err := c.doCompileModule(moduleName, moduleSrc) - if err != nil { +func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, src []byte) (*objects.CompiledFunction, error) { + if err := c.checkCyclicImports(node, modulePath); err != nil { return nil, err } - c.storeCompiledModule(moduleName, compiledModule) - - return compiledModule, nil -} - -func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error { - if c.moduleName == moduleName { - return c.errorf(node, "cyclic module import: %s", moduleName) - } else if c.parent != nil { - return c.parent.checkCyclicImports(node, moduleName) + compiledModule, exists := c.loadCompiledModule(modulePath) + if exists { + return compiledModule, nil } - return nil -} - -func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { modFile := c.file.Set().AddFile(moduleName, -1, len(src)) p := parser.NewParser(modFile, src, nil) file, err := p.ParseFile() @@ -85,36 +44,36 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp symbolTable = symbolTable.Fork(false) // compile module - moduleCompiler := c.fork(modFile, moduleName, symbolTable) + moduleCompiler := c.fork(modFile, modulePath, symbolTable) if err := moduleCompiler.Compile(file); err != nil { return nil, err } - // add OpReturn (== export undefined) if export is missing - if !moduleCompiler.lastInstructionIs(OpReturnValue) { - moduleCompiler.emit(nil, OpReturn) - } + // code optimization + moduleCompiler.optimizeFunc(node) compiledFunc := moduleCompiler.Bytecode().MainFunction compiledFunc.NumLocals = symbolTable.MaxSymbols() + c.storeCompiledModule(modulePath, compiledFunc) + return compiledFunc, nil } -func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) { +func (c *Compiler) loadCompiledModule(modulePath string) (mod *objects.CompiledFunction, ok bool) { if c.parent != nil { - return c.parent.loadCompiledModule(moduleName) + return c.parent.loadCompiledModule(modulePath) } - mod, ok = c.compiledModules[moduleName] + mod, ok = c.compiledModules[modulePath] return } -func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) { +func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) { if c.parent != nil { - c.parent.storeCompiledModule(moduleName, module) + c.parent.storeCompiledModule(modulePath, module) } - c.compiledModules[moduleName] = module + c.compiledModules[modulePath] = module } diff --git a/vendor/github.com/d5/tengo/compiler/instructions.go b/vendor/github.com/d5/tengo/compiler/instructions.go index b04b2826..80c88d13 100644 --- a/vendor/github.com/d5/tengo/compiler/instructions.go +++ b/vendor/github.com/d5/tengo/compiler/instructions.go @@ -57,3 +57,16 @@ func FormatInstructions(b []byte, posOffset int) []string { return out } + +func iterateInstructions(b []byte, fn func(pos int, opcode Opcode, operands []int) bool) { + for i := 0; i < len(b); i++ { + numOperands := OpcodeOperands[Opcode(b[i])] + operands, read := ReadOperands(numOperands, b[i+1:]) + + if !fn(i, b[i], operands) { + break + } + + i += read + } +} diff --git a/vendor/github.com/d5/tengo/compiler/opcodes.go b/vendor/github.com/d5/tengo/compiler/opcodes.go index d4cf1ba2..d832ee17 100644 --- a/vendor/github.com/d5/tengo/compiler/opcodes.go +++ b/vendor/github.com/d5/tengo/compiler/opcodes.go @@ -5,173 +5,137 @@ type Opcode = byte // List of opcodes const ( - OpConstant Opcode = iota // Load constant - OpAdd // Add - OpSub // Sub - OpMul // Multiply - OpDiv // Divide - OpRem // Remainder - OpBAnd // bitwise AND - OpBOr // bitwise OR - OpBXor // bitwise XOR - OpBShiftLeft // bitwise shift left - OpBShiftRight // bitwise shift right - OpBAndNot // bitwise AND NOT - OpBComplement // bitwise complement - OpPop // Pop - OpTrue // Push true - OpFalse // Push false - OpEqual // Equal == - OpNotEqual // Not equal != - OpGreaterThan // Greater than >= - OpGreaterThanEqual // Greater than or equal to >= - OpMinus // Minus - - OpLNot // Logical not ! - OpJumpFalsy // Jump if falsy - OpAndJump // Logical AND jump - OpOrJump // Logical OR jump - OpJump // Jump - OpNull // Push null - OpArray // Array object - OpMap // Map object - OpError // Error object - OpImmutable // Immutable object - OpIndex // Index operation - OpSliceIndex // Slice operation - OpCall // Call function - OpReturn // Return - OpReturnValue // Return value - OpGetGlobal // Get global variable - OpSetGlobal // Set global variable - OpSetSelGlobal // Set global variable using selectors - OpGetLocal // Get local variable - OpSetLocal // Set local variable - OpDefineLocal // Define local variable - OpSetSelLocal // Set local variable using selectors - OpGetFree // Get free variables - OpSetFree // Set free variables - OpSetSelFree // Set free variables using selectors - OpGetBuiltin // Get builtin function - OpGetBuiltinModule // Get builtin module - OpClosure // Push closure - OpIteratorInit // Iterator init - OpIteratorNext // Iterator next - OpIteratorKey // Iterator key - OpIteratorValue // Iterator value + OpConstant Opcode = iota // Load constant + OpBComplement // bitwise complement + OpPop // Pop + OpTrue // Push true + OpFalse // Push false + OpEqual // Equal == + OpNotEqual // Not equal != + OpMinus // Minus - + OpLNot // Logical not ! + OpJumpFalsy // Jump if falsy + OpAndJump // Logical AND jump + OpOrJump // Logical OR jump + OpJump // Jump + OpNull // Push null + OpArray // Array object + OpMap // Map object + OpError // Error object + OpImmutable // Immutable object + OpIndex // Index operation + OpSliceIndex // Slice operation + OpCall // Call function + OpReturn // Return + OpGetGlobal // Get global variable + OpSetGlobal // Set global variable + OpSetSelGlobal // Set global variable using selectors + OpGetLocal // Get local variable + OpSetLocal // Set local variable + OpDefineLocal // Define local variable + OpSetSelLocal // Set local variable using selectors + OpGetFreePtr // Get free variable pointer object + OpGetFree // Get free variables + OpSetFree // Set free variables + OpGetLocalPtr // Get local variable as a pointer + OpSetSelFree // Set free variables using selectors + OpGetBuiltin // Get builtin function + OpClosure // Push closure + OpIteratorInit // Iterator init + OpIteratorNext // Iterator next + OpIteratorKey // Iterator key + OpIteratorValue // Iterator value + OpBinaryOp // Binary Operation ) // OpcodeNames is opcode names. var OpcodeNames = [...]string{ - OpConstant: "CONST", - OpPop: "POP", - OpTrue: "TRUE", - OpFalse: "FALSE", - OpAdd: "ADD", - OpSub: "SUB", - OpMul: "MUL", - OpDiv: "DIV", - OpRem: "REM", - OpBAnd: "AND", - OpBOr: "OR", - OpBXor: "XOR", - OpBAndNot: "ANDN", - OpBShiftLeft: "SHL", - OpBShiftRight: "SHR", - OpBComplement: "NEG", - OpEqual: "EQL", - OpNotEqual: "NEQ", - OpGreaterThan: "GTR", - OpGreaterThanEqual: "GEQ", - OpMinus: "NEG", - OpLNot: "NOT", - OpJumpFalsy: "JMPF", - OpAndJump: "ANDJMP", - OpOrJump: "ORJMP", - OpJump: "JMP", - OpNull: "NULL", - OpGetGlobal: "GETG", - OpSetGlobal: "SETG", - OpSetSelGlobal: "SETSG", - OpArray: "ARR", - OpMap: "MAP", - OpError: "ERROR", - OpImmutable: "IMMUT", - OpIndex: "INDEX", - OpSliceIndex: "SLICE", - OpCall: "CALL", - OpReturn: "RET", - OpReturnValue: "RETVAL", - OpGetLocal: "GETL", - OpSetLocal: "SETL", - OpDefineLocal: "DEFL", - OpSetSelLocal: "SETSL", - OpGetBuiltin: "BUILTIN", - OpGetBuiltinModule: "BLTMOD", - OpClosure: "CLOSURE", - OpGetFree: "GETF", - OpSetFree: "SETF", - OpSetSelFree: "SETSF", - OpIteratorInit: "ITER", - OpIteratorNext: "ITNXT", - OpIteratorKey: "ITKEY", - OpIteratorValue: "ITVAL", + OpConstant: "CONST", + OpPop: "POP", + OpTrue: "TRUE", + OpFalse: "FALSE", + OpBComplement: "NEG", + OpEqual: "EQL", + OpNotEqual: "NEQ", + OpMinus: "NEG", + OpLNot: "NOT", + OpJumpFalsy: "JMPF", + OpAndJump: "ANDJMP", + OpOrJump: "ORJMP", + OpJump: "JMP", + OpNull: "NULL", + OpGetGlobal: "GETG", + OpSetGlobal: "SETG", + OpSetSelGlobal: "SETSG", + OpArray: "ARR", + OpMap: "MAP", + OpError: "ERROR", + OpImmutable: "IMMUT", + OpIndex: "INDEX", + OpSliceIndex: "SLICE", + OpCall: "CALL", + OpReturn: "RET", + OpGetLocal: "GETL", + OpSetLocal: "SETL", + OpDefineLocal: "DEFL", + OpSetSelLocal: "SETSL", + OpGetBuiltin: "BUILTIN", + OpClosure: "CLOSURE", + OpGetFreePtr: "GETFP", + OpGetFree: "GETF", + OpSetFree: "SETF", + OpGetLocalPtr: "GETLP", + OpSetSelFree: "SETSF", + OpIteratorInit: "ITER", + OpIteratorNext: "ITNXT", + OpIteratorKey: "ITKEY", + OpIteratorValue: "ITVAL", + OpBinaryOp: "BINARYOP", } // OpcodeOperands is the number of operands. var OpcodeOperands = [...][]int{ - OpConstant: {2}, - OpPop: {}, - OpTrue: {}, - OpFalse: {}, - OpAdd: {}, - OpSub: {}, - OpMul: {}, - OpDiv: {}, - OpRem: {}, - OpBAnd: {}, - OpBOr: {}, - OpBXor: {}, - OpBAndNot: {}, - OpBShiftLeft: {}, - OpBShiftRight: {}, - OpBComplement: {}, - OpEqual: {}, - OpNotEqual: {}, - OpGreaterThan: {}, - OpGreaterThanEqual: {}, - OpMinus: {}, - OpLNot: {}, - OpJumpFalsy: {2}, - OpAndJump: {2}, - OpOrJump: {2}, - OpJump: {2}, - OpNull: {}, - OpGetGlobal: {2}, - OpSetGlobal: {2}, - OpSetSelGlobal: {2, 1}, - OpArray: {2}, - OpMap: {2}, - OpError: {}, - OpImmutable: {}, - OpIndex: {}, - OpSliceIndex: {}, - OpCall: {1}, - OpReturn: {}, - OpReturnValue: {}, - OpGetLocal: {1}, - OpSetLocal: {1}, - OpDefineLocal: {1}, - OpSetSelLocal: {1, 1}, - OpGetBuiltin: {1}, - OpGetBuiltinModule: {}, - OpClosure: {2, 1}, - OpGetFree: {1}, - OpSetFree: {1}, - OpSetSelFree: {1, 1}, - OpIteratorInit: {}, - OpIteratorNext: {}, - OpIteratorKey: {}, - OpIteratorValue: {}, + OpConstant: {2}, + OpPop: {}, + OpTrue: {}, + OpFalse: {}, + OpBComplement: {}, + OpEqual: {}, + OpNotEqual: {}, + OpMinus: {}, + OpLNot: {}, + OpJumpFalsy: {2}, + OpAndJump: {2}, + OpOrJump: {2}, + OpJump: {2}, + OpNull: {}, + OpGetGlobal: {2}, + OpSetGlobal: {2}, + OpSetSelGlobal: {2, 1}, + OpArray: {2}, + OpMap: {2}, + OpError: {}, + OpImmutable: {}, + OpIndex: {}, + OpSliceIndex: {}, + OpCall: {1}, + OpReturn: {1}, + OpGetLocal: {1}, + OpSetLocal: {1}, + OpDefineLocal: {1}, + OpSetSelLocal: {1, 1}, + OpGetBuiltin: {1}, + OpClosure: {2, 1}, + OpGetFreePtr: {1}, + OpGetFree: {1}, + OpSetFree: {1}, + OpGetLocalPtr: {1}, + OpSetSelFree: {1, 1}, + OpIteratorInit: {}, + OpIteratorNext: {}, + OpIteratorKey: {}, + OpIteratorValue: {}, + OpBinaryOp: {1}, } // ReadOperands reads operands from the bytecode. diff --git a/vendor/github.com/d5/tengo/compiler/symbol_table.go b/vendor/github.com/d5/tengo/compiler/symbol_table.go index fb029b31..94c868de 100644 --- a/vendor/github.com/d5/tengo/compiler/symbol_table.go +++ b/vendor/github.com/d5/tengo/compiler/symbol_table.go @@ -64,9 +64,7 @@ func (t *SymbolTable) Resolve(name string) (symbol *Symbol, depth int, ok bool) return } - if !t.block { - depth++ - } + depth++ // if symbol is defined in parent table and if it's not global/builtin // then it's free variable. diff --git a/vendor/github.com/d5/tengo/objects/break.go b/vendor/github.com/d5/tengo/objects/break.go deleted file mode 100644 index cd473a87..00000000 --- a/vendor/github.com/d5/tengo/objects/break.go +++ /dev/null @@ -1,37 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// Break represents a break statement. -type Break struct{} - -// TypeName returns the name of the type. -func (o *Break) TypeName() string { - return "break" -} - -func (o *Break) String() string { - return "<break>" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Break) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Break) Copy() Object { - return &Break{} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Break) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Break) Equals(x Object) bool { - return false -} diff --git a/vendor/github.com/d5/tengo/objects/builtin_json.go b/vendor/github.com/d5/tengo/objects/builtin_json.go deleted file mode 100644 index b3413651..00000000 --- a/vendor/github.com/d5/tengo/objects/builtin_json.go +++ /dev/null @@ -1,60 +0,0 @@ -package objects - -import ( - "encoding/json" - - "github.com/d5/tengo" -) - -// to_json(v object) => bytes -func builtinToJSON(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - res, err := json.Marshal(objectToInterface(args[0])) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - - if len(res) > tengo.MaxBytesLen { - return nil, ErrBytesLimit - } - - return &Bytes{Value: res}, nil -} - -// from_json(data string/bytes) => object -func builtinFromJSON(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - var target interface{} - - switch o := args[0].(type) { - case *Bytes: - err := json.Unmarshal(o.Value, &target) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - case *String: - err := json.Unmarshal([]byte(o.Value), &target) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes/string", - Found: args[0].TypeName(), - } - } - - res, err := FromInterface(target) - if err != nil { - return nil, err - } - - return res, nil -} diff --git a/vendor/github.com/d5/tengo/objects/builtin_module.go b/vendor/github.com/d5/tengo/objects/builtin_module.go new file mode 100644 index 00000000..0ad1d99d --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_module.go @@ -0,0 +1,23 @@ +package objects + +// BuiltinModule is an importable module that's written in Go. +type BuiltinModule struct { + Attrs map[string]Object +} + +// Import returns an immutable map for the module. +func (m *BuiltinModule) Import(moduleName string) (interface{}, error) { + return m.AsImmutableMap(moduleName), nil +} + +// AsImmutableMap converts builtin module into an immutable map. +func (m *BuiltinModule) AsImmutableMap(moduleName string) *ImmutableMap { + attrs := make(map[string]Object, len(m.Attrs)) + for k, v := range m.Attrs { + attrs[k] = v.Copy() + } + + attrs["__module_name__"] = &String{Value: moduleName} + + return &ImmutableMap{Value: attrs} +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_print.go b/vendor/github.com/d5/tengo/objects/builtin_print.go deleted file mode 100644 index 58f22610..00000000 --- a/vendor/github.com/d5/tengo/objects/builtin_print.go +++ /dev/null @@ -1,83 +0,0 @@ -package objects - -import ( - "fmt" - - "github.com/d5/tengo" -) - -// print(args...) -func builtinPrint(args ...Object) (Object, error) { - for _, arg := range args { - if str, ok := arg.(*String); ok { - fmt.Println(str.Value) - } else { - fmt.Println(arg.String()) - } - } - - return nil, nil -} - -// printf("format", args...) -func builtinPrintf(args ...Object) (Object, error) { - numArgs := len(args) - if numArgs == 0 { - return nil, ErrWrongNumArguments - } - - format, ok := args[0].(*String) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } - if numArgs == 1 { - fmt.Print(format) - return nil, nil - } - - formatArgs := make([]interface{}, numArgs-1, numArgs-1) - for idx, arg := range args[1:] { - formatArgs[idx] = objectToInterface(arg) - } - - fmt.Printf(format.Value, formatArgs...) - - return nil, nil -} - -// sprintf("format", args...) -func builtinSprintf(args ...Object) (Object, error) { - numArgs := len(args) - if numArgs == 0 { - return nil, ErrWrongNumArguments - } - - format, ok := args[0].(*String) - if !ok { - return nil, ErrInvalidArgumentType{ - Name: "format", - Expected: "string", - Found: args[0].TypeName(), - } - } - if numArgs == 1 { - return format, nil // okay to return 'format' directly as String is immutable - } - - formatArgs := make([]interface{}, numArgs-1, numArgs-1) - for idx, arg := range args[1:] { - formatArgs[idx] = objectToInterface(arg) - } - - s := fmt.Sprintf(format.Value, formatArgs...) - - if len(s) > tengo.MaxStringLen { - return nil, ErrStringLimit - } - - return &String{Value: s}, nil -} diff --git a/vendor/github.com/d5/tengo/objects/builtin_type_checks.go b/vendor/github.com/d5/tengo/objects/builtin_type_checks.go index 960f7828..d1e8471d 100644 --- a/vendor/github.com/d5/tengo/objects/builtin_type_checks.go +++ b/vendor/github.com/d5/tengo/objects/builtin_type_checks.go @@ -181,3 +181,15 @@ func builtinIsCallable(args ...Object) (Object, error) { return FalseValue, nil } + +func builtinIsIterable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(Iterable); ok { + return TrueValue, nil + } + + return FalseValue, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtins.go b/vendor/github.com/d5/tengo/objects/builtins.go index 42c1a759..bfd004dd 100644 --- a/vendor/github.com/d5/tengo/objects/builtins.go +++ b/vendor/github.com/d5/tengo/objects/builtins.go @@ -2,19 +2,7 @@ package objects // Builtins contains all default builtin functions. // Use GetBuiltinFunctions instead of accessing Builtins directly. -var Builtins = []BuiltinFunction{ - { - Name: "print", - Value: builtinPrint, - }, - { - Name: "printf", - Value: builtinPrintf, - }, - { - Name: "sprintf", - Value: builtinSprintf, - }, +var Builtins = []*BuiltinFunction{ { Name: "len", Value: builtinLen, @@ -96,6 +84,10 @@ var Builtins = []BuiltinFunction{ Value: builtinIsImmutableMap, }, { + Name: "is_iterable", + Value: builtinIsIterable, + }, + { Name: "is_time", Value: builtinIsTime, }, @@ -116,49 +108,7 @@ var Builtins = []BuiltinFunction{ Value: builtinIsCallable, }, { - Name: "to_json", - Value: builtinToJSON, - }, - { - Name: "from_json", - Value: builtinFromJSON, - }, - { Name: "type_name", Value: builtinTypeName, }, } - -// AllBuiltinFunctionNames returns a list of all default builtin function names. -func AllBuiltinFunctionNames() []string { - var names []string - for _, bf := range Builtins { - names = append(names, bf.Name) - } - return names -} - -// GetBuiltinFunctions returns a slice of builtin function objects. -// GetBuiltinFunctions removes the duplicate names, and, the returned builtin functions -// are not guaranteed to be in the same order as names. -func GetBuiltinFunctions(names ...string) []*BuiltinFunction { - include := make(map[string]bool) - for _, name := range names { - include[name] = true - } - - var builtinFuncs []*BuiltinFunction - for _, bf := range Builtins { - if include[bf.Name] { - bf := bf - builtinFuncs = append(builtinFuncs, &bf) - } - } - - return builtinFuncs -} - -// GetAllBuiltinFunctions returns all builtin functions. -func GetAllBuiltinFunctions() []*BuiltinFunction { - return GetBuiltinFunctions(AllBuiltinFunctionNames()...) -} diff --git a/vendor/github.com/d5/tengo/objects/bytes.go b/vendor/github.com/d5/tengo/objects/bytes.go index 16b61684..6710c7c1 100644 --- a/vendor/github.com/d5/tengo/objects/bytes.go +++ b/vendor/github.com/d5/tengo/objects/bytes.go @@ -79,3 +79,11 @@ func (o *Bytes) IndexGet(index Object) (res Object, err error) { return } + +// Iterate creates a bytes iterator. +func (o *Bytes) Iterate() Iterator { + return &BytesIterator{ + v: o.Value, + l: len(o.Value), + } +} diff --git a/vendor/github.com/d5/tengo/objects/bytes_iterator.go b/vendor/github.com/d5/tengo/objects/bytes_iterator.go new file mode 100644 index 00000000..18a36e17 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/bytes_iterator.go @@ -0,0 +1,57 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// BytesIterator represents an iterator for a string. +type BytesIterator struct { + v []byte + i int + l int +} + +// TypeName returns the name of the type. +func (i *BytesIterator) TypeName() string { + return "bytes-iterator" +} + +func (i *BytesIterator) String() string { + return "<bytes-iterator>" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (i *BytesIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *BytesIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (i *BytesIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *BytesIterator) Copy() Object { + return &BytesIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *BytesIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *BytesIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *BytesIterator) Value() Object { + return &Int{Value: int64(i.v[i.i-1])} +} diff --git a/vendor/github.com/d5/tengo/objects/closure.go b/vendor/github.com/d5/tengo/objects/closure.go index d4915a52..06058b23 100644 --- a/vendor/github.com/d5/tengo/objects/closure.go +++ b/vendor/github.com/d5/tengo/objects/closure.go @@ -7,7 +7,7 @@ import ( // Closure represents a function closure. type Closure struct { Fn *CompiledFunction - Free []*Object + Free []*ObjectPtr } // TypeName returns the name of the type. @@ -29,7 +29,7 @@ func (o *Closure) BinaryOp(op token.Token, rhs Object) (Object, error) { func (o *Closure) Copy() Object { return &Closure{ Fn: o.Fn.Copy().(*CompiledFunction), - Free: append([]*Object{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers + Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers } } diff --git a/vendor/github.com/d5/tengo/objects/compiled_function.go b/vendor/github.com/d5/tengo/objects/compiled_function.go index d20f2375..606e3d90 100644 --- a/vendor/github.com/d5/tengo/objects/compiled_function.go +++ b/vendor/github.com/d5/tengo/objects/compiled_function.go @@ -47,3 +47,14 @@ func (o *CompiledFunction) IsFalsy() bool { func (o *CompiledFunction) Equals(x Object) bool { return false } + +// SourcePos returns the source position of the instruction at ip. +func (o *CompiledFunction) SourcePos(ip int) source.Pos { + for ip >= 0 { + if p, ok := o.SourceMap[ip]; ok { + return p + } + ip-- + } + return source.NoPos +} diff --git a/vendor/github.com/d5/tengo/objects/continue.go b/vendor/github.com/d5/tengo/objects/continue.go deleted file mode 100644 index 8094e686..00000000 --- a/vendor/github.com/d5/tengo/objects/continue.go +++ /dev/null @@ -1,38 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// Continue represents a continue statement. -type Continue struct { -} - -// TypeName returns the name of the type. -func (o *Continue) TypeName() string { - return "continue" -} - -func (o *Continue) String() string { - return "<continue>" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *Continue) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *Continue) Copy() Object { - return &Continue{} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *Continue) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *Continue) Equals(x Object) bool { - return false -} diff --git a/vendor/github.com/d5/tengo/objects/conversion.go b/vendor/github.com/d5/tengo/objects/conversion.go index 714f2617..d7cb3a03 100644 --- a/vendor/github.com/d5/tengo/objects/conversion.go +++ b/vendor/github.com/d5/tengo/objects/conversion.go @@ -1,6 +1,7 @@ package objects import ( + "errors" "fmt" "strconv" "time" @@ -158,8 +159,8 @@ func ToTime(o Object) (v time.Time, ok bool) { return } -// objectToInterface attempts to convert an object o to an interface{} value -func objectToInterface(o Object) (res interface{}) { +// ToInterface attempts to convert an object o to an interface{} value +func ToInterface(o Object) (res interface{}) { switch o := o.(type) { case *Int: res = o.Value @@ -176,13 +177,29 @@ func objectToInterface(o Object) (res interface{}) { case *Array: res = make([]interface{}, len(o.Value)) for i, val := range o.Value { - res.([]interface{})[i] = objectToInterface(val) + res.([]interface{})[i] = ToInterface(val) + } + case *ImmutableArray: + res = make([]interface{}, len(o.Value)) + for i, val := range o.Value { + res.([]interface{})[i] = ToInterface(val) } case *Map: res = make(map[string]interface{}) for key, v := range o.Value { - res.(map[string]interface{})[key] = objectToInterface(v) + res.(map[string]interface{})[key] = ToInterface(v) + } + case *ImmutableMap: + res = make(map[string]interface{}) + for key, v := range o.Value { + res.(map[string]interface{})[key] = ToInterface(v) } + case *Time: + res = o.Value + case *Error: + res = errors.New(o.String()) + case *Undefined: + res = nil case Object: return o } diff --git a/vendor/github.com/d5/tengo/objects/count_objects.go b/vendor/github.com/d5/tengo/objects/count_objects.go new file mode 100644 index 00000000..8c482eb3 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/count_objects.go @@ -0,0 +1,31 @@ +package objects + +// CountObjects returns the number of objects that a given object o contains. +// For scalar value types, it will always be 1. For compound value types, +// this will include its elements and all of their elements recursively. +func CountObjects(o Object) (c int) { + c = 1 + + switch o := o.(type) { + case *Array: + for _, v := range o.Value { + c += CountObjects(v) + } + case *ImmutableArray: + for _, v := range o.Value { + c += CountObjects(v) + } + case *Map: + for _, v := range o.Value { + c += CountObjects(v) + } + case *ImmutableMap: + for _, v := range o.Value { + c += CountObjects(v) + } + case *Error: + c += CountObjects(o.Value) + } + + return +} diff --git a/vendor/github.com/d5/tengo/objects/importable.go b/vendor/github.com/d5/tengo/objects/importable.go new file mode 100644 index 00000000..9fd86ae8 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/importable.go @@ -0,0 +1,7 @@ +package objects + +// Importable interface represents importable module instance. +type Importable interface { + // Import should return either an Object or module source code ([]byte). + Import(moduleName string) (interface{}, error) +} diff --git a/vendor/github.com/d5/tengo/objects/module_map.go b/vendor/github.com/d5/tengo/objects/module_map.go new file mode 100644 index 00000000..874b8a2b --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/module_map.go @@ -0,0 +1,77 @@ +package objects + +// ModuleMap represents a set of named modules. +// Use NewModuleMap to create a new module map. +type ModuleMap struct { + m map[string]Importable +} + +// NewModuleMap creates a new module map. +func NewModuleMap() *ModuleMap { + return &ModuleMap{ + m: make(map[string]Importable), + } +} + +// Add adds an import module. +func (m *ModuleMap) Add(name string, module Importable) { + m.m[name] = module +} + +// AddBuiltinModule adds a builtin module. +func (m *ModuleMap) AddBuiltinModule(name string, attrs map[string]Object) { + m.m[name] = &BuiltinModule{Attrs: attrs} +} + +// AddSourceModule adds a source module. +func (m *ModuleMap) AddSourceModule(name string, src []byte) { + m.m[name] = &SourceModule{Src: src} +} + +// Remove removes a named module. +func (m *ModuleMap) Remove(name string) { + delete(m.m, name) +} + +// Get returns an import module identified by name. +// It returns if the name is not found. +func (m *ModuleMap) Get(name string) Importable { + return m.m[name] +} + +// GetBuiltinModule returns a builtin module identified by name. +// It returns if the name is not found or the module is not a builtin module. +func (m *ModuleMap) GetBuiltinModule(name string) *BuiltinModule { + mod, _ := m.m[name].(*BuiltinModule) + return mod +} + +// GetSourceModule returns a source module identified by name. +// It returns if the name is not found or the module is not a source module. +func (m *ModuleMap) GetSourceModule(name string) *SourceModule { + mod, _ := m.m[name].(*SourceModule) + return mod +} + +// Copy creates a copy of the module map. +func (m *ModuleMap) Copy() *ModuleMap { + c := &ModuleMap{ + m: make(map[string]Importable), + } + for name, mod := range m.m { + c.m[name] = mod + } + return c +} + +// Len returns the number of named modules. +func (m *ModuleMap) Len() int { + return len(m.m) +} + +// AddMap adds named modules from another module map. +func (m *ModuleMap) AddMap(o *ModuleMap) { + for name, mod := range o.m { + m.m[name] = mod + } +} diff --git a/vendor/github.com/d5/tengo/objects/object_ptr.go b/vendor/github.com/d5/tengo/objects/object_ptr.go new file mode 100644 index 00000000..2c87c561 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/object_ptr.go @@ -0,0 +1,41 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// ObjectPtr represents a free variable. +type ObjectPtr struct { + Value *Object +} + +func (o *ObjectPtr) String() string { + return "free-var" +} + +// TypeName returns the name of the type. +func (o *ObjectPtr) TypeName() string { + return "<free-var>" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *ObjectPtr) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ObjectPtr) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ObjectPtr) IsFalsy() bool { + return o.Value == nil +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *ObjectPtr) Equals(x Object) bool { + return o == x +} diff --git a/vendor/github.com/d5/tengo/objects/return_value.go b/vendor/github.com/d5/tengo/objects/return_value.go deleted file mode 100644 index f7ef1dc4..00000000 --- a/vendor/github.com/d5/tengo/objects/return_value.go +++ /dev/null @@ -1,39 +0,0 @@ -package objects - -import "github.com/d5/tengo/compiler/token" - -// ReturnValue represents a value that is being returned. -type ReturnValue struct { - Value Object -} - -// TypeName returns the name of the type. -func (o *ReturnValue) TypeName() string { - return "return-value" -} - -func (o *ReturnValue) String() string { - return "<return-value>" -} - -// BinaryOp returns another object that is the result of -// a given binary operator and a right-hand side object. -func (o *ReturnValue) BinaryOp(op token.Token, rhs Object) (Object, error) { - return nil, ErrInvalidOperator -} - -// Copy returns a copy of the type. -func (o *ReturnValue) Copy() Object { - return &ReturnValue{Value: o.Copy()} -} - -// IsFalsy returns true if the value of the type is falsy. -func (o *ReturnValue) IsFalsy() bool { - return false -} - -// Equals returns true if the value of the type -// is equal to the value of another object. -func (o *ReturnValue) Equals(x Object) bool { - return false -} diff --git a/vendor/github.com/d5/tengo/objects/source_module.go b/vendor/github.com/d5/tengo/objects/source_module.go new file mode 100644 index 00000000..577fddf2 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/source_module.go @@ -0,0 +1,11 @@ +package objects + +// SourceModule is an importable module that's written in Tengo. +type SourceModule struct { + Src []byte +} + +// Import returns a module source code. +func (m *SourceModule) Import(_ string) (interface{}, error) { + return m.Src, nil +} diff --git a/vendor/github.com/d5/tengo/objects/user_function.go b/vendor/github.com/d5/tengo/objects/user_function.go index 1d9bb4f7..a896788b 100644 --- a/vendor/github.com/d5/tengo/objects/user_function.go +++ b/vendor/github.com/d5/tengo/objects/user_function.go @@ -6,8 +6,9 @@ import ( // UserFunction represents a user function. type UserFunction struct { - Name string - Value CallableFunc + Name string + Value CallableFunc + EncodingID string } // TypeName returns the name of the type. diff --git a/vendor/github.com/d5/tengo/runtime/errors.go b/vendor/github.com/d5/tengo/runtime/errors.go index f5f201ce..fc7ca0e9 100644 --- a/vendor/github.com/d5/tengo/runtime/errors.go +++ b/vendor/github.com/d5/tengo/runtime/errors.go @@ -6,3 +6,6 @@ import ( // ErrStackOverflow is a stack overflow error. var ErrStackOverflow = errors.New("stack overflow") + +// ErrObjectAllocLimit is an objects allocation limit error. +var ErrObjectAllocLimit = errors.New("object allocation limit exceeded") diff --git a/vendor/github.com/d5/tengo/runtime/frame.go b/vendor/github.com/d5/tengo/runtime/frame.go index 90151a1e..cbaadbdb 100644 --- a/vendor/github.com/d5/tengo/runtime/frame.go +++ b/vendor/github.com/d5/tengo/runtime/frame.go @@ -7,7 +7,7 @@ import ( // Frame represents a function call frame. type Frame struct { fn *objects.CompiledFunction - freeVars []*objects.Object + freeVars []*objects.ObjectPtr ip int basePointer int } diff --git a/vendor/github.com/d5/tengo/runtime/vm.go b/vendor/github.com/d5/tengo/runtime/vm.go index 9066bfea..dde52db5 100644 --- a/vendor/github.com/d5/tengo/runtime/vm.go +++ b/vendor/github.com/d5/tengo/runtime/vm.go @@ -21,73 +21,46 @@ const ( MaxFrames = 1024 ) -var ( - truePtr = &objects.TrueValue - falsePtr = &objects.FalseValue - undefinedPtr = &objects.UndefinedValue -) - // VM is a virtual machine that executes the bytecode compiled by Compiler. type VM struct { - constants []objects.Object - stack []*objects.Object - sp int - globals []*objects.Object - fileSet *source.FileSet - frames []Frame - framesIndex int - curFrame *Frame - curInsts []byte - curIPLimit int - ip int - aborting int64 - builtinFuncs []objects.Object - builtinModules map[string]*objects.Object - err error - errOffset int + constants []objects.Object + stack [StackSize]objects.Object + sp int + globals []objects.Object + fileSet *source.FileSet + frames [MaxFrames]Frame + framesIndex int + curFrame *Frame + curInsts []byte + ip int + aborting int64 + maxAllocs int64 + allocs int64 + err error } // NewVM creates a VM. -func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object, builtinFuncs []objects.Object, builtinModules map[string]*objects.Object) *VM { +func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, maxAllocs int64) *VM { if globals == nil { - globals = make([]*objects.Object, GlobalsSize) + globals = make([]objects.Object, GlobalsSize) } - if builtinModules == nil { - builtinModules = make(map[string]*objects.Object) + v := &VM{ + constants: bytecode.Constants, + sp: 0, + globals: globals, + fileSet: bytecode.FileSet, + framesIndex: 1, + ip: -1, + maxAllocs: maxAllocs, } - if builtinFuncs == nil { - builtinFuncs = make([]objects.Object, len(objects.Builtins)) - for idx, fn := range objects.Builtins { - builtinFuncs[idx] = &objects.BuiltinFunction{ - Name: fn.Name, - Value: fn.Value, - } - } - } + v.frames[0].fn = bytecode.MainFunction + v.frames[0].ip = -1 + v.curFrame = &v.frames[0] + v.curInsts = v.curFrame.fn.Instructions - frames := make([]Frame, MaxFrames) - frames[0].fn = bytecode.MainFunction - frames[0].freeVars = nil - frames[0].ip = -1 - frames[0].basePointer = 0 - - return &VM{ - constants: bytecode.Constants, - stack: make([]*objects.Object, StackSize), - sp: 0, - globals: globals, - fileSet: bytecode.FileSet, - frames: frames, - framesIndex: 1, - curFrame: &(frames[0]), - curInsts: frames[0].fn.Instructions, - curIPLimit: len(frames[0].fn.Instructions) - 1, - ip: -1, - builtinFuncs: builtinFuncs, - builtinModules: builtinModules, - } + return v } // Abort aborts the execution. @@ -101,109 +74,102 @@ func (v *VM) Run() (err error) { v.sp = 0 v.curFrame = &(v.frames[0]) v.curInsts = v.curFrame.fn.Instructions - v.curIPLimit = len(v.curInsts) - 1 v.framesIndex = 1 v.ip = -1 - atomic.StoreInt64(&v.aborting, 0) + v.allocs = v.maxAllocs + 1 v.run() + atomic.StoreInt64(&v.aborting, 0) + err = v.err if err != nil { - filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-v.errOffset]) + filePos := v.fileSet.Position(v.curFrame.fn.SourcePos(v.ip - 1)) err = fmt.Errorf("Runtime Error: %s\n\tat %s", err.Error(), filePos) for v.framesIndex > 1 { v.framesIndex-- v.curFrame = &v.frames[v.framesIndex-1] - filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.curFrame.ip-1]) + filePos = v.fileSet.Position(v.curFrame.fn.SourcePos(v.curFrame.ip - 1)) err = fmt.Errorf("%s\n\tat %s", err.Error(), filePos) } return err } - // check if stack still has some objects left - if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 { - panic(fmt.Errorf("non empty stack after execution: %d", v.sp)) - } - return nil } func (v *VM) run() { -mainloop: - for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) { + defer func() { + if r := recover(); r != nil { + if v.sp >= StackSize || v.framesIndex >= MaxFrames { + v.err = ErrStackOverflow + return + } + + if v.ip < len(v.curInsts)-1 { + if err, ok := r.(error); ok { + v.err = err + } else { + v.err = fmt.Errorf("panic: %v", r) + } + } + } + }() + + for atomic.LoadInt64(&v.aborting) == 0 { v.ip++ switch v.curInsts[v.ip] { case compiler.OpConstant: - cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 + cidx := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &v.constants[cidx] + v.stack[v.sp] = v.constants[cidx] v.sp++ case compiler.OpNull: - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = undefinedPtr + v.stack[v.sp] = objects.UndefinedValue v.sp++ - case compiler.OpAdd: - v.binaryOp(token.Add) - - case compiler.OpSub: - v.binaryOp(token.Sub) - - case compiler.OpMul: - v.binaryOp(token.Mul) - - case compiler.OpDiv: - v.binaryOp(token.Quo) - - case compiler.OpRem: - v.binaryOp(token.Rem) - - case compiler.OpBAnd: - v.binaryOp(token.And) + case compiler.OpBinaryOp: + v.ip++ + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] - case compiler.OpBOr: - v.binaryOp(token.Or) + tok := token.Token(v.curInsts[v.ip]) + res, e := left.BinaryOp(tok, right) + if e != nil { + v.sp -= 2 - case compiler.OpBXor: - v.binaryOp(token.Xor) + if e == objects.ErrInvalidOperator { + v.err = fmt.Errorf("invalid operation: %s %s %s", + left.TypeName(), tok.String(), right.TypeName()) + return + } - case compiler.OpBAndNot: - v.binaryOp(token.AndNot) + v.err = e + return + } - case compiler.OpBShiftLeft: - v.binaryOp(token.Shl) + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } - case compiler.OpBShiftRight: - v.binaryOp(token.Shr) + v.stack[v.sp-2] = res + v.sp-- case compiler.OpEqual: right := v.stack[v.sp-1] left := v.stack[v.sp-2] v.sp -= 2 - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - if (*left).Equals(*right) { - v.stack[v.sp] = truePtr + if left.Equals(right) { + v.stack[v.sp] = objects.TrueValue } else { - v.stack[v.sp] = falsePtr + v.stack[v.sp] = objects.FalseValue } v.sp++ @@ -212,58 +178,32 @@ mainloop: left := v.stack[v.sp-2] v.sp -= 2 - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - if (*left).Equals(*right) { - v.stack[v.sp] = falsePtr + if left.Equals(right) { + v.stack[v.sp] = objects.FalseValue } else { - v.stack[v.sp] = truePtr + v.stack[v.sp] = objects.TrueValue } v.sp++ - case compiler.OpGreaterThan: - v.binaryOp(token.Greater) - - case compiler.OpGreaterThanEqual: - v.binaryOp(token.GreaterEq) - case compiler.OpPop: v.sp-- case compiler.OpTrue: - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = truePtr + v.stack[v.sp] = objects.TrueValue v.sp++ case compiler.OpFalse: - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = falsePtr + v.stack[v.sp] = objects.FalseValue v.sp++ case compiler.OpLNot: operand := v.stack[v.sp-1] v.sp-- - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - if (*operand).IsFalsy() { - v.stack[v.sp] = truePtr + if operand.IsFalsy() { + v.stack[v.sp] = objects.TrueValue } else { - v.stack[v.sp] = falsePtr + v.stack[v.sp] = objects.FalseValue } v.sp++ @@ -271,19 +211,20 @@ mainloop: operand := v.stack[v.sp-1] v.sp-- - switch x := (*operand).(type) { + switch x := operand.(type) { case *objects.Int: - if v.sp >= StackSize { - v.err = ErrStackOverflow + var res objects.Object = &objects.Int{Value: ^x.Value} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var res objects.Object = &objects.Int{Value: ^x.Value} - - v.stack[v.sp] = &res + v.stack[v.sp] = res v.sp++ default: - v.err = fmt.Errorf("invalid operation: ^%s", (*operand).TypeName()) + v.err = fmt.Errorf("invalid operation: ^%s", operand.TypeName()) return } @@ -291,63 +232,60 @@ mainloop: operand := v.stack[v.sp-1] v.sp-- - switch x := (*operand).(type) { + switch x := operand.(type) { case *objects.Int: - if v.sp >= StackSize { - v.err = ErrStackOverflow + var res objects.Object = &objects.Int{Value: -x.Value} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var res objects.Object = &objects.Int{Value: -x.Value} - - v.stack[v.sp] = &res + v.stack[v.sp] = res v.sp++ case *objects.Float: - if v.sp >= StackSize { - v.err = ErrStackOverflow + var res objects.Object = &objects.Float{Value: -x.Value} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var res objects.Object = &objects.Float{Value: -x.Value} - - v.stack[v.sp] = &res + v.stack[v.sp] = res v.sp++ default: - v.err = fmt.Errorf("invalid operation: -%s", (*operand).TypeName()) + v.err = fmt.Errorf("invalid operation: -%s", operand.TypeName()) return } case compiler.OpJumpFalsy: - pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 - - condition := v.stack[v.sp-1] v.sp-- - - if (*condition).IsFalsy() { + if v.stack[v.sp].IsFalsy() { + pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.ip = pos - 1 } case compiler.OpAndJump: - pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 - condition := *v.stack[v.sp-1] - if condition.IsFalsy() { + if v.stack[v.sp-1].IsFalsy() { + pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.ip = pos - 1 } else { v.sp-- } case compiler.OpOrJump: - pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 - condition := *v.stack[v.sp-1] - if !condition.IsFalsy() { - v.ip = pos - 1 - } else { + if v.stack[v.sp-1].IsFalsy() { v.sp-- + } else { + pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + v.ip = pos - 1 } case compiler.OpJump: @@ -355,108 +293,127 @@ mainloop: v.ip = pos - 1 case compiler.OpSetGlobal: - globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 - v.sp-- + globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 v.globals[globalIndex] = v.stack[v.sp] case compiler.OpSetSelGlobal: - globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 - numSelectors := int(v.curInsts[v.ip+3]) v.ip += 3 + globalIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8 + numSelectors := int(v.curInsts[v.ip]) // selectors and RHS value - selectors := v.stack[v.sp-numSelectors : v.sp] + selectors := make([]objects.Object, numSelectors) + for i := 0; i < numSelectors; i++ { + selectors[i] = v.stack[v.sp-numSelectors+i] + } + val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 if e := indexAssign(v.globals[globalIndex], val, selectors); e != nil { - v.errOffset = 3 v.err = e return } case compiler.OpGetGlobal: - globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 + globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 val := v.globals[globalIndex] - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - v.stack[v.sp] = val v.sp++ case compiler.OpArray: - numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 + numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 var elements []objects.Object for i := v.sp - numElements; i < v.sp; i++ { - elements = append(elements, *v.stack[i]) + elements = append(elements, v.stack[i]) } v.sp -= numElements var arr objects.Object = &objects.Array{Value: elements} - if v.sp >= StackSize { - v.err = ErrStackOverflow + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = &arr + v.stack[v.sp] = arr v.sp++ case compiler.OpMap: - numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 + numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 kv := make(map[string]objects.Object) for i := v.sp - numElements; i < v.sp; i += 2 { - key := *v.stack[i] - value := *v.stack[i+1] + key := v.stack[i] + value := v.stack[i+1] kv[key.(*objects.String).Value] = value } v.sp -= numElements var m objects.Object = &objects.Map{Value: kv} - if v.sp >= StackSize { - v.err = ErrStackOverflow + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = &m + v.stack[v.sp] = m v.sp++ case compiler.OpError: value := v.stack[v.sp-1] var e objects.Object = &objects.Error{ - Value: *value, + Value: value, } - v.stack[v.sp-1] = &e + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp-1] = e case compiler.OpImmutable: value := v.stack[v.sp-1] - switch value := (*value).(type) { + switch value := value.(type) { case *objects.Array: var immutableArray objects.Object = &objects.ImmutableArray{ Value: value.Value, } - v.stack[v.sp-1] = &immutableArray + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp-1] = immutableArray case *objects.Map: var immutableMap objects.Object = &objects.ImmutableMap{ Value: value.Value, } - v.stack[v.sp-1] = &immutableMap + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp-1] = immutableMap } case compiler.OpIndex: @@ -464,13 +421,13 @@ mainloop: left := v.stack[v.sp-2] v.sp -= 2 - switch left := (*left).(type) { + switch left := left.(type) { case objects.Indexable: - val, e := left.IndexGet(*index) + val, e := left.IndexGet(index) if e != nil { if e == objects.ErrInvalidIndexType { - v.err = fmt.Errorf("invalid index type: %s", (*index).TypeName()) + v.err = fmt.Errorf("invalid index type: %s", index.TypeName()) return } @@ -481,27 +438,17 @@ mainloop: val = objects.UndefinedValue } - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ case *objects.Error: // e.value - key, ok := (*index).(*objects.String) + key, ok := index.(*objects.String) if !ok || key.Value != "value" { v.err = fmt.Errorf("invalid index on error") return } - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &left.Value + v.stack[v.sp] = left.Value v.sp++ default: @@ -516,8 +463,8 @@ mainloop: v.sp -= 3 var lowIdx int64 - if *low != objects.UndefinedValue { - if low, ok := (*low).(*objects.Int); ok { + if low != objects.UndefinedValue { + if low, ok := low.(*objects.Int); ok { lowIdx = low.Value } else { v.err = fmt.Errorf("invalid slice index type: %s", low.TypeName()) @@ -525,13 +472,13 @@ mainloop: } } - switch left := (*left).(type) { + switch left := left.(type) { case *objects.Array: numElements := int64(len(left.Value)) var highIdx int64 - if *high == objects.UndefinedValue { + if high == objects.UndefinedValue { highIdx = numElements - } else if high, ok := (*high).(*objects.Int); ok { + } else if high, ok := high.(*objects.Int); ok { highIdx = high.Value } else { v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) @@ -555,21 +502,23 @@ mainloop: highIdx = numElements } - if v.sp >= StackSize { - v.err = ErrStackOverflow + var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ case *objects.ImmutableArray: numElements := int64(len(left.Value)) var highIdx int64 - if *high == objects.UndefinedValue { + if high == objects.UndefinedValue { highIdx = numElements - } else if high, ok := (*high).(*objects.Int); ok { + } else if high, ok := high.(*objects.Int); ok { highIdx = high.Value } else { v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) @@ -593,22 +542,23 @@ mainloop: highIdx = numElements } - if v.sp >= StackSize { - v.err = ErrStackOverflow + var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ case *objects.String: numElements := int64(len(left.Value)) var highIdx int64 - if *high == objects.UndefinedValue { + if high == objects.UndefinedValue { highIdx = numElements - } else if high, ok := (*high).(*objects.Int); ok { + } else if high, ok := high.(*objects.Int); ok { highIdx = high.Value } else { v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) @@ -632,22 +582,23 @@ mainloop: highIdx = numElements } - if v.sp >= StackSize { - v.err = ErrStackOverflow + var val objects.Object = &objects.String{Value: left.Value[lowIdx:highIdx]} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var val objects.Object = &objects.String{Value: left.Value[lowIdx:highIdx]} - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ case *objects.Bytes: numElements := int64(len(left.Value)) var highIdx int64 - if *high == objects.UndefinedValue { + if high == objects.UndefinedValue { highIdx = numElements - } else if high, ok := (*high).(*objects.Int); ok { + } else if high, ok := high.(*objects.Int); ok { highIdx = high.Value } else { v.err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) @@ -671,14 +622,15 @@ mainloop: highIdx = numElements } - if v.sp >= StackSize { - v.err = ErrStackOverflow + var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]} + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]} - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ } @@ -686,12 +638,11 @@ mainloop: numArgs := int(v.curInsts[v.ip+1]) v.ip++ - value := *v.stack[v.sp-1-numArgs] + value := v.stack[v.sp-1-numArgs] switch callee := value.(type) { case *objects.Closure: if numArgs != callee.Fn.NumParameters { - v.errOffset = 1 v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", callee.Fn.NumParameters, numArgs) return @@ -700,14 +651,14 @@ mainloop: // test if it's tail-call if callee.Fn == v.curFrame.fn { // recursion nextOp := v.curInsts[v.ip+1] - if nextOp == compiler.OpReturnValue || + if nextOp == compiler.OpReturn || (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { for p := 0; p < numArgs; p++ { v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] } v.sp -= numArgs + 1 v.ip = -1 // reset IP to beginning of the frame - continue mainloop + continue } } @@ -719,13 +670,11 @@ mainloop: v.curFrame.basePointer = v.sp - numArgs v.curInsts = callee.Fn.Instructions v.ip = -1 - v.curIPLimit = len(v.curInsts) - 1 v.framesIndex++ v.sp = v.sp - numArgs + callee.Fn.NumLocals case *objects.CompiledFunction: if numArgs != callee.NumParameters { - v.errOffset = 1 v.err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", callee.NumParameters, numArgs) return @@ -734,14 +683,14 @@ mainloop: // test if it's tail-call if callee == v.curFrame.fn { // recursion nextOp := v.curInsts[v.ip+1] - if nextOp == compiler.OpReturnValue || + if nextOp == compiler.OpReturn || (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { for p := 0; p < numArgs; p++ { v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] } v.sp -= numArgs + 1 v.ip = -1 // reset IP to beginning of the frame - continue mainloop + continue } } @@ -753,14 +702,13 @@ mainloop: v.curFrame.basePointer = v.sp - numArgs v.curInsts = callee.Instructions v.ip = -1 - v.curIPLimit = len(v.curInsts) - 1 v.framesIndex++ v.sp = v.sp - numArgs + callee.NumLocals case objects.Callable: var args []objects.Object for _, arg := range v.stack[v.sp-numArgs : v.sp] { - args = append(args, *arg) + args = append(args, arg) } ret, e := callee.Call(args...) @@ -768,8 +716,6 @@ mainloop: // runtime error if e != nil { - v.errOffset = 1 - if e == objects.ErrWrongNumArguments { v.err = fmt.Errorf("wrong number of arguments in call to '%s'", value.TypeName()) @@ -791,65 +737,54 @@ mainloop: ret = objects.UndefinedValue } - if v.sp >= StackSize { - v.err = ErrStackOverflow + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = &ret + v.stack[v.sp] = ret v.sp++ default: - v.errOffset = 1 v.err = fmt.Errorf("not callable: %s", callee.TypeName()) return } - case compiler.OpReturnValue: - retVal := v.stack[v.sp-1] + case compiler.OpReturn: + v.ip++ + var retVal objects.Object + if int(v.curInsts[v.ip]) == 1 { + retVal = v.stack[v.sp-1] + } else { + retVal = objects.UndefinedValue + } //v.sp-- v.framesIndex-- - lastFrame := v.frames[v.framesIndex] v.curFrame = &v.frames[v.framesIndex-1] v.curInsts = v.curFrame.fn.Instructions - v.curIPLimit = len(v.curInsts) - 1 v.ip = v.curFrame.ip //v.sp = lastFrame.basePointer - 1 - v.sp = lastFrame.basePointer + v.sp = v.frames[v.framesIndex].basePointer // skip stack overflow check because (newSP) <= (oldSP) v.stack[v.sp-1] = retVal //v.sp++ - case compiler.OpReturn: - v.framesIndex-- - lastFrame := v.frames[v.framesIndex] - v.curFrame = &v.frames[v.framesIndex-1] - v.curInsts = v.curFrame.fn.Instructions - v.curIPLimit = len(v.curInsts) - 1 - v.ip = v.curFrame.ip - - //v.sp = lastFrame.basePointer - 1 - v.sp = lastFrame.basePointer - - // skip stack overflow check because (newSP) <= (oldSP) - v.stack[v.sp-1] = undefinedPtr - //v.sp++ - case compiler.OpDefineLocal: - localIndex := int(v.curInsts[v.ip+1]) v.ip++ + localIndex := int(v.curInsts[v.ip]) sp := v.curFrame.basePointer + localIndex // local variables can be mutated by other actions // so always store the copy of popped value - val := *v.stack[v.sp-1] + val := v.stack[v.sp-1] v.sp-- - v.stack[sp] = &val + v.stack[sp] = val case compiler.OpSetLocal: localIndex := int(v.curInsts[v.ip+1]) @@ -862,7 +797,11 @@ mainloop: val := v.stack[v.sp-1] v.sp-- - *v.stack[sp] = *val // also use a copy of popped value + if obj, ok := v.stack[sp].(*objects.ObjectPtr); ok { + *obj.Value = val + val = obj + } + v.stack[sp] = val // also use a copy of popped value case compiler.OpSetSelLocal: localIndex := int(v.curInsts[v.ip+1]) @@ -870,172 +809,173 @@ mainloop: v.ip += 2 // selectors and RHS value - selectors := v.stack[v.sp-numSelectors : v.sp] + selectors := make([]objects.Object, numSelectors) + for i := 0; i < numSelectors; i++ { + selectors[i] = v.stack[v.sp-numSelectors+i] + } + val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 sp := v.curFrame.basePointer + localIndex if e := indexAssign(v.stack[sp], val, selectors); e != nil { - v.errOffset = 2 v.err = e return } case compiler.OpGetLocal: - localIndex := int(v.curInsts[v.ip+1]) v.ip++ + localIndex := int(v.curInsts[v.ip]) val := v.stack[v.curFrame.basePointer+localIndex] - if v.sp >= StackSize { - v.err = ErrStackOverflow - return + if obj, ok := val.(*objects.ObjectPtr); ok { + val = *obj.Value } v.stack[v.sp] = val v.sp++ case compiler.OpGetBuiltin: - builtinIndex := int(v.curInsts[v.ip+1]) v.ip++ + builtinIndex := int(v.curInsts[v.ip]) - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &v.builtinFuncs[builtinIndex] - v.sp++ - - case compiler.OpGetBuiltinModule: - val := v.stack[v.sp-1] - v.sp-- - - moduleName := (*val).(*objects.String).Value - - module, ok := v.builtinModules[moduleName] - if !ok { - v.errOffset = 3 - v.err = fmt.Errorf("module '%s' not found", moduleName) - return - } - - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = module + v.stack[v.sp] = objects.Builtins[builtinIndex] v.sp++ case compiler.OpClosure: - constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 - numFree := int(v.curInsts[v.ip+3]) v.ip += 3 + constIndex := int(v.curInsts[v.ip-1]) | int(v.curInsts[v.ip-2])<<8 + numFree := int(v.curInsts[v.ip]) fn, ok := v.constants[constIndex].(*objects.CompiledFunction) if !ok { - v.errOffset = 3 v.err = fmt.Errorf("not function: %s", fn.TypeName()) return } - free := make([]*objects.Object, numFree) + free := make([]*objects.ObjectPtr, numFree) for i := 0; i < numFree; i++ { - free[i] = v.stack[v.sp-numFree+i] + switch freeVar := (v.stack[v.sp-numFree+i]).(type) { + case *objects.ObjectPtr: + free[i] = freeVar + default: + free[i] = &objects.ObjectPtr{Value: &v.stack[v.sp-numFree+i]} + } } - v.sp -= numFree - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } + v.sp -= numFree - var cl objects.Object = &objects.Closure{ + var cl = &objects.Closure{ Fn: fn, Free: free, } - v.stack[v.sp] = &cl + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp] = cl v.sp++ - case compiler.OpGetFree: - freeIndex := int(v.curInsts[v.ip+1]) + case compiler.OpGetFreePtr: v.ip++ + freeIndex := int(v.curInsts[v.ip]) val := v.curFrame.freeVars[freeIndex] - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } + v.stack[v.sp] = val + v.sp++ + + case compiler.OpGetFree: + v.ip++ + freeIndex := int(v.curInsts[v.ip]) + + val := *v.curFrame.freeVars[freeIndex].Value v.stack[v.sp] = val v.sp++ + case compiler.OpSetFree: + v.ip++ + freeIndex := int(v.curInsts[v.ip]) + + *v.curFrame.freeVars[freeIndex].Value = v.stack[v.sp-1] + + v.sp-- + + case compiler.OpGetLocalPtr: + v.ip++ + localIndex := int(v.curInsts[v.ip]) + + sp := v.curFrame.basePointer + localIndex + val := v.stack[sp] + + var freeVar *objects.ObjectPtr + if obj, ok := val.(*objects.ObjectPtr); ok { + freeVar = obj + } else { + freeVar = &objects.ObjectPtr{Value: &val} + v.stack[sp] = freeVar + } + + v.stack[v.sp] = freeVar + v.sp++ + case compiler.OpSetSelFree: - freeIndex := int(v.curInsts[v.ip+1]) - numSelectors := int(v.curInsts[v.ip+2]) v.ip += 2 + freeIndex := int(v.curInsts[v.ip-1]) + numSelectors := int(v.curInsts[v.ip]) // selectors and RHS value - selectors := v.stack[v.sp-numSelectors : v.sp] + selectors := make([]objects.Object, numSelectors) + for i := 0; i < numSelectors; i++ { + selectors[i] = v.stack[v.sp-numSelectors+i] + } val := v.stack[v.sp-numSelectors-1] v.sp -= numSelectors + 1 - if e := indexAssign(v.curFrame.freeVars[freeIndex], val, selectors); e != nil { - v.errOffset = 2 + if e := indexAssign(*v.curFrame.freeVars[freeIndex].Value, val, selectors); e != nil { v.err = e return } - case compiler.OpSetFree: - freeIndex := int(v.curInsts[v.ip+1]) - v.ip++ - - val := v.stack[v.sp-1] - v.sp-- - - *v.curFrame.freeVars[freeIndex] = *val - case compiler.OpIteratorInit: var iterator objects.Object dst := v.stack[v.sp-1] v.sp-- - iterable, ok := (*dst).(objects.Iterable) + iterable, ok := dst.(objects.Iterable) if !ok { - v.err = fmt.Errorf("not iterable: %s", (*dst).TypeName()) + v.err = fmt.Errorf("not iterable: %s", dst.TypeName()) return } iterator = iterable.Iterate() - if v.sp >= StackSize { - v.err = ErrStackOverflow + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit return } - v.stack[v.sp] = &iterator + v.stack[v.sp] = iterator v.sp++ case compiler.OpIteratorNext: iterator := v.stack[v.sp-1] v.sp-- - hasMore := (*iterator).(objects.Iterator).Next() - - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } + hasMore := iterator.(objects.Iterator).Next() if hasMore { - v.stack[v.sp] = truePtr + v.stack[v.sp] = objects.TrueValue } else { - v.stack[v.sp] = falsePtr + v.stack[v.sp] = objects.FalseValue } v.sp++ @@ -1043,70 +983,61 @@ mainloop: iterator := v.stack[v.sp-1] v.sp-- - val := (*iterator).(objects.Iterator).Key() + val := iterator.(objects.Iterator).Key() - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ case compiler.OpIteratorValue: iterator := v.stack[v.sp-1] v.sp-- - val := (*iterator).(objects.Iterator).Value() + val := iterator.(objects.Iterator).Value() - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = &val + v.stack[v.sp] = val v.sp++ default: - panic(fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])) + v.err = fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip]) + return } } } -// Globals returns the global variables. -func (v *VM) Globals() []*objects.Object { - return v.globals +// IsStackEmpty tests if the stack is empty or not. +func (v *VM) IsStackEmpty() bool { + return v.sp == 0 } -func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error { +func indexAssign(dst, src objects.Object, selectors []objects.Object) error { numSel := len(selectors) for sidx := numSel - 1; sidx > 0; sidx-- { - indexable, ok := (*dst).(objects.Indexable) + indexable, ok := dst.(objects.Indexable) if !ok { - return fmt.Errorf("not indexable: %s", (*dst).TypeName()) + return fmt.Errorf("not indexable: %s", dst.TypeName()) } - next, err := indexable.IndexGet(*selectors[sidx]) + next, err := indexable.IndexGet(selectors[sidx]) if err != nil { if err == objects.ErrInvalidIndexType { - return fmt.Errorf("invalid index type: %s", (*selectors[sidx]).TypeName()) + return fmt.Errorf("invalid index type: %s", selectors[sidx].TypeName()) } return err } - dst = &next + dst = next } - indexAssignable, ok := (*dst).(objects.IndexAssignable) + indexAssignable, ok := dst.(objects.IndexAssignable) if !ok { - return fmt.Errorf("not index-assignable: %s", (*dst).TypeName()) + return fmt.Errorf("not index-assignable: %s", dst.TypeName()) } - if err := indexAssignable.IndexSet(*selectors[0], *src); err != nil { + if err := indexAssignable.IndexSet(selectors[0], src); err != nil { if err == objects.ErrInvalidIndexValueType { - return fmt.Errorf("invaid index value type: %s", (*src).TypeName()) + return fmt.Errorf("invaid index value type: %s", src.TypeName()) } return err @@ -1114,26 +1045,3 @@ func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error { return nil } - -func (v *VM) binaryOp(tok token.Token) { - right := v.stack[v.sp-1] - left := v.stack[v.sp-2] - - res, e := (*left).BinaryOp(tok, *right) - if e != nil { - v.sp -= 2 - atomic.StoreInt64(&v.aborting, 1) - - if e == objects.ErrInvalidOperator { - v.err = fmt.Errorf("invalid operation: %s + %s", - (*left).TypeName(), (*right).TypeName()) - return - } - - v.err = e - return - } - - v.stack[v.sp-2] = &res - v.sp-- -} diff --git a/vendor/github.com/d5/tengo/script/compiled.go b/vendor/github.com/d5/tengo/script/compiled.go index 4acc46ee..ce50f498 100644 --- a/vendor/github.com/d5/tengo/script/compiled.go +++ b/vendor/github.com/d5/tengo/script/compiled.go @@ -3,6 +3,7 @@ package script import ( "context" "fmt" + "sync" "github.com/d5/tengo/compiler" "github.com/d5/tengo/objects" @@ -12,26 +13,39 @@ import ( // Compiled is a compiled instance of the user script. // Use Script.Compile() to create Compiled object. type Compiled struct { - symbolTable *compiler.SymbolTable - machine *runtime.VM + globalIndexes map[string]int // global symbol name to index + bytecode *compiler.Bytecode + globals []objects.Object + maxAllocs int64 + lock sync.RWMutex } // Run executes the compiled script in the virtual machine. func (c *Compiled) Run() error { - return c.machine.Run() + c.lock.Lock() + defer c.lock.Unlock() + + v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) + + return v.Run() } // RunContext is like Run but includes a context. func (c *Compiled) RunContext(ctx context.Context) (err error) { + c.lock.Lock() + defer c.lock.Unlock() + + v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) + ch := make(chan error, 1) go func() { - ch <- c.machine.Run() + ch <- v.Run() }() select { case <-ctx.Done(): - c.machine.Abort() + v.Abort() <-ch err = ctx.Err() case err = <-ch: @@ -40,30 +54,58 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) { return } +// Clone creates a new copy of Compiled. +// Cloned copies are safe for concurrent use by multiple goroutines. +func (c *Compiled) Clone() *Compiled { + c.lock.Lock() + defer c.lock.Unlock() + + clone := &Compiled{ + globalIndexes: c.globalIndexes, + bytecode: c.bytecode, + globals: make([]objects.Object, len(c.globals)), + maxAllocs: c.maxAllocs, + } + + // copy global objects + for idx, g := range c.globals { + if g != nil { + clone.globals[idx] = g + } + } + + return clone +} + // IsDefined returns true if the variable name is defined (has value) before or after the execution. func (c *Compiled) IsDefined(name string) bool { - symbol, _, ok := c.symbolTable.Resolve(name) + c.lock.RLock() + defer c.lock.RUnlock() + + idx, ok := c.globalIndexes[name] if !ok { return false } - v := c.machine.Globals()[symbol.Index] + v := c.globals[idx] if v == nil { return false } - return *v != objects.UndefinedValue + return v != objects.UndefinedValue } // Get returns a variable identified by the name. func (c *Compiled) Get(name string) *Variable { - value := &objects.UndefinedValue + c.lock.RLock() + defer c.lock.RUnlock() + + value := objects.UndefinedValue - symbol, _, ok := c.symbolTable.Resolve(name) - if ok && symbol.Scope == compiler.ScopeGlobal { - value = c.machine.Globals()[symbol.Index] + if idx, ok := c.globalIndexes[name]; ok { + value = c.globals[idx] if value == nil { - value = &objects.UndefinedValue + value = objects.UndefinedValue } } @@ -75,20 +117,21 @@ func (c *Compiled) Get(name string) *Variable { // GetAll returns all the variables that are defined by the compiled script. func (c *Compiled) GetAll() []*Variable { + c.lock.RLock() + defer c.lock.RUnlock() + var vars []*Variable - for _, name := range c.symbolTable.Names() { - symbol, _, ok := c.symbolTable.Resolve(name) - if ok && symbol.Scope == compiler.ScopeGlobal { - value := c.machine.Globals()[symbol.Index] - if value == nil { - value = &objects.UndefinedValue - } - - vars = append(vars, &Variable{ - name: name, - value: value, - }) + + for name, idx := range c.globalIndexes { + value := c.globals[idx] + if value == nil { + value = objects.UndefinedValue } + + vars = append(vars, &Variable{ + name: name, + value: value, + }) } return vars @@ -97,17 +140,20 @@ func (c *Compiled) GetAll() []*Variable { // Set replaces the value of a global variable identified by the name. // An error will be returned if the name was not defined during compilation. func (c *Compiled) Set(name string, value interface{}) error { + c.lock.Lock() + defer c.lock.Unlock() + obj, err := objects.FromInterface(value) if err != nil { return err } - symbol, _, ok := c.symbolTable.Resolve(name) - if !ok || symbol.Scope != compiler.ScopeGlobal { + idx, ok := c.globalIndexes[name] + if !ok { return fmt.Errorf("'%s' is not defined", name) } - c.machine.Globals()[symbol.Index] = &obj + c.globals[idx] = obj return nil } diff --git a/vendor/github.com/d5/tengo/script/conversion.go b/vendor/github.com/d5/tengo/script/conversion.go deleted file mode 100644 index c35c1411..00000000 --- a/vendor/github.com/d5/tengo/script/conversion.go +++ /dev/null @@ -1,33 +0,0 @@ -package script - -import ( - "github.com/d5/tengo/objects" -) - -func objectToInterface(o objects.Object) interface{} { - switch val := o.(type) { - case *objects.Array: - return val.Value - case *objects.Map: - return val.Value - case *objects.Int: - return val.Value - case *objects.Float: - return val.Value - case *objects.Bool: - if val == objects.TrueValue { - return true - } - return false - case *objects.Char: - return val.Value - case *objects.String: - return val.Value - case *objects.Bytes: - return val.Value - case *objects.Undefined: - return nil - } - - return o -} diff --git a/vendor/github.com/d5/tengo/script/script.go b/vendor/github.com/d5/tengo/script/script.go index e8db52a3..cdf36713 100644 --- a/vendor/github.com/d5/tengo/script/script.go +++ b/vendor/github.com/d5/tengo/script/script.go @@ -14,17 +14,20 @@ import ( // Script can simplify compilation and execution of embedded scripts. type Script struct { variables map[string]*Variable - builtinFuncs []objects.Object - builtinModules map[string]*objects.Object - userModuleLoader compiler.ModuleLoader + modules *objects.ModuleMap input []byte + maxAllocs int64 + maxConstObjects int + enableFileImport bool } // New creates a Script instance with an input script. func New(input []byte) *Script { return &Script{ - variables: make(map[string]*Variable), - input: input, + variables: make(map[string]*Variable), + input: input, + maxAllocs: -1, + maxConstObjects: -1, } } @@ -37,7 +40,7 @@ func (s *Script) Add(name string, value interface{}) error { s.variables[name] = &Variable{ name: name, - value: &obj, + value: obj, } return nil @@ -55,38 +58,31 @@ func (s *Script) Remove(name string) bool { return true } -// 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{} - } +// SetImports sets import modules. +func (s *Script) SetImports(modules *objects.ModuleMap) { + s.modules = modules } -// 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{} - } +// SetMaxAllocs sets the maximum number of objects allocations during the run time. +// Compiled script will return runtime.ErrObjectAllocLimit error if it exceeds this limit. +func (s *Script) SetMaxAllocs(n int64) { + s.maxAllocs = n } -// SetUserModuleLoader sets the user module loader for the compiler. -func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) { - s.userModuleLoader = loader +// SetMaxConstObjects sets the maximum number of objects in the compiled constants. +func (s *Script) SetMaxConstObjects(n int) { + s.maxConstObjects = n +} + +// EnableFileImport enables or disables module loading from local files. +// Local file modules are disabled by default. +func (s *Script) EnableFileImport(enable bool) { + s.enableFileImport = enable } // Compile compiles the script with all the defined variables, and, returns Compiled object. func (s *Script) Compile() (*Compiled, error) { - symbolTable, builtinModules, globals, err := s.prepCompile() + symbolTable, globals, err := s.prepCompile() if err != nil { return nil, err } @@ -100,19 +96,41 @@ func (s *Script) Compile() (*Compiled, error) { return nil, err } - c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil) + c := compiler.NewCompiler(srcFile, symbolTable, nil, s.modules, nil) + c.EnableFileImport(s.enableFileImport) + if err := c.Compile(file); err != nil { + return nil, err + } + + // reduce globals size + globals = globals[:symbolTable.MaxSymbols()+1] - if s.userModuleLoader != nil { - c.SetModuleLoader(s.userModuleLoader) + // global symbol names to indexes + globalIndexes := make(map[string]int, len(globals)) + for _, name := range symbolTable.Names() { + symbol, _, _ := symbolTable.Resolve(name) + if symbol.Scope == compiler.ScopeGlobal { + globalIndexes[name] = symbol.Index + } } - if err := c.Compile(file); err != nil { - return nil, err + // remove duplicates from constants + bytecode := c.Bytecode() + bytecode.RemoveDuplicates() + + // check the constant objects limit + if s.maxConstObjects >= 0 { + cnt := bytecode.CountObjects() + if cnt > s.maxConstObjects { + return nil, fmt.Errorf("exceeding constant objects limit: %d", cnt) + } } return &Compiled{ - symbolTable: symbolTable, - machine: runtime.NewVM(c.Bytecode(), globals, s.builtinFuncs, s.builtinModules), + globalIndexes: globalIndexes, + bytecode: bytecode, + globals: globals, + maxAllocs: s.maxAllocs, }, nil } @@ -141,39 +159,18 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) return } -func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModules map[string]bool, globals []*objects.Object, err error) { +func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []objects.Object, err error) { var names []string for name := range s.variables { names = append(names, name) } symbolTable = compiler.NewSymbolTable() - - 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, - } - } - } - - if s.builtinModules == nil { - s.builtinModules = make(map[string]*objects.Object) + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) } - 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 - } - - globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize) + globals = make([]objects.Object, runtime.GlobalsSize) for idx, name := range names { symbol := symbolTable.Define(name) @@ -195,7 +192,3 @@ func (s *Script) copyVariables() map[string]*Variable { return vars } - -func objectPtr(o objects.Object) *objects.Object { - return &o -} diff --git a/vendor/github.com/d5/tengo/script/variable.go b/vendor/github.com/d5/tengo/script/variable.go index c5e01bd9..df345115 100644 --- a/vendor/github.com/d5/tengo/script/variable.go +++ b/vendor/github.com/d5/tengo/script/variable.go @@ -9,7 +9,7 @@ import ( // Variable is a user-defined variable for the script. type Variable struct { name string - value *objects.Object + value objects.Object } // NewVariable creates a Variable. @@ -21,7 +21,7 @@ func NewVariable(name string, value interface{}) (*Variable, error) { return &Variable{ name: name, - value: &obj, + value: obj, }, nil } @@ -32,18 +32,18 @@ func (v *Variable) Name() string { // Value returns an empty interface of the variable value. func (v *Variable) Value() interface{} { - return objectToInterface(*v.value) + return objects.ToInterface(v.value) } // ValueType returns the name of the value type. func (v *Variable) ValueType() string { - return (*v.value).TypeName() + return v.value.TypeName() } // Int returns int value of the variable value. // It returns 0 if the value is not convertible to int. func (v *Variable) Int() int { - c, _ := objects.ToInt(*v.value) + c, _ := objects.ToInt(v.value) return c } @@ -51,7 +51,7 @@ func (v *Variable) Int() int { // Int64 returns int64 value of the variable value. // It returns 0 if the value is not convertible to int64. func (v *Variable) Int64() int64 { - c, _ := objects.ToInt64(*v.value) + c, _ := objects.ToInt64(v.value) return c } @@ -59,7 +59,7 @@ func (v *Variable) Int64() int64 { // Float returns float64 value of the variable value. // It returns 0.0 if the value is not convertible to float64. func (v *Variable) Float() float64 { - c, _ := objects.ToFloat64(*v.value) + c, _ := objects.ToFloat64(v.value) return c } @@ -67,7 +67,7 @@ func (v *Variable) Float() float64 { // Char returns rune value of the variable value. // It returns 0 if the value is not convertible to rune. func (v *Variable) Char() rune { - c, _ := objects.ToRune(*v.value) + c, _ := objects.ToRune(v.value) return c } @@ -75,7 +75,7 @@ func (v *Variable) Char() rune { // Bool returns bool value of the variable value. // It returns 0 if the value is not convertible to bool. func (v *Variable) Bool() bool { - c, _ := objects.ToBool(*v.value) + c, _ := objects.ToBool(v.value) return c } @@ -83,11 +83,11 @@ func (v *Variable) Bool() bool { // Array returns []interface value of the variable value. // It returns 0 if the value is not convertible to []interface. func (v *Variable) Array() []interface{} { - switch val := (*v.value).(type) { + switch val := v.value.(type) { case *objects.Array: var arr []interface{} for _, e := range val.Value { - arr = append(arr, objectToInterface(e)) + arr = append(arr, objects.ToInterface(e)) } return arr } @@ -98,11 +98,11 @@ func (v *Variable) Array() []interface{} { // Map returns map[string]interface{} value of the variable value. // It returns 0 if the value is not convertible to map[string]interface{}. func (v *Variable) Map() map[string]interface{} { - switch val := (*v.value).(type) { + switch val := v.value.(type) { case *objects.Map: kv := make(map[string]interface{}) for mk, mv := range val.Value { - kv[mk] = objectToInterface(mv) + kv[mk] = objects.ToInterface(mv) } return kv } @@ -113,7 +113,7 @@ func (v *Variable) Map() map[string]interface{} { // String returns string value of the variable value. // It returns 0 if the value is not convertible to string. func (v *Variable) String() string { - c, _ := objects.ToString(*v.value) + c, _ := objects.ToString(v.value) return c } @@ -121,7 +121,7 @@ func (v *Variable) String() string { // Bytes returns a byte slice of the variable value. // It returns nil if the value is not convertible to byte slice. func (v *Variable) Bytes() []byte { - c, _ := objects.ToByteSlice(*v.value) + c, _ := objects.ToByteSlice(v.value) return c } @@ -129,7 +129,7 @@ func (v *Variable) Bytes() []byte { // Error returns an error if the underlying value is error object. // If not, this returns nil. func (v *Variable) Error() error { - err, ok := (*v.value).(*objects.Error) + err, ok := v.value.(*objects.Error) if ok { return errors.New(err.String()) } @@ -140,10 +140,10 @@ func (v *Variable) Error() error { // Object returns an underlying Object of the variable value. // Note that returned Object is a copy of an actual Object used in the script. func (v *Variable) Object() objects.Object { - return *v.value + return v.value } // IsUndefined returns true if the underlying value is undefined. func (v *Variable) IsUndefined() bool { - return *v.value == objects.UndefinedValue + return v.value == objects.UndefinedValue } diff --git a/vendor/github.com/d5/tengo/stdlib/builtin_modules.go b/vendor/github.com/d5/tengo/stdlib/builtin_modules.go new file mode 100644 index 00000000..cc2796f9 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/builtin_modules.go @@ -0,0 +1,14 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +// BuiltinModules are builtin type standard library modules. +var BuiltinModules = map[string]map[string]objects.Object{ + "math": mathModule, + "os": osModule, + "text": textModule, + "times": timesModule, + "rand": randModule, + "fmt": fmtModule, + "json": jsonModule, +} diff --git a/vendor/github.com/d5/tengo/stdlib/errors.go b/vendor/github.com/d5/tengo/stdlib/errors.go new file mode 100644 index 00000000..a2942bb0 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/errors.go @@ -0,0 +1,11 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +func wrapError(err error) objects.Object { + if err == nil { + return objects.TrueValue + } + + return &objects.Error{Value: &objects.String{Value: err.Error()}} +} diff --git a/vendor/github.com/d5/tengo/stdlib/fmt.go b/vendor/github.com/d5/tengo/stdlib/fmt.go new file mode 100644 index 00000000..9c75fc33 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/fmt.go @@ -0,0 +1,116 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +var fmtModule = map[string]objects.Object{ + "print": &objects.UserFunction{Name: "print", Value: fmtPrint}, + "printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf}, + "println": &objects.UserFunction{Name: "println", Value: fmtPrintln}, + "sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf}, +} + +func fmtPrint(args ...objects.Object) (ret objects.Object, err error) { + printArgs, err := getPrintArgs(args...) + if err != nil { + return nil, err + } + + _, _ = fmt.Print(printArgs...) + + return nil, nil +} + +func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs == 0 { + return nil, objects.ErrWrongNumArguments + } + + format, ok := args[0].(*objects.String) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + fmt.Print(format) + return nil, nil + } + + formatArgs := make([]interface{}, numArgs-1, numArgs-1) + for idx, arg := range args[1:] { + formatArgs[idx] = objects.ToInterface(arg) + } + + fmt.Printf(format.Value, formatArgs...) + + return nil, nil +} + +func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) { + printArgs, err := getPrintArgs(args...) + if err != nil { + return nil, err + } + + printArgs = append(printArgs, "\n") + _, _ = fmt.Print(printArgs...) + + return nil, nil +} + +func fmtSprintf(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs == 0 { + return nil, objects.ErrWrongNumArguments + } + + format, ok := args[0].(*objects.String) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + return format, nil // okay to return 'format' directly as String is immutable + } + + formatArgs := make([]interface{}, numArgs-1, numArgs-1) + for idx, arg := range args[1:] { + formatArgs[idx] = objects.ToInterface(arg) + } + + s := fmt.Sprintf(format.Value, formatArgs...) + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil +} + +func getPrintArgs(args ...objects.Object) ([]interface{}, error) { + var printArgs []interface{} + l := 0 + for _, arg := range args { + s, _ := objects.ToString(arg) + slen := len(s) + if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit + return nil, objects.ErrStringLimit + } + l += slen + + printArgs = append(printArgs, s) + } + + return printArgs, nil +} diff --git a/vendor/github.com/d5/tengo/stdlib/func_typedefs.go b/vendor/github.com/d5/tengo/stdlib/func_typedefs.go new file mode 100644 index 00000000..26c7ddd9 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/func_typedefs.go @@ -0,0 +1,1125 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +// FuncAR transform a function of 'func()' signature +// into CallableFunc type. +func FuncAR(fn func()) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + fn() + + return objects.UndefinedValue, nil + } +} + +// FuncARI transform a function of 'func() int' signature +// into CallableFunc type. +func FuncARI(fn func() int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: int64(fn())}, nil + } +} + +// FuncARI64 transform a function of 'func() int64' signature +// into CallableFunc type. +func FuncARI64(fn func() int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: fn()}, nil + } +} + +// FuncAI64RI64 transform a function of 'func(int64) int64' signature +// into CallableFunc type. +func FuncAI64RI64(fn func(int64) int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: fn(i1)}, nil + } +} + +// FuncAI64R transform a function of 'func(int64)' signature +// into CallableFunc type. +func FuncAI64R(fn func(int64)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncARB transform a function of 'func() bool' signature +// into CallableFunc type. +func FuncARB(fn func() bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + if fn() { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncARE transform a function of 'func() error' signature +// into CallableFunc type. +func FuncARE(fn func() error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return wrapError(fn()), nil + } +} + +// FuncARS transform a function of 'func() string' signature +// into CallableFunc type. +func FuncARS(fn func() string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + s := fn() + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} + +// FuncARSE transform a function of 'func() (string, error)' signature +// into CallableFunc type. +func FuncARSE(fn func() (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + if len(res) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: res}, nil + } +} + +// FuncARYE transform a function of 'func() ([]byte, error)' signature +// into CallableFunc type. +func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + if len(res) > tengo.MaxBytesLen { + return nil, objects.ErrBytesLimit + } + + return &objects.Bytes{Value: res}, nil + } +} + +// FuncARF transform a function of 'func() float64' signature +// into CallableFunc type. +func FuncARF(fn func() float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Float{Value: fn()}, nil + } +} + +// FuncARSs transform a function of 'func() []string' signature +// into CallableFunc type. +func FuncARSs(fn func() []string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, elem := range fn() { + if len(elem) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: elem}) + } + + return arr, nil + } +} + +// FuncARIsE transform a function of 'func() ([]int, error)' signature +// into CallableFunc type. +func FuncARIsE(fn func() ([]int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAIRIs transform a function of 'func(int) []int' signature +// into CallableFunc type. +func FuncAIRIs(fn func(int) []int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(i1) + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAFRF transform a function of 'func(float64) float64' signature +// into CallableFunc type. +func FuncAFRF(fn func(float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(f1)}, nil + } +} + +// FuncAIR transform a function of 'func(int)' signature +// into CallableFunc type. +func FuncAIR(fn func(int)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncAIRF transform a function of 'func(int) float64' signature +// into CallableFunc type. +func FuncAIRF(fn func(int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(i1)}, nil + } +} + +// FuncAFRI transform a function of 'func(float64) int' signature +// into CallableFunc type. +func FuncAFRI(fn func(float64) int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(f1))}, nil + } +} + +// FuncAFFRF transform a function of 'func(float64, float64) float64' signature +// into CallableFunc type. +func FuncAFFRF(fn func(float64, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, f2)}, nil + } +} + +// FuncAIFRF transform a function of 'func(int, float64) float64' signature +// into CallableFunc type. +func FuncAIFRF(fn func(int, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(i1, f2)}, nil + } +} + +// FuncAFIRF transform a function of 'func(float64, int) float64' signature +// into CallableFunc type. +func FuncAFIRF(fn func(float64, int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, i2)}, nil + } +} + +// FuncAFIRB transform a function of 'func(float64, int) bool' signature +// into CallableFunc type. +func FuncAFIRB(fn func(float64, int) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(f1, i2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncAFRB transform a function of 'func(float64) bool' signature +// into CallableFunc type. +func FuncAFRB(fn func(float64) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + if fn(f1) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASRS transform a function of 'func(string) string' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRS(fn func(string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s := fn(s1) + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} + +// FuncASRSs transform a function of 'func(string) []string' signature into CallableFunc type. +func FuncASRSs(fn func(string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(s1) + + arr := &objects.Array{} + for _, elem := range res { + if len(elem) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: elem}) + } + + return arr, nil + } +} + +// FuncASRSE transform a function of 'func(string) (string, error)' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + if len(res) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: res}, nil + } +} + +// FuncASRE transform a function of 'func(string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRE(fn func(string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(fn(s1)), nil + } +} + +// FuncASSRE transform a function of 'func(string, string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASSRE(fn func(string, string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, s2)), nil + } +} + +// FuncASSRSs transform a function of 'func(string, string) []string' signature into CallableFunc type. +func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2) { + if len(res) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into CallableFunc type. +func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2, i3) { + if len(res) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSRI transform a function of 'func(string, string) int' signature into CallableFunc type. +func FuncASSRI(fn func(string, string) int) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(s1, s2))}, nil + } +} + +// FuncASSRS transform a function of 'func(string, string) string' signature into CallableFunc type. +func FuncASSRS(fn func(string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + s := fn(s1, s2) + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} + +// FuncASSRB transform a function of 'func(string, string) bool' signature into CallableFunc type. +func FuncASSRB(fn func(string, string) bool) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(s1, s2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASsSRS transform a function of 'func([]string, string) string' signature into CallableFunc type. +func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + var ss1 []string + switch arg0 := args[0].(type) { + case *objects.Array: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + case *objects.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + s := fn(ss1, s2) + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} + +// FuncASI64RE transform a function of 'func(string, int64) error' signature +// into CallableFunc type. +func FuncASI64RE(fn func(string, int64) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, i2)), nil + } +} + +// FuncAIIRE transform a function of 'func(int, int) error' signature +// into CallableFunc type. +func FuncAIIRE(fn func(int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(i1, i2)), nil + } +} + +// FuncASIRS transform a function of 'func(string, int) string' signature +// into CallableFunc type. +func FuncASIRS(fn func(string, int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + s := fn(s1, i2) + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} + +// FuncASIIRE transform a function of 'func(string, int, int) error' signature +// into CallableFunc type. +func FuncASIIRE(fn func(string, int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + return wrapError(fn(s1, i2, i3)), nil + } +} + +// FuncAYRIE transform a function of 'func([]byte) (int, error)' signature +// into CallableFunc type. +func FuncAYRIE(fn func([]byte) (int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := objects.ToByteSlice(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(y1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncASRIE transform a function of 'func(string) (int, error)' signature +// into CallableFunc type. +func FuncASRIE(fn func(string) (int, error)) objects.CallableFunc { + return func(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.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature +// into CallableFunc type. +func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(i1) + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, r := range res { + if len(r) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: r}) + } + + return arr, nil + } +} + +// FuncAIRS transform a function of 'func(int) string' signature +// into CallableFunc type. +func FuncAIRS(fn func(int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + s := fn(i1) + + if len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/gensrcmods.go b/vendor/github.com/d5/tengo/stdlib/gensrcmods.go new file mode 100644 index 00000000..fada66bd --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/gensrcmods.go @@ -0,0 +1,53 @@ +// +build ignore + +package main + +import ( + "bytes" + "io/ioutil" + "log" + "regexp" + "strconv" +) + +var tengoModFileRE = regexp.MustCompile(`^srcmod_(\w+).tengo$`) + +func main() { + modules := make(map[string]string) + + // enumerate all Tengo module files + files, err := ioutil.ReadDir(".") + if err != nil { + log.Fatal(err) + } + for _, file := range files { + m := tengoModFileRE.FindStringSubmatch(file.Name()) + if m != nil { + modName := m[1] + + src, err := ioutil.ReadFile(file.Name()) + if err != nil { + log.Fatalf("file '%s' read error: %s", file.Name(), err.Error()) + } + + modules[modName] = string(src) + } + } + + var out bytes.Buffer + out.WriteString(`// Code generated using gensrcmods.go; DO NOT EDIT. + +package stdlib + +// SourceModules are source type standard library modules. +var SourceModules = map[string]string{` + "\n") + for modName, modSrc := range modules { + out.WriteString("\t\"" + modName + "\": " + strconv.Quote(modSrc) + ",\n") + } + out.WriteString("}\n") + + const target = "source_modules.go" + if err := ioutil.WriteFile(target, out.Bytes(), 0644); err != nil { + log.Fatal(err) + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/json.go b/vendor/github.com/d5/tengo/stdlib/json.go new file mode 100644 index 00000000..f913dc48 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/json.go @@ -0,0 +1,126 @@ +package stdlib + +import ( + "bytes" + gojson "encoding/json" + + "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib/json" +) + +var jsonModule = map[string]objects.Object{ + "decode": &objects.UserFunction{Name: "decode", Value: jsonDecode}, + "encode": &objects.UserFunction{Name: "encode", Value: jsonEncode}, + "indent": &objects.UserFunction{Name: "encode", Value: jsonIndent}, + "html_escape": &objects.UserFunction{Name: "html_escape", Value: jsonHTMLEscape}, +} + +func jsonDecode(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + switch o := args[0].(type) { + case *objects.Bytes: + v, err := json.Decode(o.Value) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + return v, nil + case *objects.String: + v, err := json.Decode([]byte(o.Value)) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + return v, nil + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} + +func jsonEncode(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + b, err := json.Encode(args[0]) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + + return &objects.Bytes{Value: b}, nil +} + +func jsonIndent(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + prefix, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "prefix", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + indent, ok := objects.ToString(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "indent", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + switch o := args[0].(type) { + case *objects.Bytes: + var dst bytes.Buffer + err := gojson.Indent(&dst, o.Value, prefix, indent) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + return &objects.Bytes{Value: dst.Bytes()}, nil + case *objects.String: + var dst bytes.Buffer + err := gojson.Indent(&dst, []byte(o.Value), prefix, indent) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + return &objects.Bytes{Value: dst.Bytes()}, nil + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} + +func jsonHTMLEscape(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + switch o := args[0].(type) { + case *objects.Bytes: + var dst bytes.Buffer + gojson.HTMLEscape(&dst, o.Value) + return &objects.Bytes{Value: dst.Bytes()}, nil + case *objects.String: + var dst bytes.Buffer + gojson.HTMLEscape(&dst, []byte(o.Value)) + return &objects.Bytes{Value: dst.Bytes()}, nil + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/json/decode.go b/vendor/github.com/d5/tengo/stdlib/json/decode.go new file mode 100644 index 00000000..5a3fe6c7 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/json/decode.go @@ -0,0 +1,374 @@ +// A modified version of Go's JSON implementation. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "strconv" + "unicode" + "unicode/utf16" + "unicode/utf8" + + "github.com/d5/tengo/objects" +) + +// Decode parses the JSON-encoded data and returns the result object. +func Decode(data []byte) (objects.Object, error) { + var d decodeState + err := checkValid(data, &d.scan) + if err != nil { + return nil, err + } + + d.init(data) + d.scan.reset() + d.scanWhile(scanSkipSpace) + + return d.value() +} + +// decodeState represents the state while decoding a JSON value. +type decodeState struct { + data []byte + off int // next read offset in data + opcode int // last read result + scan scanner +} + +// readIndex returns the position of the last byte read. +func (d *decodeState) readIndex() int { + return d.off - 1 +} + +const phasePanicMsg = "JSON decoder out of sync - data changing underfoot?" + +func (d *decodeState) init(data []byte) *decodeState { + d.data = data + d.off = 0 + return d +} + +// scanNext processes the byte at d.data[d.off]. +func (d *decodeState) scanNext() { + if d.off < len(d.data) { + d.opcode = d.scan.step(&d.scan, d.data[d.off]) + d.off++ + } else { + d.opcode = d.scan.eof() + d.off = len(d.data) + 1 // mark processed EOF with len+1 + } +} + +// scanWhile processes bytes in d.data[d.off:] until it +// receives a scan code not equal to op. +func (d *decodeState) scanWhile(op int) { + s, data, i := &d.scan, d.data, d.off + for i < len(data) { + newOp := s.step(s, data[i]) + i++ + if newOp != op { + d.opcode = newOp + d.off = i + return + } + } + + d.off = len(data) + 1 // mark processed EOF with len+1 + d.opcode = d.scan.eof() +} + +func (d *decodeState) value() (objects.Object, error) { + switch d.opcode { + default: + panic(phasePanicMsg) + + case scanBeginArray: + o, err := d.array() + if err != nil { + return nil, err + } + + d.scanNext() + + return o, nil + + case scanBeginObject: + o, err := d.object() + if err != nil { + return nil, err + } + + d.scanNext() + + return o, nil + + case scanBeginLiteral: + return d.literal() + } +} + +func (d *decodeState) array() (objects.Object, error) { + var arr []objects.Object + for { + // Look ahead for ] - can only happen on first iteration. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndArray { + break + } + + o, err := d.value() + if err != nil { + return nil, err + } + arr = append(arr, o) + + // Next token must be , or ]. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndArray { + break + } + if d.opcode != scanArrayValue { + panic(phasePanicMsg) + } + } + + return &objects.Array{Value: arr}, nil +} + +func (d *decodeState) object() (objects.Object, error) { + m := make(map[string]objects.Object) + for { + // Read opening " of string key or closing }. + d.scanWhile(scanSkipSpace) + if d.opcode == scanEndObject { + // closing } - can only happen on first iteration. + break + } + if d.opcode != scanBeginLiteral { + panic(phasePanicMsg) + } + + // Read string key. + start := d.readIndex() + d.scanWhile(scanContinue) + item := d.data[start:d.readIndex()] + key, ok := unquote(item) + if !ok { + panic(phasePanicMsg) + } + + // Read : before value. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode != scanObjectKey { + panic(phasePanicMsg) + } + d.scanWhile(scanSkipSpace) + + // Read value. + o, err := d.value() + if err != nil { + return nil, err + } + + m[key] = o + + // Next token must be , or }. + if d.opcode == scanSkipSpace { + d.scanWhile(scanSkipSpace) + } + if d.opcode == scanEndObject { + break + } + if d.opcode != scanObjectValue { + panic(phasePanicMsg) + } + } + + return &objects.Map{Value: m}, nil +} + +func (d *decodeState) literal() (objects.Object, error) { + // All bytes inside literal return scanContinue op code. + start := d.readIndex() + d.scanWhile(scanContinue) + + item := d.data[start:d.readIndex()] + + switch c := item[0]; c { + case 'n': // null + return objects.UndefinedValue, nil + + case 't', 'f': // true, false + if c == 't' { + return objects.TrueValue, nil + } + return objects.FalseValue, nil + + case '"': // string + s, ok := unquote(item) + if !ok { + panic(phasePanicMsg) + } + return &objects.String{Value: s}, nil + + default: // number + if c != '-' && (c < '0' || c > '9') { + panic(phasePanicMsg) + } + + n, _ := strconv.ParseFloat(string(item), 10) + return &objects.Float{Value: n}, nil + } +} + +// getu4 decodes \uXXXX from the beginning of s, returning the hex value, +// or it returns -1. +func getu4(s []byte) rune { + if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { + return -1 + } + var r rune + for _, c := range s[2:6] { + switch { + case '0' <= c && c <= '9': + c = c - '0' + case 'a' <= c && c <= 'f': + c = c - 'a' + 10 + case 'A' <= c && c <= 'F': + c = c - 'A' + 10 + default: + return -1 + } + r = r*16 + rune(c) + } + return r +} + +// unquote converts a quoted JSON string literal s into an actual string t. +// The rules are different than for Go, so cannot use strconv.Unquote. +func unquote(s []byte) (t string, ok bool) { + s, ok = unquoteBytes(s) + t = string(s) + return +} + +func unquoteBytes(s []byte) (t []byte, ok bool) { + if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { + return + } + s = s[1 : len(s)-1] + + // Check for unusual characters. If there are none, + // then no unquoting is needed, so return a slice of the + // original bytes. + r := 0 + for r < len(s) { + c := s[r] + if c == '\\' || c == '"' || c < ' ' { + break + } + if c < utf8.RuneSelf { + r++ + continue + } + rr, size := utf8.DecodeRune(s[r:]) + if rr == utf8.RuneError && size == 1 { + break + } + r += size + } + if r == len(s) { + return s, true + } + + b := make([]byte, len(s)+2*utf8.UTFMax) + w := copy(b, s[0:r]) + for r < len(s) { + // Out of room? Can only happen if s is full of + // malformed UTF-8 and we're replacing each + // byte with RuneError. + if w >= len(b)-2*utf8.UTFMax { + nb := make([]byte, (len(b)+utf8.UTFMax)*2) + copy(nb, b[0:w]) + b = nb + } + switch c := s[r]; { + case c == '\\': + r++ + if r >= len(s) { + return + } + switch s[r] { + default: + return + case '"', '\\', '/', '\'': + b[w] = s[r] + r++ + w++ + case 'b': + b[w] = '\b' + r++ + w++ + case 'f': + b[w] = '\f' + r++ + w++ + case 'n': + b[w] = '\n' + r++ + w++ + case 'r': + b[w] = '\r' + r++ + w++ + case 't': + b[w] = '\t' + r++ + w++ + case 'u': + r-- + rr := getu4(s[r:]) + if rr < 0 { + return + } + r += 6 + if utf16.IsSurrogate(rr) { + rr1 := getu4(s[r:]) + if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { + // A valid pair; consume. + r += 6 + w += utf8.EncodeRune(b[w:], dec) + break + } + // Invalid surrogate; fall back to replacement rune. + rr = unicode.ReplacementChar + } + w += utf8.EncodeRune(b[w:], rr) + } + + // Quote, control characters are invalid. + case c == '"', c < ' ': + return + + // ASCII + case c < utf8.RuneSelf: + b[w] = c + r++ + w++ + + // Coerce to well-formed UTF-8. + default: + rr, size := utf8.DecodeRune(s[r:]) + r += size + w += utf8.EncodeRune(b[w:], rr) + } + } + return b[0:w], true +} diff --git a/vendor/github.com/d5/tengo/stdlib/json/encode.go b/vendor/github.com/d5/tengo/stdlib/json/encode.go new file mode 100644 index 00000000..2b8b17eb --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/json/encode.go @@ -0,0 +1,147 @@ +// A modified version of Go's JSON implementation. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import ( + "encoding/base64" + "errors" + "math" + "strconv" + + "github.com/d5/tengo/objects" +) + +// Encode returns the JSON encoding of the object. +func Encode(o objects.Object) ([]byte, error) { + var b []byte + + switch o := o.(type) { + case *objects.Array: + b = append(b, '[') + len1 := len(o.Value) - 1 + for idx, elem := range o.Value { + eb, err := Encode(elem) + if err != nil { + return nil, err + } + b = append(b, eb...) + if idx < len1 { + b = append(b, ',') + } + } + b = append(b, ']') + case *objects.ImmutableArray: + b = append(b, '[') + len1 := len(o.Value) - 1 + for idx, elem := range o.Value { + eb, err := Encode(elem) + if err != nil { + return nil, err + } + b = append(b, eb...) + if idx < len1 { + b = append(b, ',') + } + } + b = append(b, ']') + case *objects.Map: + b = append(b, '{') + len1 := len(o.Value) - 1 + idx := 0 + for key, value := range o.Value { + b = strconv.AppendQuote(b, key) + b = append(b, ':') + eb, err := Encode(value) + if err != nil { + return nil, err + } + b = append(b, eb...) + if idx < len1 { + b = append(b, ',') + } + idx++ + } + b = append(b, '}') + case *objects.ImmutableMap: + b = append(b, '{') + len1 := len(o.Value) - 1 + idx := 0 + for key, value := range o.Value { + b = strconv.AppendQuote(b, key) + b = append(b, ':') + eb, err := Encode(value) + if err != nil { + return nil, err + } + b = append(b, eb...) + if idx < len1 { + b = append(b, ',') + } + idx++ + } + b = append(b, '}') + case *objects.Bool: + if o.IsFalsy() { + b = strconv.AppendBool(b, false) + } else { + b = strconv.AppendBool(b, true) + } + case *objects.Bytes: + b = append(b, '"') + encodedLen := base64.StdEncoding.EncodedLen(len(o.Value)) + dst := make([]byte, encodedLen) + base64.StdEncoding.Encode(dst, o.Value) + b = append(b, dst...) + b = append(b, '"') + case *objects.Char: + b = strconv.AppendInt(b, int64(o.Value), 10) + case *objects.Float: + var y []byte + + f := o.Value + if math.IsInf(f, 0) || math.IsNaN(f) { + return nil, errors.New("unsupported float value") + } + + // Convert as if by ES6 number to string conversion. + // This matches most other JSON generators. + abs := math.Abs(f) + fmt := byte('f') + if abs != 0 { + if abs < 1e-6 || abs >= 1e21 { + fmt = 'e' + } + } + y = strconv.AppendFloat(y, f, fmt, -1, 64) + if fmt == 'e' { + // clean up e-09 to e-9 + n := len(y) + if n >= 4 && y[n-4] == 'e' && y[n-3] == '-' && y[n-2] == '0' { + y[n-2] = y[n-1] + y = y[:n-1] + } + } + + b = append(b, y...) + case *objects.Int: + b = strconv.AppendInt(b, o.Value, 10) + case *objects.String: + b = strconv.AppendQuote(b, o.Value) + case *objects.Time: + y, err := o.Value.MarshalJSON() + if err != nil { + return nil, err + } + b = append(b, y...) + case *objects.Undefined: + b = append(b, "null"...) + default: + // unknown type: ignore + } + + return b, nil +} diff --git a/vendor/github.com/d5/tengo/stdlib/json/scanner.go b/vendor/github.com/d5/tengo/stdlib/json/scanner.go new file mode 100644 index 00000000..8fc6776d --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/json/scanner.go @@ -0,0 +1,559 @@ +// A modified version of Go's JSON implementation. + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package json + +import "strconv" + +func checkValid(data []byte, scan *scanner) error { + scan.reset() + for _, c := range data { + scan.bytes++ + if scan.step(scan, c) == scanError { + return scan.err + } + } + if scan.eof() == scanError { + return scan.err + } + return nil +} + +// A SyntaxError is a description of a JSON syntax error. +type SyntaxError struct { + msg string // description of error + Offset int64 // error occurred after reading Offset bytes +} + +func (e *SyntaxError) Error() string { return e.msg } + +// A scanner is a JSON scanning state machine. +// Callers call scan.reset() and then pass bytes in one at a time +// by calling scan.step(&scan, c) for each byte. +// The return value, referred to as an opcode, tells the +// caller about significant parsing events like beginning +// and ending literals, objects, and arrays, so that the +// caller can follow along if it wishes. +// The return value scanEnd indicates that a single top-level +// JSON value has been completed, *before* the byte that +// just got passed in. (The indication must be delayed in order +// to recognize the end of numbers: is 123 a whole value or +// the beginning of 12345e+6?). +type scanner struct { + // The step is a func to be called to execute the next transition. + // Also tried using an integer constant and a single func + // with a switch, but using the func directly was 10% faster + // on a 64-bit Mac Mini, and it's nicer to read. + step func(*scanner, byte) int + + // Reached end of top-level value. + endTop bool + + // Stack of what we're in the middle of - array values, object keys, object values. + parseState []int + + // Error that happened, if any. + err error + + // total bytes consumed, updated by decoder.Decode + bytes int64 +} + +// These values are returned by the state transition functions +// assigned to scanner.state and the method scanner.eof. +// They give details about the current state of the scan that +// callers might be interested to know about. +// It is okay to ignore the return value of any particular +// call to scanner.state: if one call returns scanError, +// every subsequent call will return scanError too. +const ( + // Continue. + scanContinue = iota // uninteresting byte + scanBeginLiteral // end implied by next result != scanContinue + scanBeginObject // begin object + scanObjectKey // just finished object key (string) + scanObjectValue // just finished non-last object value + scanEndObject // end object (implies scanObjectValue if possible) + scanBeginArray // begin array + scanArrayValue // just finished array value + scanEndArray // end array (implies scanArrayValue if possible) + scanSkipSpace // space byte; can skip; known to be last "continue" result + + // Stop. + scanEnd // top-level value ended *before* this byte; known to be first "stop" result + scanError // hit an error, scanner.err. +) + +// These values are stored in the parseState stack. +// They give the current state of a composite value +// being scanned. If the parser is inside a nested value +// the parseState describes the nested state, outermost at entry 0. +const ( + parseObjectKey = iota // parsing object key (before colon) + parseObjectValue // parsing object value (after colon) + parseArrayValue // parsing array value +) + +// reset prepares the scanner for use. +// It must be called before calling s.step. +func (s *scanner) reset() { + s.step = stateBeginValue + s.parseState = s.parseState[0:0] + s.err = nil + s.endTop = false +} + +// eof tells the scanner that the end of input has been reached. +// It returns a scan status just as s.step does. +func (s *scanner) eof() int { + if s.err != nil { + return scanError + } + if s.endTop { + return scanEnd + } + s.step(s, ' ') + if s.endTop { + return scanEnd + } + if s.err == nil { + s.err = &SyntaxError{"unexpected end of JSON input", s.bytes} + } + return scanError +} + +// pushParseState pushes a new parse state p onto the parse stack. +func (s *scanner) pushParseState(p int) { + s.parseState = append(s.parseState, p) +} + +// popParseState pops a parse state (already obtained) off the stack +// and updates s.step accordingly. +func (s *scanner) popParseState() { + n := len(s.parseState) - 1 + s.parseState = s.parseState[0:n] + if n == 0 { + s.step = stateEndTop + s.endTop = true + } else { + s.step = stateEndValue + } +} + +func isSpace(c byte) bool { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' +} + +// stateBeginValueOrEmpty is the state after reading `[`. +func stateBeginValueOrEmpty(s *scanner, c byte) int { + if c <= ' ' && isSpace(c) { + return scanSkipSpace + } + if c == ']' { + return stateEndValue(s, c) + } + return stateBeginValue(s, c) +} + +// stateBeginValue is the state at the beginning of the input. +func stateBeginValue(s *scanner, c byte) int { + if c <= ' ' && isSpace(c) { + return scanSkipSpace + } + switch c { + case '{': + s.step = stateBeginStringOrEmpty + s.pushParseState(parseObjectKey) + return scanBeginObject + case '[': + s.step = stateBeginValueOrEmpty + s.pushParseState(parseArrayValue) + return scanBeginArray + case '"': + s.step = stateInString + return scanBeginLiteral + case '-': + s.step = stateNeg + return scanBeginLiteral + case '0': // beginning of 0.123 + s.step = state0 + return scanBeginLiteral + case 't': // beginning of true + s.step = stateT + return scanBeginLiteral + case 'f': // beginning of false + s.step = stateF + return scanBeginLiteral + case 'n': // beginning of null + s.step = stateN + return scanBeginLiteral + } + if '1' <= c && c <= '9' { // beginning of 1234.5 + s.step = state1 + return scanBeginLiteral + } + return s.error(c, "looking for beginning of value") +} + +// stateBeginStringOrEmpty is the state after reading `{`. +func stateBeginStringOrEmpty(s *scanner, c byte) int { + if c <= ' ' && isSpace(c) { + return scanSkipSpace + } + if c == '}' { + n := len(s.parseState) + s.parseState[n-1] = parseObjectValue + return stateEndValue(s, c) + } + return stateBeginString(s, c) +} + +// stateBeginString is the state after reading `{"key": value,`. +func stateBeginString(s *scanner, c byte) int { + if c <= ' ' && isSpace(c) { + return scanSkipSpace + } + if c == '"' { + s.step = stateInString + return scanBeginLiteral + } + return s.error(c, "looking for beginning of object key string") +} + +// stateEndValue is the state after completing a value, +// such as after reading `{}` or `true` or `["x"`. +func stateEndValue(s *scanner, c byte) int { + n := len(s.parseState) + if n == 0 { + // Completed top-level before the current byte. + s.step = stateEndTop + s.endTop = true + return stateEndTop(s, c) + } + if c <= ' ' && isSpace(c) { + s.step = stateEndValue + return scanSkipSpace + } + ps := s.parseState[n-1] + switch ps { + case parseObjectKey: + if c == ':' { + s.parseState[n-1] = parseObjectValue + s.step = stateBeginValue + return scanObjectKey + } + return s.error(c, "after object key") + case parseObjectValue: + if c == ',' { + s.parseState[n-1] = parseObjectKey + s.step = stateBeginString + return scanObjectValue + } + if c == '}' { + s.popParseState() + return scanEndObject + } + return s.error(c, "after object key:value pair") + case parseArrayValue: + if c == ',' { + s.step = stateBeginValue + return scanArrayValue + } + if c == ']' { + s.popParseState() + return scanEndArray + } + return s.error(c, "after array element") + } + return s.error(c, "") +} + +// stateEndTop is the state after finishing the top-level value, +// such as after reading `{}` or `[1,2,3]`. +// Only space characters should be seen now. +func stateEndTop(s *scanner, c byte) int { + if !isSpace(c) { + // Complain about non-space byte on next call. + s.error(c, "after top-level value") + } + return scanEnd +} + +// stateInString is the state after reading `"`. +func stateInString(s *scanner, c byte) int { + if c == '"' { + s.step = stateEndValue + return scanContinue + } + if c == '\\' { + s.step = stateInStringEsc + return scanContinue + } + if c < 0x20 { + return s.error(c, "in string literal") + } + return scanContinue +} + +// stateInStringEsc is the state after reading `"\` during a quoted string. +func stateInStringEsc(s *scanner, c byte) int { + switch c { + case 'b', 'f', 'n', 'r', 't', '\\', '/', '"': + s.step = stateInString + return scanContinue + case 'u': + s.step = stateInStringEscU + return scanContinue + } + return s.error(c, "in string escape code") +} + +// stateInStringEscU is the state after reading `"\u` during a quoted string. +func stateInStringEscU(s *scanner, c byte) int { + if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' { + s.step = stateInStringEscU1 + return scanContinue + } + // numbers + return s.error(c, "in \\u hexadecimal character escape") +} + +// stateInStringEscU1 is the state after reading `"\u1` during a quoted string. +func stateInStringEscU1(s *scanner, c byte) int { + if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' { + s.step = stateInStringEscU12 + return scanContinue + } + // numbers + return s.error(c, "in \\u hexadecimal character escape") +} + +// stateInStringEscU12 is the state after reading `"\u12` during a quoted string. +func stateInStringEscU12(s *scanner, c byte) int { + if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' { + s.step = stateInStringEscU123 + return scanContinue + } + // numbers + return s.error(c, "in \\u hexadecimal character escape") +} + +// stateInStringEscU123 is the state after reading `"\u123` during a quoted string. +func stateInStringEscU123(s *scanner, c byte) int { + if '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' { + s.step = stateInString + return scanContinue + } + // numbers + return s.error(c, "in \\u hexadecimal character escape") +} + +// stateNeg is the state after reading `-` during a number. +func stateNeg(s *scanner, c byte) int { + if c == '0' { + s.step = state0 + return scanContinue + } + if '1' <= c && c <= '9' { + s.step = state1 + return scanContinue + } + return s.error(c, "in numeric literal") +} + +// state1 is the state after reading a non-zero integer during a number, +// such as after reading `1` or `100` but not `0`. +func state1(s *scanner, c byte) int { + if '0' <= c && c <= '9' { + s.step = state1 + return scanContinue + } + return state0(s, c) +} + +// state0 is the state after reading `0` during a number. +func state0(s *scanner, c byte) int { + if c == '.' { + s.step = stateDot + return scanContinue + } + if c == 'e' || c == 'E' { + s.step = stateE + return scanContinue + } + return stateEndValue(s, c) +} + +// stateDot is the state after reading the integer and decimal point in a number, +// such as after reading `1.`. +func stateDot(s *scanner, c byte) int { + if '0' <= c && c <= '9' { + s.step = stateDot0 + return scanContinue + } + return s.error(c, "after decimal point in numeric literal") +} + +// stateDot0 is the state after reading the integer, decimal point, and subsequent +// digits of a number, such as after reading `3.14`. +func stateDot0(s *scanner, c byte) int { + if '0' <= c && c <= '9' { + return scanContinue + } + if c == 'e' || c == 'E' { + s.step = stateE + return scanContinue + } + return stateEndValue(s, c) +} + +// stateE is the state after reading the mantissa and e in a number, +// such as after reading `314e` or `0.314e`. +func stateE(s *scanner, c byte) int { + if c == '+' || c == '-' { + s.step = stateESign + return scanContinue + } + return stateESign(s, c) +} + +// stateESign is the state after reading the mantissa, e, and sign in a number, +// such as after reading `314e-` or `0.314e+`. +func stateESign(s *scanner, c byte) int { + if '0' <= c && c <= '9' { + s.step = stateE0 + return scanContinue + } + return s.error(c, "in exponent of numeric literal") +} + +// stateE0 is the state after reading the mantissa, e, optional sign, +// and at least one digit of the exponent in a number, +// such as after reading `314e-2` or `0.314e+1` or `3.14e0`. +func stateE0(s *scanner, c byte) int { + if '0' <= c && c <= '9' { + return scanContinue + } + return stateEndValue(s, c) +} + +// stateT is the state after reading `t`. +func stateT(s *scanner, c byte) int { + if c == 'r' { + s.step = stateTr + return scanContinue + } + return s.error(c, "in literal true (expecting 'r')") +} + +// stateTr is the state after reading `tr`. +func stateTr(s *scanner, c byte) int { + if c == 'u' { + s.step = stateTru + return scanContinue + } + return s.error(c, "in literal true (expecting 'u')") +} + +// stateTru is the state after reading `tru`. +func stateTru(s *scanner, c byte) int { + if c == 'e' { + s.step = stateEndValue + return scanContinue + } + return s.error(c, "in literal true (expecting 'e')") +} + +// stateF is the state after reading `f`. +func stateF(s *scanner, c byte) int { + if c == 'a' { + s.step = stateFa + return scanContinue + } + return s.error(c, "in literal false (expecting 'a')") +} + +// stateFa is the state after reading `fa`. +func stateFa(s *scanner, c byte) int { + if c == 'l' { + s.step = stateFal + return scanContinue + } + return s.error(c, "in literal false (expecting 'l')") +} + +// stateFal is the state after reading `fal`. +func stateFal(s *scanner, c byte) int { + if c == 's' { + s.step = stateFals + return scanContinue + } + return s.error(c, "in literal false (expecting 's')") +} + +// stateFals is the state after reading `fals`. +func stateFals(s *scanner, c byte) int { + if c == 'e' { + s.step = stateEndValue + return scanContinue + } + return s.error(c, "in literal false (expecting 'e')") +} + +// stateN is the state after reading `n`. +func stateN(s *scanner, c byte) int { + if c == 'u' { + s.step = stateNu + return scanContinue + } + return s.error(c, "in literal null (expecting 'u')") +} + +// stateNu is the state after reading `nu`. +func stateNu(s *scanner, c byte) int { + if c == 'l' { + s.step = stateNul + return scanContinue + } + return s.error(c, "in literal null (expecting 'l')") +} + +// stateNul is the state after reading `nul`. +func stateNul(s *scanner, c byte) int { + if c == 'l' { + s.step = stateEndValue + return scanContinue + } + return s.error(c, "in literal null (expecting 'l')") +} + +// stateError is the state after reaching a syntax error, +// such as after reading `[1}` or `5.1.2`. +func stateError(s *scanner, c byte) int { + return scanError +} + +// error records an error and switches to the error state. +func (s *scanner) error(c byte, context string) int { + s.step = stateError + s.err = &SyntaxError{"invalid character " + quoteChar(c) + " " + context, s.bytes} + return scanError +} + +// quoteChar formats c as a quoted character literal +func quoteChar(c byte) string { + // special cases - different from quoted strings + if c == '\'' { + return `'\''` + } + if c == '"' { + return `'"'` + } + + // use quoted string with different quotation marks + s := strconv.Quote(string(c)) + return "'" + s[1:len(s)-1] + "'" +} diff --git a/vendor/github.com/d5/tengo/stdlib/math.go b/vendor/github.com/d5/tengo/stdlib/math.go new file mode 100644 index 00000000..08d82bdf --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/math.go @@ -0,0 +1,74 @@ +package stdlib + +import ( + "math" + + "github.com/d5/tengo/objects" +) + +var mathModule = map[string]objects.Object{ + "e": &objects.Float{Value: math.E}, + "pi": &objects.Float{Value: math.Pi}, + "phi": &objects.Float{Value: math.Phi}, + "sqrt2": &objects.Float{Value: math.Sqrt2}, + "sqrtE": &objects.Float{Value: math.SqrtE}, + "sqrtPi": &objects.Float{Value: math.SqrtPi}, + "sqrtPhi": &objects.Float{Value: math.SqrtPhi}, + "ln2": &objects.Float{Value: math.Ln2}, + "log2E": &objects.Float{Value: math.Log2E}, + "ln10": &objects.Float{Value: math.Ln10}, + "log10E": &objects.Float{Value: math.Log10E}, + "abs": &objects.UserFunction{Name: "abs", Value: FuncAFRF(math.Abs)}, + "acos": &objects.UserFunction{Name: "acos", Value: FuncAFRF(math.Acos)}, + "acosh": &objects.UserFunction{Name: "acosh", Value: FuncAFRF(math.Acosh)}, + "asin": &objects.UserFunction{Name: "asin", Value: FuncAFRF(math.Asin)}, + "asinh": &objects.UserFunction{Name: "asinh", Value: FuncAFRF(math.Asinh)}, + "atan": &objects.UserFunction{Name: "atan", Value: FuncAFRF(math.Atan)}, + "atan2": &objects.UserFunction{Name: "atan2", Value: FuncAFFRF(math.Atan2)}, + "atanh": &objects.UserFunction{Name: "atanh", Value: FuncAFRF(math.Atanh)}, + "cbrt": &objects.UserFunction{Name: "cbrt", Value: FuncAFRF(math.Cbrt)}, + "ceil": &objects.UserFunction{Name: "ceil", Value: FuncAFRF(math.Ceil)}, + "copysign": &objects.UserFunction{Name: "copysign", Value: FuncAFFRF(math.Copysign)}, + "cos": &objects.UserFunction{Name: "cos", Value: FuncAFRF(math.Cos)}, + "cosh": &objects.UserFunction{Name: "cosh", Value: FuncAFRF(math.Cosh)}, + "dim": &objects.UserFunction{Name: "dim", Value: FuncAFFRF(math.Dim)}, + "erf": &objects.UserFunction{Name: "erf", Value: FuncAFRF(math.Erf)}, + "erfc": &objects.UserFunction{Name: "erfc", Value: FuncAFRF(math.Erfc)}, + "exp": &objects.UserFunction{Name: "exp", Value: FuncAFRF(math.Exp)}, + "exp2": &objects.UserFunction{Name: "exp2", Value: FuncAFRF(math.Exp2)}, + "expm1": &objects.UserFunction{Name: "expm1", Value: FuncAFRF(math.Expm1)}, + "floor": &objects.UserFunction{Name: "floor", Value: FuncAFRF(math.Floor)}, + "gamma": &objects.UserFunction{Name: "gamma", Value: FuncAFRF(math.Gamma)}, + "hypot": &objects.UserFunction{Name: "hypot", Value: FuncAFFRF(math.Hypot)}, + "ilogb": &objects.UserFunction{Name: "ilogb", Value: FuncAFRI(math.Ilogb)}, + "inf": &objects.UserFunction{Name: "inf", Value: FuncAIRF(math.Inf)}, + "is_inf": &objects.UserFunction{Name: "is_inf", Value: FuncAFIRB(math.IsInf)}, + "is_nan": &objects.UserFunction{Name: "is_nan", Value: FuncAFRB(math.IsNaN)}, + "j0": &objects.UserFunction{Name: "j0", Value: FuncAFRF(math.J0)}, + "j1": &objects.UserFunction{Name: "j1", Value: FuncAFRF(math.J1)}, + "jn": &objects.UserFunction{Name: "jn", Value: FuncAIFRF(math.Jn)}, + "ldexp": &objects.UserFunction{Name: "ldexp", Value: FuncAFIRF(math.Ldexp)}, + "log": &objects.UserFunction{Name: "log", Value: FuncAFRF(math.Log)}, + "log10": &objects.UserFunction{Name: "log10", Value: FuncAFRF(math.Log10)}, + "log1p": &objects.UserFunction{Name: "log1p", Value: FuncAFRF(math.Log1p)}, + "log2": &objects.UserFunction{Name: "log2", Value: FuncAFRF(math.Log2)}, + "logb": &objects.UserFunction{Name: "logb", Value: FuncAFRF(math.Logb)}, + "max": &objects.UserFunction{Name: "max", Value: FuncAFFRF(math.Max)}, + "min": &objects.UserFunction{Name: "min", Value: FuncAFFRF(math.Min)}, + "mod": &objects.UserFunction{Name: "mod", Value: FuncAFFRF(math.Mod)}, + "nan": &objects.UserFunction{Name: "nan", Value: FuncARF(math.NaN)}, + "nextafter": &objects.UserFunction{Name: "nextafter", Value: FuncAFFRF(math.Nextafter)}, + "pow": &objects.UserFunction{Name: "pow", Value: FuncAFFRF(math.Pow)}, + "pow10": &objects.UserFunction{Name: "pow10", Value: FuncAIRF(math.Pow10)}, + "remainder": &objects.UserFunction{Name: "remainder", Value: FuncAFFRF(math.Remainder)}, + "signbit": &objects.UserFunction{Name: "signbit", Value: FuncAFRB(math.Signbit)}, + "sin": &objects.UserFunction{Name: "sin", Value: FuncAFRF(math.Sin)}, + "sinh": &objects.UserFunction{Name: "sinh", Value: FuncAFRF(math.Sinh)}, + "sqrt": &objects.UserFunction{Name: "sqrt", Value: FuncAFRF(math.Sqrt)}, + "tan": &objects.UserFunction{Name: "tan", Value: FuncAFRF(math.Tan)}, + "tanh": &objects.UserFunction{Name: "tanh", Value: FuncAFRF(math.Tanh)}, + "trunc": &objects.UserFunction{Name: "trunc", Value: FuncAFRF(math.Trunc)}, + "y0": &objects.UserFunction{Name: "y0", Value: FuncAFRF(math.Y0)}, + "y1": &objects.UserFunction{Name: "y1", Value: FuncAFRF(math.Y1)}, + "yn": &objects.UserFunction{Name: "yn", Value: FuncAIFRF(math.Yn)}, +} diff --git a/vendor/github.com/d5/tengo/stdlib/os.go b/vendor/github.com/d5/tengo/stdlib/os.go new file mode 100644 index 00000000..a7890cc1 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os.go @@ -0,0 +1,492 @@ +package stdlib + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +var osModule = map[string]objects.Object{ + "o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)}, + "o_wronly": &objects.Int{Value: int64(os.O_WRONLY)}, + "o_rdwr": &objects.Int{Value: int64(os.O_RDWR)}, + "o_append": &objects.Int{Value: int64(os.O_APPEND)}, + "o_create": &objects.Int{Value: int64(os.O_CREATE)}, + "o_excl": &objects.Int{Value: int64(os.O_EXCL)}, + "o_sync": &objects.Int{Value: int64(os.O_SYNC)}, + "o_trunc": &objects.Int{Value: int64(os.O_TRUNC)}, + "mode_dir": &objects.Int{Value: int64(os.ModeDir)}, + "mode_append": &objects.Int{Value: int64(os.ModeAppend)}, + "mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)}, + "mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)}, + "mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)}, + "mode_device": &objects.Int{Value: int64(os.ModeDevice)}, + "mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)}, + "mode_socket": &objects.Int{Value: int64(os.ModeSocket)}, + "mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)}, + "mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)}, + "mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)}, + "mode_sticky": &objects.Int{Value: int64(os.ModeSticky)}, + "mode_type": &objects.Int{Value: int64(os.ModeType)}, + "mode_perm": &objects.Int{Value: int64(os.ModePerm)}, + "path_separator": &objects.Char{Value: os.PathSeparator}, + "path_list_separator": &objects.Char{Value: os.PathListSeparator}, + "dev_null": &objects.String{Value: os.DevNull}, + "seek_set": &objects.Int{Value: int64(io.SeekStart)}, + "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, + "seek_end": &objects.Int{Value: int64(io.SeekEnd)}, + "args": &objects.UserFunction{Name: "args", Value: osArgs}, // args() => array(string) + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error + "chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error + "chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error + "clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv() + "environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string) + "exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int) + "expand_env": &objects.UserFunction{Name: "expand_env", Value: osExpandEnv}, // expand_env(s string) => string + "getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int + "getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string + "geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int + "getgid": &objects.UserFunction{Name: "getgid", Value: FuncARI(os.Getgid)}, // getgid() => int + "getgroups": &objects.UserFunction{Name: "getgroups", Value: FuncARIsE(os.Getgroups)}, // getgroups() => array(string)/error + "getpagesize": &objects.UserFunction{Name: "getpagesize", Value: FuncARI(os.Getpagesize)}, // getpagesize() => int + "getpid": &objects.UserFunction{Name: "getpid", Value: FuncARI(os.Getpid)}, // getpid() => int + "getppid": &objects.UserFunction{Name: "getppid", Value: FuncARI(os.Getppid)}, // getppid() => int + "getuid": &objects.UserFunction{Name: "getuid", Value: FuncARI(os.Getuid)}, // getuid() => int + "getwd": &objects.UserFunction{Name: "getwd", Value: FuncARSE(os.Getwd)}, // getwd() => string/error + "hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error + "lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error + "link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error + "lookup_env": &objects.UserFunction{Name: "lookup_env", Value: osLookupEnv}, // lookup_env(key string) => string/false + "mkdir": osFuncASFmRE("mkdir", os.Mkdir), // mkdir(name string, perm int) => error + "mkdir_all": osFuncASFmRE("mkdir_all", os.MkdirAll), // mkdir_all(name string, perm int) => error + "readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error + "remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error + "remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error + "rename": &objects.UserFunction{Name: "rename", Value: FuncASSRE(os.Rename)}, // rename(oldpath string, newpath string) => error + "setenv": &objects.UserFunction{Name: "setenv", Value: FuncASSRE(os.Setenv)}, // setenv(key string, value string) => error + "symlink": &objects.UserFunction{Name: "symlink", Value: FuncASSRE(os.Symlink)}, // symlink(oldname string newname string) => error + "temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string + "truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error + "unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error + "create": &objects.UserFunction{Name: "create", Value: osCreate}, // create(name string) => imap(file)/error + "open": &objects.UserFunction{Name: "open", Value: osOpen}, // open(name string) => imap(file)/error + "open_file": &objects.UserFunction{Name: "open_file", Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error + "find_process": &objects.UserFunction{Name: "find_process", Value: osFindProcess}, // find_process(pid int) => imap(process)/error + "start_process": &objects.UserFunction{Name: "start_process", Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error + "exec": &objects.UserFunction{Name: "exec", Value: osExec}, // exec(name, args...) => command + "stat": &objects.UserFunction{Name: "stat", Value: osStat}, // stat(name) => imap(fileinfo)/error + "read_file": &objects.UserFunction{Name: "read_file", Value: osReadFile}, // readfile(name) => array(byte)/error +} + +func osReadFile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + bytes, err := ioutil.ReadFile(fname) + if err != nil { + return wrapError(err), nil + } + + if len(bytes) > tengo.MaxBytesLen { + return nil, objects.ErrBytesLimit + } + + return &objects.Bytes{Value: bytes}, nil +} + +func osStat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + stat, err := os.Stat(fname) + if err != nil { + return wrapError(err), nil + } + + fstat := &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "name": &objects.String{Value: stat.Name()}, + "mtime": &objects.Time{Value: stat.ModTime()}, + "size": &objects.Int{Value: stat.Size()}, + "mode": &objects.Int{Value: int64(stat.Mode())}, + }, + } + + if stat.IsDir() { + fstat.Value["directory"] = objects.TrueValue + } else { + fstat.Value["directory"] = objects.FalseValue + } + + return fstat, nil +} + +func osCreate(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Create(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpen(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Open(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpenFile(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + res, err := os.OpenFile(s1, i2, os.FileMode(i3)) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osArgs(args ...objects.Object) (objects.Object, error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range os.Args { + if len(osArg) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil +} + +func osFuncASFmRE(name string, fn func(string, os.FileMode) error) *objects.UserFunction { + return &objects.UserFunction{ + Name: name, + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, os.FileMode(i2))), nil + }, + } +} + +func osLookupEnv(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, ok := os.LookupEnv(s1) + if !ok { + return objects.FalseValue, nil + } + + if len(res) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: res}, nil +} + +func osExpandEnv(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var vlen int + var failed bool + s := os.Expand(s1, func(k string) string { + if failed { + return "" + } + + v := os.Getenv(k) + + // this does not count the other texts that are not being replaced + // but the code checks the final length at the end + vlen += len(v) + if vlen > tengo.MaxStringLen { + failed = true + return "" + } + + return v + }) + + if failed || len(s) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil +} + +func osExec(args ...objects.Object) (objects.Object, error) { + if len(args) == 0 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var execArgs []string + for idx, arg := range args[1:] { + execArg, ok := objects.ToString(arg) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("args[%d]", idx), + Expected: "string(compatible)", + Found: args[1+idx].TypeName(), + } + } + + execArgs = append(execArgs, execArg) + } + + return makeOSExecCommand(exec.Command(name, execArgs...)), nil +} + +func osFindProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + proc, err := os.FindProcess(i1) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func osStartProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 4 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var argv []string + var err error + switch arg1 := args[1].(type) { + case *objects.Array: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "array", + Found: arg1.TypeName(), + } + } + + dir, ok := objects.ToString(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + var env []string + switch arg3 := args[3].(type) { + case *objects.Array: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "array", + Found: arg3.TypeName(), + } + } + + proc, err := os.StartProcess(name, argv, &os.ProcAttr{ + Dir: dir, + Env: env, + }) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func stringArray(arr []objects.Object, argName string) ([]string, error) { + var sarr []string + for idx, elem := range arr { + str, ok := elem.(*objects.String) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("%s[%d]", argName, idx), + Expected: "string", + Found: elem.TypeName(), + } + } + + sarr = append(sarr, str.Value) + } + + return sarr, nil +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_exec.go b/vendor/github.com/d5/tengo/stdlib/os_exec.go new file mode 100644 index 00000000..5274c36a --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_exec.go @@ -0,0 +1,113 @@ +package stdlib + +import ( + "os/exec" + + "github.com/d5/tengo/objects" +) + +func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // combined_output() => bytes/error + "combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, // + // output() => bytes/error + "output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, // + // run() => error + "run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, // + // start() => error + "start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, // + // wait() => error + "wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, // + // set_path(path string) + "set_path": &objects.UserFunction{ + Name: "set_path", + Value: func(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.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + cmd.Path = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_dir(dir string) + "set_dir": &objects.UserFunction{ + Name: "set_dir", + Value: func(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.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + cmd.Dir = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_env(env array(string)) + "set_env": &objects.UserFunction{ + Name: "set_env", + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + var env []string + var err error + switch arg0 := args[0].(type) { + case *objects.Array: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg0.TypeName(), + } + } + + cmd.Env = env + + return objects.UndefinedValue, nil + }, + }, + // process() => imap(process) + "process": &objects.UserFunction{ + Name: "process", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return makeOSProcess(cmd.Process), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_file.go b/vendor/github.com/d5/tengo/stdlib/os_file.go new file mode 100644 index 00000000..ee9f625a --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_file.go @@ -0,0 +1,96 @@ +package stdlib + +import ( + "os" + + "github.com/d5/tengo/objects" +) + +func makeOSFile(file *os.File) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // chdir() => true/error + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, // + // chown(uid int, gid int) => true/error + "chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, // + // close() => error + "close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, // + // name() => string + "name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, // + // readdirnames(n int) => array(string)/error + "readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, // + // sync() => error + "sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, // + // write(bytes) => int/error + "write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, // + // write(string) => int/error + "write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, // + // read(bytes) => int/error + "read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, // + // chmod(mode int) => error + "chmod": &objects.UserFunction{ + Name: "chmod", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(file.Chmod(os.FileMode(i1))), nil + }, + }, + // seek(offset int, whence int) => int/error + "seek": &objects.UserFunction{ + Name: "seek", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + res, err := file.Seek(i1, i2) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: res}, nil + }, + }, + // stat() => imap(fileinfo)/error + "stat": &objects.UserFunction{ + Name: "start", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return osStat(&objects.String{Value: file.Name()}) + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_process.go b/vendor/github.com/d5/tengo/stdlib/os_process.go new file mode 100644 index 00000000..801ccdef --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_process.go @@ -0,0 +1,62 @@ +package stdlib + +import ( + "os" + "syscall" + + "github.com/d5/tengo/objects" +) + +func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, // + "pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, // + "string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, // + "success": &objects.UserFunction{Name: "success", Value: FuncARB(state.Success)}, // + }, + } +} + +func makeOSProcess(proc *os.Process) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, // + "release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // + "signal": &objects.UserFunction{ + Name: "signal", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(proc.Signal(syscall.Signal(i1))), nil + }, + }, + "wait": &objects.UserFunction{ + Name: "wait", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + state, err := proc.Wait() + if err != nil { + return wrapError(err), nil + } + + return makeOSProcessState(state), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/rand.go b/vendor/github.com/d5/tengo/stdlib/rand.go new file mode 100644 index 00000000..6efe1de8 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/rand.go @@ -0,0 +1,102 @@ +package stdlib + +import ( + "math/rand" + + "github.com/d5/tengo/objects" +) + +var randModule = map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(rand.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(rand.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(rand.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(rand.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(rand.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, + "read": &objects.UserFunction{ + Name: "read", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := rand.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + "rand": &objects.UserFunction{ + Name: "rand", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + src := rand.NewSource(i1) + + return randRand(rand.New(src)), nil + }, + }, +} + +func randRand(r *rand.Rand) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(r.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(r.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(r.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(r.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(r.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, + "read": &objects.UserFunction{ + Name: "read", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := r.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/source_modules.go b/vendor/github.com/d5/tengo/stdlib/source_modules.go new file mode 100644 index 00000000..ca69d7d1 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/source_modules.go @@ -0,0 +1,8 @@ +// Code generated using gensrcmods.go; DO NOT EDIT. + +package stdlib + +// SourceModules are source type standard library modules. +var SourceModules = map[string]string{ + "enum": "is_enumerable := func(x) {\n return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x)\n}\n\nis_array_like := func(x) {\n return is_array(x) || is_immutable_array(x)\n}\n\nexport {\n // all returns true if the given function `fn` evaluates to a truthy value on\n // all of the items in `x`. It returns undefined if `x` is not enumerable.\n all: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if !fn(k, v) { return false }\n }\n\n return true\n },\n // any returns true if the given function `fn` evaluates to a truthy value on\n // any of the items in `x`. It returns undefined if `x` is not enumerable.\n any: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return true }\n }\n\n return false\n },\n // chunk returns an array of elements split into groups the length of size.\n // If `x` can't be split evenly, the final chunk will be the remaining elements.\n // It returns undefined if `x` is not array.\n chunk: func(x, size) {\n if !is_array_like(x) || !size { return undefined }\n\n numElements := len(x)\n if !numElements { return [] }\n\n res := []\n idx := 0\n for idx < numElements {\n res = append(res, x[idx:idx+size])\n idx += size\n }\n\n return res\n },\n // at returns an element at the given index (if `x` is array) or\n // key (if `x` is map). It returns undefined if `x` is not enumerable.\n at: func(x, key) {\n if !is_enumerable(x) { return undefined }\n\n if is_array_like(x) {\n if !is_int(key) { return undefined }\n } else {\n if !is_string(key) { return undefined }\n }\n\n return x[key]\n },\n // each iterates over elements of `x` and invokes `fn` for each element. `fn` is\n // invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It does not iterate\n // and returns undefined if `x` is not enumerable.\n each: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n fn(k, v)\n }\n },\n // filter iterates over elements of `x`, returning an array of all elements `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n filter: func(x, fn) {\n if !is_array_like(x) { return undefined }\n\n dst := []\n for k, v in x {\n if fn(k, v) { dst = append(dst, v) }\n }\n\n return dst\n },\n // find iterates over elements of `x`, returning value of the first element `fn`\n // returns truthy for. `fn` is invoked with two arguments: `key` and `value`.\n // `key` is an int index if `x` is array. `key` is a string key if `x` is map.\n // It returns undefined if `x` is not enumerable.\n find: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return v }\n }\n },\n // find_key iterates over elements of `x`, returning key or index of the first\n // element `fn` returns truthy for. `fn` is invoked with two arguments: `key`\n // and `value`. `key` is an int index if `x` is array. `key` is a string key if\n // `x` is map. It returns undefined if `x` is not enumerable.\n find_key: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n for k, v in x {\n if fn(k, v) { return k }\n }\n },\n // map creates an array of values by running each element in `x` through `fn`.\n // `fn` is invoked with two arguments: `key` and `value`. `key` is an int index\n // if `x` is array. `key` is a string key if `x` is map. It returns undefined\n // if `x` is not enumerable.\n map: func(x, fn) {\n if !is_enumerable(x) { return undefined }\n\n dst := []\n for k, v in x {\n dst = append(dst, fn(k, v))\n }\n\n return dst\n },\n // key returns the first argument.\n key: func(k, _) { return k },\n // value returns the second argument.\n value: func(_, v) { return v }\n}\n", +} diff --git a/vendor/github.com/d5/tengo/stdlib/srcmod_enum.tengo b/vendor/github.com/d5/tengo/stdlib/srcmod_enum.tengo new file mode 100644 index 00000000..7a5ea637 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/srcmod_enum.tengo @@ -0,0 +1,128 @@ +is_enumerable := func(x) { + return is_array(x) || is_map(x) || is_immutable_array(x) || is_immutable_map(x) +} + +is_array_like := func(x) { + return is_array(x) || is_immutable_array(x) +} + +export { + // all returns true if the given function `fn` evaluates to a truthy value on + // all of the items in `x`. It returns undefined if `x` is not enumerable. + all: func(x, fn) { + if !is_enumerable(x) { return undefined } + + for k, v in x { + if !fn(k, v) { return false } + } + + return true + }, + // any returns true if the given function `fn` evaluates to a truthy value on + // any of the items in `x`. It returns undefined if `x` is not enumerable. + any: func(x, fn) { + if !is_enumerable(x) { return undefined } + + for k, v in x { + if fn(k, v) { return true } + } + + return false + }, + // chunk returns an array of elements split into groups the length of size. + // If `x` can't be split evenly, the final chunk will be the remaining elements. + // It returns undefined if `x` is not array. + chunk: func(x, size) { + if !is_array_like(x) || !size { return undefined } + + numElements := len(x) + if !numElements { return [] } + + res := [] + idx := 0 + for idx < numElements { + res = append(res, x[idx:idx+size]) + idx += size + } + + return res + }, + // at returns an element at the given index (if `x` is array) or + // key (if `x` is map). It returns undefined if `x` is not enumerable. + at: func(x, key) { + if !is_enumerable(x) { return undefined } + + if is_array_like(x) { + if !is_int(key) { return undefined } + } else { + if !is_string(key) { return undefined } + } + + return x[key] + }, + // each iterates over elements of `x` and invokes `fn` for each element. `fn` is + // invoked with two arguments: `key` and `value`. `key` is an int index + // if `x` is array. `key` is a string key if `x` is map. It does not iterate + // and returns undefined if `x` is not enumerable. + each: func(x, fn) { + if !is_enumerable(x) { return undefined } + + for k, v in x { + fn(k, v) + } + }, + // filter iterates over elements of `x`, returning an array of all elements `fn` + // returns truthy for. `fn` is invoked with two arguments: `key` and `value`. + // `key` is an int index if `x` is array. `key` is a string key if `x` is map. + // It returns undefined if `x` is not enumerable. + filter: func(x, fn) { + if !is_array_like(x) { return undefined } + + dst := [] + for k, v in x { + if fn(k, v) { dst = append(dst, v) } + } + + return dst + }, + // find iterates over elements of `x`, returning value of the first element `fn` + // returns truthy for. `fn` is invoked with two arguments: `key` and `value`. + // `key` is an int index if `x` is array. `key` is a string key if `x` is map. + // It returns undefined if `x` is not enumerable. + find: func(x, fn) { + if !is_enumerable(x) { return undefined } + + for k, v in x { + if fn(k, v) { return v } + } + }, + // find_key iterates over elements of `x`, returning key or index of the first + // element `fn` returns truthy for. `fn` is invoked with two arguments: `key` + // and `value`. `key` is an int index if `x` is array. `key` is a string key if + // `x` is map. It returns undefined if `x` is not enumerable. + find_key: func(x, fn) { + if !is_enumerable(x) { return undefined } + + for k, v in x { + if fn(k, v) { return k } + } + }, + // map creates an array of values by running each element in `x` through `fn`. + // `fn` is invoked with two arguments: `key` and `value`. `key` is an int index + // if `x` is array. `key` is a string key if `x` is map. It returns undefined + // if `x` is not enumerable. + map: func(x, fn) { + if !is_enumerable(x) { return undefined } + + dst := [] + for k, v in x { + dst = append(dst, fn(k, v)) + } + + return dst + }, + // key returns the first argument. + key: func(k, _) { return k }, + // value returns the second argument. + value: func(_, v) { return v } +} diff --git a/vendor/github.com/d5/tengo/stdlib/stdlib.go b/vendor/github.com/d5/tengo/stdlib/stdlib.go new file mode 100644 index 00000000..aad220ee --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/stdlib.go @@ -0,0 +1,34 @@ +package stdlib + +//go:generate go run gensrcmods.go + +import "github.com/d5/tengo/objects" + +// AllModuleNames returns a list of all default module names. +func AllModuleNames() []string { + var names []string + for name := range BuiltinModules { + names = append(names, name) + } + for name := range SourceModules { + names = append(names, name) + } + return names +} + +// GetModuleMap returns the module map that includes all modules +// for the given module names. +func GetModuleMap(names ...string) *objects.ModuleMap { + modules := objects.NewModuleMap() + + for _, name := range names { + if mod := BuiltinModules[name]; mod != nil { + modules.AddBuiltinModule(name, mod) + } + if mod := SourceModules[name]; mod != "" { + modules.AddSourceModule(name, []byte(mod)) + } + } + + return modules +} diff --git a/vendor/github.com/d5/tengo/stdlib/text.go b/vendor/github.com/d5/tengo/stdlib/text.go new file mode 100644 index 00000000..9f9770b8 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/text.go @@ -0,0 +1,737 @@ +package stdlib + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "unicode/utf8" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +var textModule = map[string]objects.Object{ + "re_match": &objects.UserFunction{Name: "re_match", Value: textREMatch}, // re_match(pattern, text) => bool/error + "re_find": &objects.UserFunction{Name: "re_find", Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined + "re_replace": &objects.UserFunction{Name: "re_replace", Value: textREReplace}, // re_replace(pattern, text, repl) => string/error + "re_split": &objects.UserFunction{Name: "re_split", Value: textRESplit}, // re_split(pattern, text, count) => [string]/error + "re_compile": &objects.UserFunction{Name: "re_compile", Value: textRECompile}, // re_compile(pattern) => Regexp/error + "compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int + "contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool + "contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool + "count": &objects.UserFunction{Name: "count", Value: FuncASSRI(strings.Count)}, // count(s, substr) => int + "equal_fold": &objects.UserFunction{Name: "equal_fold", Value: FuncASSRB(strings.EqualFold)}, // "equal_fold(s, t) => bool + "fields": &objects.UserFunction{Name: "fields", Value: FuncASRSs(strings.Fields)}, // fields(s) => [string] + "has_prefix": &objects.UserFunction{Name: "has_prefix", Value: FuncASSRB(strings.HasPrefix)}, // has_prefix(s, prefix) => bool + "has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool + "index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int + "index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int + "join": &objects.UserFunction{Name: "join", Value: textJoin}, // join(arr, sep) => string + "last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int + "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int + "repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string + "replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string + "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] + "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] + "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] + "split_n": &objects.UserFunction{Name: "split_n", Value: FuncASSIRSs(strings.SplitN)}, // split_n(s, sep, n) => [string] + "title": &objects.UserFunction{Name: "title", Value: FuncASRS(strings.Title)}, // title(s) => string + "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string + "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string + "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string + "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string + "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string + "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string + "trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string + "trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string + "atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error + "format_bool": &objects.UserFunction{Name: "format_bool", Value: textFormatBool}, // format_bool(b) => string + "format_float": &objects.UserFunction{Name: "format_float", Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string + "format_int": &objects.UserFunction{Name: "format_int", Value: textFormatInt}, // format_int(i, base) => string + "itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string + "parse_bool": &objects.UserFunction{Name: "parse_bool", Value: textParseBool}, // parse_bool(str) => bool/error + "parse_float": &objects.UserFunction{Name: "parse_float", Value: textParseFloat}, // parse_float(str, bits) => float/error + "parse_int": &objects.UserFunction{Name: "parse_int", Value: textParseInt}, // parse_int(str, base, bits) => int/error + "quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string + "unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error +} + +func textREMatch(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + matched, err := regexp.MatchString(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + if matched { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textREFind(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + if numArgs < 3 { + m := re.FindStringSubmatchIndex(s2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s2, i3) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return +} + +func textREReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + s, ok := doTextRegexpReplace(re, s2, s3) + if !ok { + return nil, objects.ErrStringLimit + } + + ret = &objects.String{Value: s} + } + + return +} + +func textRESplit(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + var i3 = -1 + if numArgs > 2 { + i3, ok = objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + arr := &objects.Array{} + for _, s := range re.Split(s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return +} + +func textRECompile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = makeTextRegexp(re) + } + + return +} + +func textReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + s, ok := doTextReplace(s1, s2, s3, i4) + if !ok { + err = objects.ErrStringLimit + return + } + + ret = &objects.String{Value: s} + + return +} + +func textRepeat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + if len(s1)*i2 > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: strings.Repeat(s1, i2)}, nil +} + +func textJoin(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + var slen int + var ss1 []string + switch arg0 := args[0].(type) { + case *objects.Array: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + slen += len(as) + ss1 = append(ss1, as) + } + case *objects.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + slen += len(as) + ss1 = append(ss1, as) + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + // make sure output length does not exceed the limit + if slen+len(s2)*(len(ss1)-1) > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: strings.Join(ss1, s2)}, nil +} + +func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + b1, ok := args[0].(*objects.Bool) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bool", + Found: args[0].TypeName(), + } + return + } + + if b1 == objects.TrueValue { + ret = &objects.String{Value: "true"} + } else { + ret = &objects.String{Value: "false"} + } + + return +} + +func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + f1, ok := args[0].(*objects.Float) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + + return +} + +func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := args[0].(*objects.Int) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)} + + return +} + +func textParseBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + parsed, err := strconv.ParseBool(s1.Value) + if err != nil { + ret = wrapError(err) + return + } + + if parsed { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + parsed, err := strconv.ParseFloat(s1.Value, i2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Float{Value: parsed} + + return +} + +func textParseInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + parsed, err := strconv.ParseInt(s1.Value, i2, i3) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Int{Value: parsed} + + return +} + +// Modified implementation of strings.Replace +// to limit the maximum length of output string. +func doTextReplace(s, old, new string, n int) (string, bool) { + if old == new || n == 0 { + return s, true // avoid allocation + } + + // Compute number of replacements. + if m := strings.Count(s, old); m == 0 { + return s, true // avoid allocation + } else if n < 0 || m < n { + n = m + } + + // Apply replacements to buffer. + t := make([]byte, len(s)+n*(len(new)-len(old))) + w := 0 + start := 0 + for i := 0; i < n; i++ { + j := start + if len(old) == 0 { + if i > 0 { + _, wid := utf8.DecodeRuneInString(s[start:]) + j += wid + } + } else { + j += strings.Index(s[start:], old) + } + + ssj := s[start:j] + if w+len(ssj)+len(new) > tengo.MaxStringLen { + return "", false + } + + w += copy(t[w:], ssj) + w += copy(t[w:], new) + start = j + len(old) + } + + ss := s[start:] + if w+len(ss) > tengo.MaxStringLen { + return "", false + } + + w += copy(t[w:], ss) + + return string(t[0:w]), true +} diff --git a/vendor/github.com/d5/tengo/stdlib/text_regexp.go b/vendor/github.com/d5/tengo/stdlib/text_regexp.go new file mode 100644 index 00000000..16f135bf --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/text_regexp.go @@ -0,0 +1,228 @@ +package stdlib + +import ( + "regexp" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // match(text) => bool + "match": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if re.MatchString(s1) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return + }, + }, + + // find(text) => array(array({text:,begin:,end:}))/undefined + // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined + "find": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if numArgs == 1 { + m := re.FindStringSubmatchIndex(s1) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s1, i2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return + }, + }, + + // replace(src, repl) => string + "replace": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s, ok := doTextRegexpReplace(re, s1, s2) + if !ok { + return nil, objects.ErrStringLimit + } + + ret = &objects.String{Value: s} + + return + }, + }, + + // split(text) => array(string) + // split(text, maxCount) => array(string) + "split": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + var i2 = -1 + if numArgs > 1 { + i2, ok = objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + } + + arr := &objects.Array{} + for _, s := range re.Split(s1, i2) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return + }, + }, + }, + } +} + +// Size-limit checking implementation of regexp.ReplaceAllString. +func doTextRegexpReplace(re *regexp.Regexp, src, repl string) (string, bool) { + idx := 0 + out := "" + + for _, m := range re.FindAllStringSubmatchIndex(src, -1) { + var exp []byte + exp = re.ExpandString(exp, repl, src, m) + + if len(out)+m[0]-idx+len(exp) > tengo.MaxStringLen { + return "", false + } + + out += src[idx:m[0]] + string(exp) + idx = m[1] + } + if idx < len(src) { + if len(out)+len(src)-idx > tengo.MaxStringLen { + return "", false + } + + out += src[idx:] + } + + return string(out), true +} diff --git a/vendor/github.com/d5/tengo/stdlib/times.go b/vendor/github.com/d5/tengo/stdlib/times.go new file mode 100644 index 00000000..111c877a --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/times.go @@ -0,0 +1,989 @@ +package stdlib + +import ( + "time" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +var timesModule = map[string]objects.Object{ + "format_ansic": &objects.String{Value: time.ANSIC}, + "format_unix_date": &objects.String{Value: time.UnixDate}, + "format_ruby_date": &objects.String{Value: time.RubyDate}, + "format_rfc822": &objects.String{Value: time.RFC822}, + "format_rfc822z": &objects.String{Value: time.RFC822Z}, + "format_rfc850": &objects.String{Value: time.RFC850}, + "format_rfc1123": &objects.String{Value: time.RFC1123}, + "format_rfc1123z": &objects.String{Value: time.RFC1123Z}, + "format_rfc3339": &objects.String{Value: time.RFC3339}, + "format_rfc3339_nano": &objects.String{Value: time.RFC3339Nano}, + "format_kitchen": &objects.String{Value: time.Kitchen}, + "format_stamp": &objects.String{Value: time.Stamp}, + "format_stamp_milli": &objects.String{Value: time.StampMilli}, + "format_stamp_micro": &objects.String{Value: time.StampMicro}, + "format_stamp_nano": &objects.String{Value: time.StampNano}, + "nanosecond": &objects.Int{Value: int64(time.Nanosecond)}, + "microsecond": &objects.Int{Value: int64(time.Microsecond)}, + "millisecond": &objects.Int{Value: int64(time.Millisecond)}, + "second": &objects.Int{Value: int64(time.Second)}, + "minute": &objects.Int{Value: int64(time.Minute)}, + "hour": &objects.Int{Value: int64(time.Hour)}, + "january": &objects.Int{Value: int64(time.January)}, + "february": &objects.Int{Value: int64(time.February)}, + "march": &objects.Int{Value: int64(time.March)}, + "april": &objects.Int{Value: int64(time.April)}, + "may": &objects.Int{Value: int64(time.May)}, + "june": &objects.Int{Value: int64(time.June)}, + "july": &objects.Int{Value: int64(time.July)}, + "august": &objects.Int{Value: int64(time.August)}, + "september": &objects.Int{Value: int64(time.September)}, + "october": &objects.Int{Value: int64(time.October)}, + "november": &objects.Int{Value: int64(time.November)}, + "december": &objects.Int{Value: int64(time.December)}, + "sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int) + "parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int + "since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int + "until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int + "duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float + "duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float + "duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int + "duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float + "duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string + "month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string + "date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time + "now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time + "parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time + "unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time + "add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time + "add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time + "sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int + "after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool + "before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool + "time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int + "time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int + "time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int + "time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int + "time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int + "time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int + "time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int + "time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int + "time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int + "time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int + "time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string + "time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string + "time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string + "is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool + "to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time + "to_utc": &objects.UserFunction{Name: "to_utc", Value: timesToUTC}, // to_utc(time) => time +} + +func timesSleep(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + time.Sleep(time.Duration(i1)) + ret = objects.UndefinedValue + + return +} + +func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + dur, err := time.ParseDuration(s1) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Int{Value: int64(dur)} + + return +} + +func timesSince(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(time.Since(t1))} + + return +} + +func timesUntil(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(time.Until(t1))} + + return +} + +func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Hours()} + + return +} + +func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Minutes()} + + return +} + +func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: time.Duration(i1).Nanoseconds()} + + return +} + +func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Seconds()} + + return +} + +func timesDurationString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: time.Duration(i1).String()} + + return +} + +func timesMonthString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: time.Month(i1).String()} + + return +} + +func timesDate(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 7 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + i5, ok := objects.ToInt(args[4]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fifth", + Expected: "int(compatible)", + Found: args[4].TypeName(), + } + return + } + i6, ok := objects.ToInt(args[5]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "sixth", + Expected: "int(compatible)", + Found: args[5].TypeName(), + } + return + } + i7, ok := objects.ToInt(args[6]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "seventh", + Expected: "int(compatible)", + Found: args[6].TypeName(), + } + return + } + + ret = &objects.Time{Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location())} + + return +} + +func timesNow(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + err = objects.ErrWrongNumArguments + return + } + + ret = &objects.Time{Value: time.Now()} + + return +} + +func timesParse(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + parsed, err := time.Parse(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Time{Value: parsed} + + return +} + +func timesUnix(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Time{Value: time.Unix(i1, i2)} + + return +} + +func timesAdd(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.Add(time.Duration(i2))} + + return +} + +func timesSub(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Sub(t2))} + + return +} + +func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.AddDate(i2, i3, i4)} + + return +} + +func timesAfter(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + if t1.After(t2) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesBefore(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.Before(t2) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Year())} + + return +} + +func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Month())} + + return +} + +func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Day())} + + return +} + +func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Weekday())} + + return +} + +func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Hour())} + + return +} + +func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Minute())} + + return +} + +func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Second())} + + return +} + +func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Nanosecond())} + + return +} + +func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Unix())} + + return +} + +func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.UnixNano())} + + return +} + +func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s := t1.Format(s2) + if len(s) > tengo.MaxStringLen { + + return nil, objects.ErrStringLimit + } + + ret = &objects.String{Value: s} + + return +} + +func timesIsZero(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.IsZero() { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesToLocal(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.Local()} + + return +} + +func timesToUTC(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.UTC()} + + return +} + +func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: t1.Location().String()} + + return +} + +func timesTimeString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: t1.String()} + + return +} |