From 9d84d6dd643c4017074e81465671cd9b25f9539a Mon Sep 17 00:00:00 2001 From: Wim Date: Thu, 9 Jan 2020 21:52:19 +0100 Subject: Update to tengo v2 (#976) --- vendor/github.com/d5/tengo/v2/.gitignore | 1 + vendor/github.com/d5/tengo/v2/.goreleaser.yml | 20 + vendor/github.com/d5/tengo/v2/LICENSE | 21 + vendor/github.com/d5/tengo/v2/Makefile | 11 + vendor/github.com/d5/tengo/v2/README.md | 135 ++ vendor/github.com/d5/tengo/v2/builtins.go | 502 +++++++ vendor/github.com/d5/tengo/v2/bytecode.go | 292 ++++ vendor/github.com/d5/tengo/v2/compiler.go | 1312 ++++++++++++++++ vendor/github.com/d5/tengo/v2/doc.go | 3 + vendor/github.com/d5/tengo/v2/errors.go | 64 + vendor/github.com/d5/tengo/v2/formatter.go | 1245 +++++++++++++++ vendor/github.com/d5/tengo/v2/go.mod | 3 + vendor/github.com/d5/tengo/v2/go.sum | 0 vendor/github.com/d5/tengo/v2/instructions.go | 61 + vendor/github.com/d5/tengo/v2/iterator.go | 209 +++ vendor/github.com/d5/tengo/v2/modules.go | 93 ++ vendor/github.com/d5/tengo/v2/objects.go | 1581 ++++++++++++++++++++ vendor/github.com/d5/tengo/v2/parser/ast.go | 69 + vendor/github.com/d5/tengo/v2/parser/expr.go | 597 ++++++++ vendor/github.com/d5/tengo/v2/parser/file.go | 29 + vendor/github.com/d5/tengo/v2/parser/opcodes.go | 156 ++ vendor/github.com/d5/tengo/v2/parser/parser.go | 1196 +++++++++++++++ vendor/github.com/d5/tengo/v2/parser/pos.go | 12 + vendor/github.com/d5/tengo/v2/parser/scanner.go | 689 +++++++++ .../github.com/d5/tengo/v2/parser/source_file.go | 231 +++ vendor/github.com/d5/tengo/v2/parser/stmt.go | 349 +++++ vendor/github.com/d5/tengo/v2/script.go | 313 ++++ vendor/github.com/d5/tengo/v2/stdlib/base64.go | 34 + .../d5/tengo/v2/stdlib/builtin_modules.go | 18 + vendor/github.com/d5/tengo/v2/stdlib/errors.go | 12 + vendor/github.com/d5/tengo/v2/stdlib/fmt.go | 101 ++ .../github.com/d5/tengo/v2/stdlib/func_typedefs.go | 1049 +++++++++++++ vendor/github.com/d5/tengo/v2/stdlib/hex.go | 12 + vendor/github.com/d5/tengo/v2/stdlib/json.go | 146 ++ .../github.com/d5/tengo/v2/stdlib/json/decode.go | 358 +++++ .../github.com/d5/tengo/v2/stdlib/json/encode.go | 146 ++ .../github.com/d5/tengo/v2/stdlib/json/scanner.go | 562 +++++++ vendor/github.com/d5/tengo/v2/stdlib/math.go | 233 +++ vendor/github.com/d5/tengo/v2/stdlib/os.go | 564 +++++++ vendor/github.com/d5/tengo/v2/stdlib/os_exec.go | 119 ++ vendor/github.com/d5/tengo/v2/stdlib/os_file.go | 117 ++ vendor/github.com/d5/tengo/v2/stdlib/os_process.go | 76 + vendor/github.com/d5/tengo/v2/stdlib/rand.go | 138 ++ .../d5/tengo/v2/stdlib/source_modules.go | 8 + .../d5/tengo/v2/stdlib/srcmod_enum.tengo | 128 ++ vendor/github.com/d5/tengo/v2/stdlib/stdlib.go | 34 + vendor/github.com/d5/tengo/v2/stdlib/text.go | 1072 +++++++++++++ .../github.com/d5/tengo/v2/stdlib/text_regexp.go | 251 ++++ vendor/github.com/d5/tengo/v2/stdlib/times.go | 1135 ++++++++++++++ vendor/github.com/d5/tengo/v2/symbol_table.go | 165 ++ vendor/github.com/d5/tengo/v2/tengo.go | 306 ++++ vendor/github.com/d5/tengo/v2/token/token.go | 225 +++ vendor/github.com/d5/tengo/v2/variable.go | 136 ++ vendor/github.com/d5/tengo/v2/vm.go | 883 +++++++++++ 54 files changed, 17222 insertions(+) create mode 100644 vendor/github.com/d5/tengo/v2/.gitignore create mode 100644 vendor/github.com/d5/tengo/v2/.goreleaser.yml create mode 100644 vendor/github.com/d5/tengo/v2/LICENSE create mode 100644 vendor/github.com/d5/tengo/v2/Makefile create mode 100644 vendor/github.com/d5/tengo/v2/README.md create mode 100644 vendor/github.com/d5/tengo/v2/builtins.go create mode 100644 vendor/github.com/d5/tengo/v2/bytecode.go create mode 100644 vendor/github.com/d5/tengo/v2/compiler.go create mode 100644 vendor/github.com/d5/tengo/v2/doc.go create mode 100644 vendor/github.com/d5/tengo/v2/errors.go create mode 100644 vendor/github.com/d5/tengo/v2/formatter.go create mode 100644 vendor/github.com/d5/tengo/v2/go.mod create mode 100644 vendor/github.com/d5/tengo/v2/go.sum create mode 100644 vendor/github.com/d5/tengo/v2/instructions.go create mode 100644 vendor/github.com/d5/tengo/v2/iterator.go create mode 100644 vendor/github.com/d5/tengo/v2/modules.go create mode 100644 vendor/github.com/d5/tengo/v2/objects.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/ast.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/expr.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/file.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/opcodes.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/parser.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/pos.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/scanner.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/source_file.go create mode 100644 vendor/github.com/d5/tengo/v2/parser/stmt.go create mode 100644 vendor/github.com/d5/tengo/v2/script.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/base64.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/builtin_modules.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/errors.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/fmt.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/func_typedefs.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/hex.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/json.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/json/decode.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/json/encode.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/json/scanner.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/math.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/os.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/os_exec.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/os_file.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/os_process.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/rand.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/source_modules.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/srcmod_enum.tengo create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/stdlib.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/text.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/text_regexp.go create mode 100644 vendor/github.com/d5/tengo/v2/stdlib/times.go create mode 100644 vendor/github.com/d5/tengo/v2/symbol_table.go create mode 100644 vendor/github.com/d5/tengo/v2/tengo.go create mode 100644 vendor/github.com/d5/tengo/v2/token/token.go create mode 100644 vendor/github.com/d5/tengo/v2/variable.go create mode 100644 vendor/github.com/d5/tengo/v2/vm.go (limited to 'vendor/github.com/d5/tengo/v2') diff --git a/vendor/github.com/d5/tengo/v2/.gitignore b/vendor/github.com/d5/tengo/v2/.gitignore new file mode 100644 index 00000000..77738287 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/.gitignore @@ -0,0 +1 @@ +dist/ \ No newline at end of file diff --git a/vendor/github.com/d5/tengo/v2/.goreleaser.yml b/vendor/github.com/d5/tengo/v2/.goreleaser.yml new file mode 100644 index 00000000..1bd14324 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/.goreleaser.yml @@ -0,0 +1,20 @@ +env: + - GO111MODULE=on +before: + hooks: + - go mod tidy +builds: + - env: + - CGO_ENABLED=0 + main: ./cmd/tengo/main.go + goos: + - darwin + - linux + - windows +archive: + files: + - none* +checksum: + name_template: 'checksums.txt' +changelog: + sort: asc diff --git a/vendor/github.com/d5/tengo/v2/LICENSE b/vendor/github.com/d5/tengo/v2/LICENSE new file mode 100644 index 00000000..2516341a --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Daniel Kang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/d5/tengo/v2/Makefile b/vendor/github.com/d5/tengo/v2/Makefile new file mode 100644 index 00000000..793bc129 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/Makefile @@ -0,0 +1,11 @@ +generate: + go generate ./... + +lint: + golint -set_exit_status ./... + +test: generate lint + go test -race -cover ./... + +fmt: + go fmt ./... diff --git a/vendor/github.com/d5/tengo/v2/README.md b/vendor/github.com/d5/tengo/v2/README.md new file mode 100644 index 00000000..92277cfe --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/README.md @@ -0,0 +1,135 @@ +

+ +

+ +# The Tengo Language + +[![GoDoc](https://godoc.org/github.com/d5/tengo?status.svg)](https://godoc.org/github.com/d5/tengo) +[![Go Report Card](https://goreportcard.com/badge/github.com/d5/tengo)](https://goreportcard.com/report/github.com/d5/tengo) +[![CircleCI](https://circleci.com/gh/d5/tengo.svg?style=svg)](https://circleci.com/gh/d5/tengo) +[![Sourcegraph](https://sourcegraph.com/github.com/d5/tengo/-/badge.svg)](https://sourcegraph.com/github.com/d5/tengo?badge) + +**Tengo is a small, dynamic, fast, secure script language for Go.** + +Tengo is **[fast](#benchmark)** and secure because it's compiled/executed as +bytecode on stack-based VM that's written in native Go. + +```golang +/* The Tengo Language */ +fmt := import("fmt") + +each := func(seq, fn) { + for x in seq { fn(x) } +} + +sum := func(init, seq) { + each(seq, func(x) { init += x }) + return init +} + +fmt.println(sum(0, [1, 2, 3])) // "6" +fmt.println(sum("", [1, 2, 3])) // "123" +``` + +> Test this Tengo code in the +> [Tengo Playground](https://tengolang.com/?s=0c8d5d0d88f2795a7093d7f35ae12c3afa17bea3) + +## Features + +- Simple and highly readable + [Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) + - Dynamic typing with type coercion + - Higher-order functions and closures + - Immutable values +- [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), + data pipeline, [transpiler](https://github.com/d5/tengo2lua) + +## Benchmark + +| | fib(35) | fibt(35) | Type | +| :--- | ---: | ---: | :---: | +| 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)_ +_* **Go** does not read the source code from file, while all other cases do_ +_* See [here](https://github.com/d5/tengobench) for commands/codes used_ + +## Quick Start + +A simple Go example code that compiles/runs Tengo script code with some input/output values: + +```golang +package main + +import ( + "context" + "fmt" + + "github.com/d5/tengo/v2" +) + +func main() { + // Tengo script code + src := ` +each := func(seq, fn) { + for x in seq { fn(x) } +} + +sum := 0 +mul := 1 +each([a, b, c, d], func(x) { + sum += x + mul *= x +})` + + // create a new Script instance + script := tengo.NewScript([]byte(src)) + + // set values + _ = script.Add("a", 1) + _ = script.Add("b", 9) + _ = script.Add("c", 8) + _ = script.Add("d", 4) + + // run the script + compiled, err := script.RunContext(context.Background()) + if err != nil { + panic(err) + } + + // retrieve values + sum := compiled.Get("sum") + mul := compiled.Get("mul") + fmt.Println(sum, mul) // "22 288" +} +``` + +## References + +- [Language Syntax](https://github.com/d5/tengo/blob/master/docs/tutorial.md) +- [Object Types](https://github.com/d5/tengo/blob/master/docs/objects.md) +- [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) + and [Operators](https://github.com/d5/tengo/blob/master/docs/operators.md) +- [Builtin Functions](https://github.com/d5/tengo/blob/master/docs/builtins.md) +- [Interoperability](https://github.com/d5/tengo/blob/master/docs/interoperability.md) +- [Tengo CLI](https://github.com/d5/tengo/blob/master/docs/tengo-cli.md) +- [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) diff --git a/vendor/github.com/d5/tengo/v2/builtins.go b/vendor/github.com/d5/tengo/v2/builtins.go new file mode 100644 index 00000000..87f7fc3d --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/builtins.go @@ -0,0 +1,502 @@ +package tengo + +var builtinFuncs = []*BuiltinFunction{ + { + Name: "len", + Value: builtinLen, + }, + { + Name: "copy", + Value: builtinCopy, + }, + { + Name: "append", + Value: builtinAppend, + }, + { + Name: "string", + Value: builtinString, + }, + { + Name: "int", + Value: builtinInt, + }, + { + Name: "bool", + Value: builtinBool, + }, + { + Name: "float", + Value: builtinFloat, + }, + { + Name: "char", + Value: builtinChar, + }, + { + Name: "bytes", + Value: builtinBytes, + }, + { + Name: "time", + Value: builtinTime, + }, + { + Name: "is_int", + Value: builtinIsInt, + }, + { + Name: "is_float", + Value: builtinIsFloat, + }, + { + Name: "is_string", + Value: builtinIsString, + }, + { + Name: "is_bool", + Value: builtinIsBool, + }, + { + Name: "is_char", + Value: builtinIsChar, + }, + { + Name: "is_bytes", + Value: builtinIsBytes, + }, + { + Name: "is_array", + Value: builtinIsArray, + }, + { + Name: "is_immutable_array", + Value: builtinIsImmutableArray, + }, + { + Name: "is_map", + Value: builtinIsMap, + }, + { + Name: "is_immutable_map", + Value: builtinIsImmutableMap, + }, + { + Name: "is_iterable", + Value: builtinIsIterable, + }, + { + Name: "is_time", + Value: builtinIsTime, + }, + { + Name: "is_error", + Value: builtinIsError, + }, + { + Name: "is_undefined", + Value: builtinIsUndefined, + }, + { + Name: "is_function", + Value: builtinIsFunction, + }, + { + Name: "is_callable", + Value: builtinIsCallable, + }, + { + Name: "type_name", + Value: builtinTypeName, + }, + { + Name: "format", + Value: builtinFormat, + }, +} + +// GetAllBuiltinFunctions returns all builtin function objects. +func GetAllBuiltinFunctions() []*BuiltinFunction { + return append([]*BuiltinFunction{}, builtinFuncs...) +} + +func builtinTypeName(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + return &String{Value: args[0].TypeName()}, nil +} + +func builtinIsString(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*String); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsInt(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Int); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsFloat(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Float); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bool); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsChar(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Char); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsBytes(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bytes); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsArray(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Array); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsImmutableArray(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*ImmutableArray); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsMap(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Map); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsImmutableMap(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*ImmutableMap); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsTime(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Time); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsError(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Error); ok { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsUndefined(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0] == UndefinedValue { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsFunction(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + switch args[0].(type) { + case *CompiledFunction: + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsCallable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0].CanCall() { + return TrueValue, nil + } + return FalseValue, nil +} + +func builtinIsIterable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if args[0].CanIterate() { + return TrueValue, nil + } + return FalseValue, nil +} + +// len(obj object) => int +func builtinLen(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + switch arg := args[0].(type) { + case *Array: + return &Int{Value: int64(len(arg.Value))}, nil + case *ImmutableArray: + return &Int{Value: int64(len(arg.Value))}, nil + case *String: + return &Int{Value: int64(len(arg.Value))}, nil + case *Bytes: + return &Int{Value: int64(len(arg.Value))}, nil + case *Map: + return &Int{Value: int64(len(arg.Value))}, nil + case *ImmutableMap: + return &Int{Value: int64(len(arg.Value))}, nil + default: + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array/string/bytes/map", + Found: arg.TypeName(), + } + } +} + +func builtinFormat(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 { + // okay to return 'format' directly as String is immutable + return format, nil + } + s, err := Format(format.Value, args[1:]...) + if err != nil { + return nil, err + } + return &String{Value: s}, nil +} + +func builtinCopy(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + return args[0].Copy(), nil +} + +func builtinString(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*String); ok { + return args[0], nil + } + v, ok := ToString(args[0]) + if ok { + if len(v) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinInt(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Int); ok { + return args[0], nil + } + v, ok := ToInt64(args[0]) + if ok { + return &Int{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinFloat(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Float); ok { + return args[0], nil + } + v, ok := ToFloat64(args[0]) + if ok { + return &Float{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Bool); ok { + return args[0], nil + } + v, ok := ToBool(args[0]) + if ok { + if v { + return TrueValue, nil + } + return FalseValue, nil + } + return UndefinedValue, nil +} + +func builtinChar(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Char); ok { + return args[0], nil + } + v, ok := ToRune(args[0]) + if ok { + return &Char{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinBytes(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + + // bytes(N) => create a new bytes with given size N + if n, ok := args[0].(*Int); ok { + if n.Value > int64(MaxBytesLen) { + return nil, ErrBytesLimit + } + return &Bytes{Value: make([]byte, int(n.Value))}, nil + } + v, ok := ToByteSlice(args[0]) + if ok { + if len(v) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +func builtinTime(args ...Object) (Object, error) { + argsLen := len(args) + if !(argsLen == 1 || argsLen == 2) { + return nil, ErrWrongNumArguments + } + if _, ok := args[0].(*Time); ok { + return args[0], nil + } + v, ok := ToTime(args[0]) + if ok { + return &Time{Value: v}, nil + } + if argsLen == 2 { + return args[1], nil + } + return UndefinedValue, nil +} + +// append(arr, items...) +func builtinAppend(args ...Object) (Object, error) { + if len(args) < 2 { + return nil, ErrWrongNumArguments + } + switch arg := args[0].(type) { + case *Array: + return &Array{Value: append(arg.Value, args[1:]...)}, nil + case *ImmutableArray: + return &Array{Value: append(arg.Value, args[1:]...)}, nil + default: + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg.TypeName(), + } + } +} diff --git a/vendor/github.com/d5/tengo/v2/bytecode.go b/vendor/github.com/d5/tengo/v2/bytecode.go new file mode 100644 index 00000000..cfd0d0b5 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/bytecode.go @@ -0,0 +1,292 @@ +package tengo + +import ( + "encoding/gob" + "fmt" + "io" + "reflect" + + "github.com/d5/tengo/v2/parser" +) + +// Bytecode is a compiled instructions and constants. +type Bytecode struct { + FileSet *parser.SourceFileSet + MainFunction *CompiledFunction + Constants []Object +} + +// Encode writes Bytecode data to the writer. +func (b *Bytecode) Encode(w io.Writer) error { + enc := gob.NewEncoder(w) + if err := enc.Encode(b.FileSet); err != nil { + return err + } + if err := enc.Encode(b.MainFunction); err != nil { + return err + } + 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 += CountObjects(c) + } + return n +} + +// FormatInstructions returns human readable string representations of +// compiled instructions. +func (b *Bytecode) FormatInstructions() []string { + return FormatInstructions(b.MainFunction.Instructions, 0) +} + +// FormatConstants returns human readable string representations of +// compiled constants. +func (b *Bytecode) FormatConstants() (output []string) { + for cidx, cn := range b.Constants { + switch cn := cn.(type) { + case *CompiledFunction: + output = append(output, fmt.Sprintf( + "[% 3d] (Compiled Function|%p)", cidx, &cn)) + for _, l := range FormatInstructions(cn.Instructions, 0) { + output = append(output, fmt.Sprintf(" %s", l)) + } + default: + output = append(output, fmt.Sprintf("[% 3d] %s (%s|%p)", + cidx, cn, reflect.TypeOf(cn).Elem().Name(), &cn)) + } + } + return +} + +// Decode reads Bytecode data from the reader. +func (b *Bytecode) Decode(r io.Reader, modules *ModuleMap) error { + if modules == nil { + modules = 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 := fixDecodedObject(v, modules) + if err != nil { + return err + } + b.Constants[i] = fv + } + return nil +} + +// RemoveDuplicates finds and remove the duplicate values in Constants. +// Note this function mutates Bytecode. +func (b *Bytecode) RemoveDuplicates() { + var deduped []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 *CompiledFunction: + // add to deduped list + indexMap[curIdx] = len(deduped) + deduped = append(deduped, c) + case *ImmutableMap: + modName := inferModuleName(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 *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 *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 *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 *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 *CompiledFunction: + updateConstIndexes(c.Instructions, indexMap) + } + } +} + +func fixDecodedObject( + o Object, + modules *ModuleMap, +) (Object, error) { + switch o := o.(type) { + case *Bool: + if o.IsFalsy() { + return FalseValue, nil + } + return TrueValue, nil + case *Undefined: + return UndefinedValue, nil + case *Array: + for i, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *ImmutableArray: + for i, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[i] = fv + } + case *Map: + for k, v := range o.Value { + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + case *ImmutableMap: + modName := inferModuleName(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.(*UserFunction); isUserFunction { + return nil, fmt.Errorf("user function not decodable") + } + + fv, err := fixDecodedObject(v, modules) + if err != nil { + return nil, err + } + o.Value[k] = fv + } + } + return o, nil +} + +func updateConstIndexes(insts []byte, indexMap map[int]int) { + i := 0 + for i < len(insts) { + op := insts[i] + numOperands := parser.OpcodeOperands[op] + _, read := parser.ReadOperands(numOperands, insts[i+1:]) + + switch op { + case parser.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 parser.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 inferModuleName(mod *ImmutableMap) string { + if modName, ok := mod.Value["__module_name__"].(*String); ok { + return modName.Value + } + return "" +} + +func init() { + gob.Register(&parser.SourceFileSet{}) + gob.Register(&parser.SourceFile{}) + gob.Register(&Array{}) + gob.Register(&Bool{}) + gob.Register(&Bytes{}) + gob.Register(&Char{}) + gob.Register(&CompiledFunction{}) + gob.Register(&Error{}) + gob.Register(&Float{}) + gob.Register(&ImmutableArray{}) + gob.Register(&ImmutableMap{}) + gob.Register(&Int{}) + gob.Register(&Map{}) + gob.Register(&String{}) + gob.Register(&Time{}) + gob.Register(&Undefined{}) + gob.Register(&UserFunction{}) +} diff --git a/vendor/github.com/d5/tengo/v2/compiler.go b/vendor/github.com/d5/tengo/v2/compiler.go new file mode 100644 index 00000000..eb686ed6 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/compiler.go @@ -0,0 +1,1312 @@ +package tengo + +import ( + "fmt" + "io" + "io/ioutil" + "path/filepath" + "reflect" + "strings" + + "github.com/d5/tengo/v2/parser" + "github.com/d5/tengo/v2/token" +) + +// compilationScope represents a compiled instructions and the last two +// instructions that were emitted. +type compilationScope struct { + Instructions []byte + SymbolInit map[string]bool + SourceMap map[int]parser.Pos +} + +// loop represents a loop construct that the compiler uses to track the current +// loop. +type loop struct { + Continues []int + Breaks []int +} + +// CompilerError represents a compiler error. +type CompilerError struct { + FileSet *parser.SourceFileSet + Node parser.Node + Err error +} + +func (e *CompilerError) Error() string { + filePos := e.FileSet.Position(e.Node.Pos()) + return fmt.Sprintf("Compile Error: %s\n\tat %s", e.Err.Error(), filePos) +} + +// Compiler compiles the AST into a bytecode. +type Compiler struct { + file *parser.SourceFile + parent *Compiler + modulePath string + constants []Object + symbolTable *SymbolTable + scopes []compilationScope + scopeIndex int + modules *ModuleMap + compiledModules map[string]*CompiledFunction + allowFileImport bool + loops []*loop + loopIndex int + trace io.Writer + indent int +} + +// NewCompiler creates a Compiler. +func NewCompiler( + file *parser.SourceFile, + symbolTable *SymbolTable, + constants []Object, + modules *ModuleMap, + trace io.Writer, +) *Compiler { + mainScope := compilationScope{ + SymbolInit: make(map[string]bool), + SourceMap: make(map[int]parser.Pos), + } + + // symbol table + if symbolTable == nil { + symbolTable = NewSymbolTable() + } + + // add builtin functions to the symbol table + for idx, fn := range builtinFuncs { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + // builtin modules + if modules == nil { + modules = NewModuleMap() + } + + return &Compiler{ + file: file, + symbolTable: symbolTable, + constants: constants, + scopes: []compilationScope{mainScope}, + scopeIndex: 0, + loopIndex: -1, + trace: trace, + modules: modules, + compiledModules: make(map[string]*CompiledFunction), + } +} + +// Compile compiles the AST node. +func (c *Compiler) Compile(node parser.Node) error { + if c.trace != nil { + if node != nil { + defer untracec(tracec(c, fmt.Sprintf("%s (%s)", + node.String(), reflect.TypeOf(node).Elem().Name()))) + } else { + defer untracec(tracec(c, "")) + } + } + + switch node := node.(type) { + case *parser.File: + for _, stmt := range node.Stmts { + if err := c.Compile(stmt); err != nil { + return err + } + } + case *parser.ExprStmt: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, parser.OpPop) + case *parser.IncDecStmt: + op := token.AddAssign + if node.Token == token.Dec { + op = token.SubAssign + } + return c.compileAssign(node, []parser.Expr{node.Expr}, + []parser.Expr{&parser.IntLit{Value: 1}}, op) + case *parser.ParenExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + case *parser.BinaryExpr: + if node.Token == token.LAnd || node.Token == token.LOr { + return c.compileLogical(node) + } + if node.Token == token.Less { + if err := c.Compile(node.RHS); err != nil { + return err + } + if err := c.Compile(node.LHS); err != nil { + return err + } + c.emit(node, parser.OpBinaryOp, int(token.Greater)) + return nil + } else if node.Token == token.LessEq { + if err := c.Compile(node.RHS); err != nil { + return err + } + if err := c.Compile(node.LHS); err != nil { + return err + } + c.emit(node, parser.OpBinaryOp, int(token.GreaterEq)) + return nil + } + if err := c.Compile(node.LHS); err != nil { + return err + } + if err := c.Compile(node.RHS); err != nil { + return err + } + + switch node.Token { + case token.Add: + c.emit(node, parser.OpBinaryOp, int(token.Add)) + case token.Sub: + c.emit(node, parser.OpBinaryOp, int(token.Sub)) + case token.Mul: + c.emit(node, parser.OpBinaryOp, int(token.Mul)) + case token.Quo: + c.emit(node, parser.OpBinaryOp, int(token.Quo)) + case token.Rem: + c.emit(node, parser.OpBinaryOp, int(token.Rem)) + case token.Greater: + c.emit(node, parser.OpBinaryOp, int(token.Greater)) + case token.GreaterEq: + c.emit(node, parser.OpBinaryOp, int(token.GreaterEq)) + case token.Equal: + c.emit(node, parser.OpEqual) + case token.NotEqual: + c.emit(node, parser.OpNotEqual) + case token.And: + c.emit(node, parser.OpBinaryOp, int(token.And)) + case token.Or: + c.emit(node, parser.OpBinaryOp, int(token.Or)) + case token.Xor: + c.emit(node, parser.OpBinaryOp, int(token.Xor)) + case token.AndNot: + c.emit(node, parser.OpBinaryOp, int(token.AndNot)) + case token.Shl: + c.emit(node, parser.OpBinaryOp, int(token.Shl)) + case token.Shr: + c.emit(node, parser.OpBinaryOp, int(token.Shr)) + default: + return c.errorf(node, "invalid binary operator: %s", + node.Token.String()) + } + case *parser.IntLit: + c.emit(node, parser.OpConstant, + c.addConstant(&Int{Value: node.Value})) + case *parser.FloatLit: + c.emit(node, parser.OpConstant, + c.addConstant(&Float{Value: node.Value})) + case *parser.BoolLit: + if node.Value { + c.emit(node, parser.OpTrue) + } else { + c.emit(node, parser.OpFalse) + } + case *parser.StringLit: + if len(node.Value) > MaxStringLen { + return c.error(node, ErrStringLimit) + } + c.emit(node, parser.OpConstant, + c.addConstant(&String{Value: node.Value})) + case *parser.CharLit: + c.emit(node, parser.OpConstant, + c.addConstant(&Char{Value: node.Value})) + case *parser.UndefinedLit: + c.emit(node, parser.OpNull) + case *parser.UnaryExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + switch node.Token { + case token.Not: + c.emit(node, parser.OpLNot) + case token.Sub: + c.emit(node, parser.OpMinus) + case token.Xor: + c.emit(node, parser.OpBComplement) + case token.Add: + // do nothing? + default: + return c.errorf(node, + "invalid unary operator: %s", node.Token.String()) + } + case *parser.IfStmt: + // open new symbol table for the statement + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + if node.Init != nil { + if err := c.Compile(node.Init); err != nil { + return err + } + } + if err := c.Compile(node.Cond); err != nil { + return err + } + + // first jump placeholder + jumpPos1 := c.emit(node, parser.OpJumpFalsy, 0) + if err := c.Compile(node.Body); err != nil { + return err + } + if node.Else != nil { + // second jump placeholder + jumpPos2 := c.emit(node, parser.OpJump, 0) + + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + if err := c.Compile(node.Else); err != nil { + return err + } + + // update second jump offset + curPos = len(c.currentInstructions()) + c.changeOperand(jumpPos2, curPos) + } else { + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + } + case *parser.ForStmt: + return c.compileForStmt(node) + case *parser.ForInStmt: + return c.compileForInStmt(node) + case *parser.BranchStmt: + if node.Token == token.Break { + curLoop := c.currentLoop() + if curLoop == nil { + return c.errorf(node, "break not allowed outside loop") + } + pos := c.emit(node, parser.OpJump, 0) + curLoop.Breaks = append(curLoop.Breaks, pos) + } else if node.Token == token.Continue { + curLoop := c.currentLoop() + if curLoop == nil { + return c.errorf(node, "continue not allowed outside loop") + } + pos := c.emit(node, parser.OpJump, 0) + curLoop.Continues = append(curLoop.Continues, pos) + } else { + panic(fmt.Errorf("invalid branch statement: %s", + node.Token.String())) + } + case *parser.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 + } + } + case *parser.AssignStmt: + err := c.compileAssign(node, node.LHS, node.RHS, node.Token) + if err != nil { + return err + } + case *parser.Ident: + symbol, _, ok := c.symbolTable.Resolve(node.Name) + if !ok { + return c.errorf(node, "unresolved reference '%s'", node.Name) + } + + switch symbol.Scope { + case ScopeGlobal: + c.emit(node, parser.OpGetGlobal, symbol.Index) + case ScopeLocal: + c.emit(node, parser.OpGetLocal, symbol.Index) + case ScopeBuiltin: + c.emit(node, parser.OpGetBuiltin, symbol.Index) + case ScopeFree: + c.emit(node, parser.OpGetFree, symbol.Index) + } + case *parser.ArrayLit: + for _, elem := range node.Elements { + if err := c.Compile(elem); err != nil { + return err + } + } + c.emit(node, parser.OpArray, len(node.Elements)) + case *parser.MapLit: + for _, elt := range node.Elements { + // key + if len(elt.Key) > MaxStringLen { + return c.error(node, ErrStringLimit) + } + c.emit(node, parser.OpConstant, + c.addConstant(&String{Value: elt.Key})) + + // value + if err := c.Compile(elt.Value); err != nil { + return err + } + } + c.emit(node, parser.OpMap, len(node.Elements)*2) + + case *parser.SelectorExpr: // selector on RHS side + if err := c.Compile(node.Expr); err != nil { + return err + } + if err := c.Compile(node.Sel); err != nil { + return err + } + c.emit(node, parser.OpIndex) + case *parser.IndexExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + if err := c.Compile(node.Index); err != nil { + return err + } + c.emit(node, parser.OpIndex) + case *parser.SliceExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + if node.Low != nil { + if err := c.Compile(node.Low); err != nil { + return err + } + } else { + c.emit(node, parser.OpNull) + } + if node.High != nil { + if err := c.Compile(node.High); err != nil { + return err + } + } else { + c.emit(node, parser.OpNull) + } + c.emit(node, parser.OpSliceIndex) + case *parser.FuncLit: + c.enterScope() + + for _, p := range node.Type.Params.List { + s := c.symbolTable.Define(p.Name) + + // function arguments is not assigned directly. + s.LocalAssigned = true + } + + if err := c.Compile(node.Body); err != nil { + return err + } + + // code optimization + c.optimizeFunc(node) + + freeSymbols := c.symbolTable.FreeSymbols() + numLocals := c.symbolTable.MaxSymbols() + instructions, sourceMap := c.leaveScope() + + for _, s := range freeSymbols { + switch s.Scope { + case ScopeLocal: + if !s.LocalAssigned { + // Here, the closure is capturing a local variable that's + // not yet assigned its value. One example is a local + // recursive function: + // + // func() { + // foo := func(x) { + // // .. + // return foo(x-1) + // } + // } + // + // which translate into + // + // 0000 GETL 0 + // 0002 CLOSURE ? 1 + // 0006 DEFL 0 + // + // . So the local variable (0) is being captured before + // it's assigned the value. + // + // Solution is to transform the code into something like + // this: + // + // func() { + // foo := undefined + // foo = func(x) { + // // .. + // return foo(x-1) + // } + // } + // + // that is equivalent to + // + // 0000 NULL + // 0001 DEFL 0 + // 0003 GETL 0 + // 0005 CLOSURE ? 1 + // 0009 SETL 0 + // + c.emit(node, parser.OpNull) + c.emit(node, parser.OpDefineLocal, s.Index) + s.LocalAssigned = true + } + c.emit(node, parser.OpGetLocalPtr, s.Index) + case ScopeFree: + c.emit(node, parser.OpGetFreePtr, s.Index) + } + } + + compiledFunction := &CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Type.Params.List), + VarArgs: node.Type.Params.VarArgs, + SourceMap: sourceMap, + } + if len(freeSymbols) > 0 { + c.emit(node, parser.OpClosure, + c.addConstant(compiledFunction), len(freeSymbols)) + } else { + c.emit(node, parser.OpConstant, c.addConstant(compiledFunction)) + } + case *parser.ReturnStmt: + if c.symbolTable.Parent(true) == nil { + // outside the function + return c.errorf(node, "return not allowed outside function") + } + + if node.Result == nil { + c.emit(node, parser.OpReturn, 0) + } else { + if err := c.Compile(node.Result); err != nil { + return err + } + c.emit(node, parser.OpReturn, 1) + } + case *parser.CallExpr: + if err := c.Compile(node.Func); err != nil { + return err + } + for _, arg := range node.Args { + if err := c.Compile(arg); err != nil { + return err + } + } + c.emit(node, parser.OpCall, len(node.Args)) + case *parser.ImportExpr: + 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 + } + + 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, parser.OpConstant, c.addConstant(compiled)) + c.emit(node, parser.OpCall, 0) + case Object: // builtin module + c.emit(node, parser.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 + } + + 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, parser.OpConstant, c.addConstant(compiled)) + c.emit(node, parser.OpCall, 0) + } else { + return c.errorf(node, "module '%s' not found", node.ModuleName) + } + case *parser.ExportStmt: + // export statement must be in top-level scope + if c.scopeIndex != 0 { + return c.errorf(node, "export not allowed inside function") + } + + // export statement is simply ignore when compiling non-module code + if c.parent == nil { + break + } + if err := c.Compile(node.Result); err != nil { + return err + } + c.emit(node, parser.OpImmutable) + c.emit(node, parser.OpReturn, 1) + case *parser.ErrorExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, parser.OpError) + case *parser.ImmutableExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, parser.OpImmutable) + case *parser.CondExpr: + if err := c.Compile(node.Cond); err != nil { + return err + } + + // first jump placeholder + jumpPos1 := c.emit(node, parser.OpJumpFalsy, 0) + if err := c.Compile(node.True); err != nil { + return err + } + + // second jump placeholder + jumpPos2 := c.emit(node, parser.OpJump, 0) + + // update first jump offset + curPos := len(c.currentInstructions()) + c.changeOperand(jumpPos1, curPos) + if err := c.Compile(node.False); err != nil { + return err + } + + // update second jump offset + curPos = len(c.currentInstructions()) + c.changeOperand(jumpPos2, curPos) + } + return nil +} + +// Bytecode returns a compiled bytecode. +func (c *Compiler) Bytecode() *Bytecode { + return &Bytecode{ + FileSet: c.file.Set(), + MainFunction: &CompiledFunction{ + Instructions: append(c.currentInstructions(), parser.OpSuspend), + SourceMap: c.currentSourceMap(), + }, + Constants: c.constants, + } +} + +// 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) compileAssign( + node parser.Node, + lhs, rhs []parser.Expr, + op token.Token, +) error { + numLHS, numRHS := len(lhs), len(rhs) + if numLHS > 1 || numRHS > 1 { + return c.errorf(node, "tuple assignment not allowed") + } + + // resolve and compile left-hand side + ident, selectors := resolveAssignLHS(lhs[0]) + numSel := len(selectors) + + if op == token.Define && numSel > 0 { + // using selector on new variable does not make sense + return c.errorf(node, "operator ':=' not allowed with selector") + } + + symbol, depth, exists := c.symbolTable.Resolve(ident) + if op == token.Define { + if depth == 0 && exists { + return c.errorf(node, "'%s' redeclared in this block", ident) + } + symbol = c.symbolTable.Define(ident) + } else { + if !exists { + return c.errorf(node, "unresolved reference '%s'", ident) + } + } + + // +=, -=, *=, /= + if op != token.Assign && op != token.Define { + if err := c.Compile(lhs[0]); err != nil { + return err + } + } + + // compile RHSs + for _, expr := range rhs { + if err := c.Compile(expr); err != nil { + return err + } + } + + switch op { + case token.AddAssign: + c.emit(node, parser.OpBinaryOp, int(token.Add)) + case token.SubAssign: + c.emit(node, parser.OpBinaryOp, int(token.Sub)) + case token.MulAssign: + c.emit(node, parser.OpBinaryOp, int(token.Mul)) + case token.QuoAssign: + c.emit(node, parser.OpBinaryOp, int(token.Quo)) + case token.RemAssign: + c.emit(node, parser.OpBinaryOp, int(token.Rem)) + case token.AndAssign: + c.emit(node, parser.OpBinaryOp, int(token.And)) + case token.OrAssign: + c.emit(node, parser.OpBinaryOp, int(token.Or)) + case token.AndNotAssign: + c.emit(node, parser.OpBinaryOp, int(token.AndNot)) + case token.XorAssign: + c.emit(node, parser.OpBinaryOp, int(token.Xor)) + case token.ShlAssign: + c.emit(node, parser.OpBinaryOp, int(token.Shl)) + case token.ShrAssign: + c.emit(node, parser.OpBinaryOp, int(token.Shr)) + } + + // compile selector expressions (right to left) + for i := numSel - 1; i >= 0; i-- { + if err := c.Compile(selectors[i]); err != nil { + return err + } + } + + switch symbol.Scope { + case ScopeGlobal: + if numSel > 0 { + c.emit(node, parser.OpSetSelGlobal, symbol.Index, numSel) + } else { + c.emit(node, parser.OpSetGlobal, symbol.Index) + } + case ScopeLocal: + if numSel > 0 { + c.emit(node, parser.OpSetSelLocal, symbol.Index, numSel) + } else { + if op == token.Define && !symbol.LocalAssigned { + c.emit(node, parser.OpDefineLocal, symbol.Index) + } else { + c.emit(node, parser.OpSetLocal, symbol.Index) + } + } + + // mark the symbol as local-assigned + symbol.LocalAssigned = true + case ScopeFree: + if numSel > 0 { + c.emit(node, parser.OpSetSelFree, symbol.Index, numSel) + } else { + c.emit(node, parser.OpSetFree, symbol.Index) + } + default: + panic(fmt.Errorf("invalid assignment variable scope: %s", + symbol.Scope)) + } + return nil +} + +func (c *Compiler) compileLogical(node *parser.BinaryExpr) error { + // left side term + if err := c.Compile(node.LHS); err != nil { + return err + } + + // jump position + var jumpPos int + if node.Token == token.LAnd { + jumpPos = c.emit(node, parser.OpAndJump, 0) + } else { + jumpPos = c.emit(node, parser.OpOrJump, 0) + } + + // right side term + if err := c.Compile(node.RHS); err != nil { + return err + } + + c.changeOperand(jumpPos, len(c.currentInstructions())) + return nil +} + +func (c *Compiler) compileForStmt(stmt *parser.ForStmt) error { + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + // init statement + if stmt.Init != nil { + if err := c.Compile(stmt.Init); err != nil { + return err + } + } + + // pre-condition position + preCondPos := len(c.currentInstructions()) + + // condition expression + postCondPos := -1 + if stmt.Cond != nil { + if err := c.Compile(stmt.Cond); err != nil { + return err + } + // condition jump position + postCondPos = c.emit(stmt, parser.OpJumpFalsy, 0) + } + + // enter loop + loop := c.enterLoop() + + // body statement + if err := c.Compile(stmt.Body); err != nil { + c.leaveLoop() + return err + } + + c.leaveLoop() + + // post-body position + postBodyPos := len(c.currentInstructions()) + + // post statement + if stmt.Post != nil { + if err := c.Compile(stmt.Post); err != nil { + return err + } + } + + // back to condition + c.emit(stmt, parser.OpJump, preCondPos) + + // post-statement position + postStmtPos := len(c.currentInstructions()) + if postCondPos >= 0 { + c.changeOperand(postCondPos, postStmtPos) + } + + // update all break/continue jump positions + for _, pos := range loop.Breaks { + c.changeOperand(pos, postStmtPos) + } + for _, pos := range loop.Continues { + c.changeOperand(pos, postBodyPos) + } + return nil +} + +func (c *Compiler) compileForInStmt(stmt *parser.ForInStmt) error { + c.symbolTable = c.symbolTable.Fork(true) + defer func() { + c.symbolTable = c.symbolTable.Parent(false) + }() + + // for-in statement is compiled like following: + // + // for :it := iterator(iterable); :it.next(); { + // k, v := :it.get() // DEFINE operator + // + // ... body ... + // } + // + // ":it" is a local variable but will be conflict with other user variables + // because character ":" is not allowed. + + // init + // :it = iterator(iterable) + itSymbol := c.symbolTable.Define(":it") + if err := c.Compile(stmt.Iterable); err != nil { + return err + } + c.emit(stmt, parser.OpIteratorInit) + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpSetGlobal, itSymbol.Index) + } else { + c.emit(stmt, parser.OpDefineLocal, itSymbol.Index) + } + + // pre-condition position + preCondPos := len(c.currentInstructions()) + + // condition + // :it.HasMore() + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, parser.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, parser.OpIteratorNext) + + // condition jump position + postCondPos := c.emit(stmt, parser.OpJumpFalsy, 0) + + // enter loop + loop := c.enterLoop() + + // assign key variable + if stmt.Key.Name != "_" { + keySymbol := c.symbolTable.Define(stmt.Key.Name) + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, parser.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, parser.OpIteratorKey) + if keySymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpSetGlobal, keySymbol.Index) + } else { + c.emit(stmt, parser.OpDefineLocal, keySymbol.Index) + } + } + + // assign value variable + if stmt.Value.Name != "_" { + valueSymbol := c.symbolTable.Define(stmt.Value.Name) + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, parser.OpGetLocal, itSymbol.Index) + } + c.emit(stmt, parser.OpIteratorValue) + if valueSymbol.Scope == ScopeGlobal { + c.emit(stmt, parser.OpSetGlobal, valueSymbol.Index) + } else { + c.emit(stmt, parser.OpDefineLocal, valueSymbol.Index) + } + } + + // body statement + if err := c.Compile(stmt.Body); err != nil { + c.leaveLoop() + return err + } + + c.leaveLoop() + + // post-body position + postBodyPos := len(c.currentInstructions()) + + // back to condition + c.emit(stmt, parser.OpJump, preCondPos) + + // post-statement position + postStmtPos := len(c.currentInstructions()) + c.changeOperand(postCondPos, postStmtPos) + + // update all break/continue jump positions + for _, pos := range loop.Breaks { + c.changeOperand(pos, postStmtPos) + } + for _, pos := range loop.Continues { + c.changeOperand(pos, postBodyPos) + } + return nil +} + +func (c *Compiler) checkCyclicImports( + node parser.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) + } + return nil +} + +func (c *Compiler) compileModule( + node parser.Node, + moduleName, modulePath string, + src []byte, +) (*CompiledFunction, error) { + if err := c.checkCyclicImports(node, modulePath); err != nil { + return nil, err + } + + compiledModule, exists := c.loadCompiledModule(modulePath) + if exists { + return compiledModule, nil + } + + modFile := c.file.Set().AddFile(moduleName, -1, len(src)) + p := parser.NewParser(modFile, src, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + // inherit builtin functions + symbolTable := NewSymbolTable() + for _, sym := range c.symbolTable.BuiltinSymbols() { + symbolTable.DefineBuiltin(sym.Index, sym.Name) + } + + // no global scope for the module + symbolTable = symbolTable.Fork(false) + + // compile module + moduleCompiler := c.fork(modFile, modulePath, symbolTable) + if err := moduleCompiler.Compile(file); err != nil { + return nil, err + } + + // code optimization + moduleCompiler.optimizeFunc(node) + compiledFunc := moduleCompiler.Bytecode().MainFunction + compiledFunc.NumLocals = symbolTable.MaxSymbols() + c.storeCompiledModule(modulePath, compiledFunc) + return compiledFunc, nil +} + +func (c *Compiler) loadCompiledModule( + modulePath string, +) (mod *CompiledFunction, ok bool) { + if c.parent != nil { + return c.parent.loadCompiledModule(modulePath) + } + mod, ok = c.compiledModules[modulePath] + return +} + +func (c *Compiler) storeCompiledModule( + modulePath string, + module *CompiledFunction, +) { + if c.parent != nil { + c.parent.storeCompiledModule(modulePath, module) + } + c.compiledModules[modulePath] = module +} + +func (c *Compiler) enterLoop() *loop { + loop := &loop{} + c.loops = append(c.loops, loop) + c.loopIndex++ + if c.trace != nil { + c.printTrace("LOOPE", c.loopIndex) + } + return loop +} + +func (c *Compiler) leaveLoop() { + if c.trace != nil { + c.printTrace("LOOPL", c.loopIndex) + } + c.loops = c.loops[:len(c.loops)-1] + c.loopIndex-- +} + +func (c *Compiler) currentLoop() *loop { + if c.loopIndex >= 0 { + return c.loops[c.loopIndex] + } + return nil +} + +func (c *Compiler) currentInstructions() []byte { + return c.scopes[c.scopeIndex].Instructions +} + +func (c *Compiler) currentSourceMap() map[int]parser.Pos { + return c.scopes[c.scopeIndex].SourceMap +} + +func (c *Compiler) enterScope() { + scope := compilationScope{ + SymbolInit: make(map[string]bool), + SourceMap: make(map[int]parser.Pos), + } + c.scopes = append(c.scopes, scope) + c.scopeIndex++ + c.symbolTable = c.symbolTable.Fork(false) + if c.trace != nil { + c.printTrace("SCOPE", c.scopeIndex) + } +} + +func (c *Compiler) leaveScope() ( + instructions []byte, + sourceMap map[int]parser.Pos, +) { + instructions = c.currentInstructions() + sourceMap = c.currentSourceMap() + c.scopes = c.scopes[:len(c.scopes)-1] + c.scopeIndex-- + c.symbolTable = c.symbolTable.Parent(true) + if c.trace != nil { + c.printTrace("SCOPL", c.scopeIndex) + } + return +} + +func (c *Compiler) fork( + file *parser.SourceFile, + 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 +} + +func (c *Compiler) error(node parser.Node, err error) error { + return &CompilerError{ + FileSet: c.file.Set(), + Node: node, + Err: err, + } +} + +func (c *Compiler) errorf( + node parser.Node, + format string, + args ...interface{}, +) error { + return &CompilerError{ + FileSet: c.file.Set(), + Node: node, + Err: fmt.Errorf(format, args...), + } +} + +func (c *Compiler) addConstant(o Object) int { + if c.parent != nil { + // module compilers will use their parent's constants array + return c.parent.addConstant(o) + } + c.constants = append(c.constants, o) + if c.trace != nil { + c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o)) + } + return len(c.constants) - 1 +} + +func (c *Compiler) addInstruction(b []byte) int { + posNewIns := len(c.currentInstructions()) + c.scopes[c.scopeIndex].Instructions = append( + c.currentInstructions(), b...) + return posNewIns +} + +func (c *Compiler) replaceInstruction(pos int, inst []byte) { + copy(c.currentInstructions()[pos:], inst) + if c.trace != nil { + c.printTrace(fmt.Sprintf("REPLC %s", + FormatInstructions( + c.scopes[c.scopeIndex].Instructions[pos:], pos)[0])) + } +} + +func (c *Compiler) changeOperand(opPos int, operand ...int) { + op := c.currentInstructions()[opPos] + inst := MakeInstruction(op, operand...) + c.replaceInstruction(opPos, inst) +} + +// optimizeFunc performs some code-level optimization for the current function +// instructions. It also removes unreachable (dead code) instructions and adds +// "returns" instruction if needed. +func (c *Compiler) optimizeFunc(node parser.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 parser.Opcode, operands []int) bool { + switch opcode { + case parser.OpJump, parser.OpJumpFalsy, + parser.OpAndJump, parser.OpOrJump: + dsts[operands[0]] = true + } + return true + }) + + // pass 2. eliminate dead code + var newInsts []byte + 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 parser.Opcode, operands []int) bool { + switch { + case opcode == parser.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 parser.Opcode + var appendReturn bool + endPos := len(c.scopes[c.scopeIndex].Instructions) + iterateInstructions(newInsts, + func(pos int, opcode parser.Opcode, operands []int) bool { + switch opcode { + case parser.OpJump, parser.OpJumpFalsy, parser.OpAndJump, + parser.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 != parser.OpReturn { + appendReturn = true + } + + // pass 4. update source map + newSourceMap := make(map[int]parser.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, parser.OpReturn, 0) + } +} + +func (c *Compiler) emit( + node parser.Node, + opcode parser.Opcode, + operands ...int, +) int { + filePos := parser.NoPos + if node != nil { + filePos = node.Pos() + } + + inst := MakeInstruction(opcode, operands...) + pos := c.addInstruction(inst) + c.scopes[c.scopeIndex].SourceMap[pos] = filePos + if c.trace != nil { + c.printTrace(fmt.Sprintf("EMIT %s", + FormatInstructions( + c.scopes[c.scopeIndex].Instructions[pos:], pos)[0])) + } + return pos +} + +func (c *Compiler) printTrace(a ...interface{}) { + const ( + dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + n = len(dots) + ) + + i := 2 * c.indent + for i > n { + _, _ = fmt.Fprint(c.trace, dots) + i -= n + } + _, _ = fmt.Fprint(c.trace, dots[0:i]) + _, _ = fmt.Fprintln(c.trace, a...) +} + +func resolveAssignLHS( + expr parser.Expr, +) (name string, selectors []parser.Expr) { + switch term := expr.(type) { + case *parser.SelectorExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Sel) + return + case *parser.IndexExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Index) + case *parser.Ident: + name = term.Name + } + return +} + +func iterateInstructions( + b []byte, + fn func(pos int, opcode parser.Opcode, operands []int) bool, +) { + for i := 0; i < len(b); i++ { + numOperands := parser.OpcodeOperands[b[i]] + operands, read := parser.ReadOperands(numOperands, b[i+1:]) + if !fn(i, b[i], operands) { + break + } + i += read + } +} + +func tracec(c *Compiler, msg string) *Compiler { + c.printTrace(msg, "{") + c.indent++ + return c +} + +func untracec(c *Compiler) { + c.indent-- + c.printTrace("}") +} diff --git a/vendor/github.com/d5/tengo/v2/doc.go b/vendor/github.com/d5/tengo/v2/doc.go new file mode 100644 index 00000000..05b47de1 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/doc.go @@ -0,0 +1,3 @@ +// tengo is a small, dynamic, fast, secure script language for Go. + +package tengo diff --git a/vendor/github.com/d5/tengo/v2/errors.go b/vendor/github.com/d5/tengo/v2/errors.go new file mode 100644 index 00000000..a3fd1f3b --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/errors.go @@ -0,0 +1,64 @@ +package tengo + +import ( + "errors" + "fmt" +) + +var ( + // ErrStackOverflow is a stack overflow error. + ErrStackOverflow = errors.New("stack overflow") + + // ErrObjectAllocLimit is an objects allocation limit error. + ErrObjectAllocLimit = errors.New("object allocation limit exceeded") + + // ErrIndexOutOfBounds is an error where a given index is out of the + // bounds. + ErrIndexOutOfBounds = errors.New("index out of bounds") + + // ErrInvalidIndexType represents an invalid index type. + ErrInvalidIndexType = errors.New("invalid index type") + + // ErrInvalidIndexValueType represents an invalid index value type. + ErrInvalidIndexValueType = errors.New("invalid index value type") + + // ErrInvalidIndexOnError represents an invalid index on error. + ErrInvalidIndexOnError = errors.New("invalid index on error") + + // ErrInvalidOperator represents an error for invalid operator usage. + ErrInvalidOperator = errors.New("invalid operator") + + // ErrWrongNumArguments represents a wrong number of arguments error. + ErrWrongNumArguments = errors.New("wrong number of arguments") + + // ErrBytesLimit represents an error where the size of bytes value exceeds + // the limit. + ErrBytesLimit = errors.New("exceeding bytes size limit") + + // ErrStringLimit represents an error where the size of string value + // exceeds the limit. + ErrStringLimit = errors.New("exceeding string size limit") + + // ErrNotIndexable is an error where an Object is not indexable. + ErrNotIndexable = errors.New("not indexable") + + // ErrNotIndexAssignable is an error where an Object is not index + // assignable. + ErrNotIndexAssignable = errors.New("not index-assignable") + + // ErrNotImplemented is an error where an Object has not implemented a + // required method. + ErrNotImplemented = errors.New("not implemented") +) + +// ErrInvalidArgumentType represents an invalid argument value type error. +type ErrInvalidArgumentType struct { + Name string + Expected string + Found string +} + +func (e ErrInvalidArgumentType) Error() string { + return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", + e.Name, e.Expected, e.Found) +} diff --git a/vendor/github.com/d5/tengo/v2/formatter.go b/vendor/github.com/d5/tengo/v2/formatter.go new file mode 100644 index 00000000..0dbf71c6 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/formatter.go @@ -0,0 +1,1245 @@ +package tengo + +import ( + "strconv" + "sync" + "unicode/utf8" +) + +// Strings for use with fmtbuf.WriteString. This is less overhead than using +// fmtbuf.Write with byte arrays. +const ( + commaSpaceString = ", " + nilParenString = "(nil)" + percentBangString = "%!" + missingString = "(MISSING)" + badIndexString = "(BADINDEX)" + extraString = "%!(EXTRA " + badWidthString = "%!(BADWIDTH)" + badPrecString = "%!(BADPREC)" + noVerbString = "%!(NOVERB)" +) + +const ( + ldigits = "0123456789abcdefx" + udigits = "0123456789ABCDEFX" +) + +const ( + signed = true + unsigned = false +) + +// flags placed in a separate struct for easy clearing. +type fmtFlags struct { + widPresent bool + precPresent bool + minus bool + plus bool + sharp bool + space bool + zero bool + + // For the formats %+v %#v, we set the plusV/sharpV flags + // and clear the plus/sharp flags since %+v and %#v are in effect + // different, flagless formats set at the top level. + plusV bool + sharpV bool + + // error-related flags. + inDetail bool + needNewline bool + needColon bool +} + +// A formatter is the raw formatter used by Printf etc. +// It prints into a fmtbuf that must be set up separately. +type formatter struct { + buf *fmtbuf + + fmtFlags + + wid int // width + prec int // precision + + // intbuf is large enough to store %b of an int64 with a sign and + // avoids padding at the end of the struct on 32 bit architectures. + intbuf [68]byte +} + +func (f *formatter) clearFlags() { + f.fmtFlags = fmtFlags{} +} + +func (f *formatter) init(buf *fmtbuf) { + f.buf = buf + f.clearFlags() +} + +// writePadding generates n bytes of padding. +func (f *formatter) writePadding(n int) { + if n <= 0 { // No padding bytes needed. + return + } + buf := *f.buf + oldLen := len(buf) + newLen := oldLen + n + + if newLen > MaxStringLen { + panic(ErrStringLimit) + } + + // Make enough room for padding. + if newLen > cap(buf) { + buf = make(fmtbuf, cap(buf)*2+n) + copy(buf, *f.buf) + } + // Decide which byte the padding should be filled with. + padByte := byte(' ') + if f.zero { + padByte = byte('0') + } + // Fill padding with padByte. + padding := buf[oldLen:newLen] + for i := range padding { + padding[i] = padByte + } + *f.buf = buf[:newLen] +} + +// pad appends b to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatter) pad(b []byte) { + if !f.widPresent || f.wid == 0 { + f.buf.Write(b) + return + } + width := f.wid - utf8.RuneCount(b) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.Write(b) + } else { + // right padding + f.buf.Write(b) + f.writePadding(width) + } +} + +// padString appends s to f.buf, padded on left (!f.minus) or right (f.minus). +func (f *formatter) padString(s string) { + if !f.widPresent || f.wid == 0 { + f.buf.WriteString(s) + return + } + width := f.wid - utf8.RuneCountInString(s) + if !f.minus { + // left padding + f.writePadding(width) + f.buf.WriteString(s) + } else { + // right padding + f.buf.WriteString(s) + f.writePadding(width) + } +} + +// fmtBoolean formats a boolean. +func (f *formatter) fmtBoolean(v bool) { + if v { + f.padString("true") + } else { + f.padString("false") + } +} + +// fmtUnicode formats a uint64 as "U+0078" or with f.sharp set as "U+0078 'x'". +func (f *formatter) fmtUnicode(u uint64) { + buf := f.intbuf[0:] + + // With default precision set the maximum needed buf length is 18 + // for formatting -1 with %#U ("U+FFFFFFFFFFFFFFFF") which fits + // into the already allocated intbuf with a capacity of 68 bytes. + prec := 4 + if f.precPresent && f.prec > 4 { + prec = f.prec + // Compute space needed for "U+" , number, " '", character, "'". + width := 2 + prec + 2 + utf8.UTFMax + 1 + if width > len(buf) { + buf = make([]byte, width) + } + } + + // Format into buf, ending at buf[i]. Formatting numbers is easier + // right-to-left. + i := len(buf) + + // For %#U we want to add a space and a quoted character at the end of + // the fmtbuf. + if f.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) { + i-- + buf[i] = '\'' + i -= utf8.RuneLen(rune(u)) + utf8.EncodeRune(buf[i:], rune(u)) + i-- + buf[i] = '\'' + i-- + buf[i] = ' ' + } + // Format the Unicode code point u as a hexadecimal number. + for u >= 16 { + i-- + buf[i] = udigits[u&0xF] + prec-- + u >>= 4 + } + i-- + buf[i] = udigits[u] + prec-- + // Add zeros in front of the number until requested precision is reached. + for prec > 0 { + i-- + buf[i] = '0' + prec-- + } + // Add a leading "U+". + i-- + buf[i] = '+' + i-- + buf[i] = 'U' + + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// fmtInteger formats signed and unsigned integers. +func (f *formatter) fmtInteger( + u uint64, + base int, + isSigned bool, + verb rune, + digits string, +) { + negative := isSigned && int64(u) < 0 + if negative { + u = -u + } + + buf := f.intbuf[0:] + // The already allocated f.intbuf with a capacity of 68 bytes + // is large enough for integer formatting when no precision or width is set. + if f.widPresent || f.precPresent { + // Account 3 extra bytes for possible addition of a sign and "0x". + width := 3 + f.wid + f.prec // wid and prec are always positive. + if width > len(buf) { + // We're going to need a bigger boat. + buf = make([]byte, width) + } + } + + // Two ways to ask for extra leading zero digits: %.3d or %03d. + // If both are specified the f.zero flag is ignored and + // padding with spaces is used instead. + prec := 0 + if f.precPresent { + prec = f.prec + // Precision of 0 and value of 0 means "print nothing" but padding. + if prec == 0 && u == 0 { + oldZero := f.zero + f.zero = false + f.writePadding(f.wid) + f.zero = oldZero + return + } + } else if f.zero && f.widPresent { + prec = f.wid + if negative || f.plus || f.space { + prec-- // leave room for sign + } + } + + // Because printing is easier right-to-left: format u into buf, ending at + // buf[i]. We could make things marginally faster by splitting the 32-bit + // case out into a separate block but it's not worth the duplication, so + // u has 64 bits. + i := len(buf) + // Use constants for the division and modulo for more efficient code. + // Switch cases ordered by popularity. + switch base { + case 10: + for u >= 10 { + i-- + next := u / 10 + buf[i] = byte('0' + u - next*10) + u = next + } + case 16: + for u >= 16 { + i-- + buf[i] = digits[u&0xF] + u >>= 4 + } + case 8: + for u >= 8 { + i-- + buf[i] = byte('0' + u&7) + u >>= 3 + } + case 2: + for u >= 2 { + i-- + buf[i] = byte('0' + u&1) + u >>= 1 + } + default: + panic("fmt: unknown base; can't happen") + } + i-- + buf[i] = digits[u] + for i > 0 && prec > len(buf)-i { + i-- + buf[i] = '0' + } + + // Various prefixes: 0x, -, etc. + if f.sharp { + switch base { + case 2: + // Add a leading 0b. + i-- + buf[i] = 'b' + i-- + buf[i] = '0' + case 8: + if buf[i] != '0' { + i-- + buf[i] = '0' + } + case 16: + // Add a leading 0x or 0X. + i-- + buf[i] = digits[16] + i-- + buf[i] = '0' + } + } + if verb == 'O' { + i-- + buf[i] = 'o' + i-- + buf[i] = '0' + } + + if negative { + i-- + buf[i] = '-' + } else if f.plus { + i-- + buf[i] = '+' + } else if f.space { + i-- + buf[i] = ' ' + } + + // Left padding with zeros has already been handled like precision earlier + // or the f.zero flag is ignored due to an explicitly set precision. + oldZero := f.zero + f.zero = false + f.pad(buf[i:]) + f.zero = oldZero +} + +// truncate truncates the string s to the specified precision, if present. +func (f *formatter) truncateString(s string) string { + if f.precPresent { + n := f.prec + for i := range s { + n-- + if n < 0 { + return s[:i] + } + } + } + return s +} + +// truncate truncates the byte slice b as a string of the specified precision, +// if present. +func (f *formatter) truncate(b []byte) []byte { + if f.precPresent { + n := f.prec + for i := 0; i < len(b); { + n-- + if n < 0 { + return b[:i] + } + wid := 1 + if b[i] >= utf8.RuneSelf { + _, wid = utf8.DecodeRune(b[i:]) + } + i += wid + } + } + return b +} + +// fmtS formats a string. +func (f *formatter) fmtS(s string) { + s = f.truncateString(s) + f.padString(s) +} + +// fmtBs formats the byte slice b as if it was formatted as string with fmtS. +func (f *formatter) fmtBs(b []byte) { + b = f.truncate(b) + f.pad(b) +} + +// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes. +func (f *formatter) fmtSbx(s string, b []byte, digits string) { + length := len(b) + if b == nil { + // No byte slice present. Assume string s should be encoded. + length = len(s) + } + // Set length to not process more bytes than the precision demands. + if f.precPresent && f.prec < length { + length = f.prec + } + // Compute width of the encoding taking into account the f.sharp and + // f.space flag. + width := 2 * length + if width > 0 { + if f.space { + // Each element encoded by two hexadecimals will get a leading + // 0x or 0X. + if f.sharp { + width *= 2 + } + // Elements will be separated by a space. + width += length - 1 + } else if f.sharp { + // Only a leading 0x or 0X will be added for the whole string. + width += 2 + } + } else { // The byte slice or string that should be encoded is empty. + if f.widPresent { + f.writePadding(f.wid) + } + return + } + // Handle padding to the left. + if f.widPresent && f.wid > width && !f.minus { + f.writePadding(f.wid - width) + } + // Write the encoding directly into the output fmtbuf. + buf := *f.buf + if f.sharp { + // Add leading 0x or 0X. + buf = append(buf, '0', digits[16]) + } + var c byte + for i := 0; i < length; i++ { + if f.space && i > 0 { + // Separate elements with a space. + buf = append(buf, ' ') + if f.sharp { + // Add leading 0x or 0X for each element. + buf = append(buf, '0', digits[16]) + } + } + if b != nil { + c = b[i] // Take a byte from the input byte slice. + } else { + c = s[i] // Take a byte from the input string. + } + // Encode each byte as two hexadecimal digits. + buf = append(buf, digits[c>>4], digits[c&0xF]) + } + *f.buf = buf + // Handle padding to the right. + if f.widPresent && f.wid > width && f.minus { + f.writePadding(f.wid - width) + } +} + +// fmtSx formats a string as a hexadecimal encoding of its bytes. +func (f *formatter) fmtSx(s, digits string) { + f.fmtSbx(s, nil, digits) +} + +// fmtBx formats a byte slice as a hexadecimal encoding of its bytes. +func (f *formatter) fmtBx(b []byte, digits string) { + f.fmtSbx("", b, digits) +} + +// fmtQ formats a string as a double-quoted, escaped Go string constant. +// If f.sharp is set a raw (backquoted) string may be returned instead +// if the string does not contain any control characters other than tab. +func (f *formatter) fmtQ(s string) { + s = f.truncateString(s) + if f.sharp && strconv.CanBackquote(s) { + f.padString("`" + s + "`") + return + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteToASCII(buf, s)) + } else { + f.pad(strconv.AppendQuote(buf, s)) + } +} + +// fmtC formats an integer as a Unicode character. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatter) fmtC(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + w := utf8.EncodeRune(buf[:utf8.UTFMax], r) + f.pad(buf[:w]) +} + +// fmtQc formats an integer as a single-quoted, escaped Go character constant. +// If the character is not valid Unicode, it will print '\ufffd'. +func (f *formatter) fmtQc(c uint64) { + r := rune(c) + if c > utf8.MaxRune { + r = utf8.RuneError + } + buf := f.intbuf[:0] + if f.plus { + f.pad(strconv.AppendQuoteRuneToASCII(buf, r)) + } else { + f.pad(strconv.AppendQuoteRune(buf, r)) + } +} + +// fmtFloat formats a float64. It assumes that verb is a valid format specifier +// for strconv.AppendFloat and therefore fits into a byte. +func (f *formatter) fmtFloat(v float64, size int, verb rune, prec int) { + // Explicit precision in format specifier overrules default precision. + if f.precPresent { + prec = f.prec + } + // Format number, reserving space for leading + sign if needed. + num := strconv.AppendFloat(f.intbuf[:1], v, byte(verb), prec, size) + if num[1] == '-' || num[1] == '+' { + num = num[1:] + } else { + num[0] = '+' + } + // f.space means to add a leading space instead of a "+" sign unless + // the sign is explicitly asked for by f.plus. + if f.space && num[0] == '+' && !f.plus { + num[0] = ' ' + } + // Special handling for infinities and NaN, + // which don't look like a number so shouldn't be padded with zeros. + if num[1] == 'I' || num[1] == 'N' { + oldZero := f.zero + f.zero = false + // Remove sign before NaN if not asked for. + if num[1] == 'N' && !f.space && !f.plus { + num = num[1:] + } + f.pad(num) + f.zero = oldZero + return + } + // The sharp flag forces printing a decimal point for non-binary formats + // and retains trailing zeros, which we may need to restore. + if f.sharp && verb != 'b' { + digits := 0 + switch verb { + case 'v', 'g', 'G', 'x': + digits = prec + // If no precision is set explicitly use a precision of 6. + if digits == -1 { + digits = 6 + } + } + + // Buffer pre-allocated with enough room for + // exponent notations of the form "e+123" or "p-1023". + var tailBuf [6]byte + tail := tailBuf[:0] + + hasDecimalPoint := false + // Starting from i = 1 to skip sign at num[0]. + for i := 1; i < len(num); i++ { + switch num[i] { + case '.': + hasDecimalPoint = true + case 'p', 'P': + tail = append(tail, num[i:]...) + num = num[:i] + case 'e', 'E': + if verb != 'x' && verb != 'X' { + tail = append(tail, num[i:]...) + num = num[:i] + break + } + fallthrough + default: + digits-- + } + } + if !hasDecimalPoint { + num = append(num, '.') + } + for digits > 0 { + num = append(num, '0') + digits-- + } + num = append(num, tail...) + } + // We want a sign if asked for and if the sign is not positive. + if f.plus || num[0] != '+' { + // If we're zero padding to the left we want the sign before the + // leading zeros. Achieve this by writing the sign out and then padding + // the unsigned number. + if f.zero && f.widPresent && f.wid > len(num) { + f.buf.WriteSingleByte(num[0]) + f.writePadding(f.wid - len(num)) + f.buf.Write(num[1:]) + return + } + f.pad(num) + return + } + // No sign to show and the number is positive; just print the unsigned + // number. + f.pad(num[1:]) +} + +// Use simple []byte instead of bytes.Buffer to avoid large dependency. +type fmtbuf []byte + +func (b *fmtbuf) Write(p []byte) { + if len(*b)+len(p) > MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, p...) +} + +func (b *fmtbuf) WriteString(s string) { + if len(*b)+len(s) > MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, s...) +} + +func (b *fmtbuf) WriteSingleByte(c byte) { + if len(*b) >= MaxStringLen { + panic(ErrStringLimit) + } + + *b = append(*b, c) +} + +func (b *fmtbuf) WriteRune(r rune) { + if len(*b)+utf8.RuneLen(r) > MaxStringLen { + panic(ErrStringLimit) + } + + if r < utf8.RuneSelf { + *b = append(*b, byte(r)) + return + } + + b2 := *b + n := len(b2) + for n+utf8.UTFMax > cap(b2) { + b2 = append(b2, 0) + } + w := utf8.EncodeRune(b2[n:n+utf8.UTFMax], r) + *b = b2[:n+w] +} + +// pp is used to store a printer's state and is reused with sync.Pool to avoid +// allocations. +type pp struct { + buf fmtbuf + + // arg holds the current item. + arg Object + + // fmt is used to format basic items such as integers or strings. + fmt formatter + + // reordered records whether the format string used argument reordering. + reordered bool + + // goodArgNum records whether the most recent reordering directive was + // valid. + goodArgNum bool + + // erroring is set when printing an error string to guard against calling + // handleMethods. + erroring bool +} + +var ppFree = sync.Pool{ + New: func() interface{} { return new(pp) }, +} + +// newPrinter allocates a new pp struct or grabs a cached one. +func newPrinter() *pp { + p := ppFree.Get().(*pp) + p.erroring = false + p.fmt.init(&p.buf) + return p +} + +// free saves used pp structs in ppFree; avoids an allocation per invocation. +func (p *pp) free() { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized fmtbuf, we add a hard limit on the maximum + // fmtbuf to place back in the pool. + // + // See https://golang.org/issue/23199 + if cap(p.buf) > 64<<10 { + return + } + + p.buf = p.buf[:0] + p.arg = nil + ppFree.Put(p) +} + +func (p *pp) Width() (wid int, ok bool) { + return p.fmt.wid, p.fmt.widPresent +} + +func (p *pp) Precision() (prec int, ok bool) { + return p.fmt.prec, p.fmt.precPresent +} + +func (p *pp) Flag(b int) bool { + switch b { + case '-': + return p.fmt.minus + case '+': + return p.fmt.plus || p.fmt.plusV + case '#': + return p.fmt.sharp || p.fmt.sharpV + case ' ': + return p.fmt.space + case '0': + return p.fmt.zero + } + return false +} + +// Implement Write so we can call Fprintf on a pp (through State), for +// recursive use in custom verbs. +func (p *pp) Write(b []byte) (ret int, err error) { + p.buf.Write(b) + return len(b), nil +} + +// Implement WriteString so that we can call io.WriteString +// on a pp (through state), for efficiency. +func (p *pp) WriteString(s string) (ret int, err error) { + p.buf.WriteString(s) + return len(s), nil +} + +func (p *pp) WriteRune(r rune) (ret int, err error) { + p.buf.WriteRune(r) + return utf8.RuneLen(r), nil +} + +func (p *pp) WriteSingleByte(c byte) (ret int, err error) { + p.buf.WriteSingleByte(c) + return 1, nil +} + +// tooLarge reports whether the magnitude of the integer is +// too large to be used as a formatting width or precision. +func tooLarge(x int) bool { + const max int = 1e6 + return x > max || x < -max +} + +// parsenum converts ASCII to integer. num is 0 (and isnum is false) if no +// number present. +func parsenum(s string, start, end int) (num int, isnum bool, newi int) { + if start >= end { + return 0, false, end + } + for newi = start; newi < end && '0' <= s[newi] && s[newi] <= '9'; newi++ { + if tooLarge(num) { + return 0, false, end // Overflow; crazy long number most likely. + } + num = num*10 + int(s[newi]-'0') + isnum = true + } + return +} + +func (p *pp) badVerb(verb rune) { + p.erroring = true + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteSingleByte('(') + switch { + case p.arg != nil: + _, _ = p.WriteString(p.arg.String()) + _, _ = p.WriteSingleByte('=') + p.printArg(p.arg, 'v') + default: + _, _ = p.WriteString(UndefinedValue.String()) + } + _, _ = p.WriteSingleByte(')') + p.erroring = false +} + +func (p *pp) fmtBool(v bool, verb rune) { + switch verb { + case 't', 'v': + p.fmt.fmtBoolean(v) + default: + p.badVerb(verb) + } +} + +// fmt0x64 formats a uint64 in hexadecimal and prefixes it with 0x or +// not, as requested, by temporarily setting the sharp flag. +func (p *pp) fmt0x64(v uint64, leading0x bool) { + sharp := p.fmt.sharp + p.fmt.sharp = leading0x + p.fmt.fmtInteger(v, 16, unsigned, 'v', ldigits) + p.fmt.sharp = sharp +} + +// fmtInteger formats a signed or unsigned integer. +func (p *pp) fmtInteger(v uint64, isSigned bool, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV && !isSigned { + p.fmt0x64(v, true) + } else { + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + } + case 'd': + p.fmt.fmtInteger(v, 10, isSigned, verb, ldigits) + case 'b': + p.fmt.fmtInteger(v, 2, isSigned, verb, ldigits) + case 'o', 'O': + p.fmt.fmtInteger(v, 8, isSigned, verb, ldigits) + case 'x': + p.fmt.fmtInteger(v, 16, isSigned, verb, ldigits) + case 'X': + p.fmt.fmtInteger(v, 16, isSigned, verb, udigits) + case 'c': + p.fmt.fmtC(v) + case 'q': + if v <= utf8.MaxRune { + p.fmt.fmtQc(v) + } else { + p.badVerb(verb) + } + case 'U': + p.fmt.fmtUnicode(v) + default: + p.badVerb(verb) + } +} + +// fmtFloat formats a float. The default precision for each verb +// is specified as last argument in the call to fmt_float. +func (p *pp) fmtFloat(v float64, size int, verb rune) { + switch verb { + case 'v': + p.fmt.fmtFloat(v, size, 'g', -1) + case 'b', 'g', 'G', 'x', 'X': + p.fmt.fmtFloat(v, size, verb, -1) + case 'f', 'e', 'E': + p.fmt.fmtFloat(v, size, verb, 6) + case 'F': + p.fmt.fmtFloat(v, size, 'f', 6) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtString(v string, verb rune) { + switch verb { + case 'v': + if p.fmt.sharpV { + p.fmt.fmtQ(v) + } else { + p.fmt.fmtS(v) + } + case 's': + p.fmt.fmtS(v) + case 'x': + p.fmt.fmtSx(v, ldigits) + case 'X': + p.fmt.fmtSx(v, udigits) + case 'q': + p.fmt.fmtQ(v) + default: + p.badVerb(verb) + } +} + +func (p *pp) fmtBytes(v []byte, verb rune, typeString string) { + switch verb { + case 'v', 'd': + if p.fmt.sharpV { + _, _ = p.WriteString(typeString) + if v == nil { + _, _ = p.WriteString(nilParenString) + return + } + _, _ = p.WriteSingleByte('{') + for i, c := range v { + if i > 0 { + _, _ = p.WriteString(commaSpaceString) + } + p.fmt0x64(uint64(c), true) + } + _, _ = p.WriteSingleByte('}') + } else { + _, _ = p.WriteSingleByte('[') + for i, c := range v { + if i > 0 { + _, _ = p.WriteSingleByte(' ') + } + p.fmt.fmtInteger(uint64(c), 10, unsigned, verb, ldigits) + } + _, _ = p.WriteSingleByte(']') + } + case 's': + p.fmt.fmtBs(v) + case 'x': + p.fmt.fmtBx(v, ldigits) + case 'X': + p.fmt.fmtBx(v, udigits) + case 'q': + p.fmt.fmtQ(string(v)) + } +} + +func (p *pp) printArg(arg Object, verb rune) { + p.arg = arg + + if arg == nil { + arg = UndefinedValue + } + + // Special processing considerations. + // %T (the value's type) and %p (its address) are special; we always do + // them first. + switch verb { + case 'T': + p.fmt.fmtS(arg.TypeName()) + return + case 'v': + p.fmt.fmtS(arg.String()) + return + } + + // Some types can be done without reflection. + switch f := arg.(type) { + case *Bool: + p.fmtBool(!f.IsFalsy(), verb) + case *Float: + p.fmtFloat(f.Value, 64, verb) + case *Int: + p.fmtInteger(uint64(f.Value), signed, verb) + case *String: + p.fmtString(f.Value, verb) + case *Bytes: + p.fmtBytes(f.Value, verb, "[]byte") + default: + p.fmtString(f.String(), verb) + } +} + +// intFromArg gets the argNumth element of a. On return, isInt reports whether +// the argument has integer type. +func intFromArg(a []Object, argNum int) (num int, isInt bool, newArgNum int) { + newArgNum = argNum + if argNum < len(a) { + var num64 int64 + num64, isInt = ToInt64(a[argNum]) + num = int(num64) + newArgNum = argNum + 1 + if tooLarge(num) { + num = 0 + isInt = false + } + } + return +} + +// parseArgNumber returns the value of the bracketed number, minus 1 +// (explicit argument numbers are one-indexed but we want zero-indexed). +// The opening bracket is known to be present at format[0]. +// The returned values are the index, the number of bytes to consume +// up to the closing paren, if present, and whether the number parsed +// ok. The bytes to consume will be 1 if no closing paren is present. +func parseArgNumber(format string) (index int, wid int, ok bool) { + // There must be at least 3 bytes: [n]. + if len(format) < 3 { + return 0, 1, false + } + + // Find closing bracket. + for i := 1; i < len(format); i++ { + if format[i] == ']' { + width, ok, newi := parsenum(format, 1, i) + if !ok || newi != i { + return 0, i + 1, false + } + // arg numbers are one-indexed andskip paren. + return width - 1, i + 1, true + } + } + return 0, 1, false +} + +// argNumber returns the next argument to evaluate, which is either the value +// of the passed-in argNum or the value of the bracketed integer that begins +// format[i:]. It also returns the new value of i, that is, the index of the +// next byte of the format to process. +func (p *pp) argNumber( + argNum int, + format string, + i int, + numArgs int, +) (newArgNum, newi int, found bool) { + if len(format) <= i || format[i] != '[' { + return argNum, i, false + } + p.reordered = true + index, wid, ok := parseArgNumber(format[i:]) + if ok && 0 <= index && index < numArgs { + return index, i + wid, true + } + p.goodArgNum = false + return argNum, i + wid, ok +} + +func (p *pp) badArgNum(verb rune) { + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteString(badIndexString) +} + +func (p *pp) missingArg(verb rune) { + _, _ = p.WriteString(percentBangString) + _, _ = p.WriteRune(verb) + _, _ = p.WriteString(missingString) +} + +func (p *pp) doFormat(format string, a []Object) (err error) { + defer func() { + if r := recover(); r != nil { + if e, ok := r.(error); ok && e == ErrStringLimit { + err = e + return + } + panic(r) + } + }() + + end := len(format) + argNum := 0 // we process one argument per non-trivial format + afterIndex := false // previous item in format was an index like [3]. + p.reordered = false +formatLoop: + for i := 0; i < end; { + p.goodArgNum = true + lasti := i + for i < end && format[i] != '%' { + i++ + } + if i > lasti { + _, _ = p.WriteString(format[lasti:i]) + } + if i >= end { + // done processing format string + break + } + + // Process one verb + i++ + + // Do we have flags? + p.fmt.clearFlags() + simpleFormat: + for ; i < end; i++ { + c := format[i] + switch c { + case '#': + p.fmt.sharp = true + case '0': + // Only allow zero padding to the left. + p.fmt.zero = !p.fmt.minus + case '+': + p.fmt.plus = true + case '-': + p.fmt.minus = true + p.fmt.zero = false // Do not pad with zeros to the right. + case ' ': + p.fmt.space = true + default: + // Fast path for common case of ascii lower case simple verbs + // without precision or width or argument indices. + if 'a' <= c && c <= 'z' && argNum < len(a) { + if c == 'v' { + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + } + p.printArg(a[argNum], rune(c)) + argNum++ + i++ + continue formatLoop + } + // Format is more complex than simple flags and a verb or is + // malformed. + break simpleFormat + } + } + + // Do we have an explicit argument index? + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + + // Do we have width? + if i < end && format[i] == '*' { + i++ + p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum) + + if !p.fmt.widPresent { + _, _ = p.WriteString(badWidthString) + } + + // We have a negative width, so take its value and ensure + // that the minus flag is set + if p.fmt.wid < 0 { + p.fmt.wid = -p.fmt.wid + p.fmt.minus = true + p.fmt.zero = false // Do not pad with zeros to the right. + } + afterIndex = false + } else { + p.fmt.wid, p.fmt.widPresent, i = parsenum(format, i, end) + if afterIndex && p.fmt.widPresent { // "%[3]2d" + p.goodArgNum = false + } + } + + // Do we have precision? + if i+1 < end && format[i] == '.' { + i++ + if afterIndex { // "%[3].2d" + p.goodArgNum = false + } + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + if i < end && format[i] == '*' { + i++ + p.fmt.prec, p.fmt.precPresent, argNum = intFromArg(a, argNum) + // Negative precision arguments don't make sense + if p.fmt.prec < 0 { + p.fmt.prec = 0 + p.fmt.precPresent = false + } + if !p.fmt.precPresent { + _, _ = p.WriteString(badPrecString) + } + afterIndex = false + } else { + p.fmt.prec, p.fmt.precPresent, i = parsenum(format, i, end) + if !p.fmt.precPresent { + p.fmt.prec = 0 + p.fmt.precPresent = true + } + } + } + + if !afterIndex { + argNum, i, afterIndex = p.argNumber(argNum, format, i, len(a)) + } + + if i >= end { + _, _ = p.WriteString(noVerbString) + break + } + + verb, size := rune(format[i]), 1 + if verb >= utf8.RuneSelf { + verb, size = utf8.DecodeRuneInString(format[i:]) + } + i += size + + switch { + case verb == '%': + // Percent does not absorb operands and ignores f.wid and f.prec. + _, _ = p.WriteSingleByte('%') + case !p.goodArgNum: + p.badArgNum(verb) + case argNum >= len(a): + // No argument left over to print for the current verb. + p.missingArg(verb) + case verb == 'v': + // Go syntax + p.fmt.sharpV = p.fmt.sharp + p.fmt.sharp = false + // Struct-field syntax + p.fmt.plusV = p.fmt.plus + p.fmt.plus = false + fallthrough + default: + p.printArg(a[argNum], verb) + argNum++ + } + } + + // Check for extra arguments unless the call accessed the arguments + // out of order, in which case it's too expensive to detect if they've all + // been used and arguably OK if they're not. + if !p.reordered && argNum < len(a) { + p.fmt.clearFlags() + _, _ = p.WriteString(extraString) + for i, arg := range a[argNum:] { + if i > 0 { + _, _ = p.WriteString(commaSpaceString) + } + if arg == nil { + _, _ = p.WriteString(UndefinedValue.String()) + } else { + _, _ = p.WriteString(arg.TypeName()) + _, _ = p.WriteSingleByte('=') + p.printArg(arg, 'v') + } + } + _, _ = p.WriteSingleByte(')') + } + + return nil +} + +// Format is like fmt.Sprintf but using Objects. +func Format(format string, a ...Object) (string, error) { + p := newPrinter() + err := p.doFormat(format, a) + s := string(p.buf) + p.free() + + return s, err +} diff --git a/vendor/github.com/d5/tengo/v2/go.mod b/vendor/github.com/d5/tengo/v2/go.mod new file mode 100644 index 00000000..732ff142 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/go.mod @@ -0,0 +1,3 @@ +module github.com/d5/tengo/v2 + +go 1.13 diff --git a/vendor/github.com/d5/tengo/v2/go.sum b/vendor/github.com/d5/tengo/v2/go.sum new file mode 100644 index 00000000..e69de29b diff --git a/vendor/github.com/d5/tengo/v2/instructions.go b/vendor/github.com/d5/tengo/v2/instructions.go new file mode 100644 index 00000000..eb1fbf27 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/instructions.go @@ -0,0 +1,61 @@ +package tengo + +import ( + "fmt" + + "github.com/d5/tengo/v2/parser" +) + +// MakeInstruction returns a bytecode for an opcode and the operands. +func MakeInstruction(opcode parser.Opcode, operands ...int) []byte { + numOperands := parser.OpcodeOperands[opcode] + + totalLen := 1 + for _, w := range numOperands { + totalLen += w + } + + instruction := make([]byte, totalLen) + instruction[0] = opcode + + offset := 1 + for i, o := range operands { + width := numOperands[i] + switch width { + case 1: + instruction[offset] = byte(o) + case 2: + n := uint16(o) + instruction[offset] = byte(n >> 8) + instruction[offset+1] = byte(n) + } + offset += width + } + return instruction +} + +// FormatInstructions returns string representation of bytecode instructions. +func FormatInstructions(b []byte, posOffset int) []string { + var out []string + + i := 0 + for i < len(b) { + numOperands := parser.OpcodeOperands[b[i]] + operands, read := parser.ReadOperands(numOperands, b[i+1:]) + + switch len(numOperands) { + case 0: + out = append(out, fmt.Sprintf("%04d %-7s", + posOffset+i, parser.OpcodeNames[b[i]])) + case 1: + out = append(out, fmt.Sprintf("%04d %-7s %-5d", + posOffset+i, parser.OpcodeNames[b[i]], operands[0])) + case 2: + out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", + posOffset+i, parser.OpcodeNames[b[i]], + operands[0], operands[1])) + } + i += 1 + read + } + return out +} diff --git a/vendor/github.com/d5/tengo/v2/iterator.go b/vendor/github.com/d5/tengo/v2/iterator.go new file mode 100644 index 00000000..13adbbab --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/iterator.go @@ -0,0 +1,209 @@ +package tengo + +// Iterator represents an iterator for underlying data type. +type Iterator interface { + Object + + // Next returns true if there are more elements to iterate. + Next() bool + + // Key returns the key or index value of the current element. + Key() Object + + // Value returns the value of the current element. + Value() Object +} + +// ArrayIterator is an iterator for an array. +type ArrayIterator struct { + ObjectImpl + v []Object + i int + l int +} + +// TypeName returns the name of the type. +func (i *ArrayIterator) TypeName() string { + return "array-iterator" +} + +func (i *ArrayIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *ArrayIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *ArrayIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *ArrayIterator) Copy() Object { + return &ArrayIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *ArrayIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *ArrayIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *ArrayIterator) Value() Object { + return i.v[i.i-1] +} + +// BytesIterator represents an iterator for a string. +type BytesIterator struct { + ObjectImpl + 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 "" +} + +// 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])} +} + +// MapIterator represents an iterator for the map. +type MapIterator struct { + ObjectImpl + v map[string]Object + k []string + i int + l int +} + +// TypeName returns the name of the type. +func (i *MapIterator) TypeName() string { + return "map-iterator" +} + +func (i *MapIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *MapIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *MapIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *MapIterator) Copy() Object { + return &MapIterator{v: i.v, k: i.k, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *MapIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *MapIterator) Key() Object { + k := i.k[i.i-1] + return &String{Value: k} +} + +// Value returns the value of the current element. +func (i *MapIterator) Value() Object { + k := i.k[i.i-1] + return i.v[k] +} + +// StringIterator represents an iterator for a string. +type StringIterator struct { + ObjectImpl + v []rune + i int + l int +} + +// TypeName returns the name of the type. +func (i *StringIterator) TypeName() string { + return "string-iterator" +} + +func (i *StringIterator) String() string { + return "" +} + +// IsFalsy returns true if the value of the type is falsy. +func (i *StringIterator) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (i *StringIterator) Equals(Object) bool { + return false +} + +// Copy returns a copy of the type. +func (i *StringIterator) Copy() Object { + return &StringIterator{v: i.v, i: i.i, l: i.l} +} + +// Next returns true if there are more elements to iterate. +func (i *StringIterator) Next() bool { + i.i++ + return i.i <= i.l +} + +// Key returns the key or index value of the current element. +func (i *StringIterator) Key() Object { + return &Int{Value: int64(i.i - 1)} +} + +// Value returns the value of the current element. +func (i *StringIterator) Value() Object { + return &Char{Value: i.v[i.i-1]} +} diff --git a/vendor/github.com/d5/tengo/v2/modules.go b/vendor/github.com/d5/tengo/v2/modules.go new file mode 100644 index 00000000..c8fcde7f --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/modules.go @@ -0,0 +1,93 @@ +package tengo + +// 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) +} + +// 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 + } +} + +// 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/v2/objects.go b/vendor/github.com/d5/tengo/v2/objects.go new file mode 100644 index 00000000..27c1d493 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/objects.go @@ -0,0 +1,1581 @@ +package tengo + +import ( + "bytes" + "fmt" + "math" + "strconv" + "strings" + "time" + + "github.com/d5/tengo/v2/parser" + "github.com/d5/tengo/v2/token" +) + +var ( + // TrueValue represents a true value. + TrueValue Object = &Bool{value: true} + + // FalseValue represents a false value. + FalseValue Object = &Bool{value: false} + + // UndefinedValue represents an undefined value. + UndefinedValue Object = &Undefined{} +) + +// Object represents an object in the VM. +type Object interface { + // TypeName should return the name of the type. + TypeName() string + + // String should return a string representation of the type's value. + String() string + + // BinaryOp should return another object that is the result of a given + // binary operator and a right-hand side object. If BinaryOp returns an + // error, the VM will treat it as a run-time error. + BinaryOp(op token.Token, rhs Object) (Object, error) + + // IsFalsy should return true if the value of the type should be considered + // as falsy. + IsFalsy() bool + + // Equals should return true if the value of the type should be considered + // as equal to the value of another object. + Equals(another Object) bool + + // Copy should return a copy of the type (and its value). Copy function + // will be used for copy() builtin function which is expected to deep-copy + // the values generally. + Copy() Object + + // IndexGet should take an index Object and return a result Object or an + // error for indexable objects. Indexable is an object that can take an + // index and return an object. If error is returned, the runtime will treat + // it as a run-time error and ignore returned value. If Object is not + // indexable, ErrNotIndexable should be returned as error. If nil is + // returned as value, it will be converted to UndefinedToken value by the + // runtime. + IndexGet(index Object) (value Object, err error) + + // IndexSet should take an index Object and a value Object for index + // assignable objects. Index assignable is an object that can take an index + // and a value on the left-hand side of the assignment statement. If Object + // is not index assignable, ErrNotIndexAssignable should be returned as + // error. If an error is returned, it will be treated as a run-time error. + IndexSet(index, value Object) error + + // Iterate should return an Iterator for the type. + Iterate() Iterator + + // CanIterate should return whether the Object can be Iterated. + CanIterate() bool + + // Call should take an arbitrary number of arguments and returns a return + // value and/or an error, which the VM will consider as a run-time error. + Call(args ...Object) (ret Object, err error) + + // CanCall should return whether the Object can be Called. + CanCall() bool +} + +// ObjectImpl represents a default Object Implementation. To defined a new +// value type, one can embed ObjectImpl in their type declarations to avoid +// implementing all non-significant methods. TypeName() and String() methods +// still need to be implemented. +type ObjectImpl struct { +} + +// TypeName returns the name of the type. +func (o *ObjectImpl) TypeName() string { + panic(ErrNotImplemented) +} + +func (o *ObjectImpl) String() string { + panic(ErrNotImplemented) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *ObjectImpl) BinaryOp(_ token.Token, _ Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ObjectImpl) Copy() Object { + return nil +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ObjectImpl) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ObjectImpl) Equals(x Object) bool { + return o == x +} + +// IndexGet returns an element at a given index. +func (o *ObjectImpl) IndexGet(_ Object) (res Object, err error) { + return nil, ErrNotIndexable +} + +// IndexSet sets an element at a given index. +func (o *ObjectImpl) IndexSet(_, _ Object) (err error) { + return ErrNotIndexAssignable +} + +// Iterate returns an iterator. +func (o *ObjectImpl) Iterate() Iterator { + return nil +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ObjectImpl) CanIterate() bool { + return false +} + +// Call takes an arbitrary number of arguments and returns a return value +// and/or an error. +func (o *ObjectImpl) Call(_ ...Object) (ret Object, err error) { + return nil, nil +} + +// CanCall returns whether the Object can be Called. +func (o *ObjectImpl) CanCall() bool { + return false +} + +// Array represents an array of objects. +type Array struct { + ObjectImpl + Value []Object +} + +// TypeName returns the name of the type. +func (o *Array) TypeName() string { + return "array" +} + +func (o *Array) String() string { + var elements []string + for _, e := range o.Value { + elements = append(elements, e.String()) + } + return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Array) BinaryOp(op token.Token, rhs Object) (Object, error) { + if rhs, ok := rhs.(*Array); ok { + switch op { + case token.Add: + if len(rhs.Value) == 0 { + return o, nil + } + return &Array{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Array) Copy() Object { + var c []Object + for _, elem := range o.Value { + c = append(c, elem.Copy()) + } + return &Array{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Array) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Array) Equals(x Object) bool { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for i, e := range o.Value { + if !e.Equals(xVal[i]) { + return false + } + } + return true +} + +// IndexGet returns an element at a given index. +func (o *Array) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = o.Value[idxVal] + return +} + +// IndexSet sets an element at a given index. +func (o *Array) IndexSet(index, value Object) (err error) { + intIdx, ok := ToInt(index) + if !ok { + err = ErrInvalidIndexType + return + } + if intIdx < 0 || intIdx >= len(o.Value) { + err = ErrIndexOutOfBounds + return + } + o.Value[intIdx] = value + return nil +} + +// Iterate creates an array iterator. +func (o *Array) Iterate() Iterator { + return &ArrayIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Array) CanIterate() bool { + return true +} + +// Bool represents a boolean value. +type Bool struct { + ObjectImpl + + // this is intentionally non-public to force using objects.TrueValue and + // FalseValue always + value bool +} + +func (o *Bool) String() string { + if o.value { + return "true" + } + + return "false" +} + +// TypeName returns the name of the type. +func (o *Bool) TypeName() string { + return "bool" +} + +// Copy returns a copy of the type. +func (o *Bool) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Bool) IsFalsy() bool { + return !o.value +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Bool) Equals(x Object) bool { + return o == x +} + +// GobDecode decodes bool value from input bytes. +func (o *Bool) GobDecode(b []byte) (err error) { + o.value = b[0] == 1 + return +} + +// GobEncode encodes bool values into bytes. +func (o *Bool) GobEncode() (b []byte, err error) { + if o.value { + b = []byte{1} + } else { + b = []byte{0} + } + return +} + +// BuiltinFunction represents a builtin function. +type BuiltinFunction struct { + ObjectImpl + Name string + Value CallableFunc +} + +// TypeName returns the name of the type. +func (o *BuiltinFunction) TypeName() string { + return "builtin-function:" + o.Name +} + +func (o *BuiltinFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *BuiltinFunction) Copy() Object { + return &BuiltinFunction{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *BuiltinFunction) Equals(_ Object) bool { + return false +} + +// Call executes a builtin function. +func (o *BuiltinFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} + +// CanCall returns whether the Object can be Called. +func (o *BuiltinFunction) CanCall() bool { + return true +} + +// 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} +} + +// Bytes represents a byte array. +type Bytes struct { + ObjectImpl + Value []byte +} + +func (o *Bytes) String() string { + return string(o.Value) +} + +// TypeName returns the name of the type. +func (o *Bytes) TypeName() string { + return "bytes" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch op { + case token.Add: + switch rhs := rhs.(type) { + case *Bytes: + if len(o.Value)+len(rhs.Value) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Bytes) Copy() Object { + return &Bytes{Value: append([]byte{}, o.Value...)} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Bytes) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Bytes) Equals(x Object) bool { + t, ok := x.(*Bytes) + if !ok { + return false + } + return bytes.Equal(o.Value, t.Value) +} + +// IndexGet returns an element (as Int) at a given index. +func (o *Bytes) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = &Int{Value: int64(o.Value[idxVal])} + return +} + +// Iterate creates a bytes iterator. +func (o *Bytes) Iterate() Iterator { + return &BytesIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Bytes) CanIterate() bool { + return true +} + +// Char represents a character value. +type Char struct { + ObjectImpl + Value rune +} + +func (o *Char) String() string { + return string(o.Value) +} + +// TypeName returns the name of the type. +func (o *Char) TypeName() string { + return "char" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Char) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Char: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Int: + switch op { + case token.Add: + r := o.Value + rune(rhs.Value) + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Sub: + r := o.Value - rune(rhs.Value) + if r == o.Value { + return o, nil + } + return &Char{Value: r}, nil + case token.Less: + if int64(o.Value) < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if int64(o.Value) > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if int64(o.Value) <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if int64(o.Value) >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Char) Copy() Object { + return &Char{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Char) IsFalsy() bool { + return o.Value == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Char) Equals(x Object) bool { + t, ok := x.(*Char) + if !ok { + return false + } + return o.Value == t.Value +} + +// CompiledFunction represents a compiled function. +type CompiledFunction struct { + ObjectImpl + Instructions []byte + NumLocals int // number of local variables (including function parameters) + NumParameters int + VarArgs bool + SourceMap map[int]parser.Pos + Free []*ObjectPtr +} + +// TypeName returns the name of the type. +func (o *CompiledFunction) TypeName() string { + return "compiled-function" +} + +func (o *CompiledFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *CompiledFunction) Copy() Object { + return &CompiledFunction{ + Instructions: append([]byte{}, o.Instructions...), + NumLocals: o.NumLocals, + NumParameters: o.NumParameters, + VarArgs: o.VarArgs, + Free: append([]*ObjectPtr{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers + } +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *CompiledFunction) Equals(_ Object) bool { + return false +} + +// SourcePos returns the source position of the instruction at ip. +func (o *CompiledFunction) SourcePos(ip int) parser.Pos { + for ip >= 0 { + if p, ok := o.SourceMap[ip]; ok { + return p + } + ip-- + } + return parser.NoPos +} + +// CanCall returns whether the Object can be Called. +func (o *CompiledFunction) CanCall() bool { + return true +} + +// Error represents an error value. +type Error struct { + ObjectImpl + Value Object +} + +// TypeName returns the name of the type. +func (o *Error) TypeName() string { + return "error" +} + +func (o *Error) String() string { + if o.Value != nil { + return fmt.Sprintf("error: %s", o.Value.String()) + } + return "error" +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Error) IsFalsy() bool { + return true // error is always false. +} + +// Copy returns a copy of the type. +func (o *Error) Copy() Object { + return &Error{Value: o.Value.Copy()} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Error) Equals(x Object) bool { + return o == x // pointer equality +} + +// IndexGet returns an element at a given index. +func (o *Error) IndexGet(index Object) (res Object, err error) { + if strIdx, _ := ToString(index); strIdx != "value" { + err = ErrInvalidIndexOnError + return + } + res = o.Value + return +} + +// Float represents a floating point number value. +type Float struct { + ObjectImpl + Value float64 +} + +func (o *Float) String() string { + return strconv.FormatFloat(o.Value, 'f', -1, 64) +} + +// TypeName returns the name of the type. +func (o *Float) TypeName() string { + return "float" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Float) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Float: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Mul: + r := o.Value * rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Quo: + r := o.Value / rhs.Value + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Int: + switch op { + case token.Add: + r := o.Value + float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Sub: + r := o.Value - float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Mul: + r := o.Value * float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Quo: + r := o.Value / float64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Float{Value: r}, nil + case token.Less: + if o.Value < float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= float64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Float) Copy() Object { + return &Float{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Float) IsFalsy() bool { + return math.IsNaN(o.Value) +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Float) Equals(x Object) bool { + t, ok := x.(*Float) + if !ok { + return false + } + return o.Value == t.Value +} + +// ImmutableArray represents an immutable array of objects. +type ImmutableArray struct { + ObjectImpl + Value []Object +} + +// TypeName returns the name of the type. +func (o *ImmutableArray) TypeName() string { + return "immutable-array" +} + +func (o *ImmutableArray) String() string { + var elements []string + for _, e := range o.Value { + elements = append(elements, e.String()) + } + return fmt.Sprintf("[%s]", strings.Join(elements, ", ")) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *ImmutableArray) BinaryOp(op token.Token, rhs Object) (Object, error) { + if rhs, ok := rhs.(*ImmutableArray); ok { + switch op { + case token.Add: + return &Array{Value: append(o.Value, rhs.Value...)}, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ImmutableArray) Copy() Object { + var c []Object + for _, elem := range o.Value { + c = append(c, elem.Copy()) + } + return &Array{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ImmutableArray) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ImmutableArray) Equals(x Object) bool { + var xVal []Object + switch x := x.(type) { + case *Array: + xVal = x.Value + case *ImmutableArray: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for i, e := range o.Value { + if !e.Equals(xVal[i]) { + return false + } + } + return true +} + +// IndexGet returns an element at a given index. +func (o *ImmutableArray) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if idxVal < 0 || idxVal >= len(o.Value) { + res = UndefinedValue + return + } + res = o.Value[idxVal] + return +} + +// Iterate creates an array iterator. +func (o *ImmutableArray) Iterate() Iterator { + return &ArrayIterator{ + v: o.Value, + l: len(o.Value), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ImmutableArray) CanIterate() bool { + return true +} + +// ImmutableMap represents an immutable map object. +type ImmutableMap struct { + ObjectImpl + Value map[string]Object +} + +// TypeName returns the name of the type. +func (o *ImmutableMap) TypeName() string { + return "immutable-map" +} + +func (o *ImmutableMap) String() string { + var pairs []string + for k, v := range o.Value { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) + } + return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) +} + +// Copy returns a copy of the type. +func (o *ImmutableMap) Copy() Object { + c := make(map[string]Object) + for k, v := range o.Value { + c[k] = v.Copy() + } + return &Map{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ImmutableMap) IsFalsy() bool { + return len(o.Value) == 0 +} + +// IndexGet returns the value for the given key. +func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + res, ok = o.Value[strIdx] + if !ok { + res = UndefinedValue + } + return +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *ImmutableMap) Equals(x Object) bool { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for k, v := range o.Value { + tv := xVal[k] + if !v.Equals(tv) { + return false + } + } + return true +} + +// Iterate creates an immutable map iterator. +func (o *ImmutableMap) Iterate() Iterator { + var keys []string + for k := range o.Value { + keys = append(keys, k) + } + return &MapIterator{ + v: o.Value, + k: keys, + l: len(keys), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *ImmutableMap) CanIterate() bool { + return true +} + +// Int represents an integer value. +type Int struct { + ObjectImpl + Value int64 +} + +func (o *Int) String() string { + return strconv.FormatInt(o.Value, 10) +} + +// TypeName returns the name of the type. +func (o *Int) TypeName() string { + return "int" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Int) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Int: + switch op { + case token.Add: + r := o.Value + rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Sub: + r := o.Value - rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Mul: + r := o.Value * rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Quo: + r := o.Value / rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Rem: + r := o.Value % rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.And: + r := o.Value & rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Or: + r := o.Value | rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Xor: + r := o.Value ^ rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.AndNot: + r := o.Value &^ rhs.Value + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Shl: + r := o.Value << uint64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Shr: + r := o.Value >> uint64(rhs.Value) + if r == o.Value { + return o, nil + } + return &Int{Value: r}, nil + case token.Less: + if o.Value < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Float: + switch op { + case token.Add: + return &Float{Value: float64(o.Value) + rhs.Value}, nil + case token.Sub: + return &Float{Value: float64(o.Value) - rhs.Value}, nil + case token.Mul: + return &Float{Value: float64(o.Value) * rhs.Value}, nil + case token.Quo: + return &Float{Value: float64(o.Value) / rhs.Value}, nil + case token.Less: + if float64(o.Value) < rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if float64(o.Value) > rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if float64(o.Value) <= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if float64(o.Value) >= rhs.Value { + return TrueValue, nil + } + return FalseValue, nil + } + case *Char: + switch op { + case token.Add: + return &Char{Value: rune(o.Value) + rhs.Value}, nil + case token.Sub: + return &Char{Value: rune(o.Value) - rhs.Value}, nil + case token.Less: + if o.Value < int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value > int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value <= int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value >= int64(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Int) Copy() Object { + return &Int{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Int) IsFalsy() bool { + return o.Value == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Int) Equals(x Object) bool { + t, ok := x.(*Int) + if !ok { + return false + } + return o.Value == t.Value +} + +// Map represents a map of objects. +type Map struct { + ObjectImpl + Value map[string]Object +} + +// TypeName returns the name of the type. +func (o *Map) TypeName() string { + return "map" +} + +func (o *Map) String() string { + var pairs []string + for k, v := range o.Value { + pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) + } + return fmt.Sprintf("{%s}", strings.Join(pairs, ", ")) +} + +// Copy returns a copy of the type. +func (o *Map) Copy() Object { + c := make(map[string]Object) + for k, v := range o.Value { + c[k] = v.Copy() + } + return &Map{Value: c} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Map) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Map) Equals(x Object) bool { + var xVal map[string]Object + switch x := x.(type) { + case *Map: + xVal = x.Value + case *ImmutableMap: + xVal = x.Value + default: + return false + } + if len(o.Value) != len(xVal) { + return false + } + for k, v := range o.Value { + tv := xVal[k] + if !v.Equals(tv) { + return false + } + } + return true +} + +// IndexGet returns the value for the given key. +func (o *Map) IndexGet(index Object) (res Object, err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + res, ok = o.Value[strIdx] + if !ok { + res = UndefinedValue + } + return +} + +// IndexSet sets the value for the given key. +func (o *Map) IndexSet(index, value Object) (err error) { + strIdx, ok := ToString(index) + if !ok { + err = ErrInvalidIndexType + return + } + o.Value[strIdx] = value + return nil +} + +// Iterate creates a map iterator. +func (o *Map) Iterate() Iterator { + var keys []string + for k := range o.Value { + keys = append(keys, k) + } + return &MapIterator{ + v: o.Value, + k: keys, + l: len(keys), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Map) CanIterate() bool { + return true +} + +// ObjectPtr represents a free variable. +type ObjectPtr struct { + ObjectImpl + Value *Object +} + +func (o *ObjectPtr) String() string { + return "free-var" +} + +// TypeName returns the name of the type. +func (o *ObjectPtr) TypeName() string { + return "" +} + +// 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 +} + +// String represents a string value. +type String struct { + ObjectImpl + Value string + runeStr []rune +} + +// TypeName returns the name of the type. +func (o *String) TypeName() string { + return "string" +} + +func (o *String) String() string { + return strconv.Quote(o.Value) +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *String) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch op { + case token.Add: + switch rhs := rhs.(type) { + case *String: + if len(o.Value)+len(rhs.Value) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: o.Value + rhs.Value}, nil + default: + rhsStr := rhs.String() + if len(o.Value)+len(rhsStr) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: o.Value + rhsStr}, nil + } + } + return nil, ErrInvalidOperator +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *String) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Copy returns a copy of the type. +func (o *String) Copy() Object { + return &String{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *String) Equals(x Object) bool { + t, ok := x.(*String) + if !ok { + return false + } + return o.Value == t.Value +} + +// IndexGet returns a character at a given index. +func (o *String) IndexGet(index Object) (res Object, err error) { + intIdx, ok := index.(*Int) + if !ok { + err = ErrInvalidIndexType + return + } + idxVal := int(intIdx.Value) + if o.runeStr == nil { + o.runeStr = []rune(o.Value) + } + if idxVal < 0 || idxVal >= len(o.runeStr) { + res = UndefinedValue + return + } + res = &Char{Value: o.runeStr[idxVal]} + return +} + +// Iterate creates a string iterator. +func (o *String) Iterate() Iterator { + if o.runeStr == nil { + o.runeStr = []rune(o.Value) + } + return &StringIterator{ + v: o.runeStr, + l: len(o.runeStr), + } +} + +// CanIterate returns whether the Object can be Iterated. +func (o *String) CanIterate() bool { + return true +} + +// Time represents a time value. +type Time struct { + ObjectImpl + Value time.Time +} + +func (o *Time) String() string { + return o.Value.String() +} + +// TypeName returns the name of the type. +func (o *Time) TypeName() string { + return "time" +} + +// BinaryOp returns another object that is the result of a given binary +// operator and a right-hand side object. +func (o *Time) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch rhs := rhs.(type) { + case *Int: + switch op { + case token.Add: // time + int => time + if rhs.Value == 0 { + return o, nil + } + return &Time{Value: o.Value.Add(time.Duration(rhs.Value))}, nil + case token.Sub: // time - int => time + if rhs.Value == 0 { + return o, nil + } + return &Time{Value: o.Value.Add(time.Duration(-rhs.Value))}, nil + } + case *Time: + switch op { + case token.Sub: // time - time => int (duration) + return &Int{Value: int64(o.Value.Sub(rhs.Value))}, nil + case token.Less: // time < time => bool + if o.Value.Before(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.Greater: + if o.Value.After(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.LessEq: + if o.Value.Equal(rhs.Value) || o.Value.Before(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + case token.GreaterEq: + if o.Value.Equal(rhs.Value) || o.Value.After(rhs.Value) { + return TrueValue, nil + } + return FalseValue, nil + } + } + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Time) Copy() Object { + return &Time{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Time) IsFalsy() bool { + return o.Value.IsZero() +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Time) Equals(x Object) bool { + t, ok := x.(*Time) + if !ok { + return false + } + return o.Value.Equal(t.Value) +} + +// Undefined represents an undefined value. +type Undefined struct { + ObjectImpl +} + +// TypeName returns the name of the type. +func (o *Undefined) TypeName() string { + return "undefined" +} + +func (o *Undefined) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *Undefined) Copy() Object { + return o +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Undefined) IsFalsy() bool { + return true +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *Undefined) Equals(x Object) bool { + return o == x +} + +// IndexGet returns an element at a given index. +func (o *Undefined) IndexGet(_ Object) (Object, error) { + return UndefinedValue, nil +} + +// Iterate creates a map iterator. +func (o *Undefined) Iterate() Iterator { + return o +} + +// CanIterate returns whether the Object can be Iterated. +func (o *Undefined) CanIterate() bool { + return true +} + +// Next returns true if there are more elements to iterate. +func (o *Undefined) Next() bool { + return false +} + +// Key returns the key or index value of the current element. +func (o *Undefined) Key() Object { + return o +} + +// Value returns the value of the current element. +func (o *Undefined) Value() Object { + return o +} + +// UserFunction represents a user function. +type UserFunction struct { + ObjectImpl + Name string + Value CallableFunc + EncodingID string +} + +// TypeName returns the name of the type. +func (o *UserFunction) TypeName() string { + return "user-function:" + o.Name +} + +func (o *UserFunction) String() string { + return "" +} + +// Copy returns a copy of the type. +func (o *UserFunction) Copy() Object { + return &UserFunction{Value: o.Value} +} + +// Equals returns true if the value of the type is equal to the value of +// another object. +func (o *UserFunction) Equals(_ Object) bool { + return false +} + +// Call invokes a user function. +func (o *UserFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} + +// CanCall returns whether the Object can be Called. +func (o *UserFunction) CanCall() bool { + return true +} diff --git a/vendor/github.com/d5/tengo/v2/parser/ast.go b/vendor/github.com/d5/tengo/v2/parser/ast.go new file mode 100644 index 00000000..8c2f7c07 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/ast.go @@ -0,0 +1,69 @@ +package parser + +import ( + "strings" +) + +const ( + nullRep = "" +) + +// Node represents a node in the AST. +type Node interface { + // Pos returns the position of first character belonging to the node. + Pos() Pos + // End returns the position of first character immediately after the node. + End() Pos + // String returns a string representation of the node. + String() string +} + +// IdentList represents a list of identifiers. +type IdentList struct { + LParen Pos + VarArgs bool + List []*Ident + RParen Pos +} + +// Pos returns the position of first character belonging to the node. +func (n *IdentList) Pos() Pos { + if n.LParen.IsValid() { + return n.LParen + } + if len(n.List) > 0 { + return n.List[0].Pos() + } + return NoPos +} + +// End returns the position of first character immediately after the node. +func (n *IdentList) End() Pos { + if n.RParen.IsValid() { + return n.RParen + 1 + } + if l := len(n.List); l > 0 { + return n.List[l-1].End() + } + return NoPos +} + +// NumFields returns the number of fields. +func (n *IdentList) NumFields() int { + if n == nil { + return 0 + } + return len(n.List) +} + +func (n *IdentList) String() string { + var list []string + for i, e := range n.List { + if n.VarArgs && i == len(n.List)-1 { + list = append(list, "..."+e.String()) + } else { + list = append(list, e.String()) + } + } + return "(" + strings.Join(list, ", ") + ")" +} diff --git a/vendor/github.com/d5/tengo/v2/parser/expr.go b/vendor/github.com/d5/tengo/v2/parser/expr.go new file mode 100644 index 00000000..71e5155b --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/expr.go @@ -0,0 +1,597 @@ +package parser + +import ( + "strings" + + "github.com/d5/tengo/v2/token" +) + +// Expr represents an expression node in the AST. +type Expr interface { + Node + exprNode() +} + +// ArrayLit represents an array literal. +type ArrayLit struct { + Elements []Expr + LBrack Pos + RBrack Pos +} + +func (e *ArrayLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ArrayLit) Pos() Pos { + return e.LBrack +} + +// End returns the position of first character immediately after the node. +func (e *ArrayLit) End() Pos { + return e.RBrack + 1 +} + +func (e *ArrayLit) String() string { + var elements []string + for _, m := range e.Elements { + elements = append(elements, m.String()) + } + return "[" + strings.Join(elements, ", ") + "]" +} + +// BadExpr represents a bad expression. +type BadExpr struct { + From Pos + To Pos +} + +func (e *BadExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BadExpr) Pos() Pos { + return e.From +} + +// End returns the position of first character immediately after the node. +func (e *BadExpr) End() Pos { + return e.To +} + +func (e *BadExpr) String() string { + return "" +} + +// BinaryExpr represents a binary operator expression. +type BinaryExpr struct { + LHS Expr + RHS Expr + Token token.Token + TokenPos Pos +} + +func (e *BinaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BinaryExpr) Pos() Pos { + return e.LHS.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *BinaryExpr) End() Pos { + return e.RHS.End() +} + +func (e *BinaryExpr) String() string { + return "(" + e.LHS.String() + " " + e.Token.String() + + " " + e.RHS.String() + ")" +} + +// BoolLit represents a boolean literal. +type BoolLit struct { + Value bool + ValuePos Pos + Literal string +} + +func (e *BoolLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BoolLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *BoolLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *BoolLit) String() string { + return e.Literal +} + +// CallExpr represents a function call expression. +type CallExpr struct { + Func Expr + LParen Pos + Args []Expr + RParen Pos +} + +func (e *CallExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CallExpr) Pos() Pos { + return e.Func.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CallExpr) End() Pos { + return e.RParen + 1 +} + +func (e *CallExpr) String() string { + var args []string + for _, e := range e.Args { + args = append(args, e.String()) + } + return e.Func.String() + "(" + strings.Join(args, ", ") + ")" +} + +// CharLit represents a character literal. +type CharLit struct { + Value rune + ValuePos Pos + Literal string +} + +func (e *CharLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CharLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *CharLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *CharLit) String() string { + return e.Literal +} + +// CondExpr represents a ternary conditional expression. +type CondExpr struct { + Cond Expr + True Expr + False Expr + QuestionPos Pos + ColonPos Pos +} + +func (e *CondExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CondExpr) Pos() Pos { + return e.Cond.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CondExpr) End() Pos { + return e.False.End() +} + +func (e *CondExpr) String() string { + return "(" + e.Cond.String() + " ? " + e.True.String() + + " : " + e.False.String() + ")" +} + +// ErrorExpr represents an error expression +type ErrorExpr struct { + Expr Expr + ErrorPos Pos + LParen Pos + RParen Pos +} + +func (e *ErrorExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ErrorExpr) Pos() Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ErrorExpr) End() Pos { + return e.RParen +} + +func (e *ErrorExpr) String() string { + return "error(" + e.Expr.String() + ")" +} + +// FloatLit represents a floating point literal. +type FloatLit struct { + Value float64 + ValuePos Pos + Literal string +} + +func (e *FloatLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FloatLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *FloatLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *FloatLit) String() string { + return e.Literal +} + +// FuncLit represents a function literal. +type FuncLit struct { + Type *FuncType + Body *BlockStmt +} + +func (e *FuncLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FuncLit) Pos() Pos { + return e.Type.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *FuncLit) End() Pos { + return e.Body.End() +} + +func (e *FuncLit) String() string { + return "func" + e.Type.Params.String() + " " + e.Body.String() +} + +// FuncType represents a function type definition. +type FuncType struct { + FuncPos Pos + Params *IdentList +} + +func (e *FuncType) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FuncType) Pos() Pos { + return e.FuncPos +} + +// End returns the position of first character immediately after the node. +func (e *FuncType) End() Pos { + return e.Params.End() +} + +func (e *FuncType) String() string { + return "func" + e.Params.String() +} + +// Ident represents an identifier. +type Ident struct { + Name string + NamePos Pos +} + +func (e *Ident) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *Ident) Pos() Pos { + return e.NamePos +} + +// End returns the position of first character immediately after the node. +func (e *Ident) End() Pos { + return Pos(int(e.NamePos) + len(e.Name)) +} + +func (e *Ident) String() string { + if e != nil { + return e.Name + } + return nullRep +} + +// ImmutableExpr represents an immutable expression +type ImmutableExpr struct { + Expr Expr + ErrorPos Pos + LParen Pos + RParen Pos +} + +func (e *ImmutableExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImmutableExpr) Pos() Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ImmutableExpr) End() Pos { + return e.RParen +} + +func (e *ImmutableExpr) String() string { + return "immutable(" + e.Expr.String() + ")" +} + +// ImportExpr represents an import expression +type ImportExpr struct { + ModuleName string + Token token.Token + TokenPos Pos +} + +func (e *ImportExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImportExpr) Pos() Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *ImportExpr) End() Pos { + // import("moduleName") + return Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) +} + +func (e *ImportExpr) String() string { + return `import("` + e.ModuleName + `")"` +} + +// IndexExpr represents an index expression. +type IndexExpr struct { + Expr Expr + LBrack Pos + Index Expr + RBrack Pos +} + +func (e *IndexExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IndexExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *IndexExpr) End() Pos { + return e.RBrack + 1 +} + +func (e *IndexExpr) String() string { + var index string + if e.Index != nil { + index = e.Index.String() + } + return e.Expr.String() + "[" + index + "]" +} + +// IntLit represents an integer literal. +type IntLit struct { + Value int64 + ValuePos Pos + Literal string +} + +func (e *IntLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IntLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *IntLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *IntLit) String() string { + return e.Literal +} + +// MapElementLit represents a map element. +type MapElementLit struct { + Key string + KeyPos Pos + ColonPos Pos + Value Expr +} + +func (e *MapElementLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapElementLit) Pos() Pos { + return e.KeyPos +} + +// End returns the position of first character immediately after the node. +func (e *MapElementLit) End() Pos { + return e.Value.End() +} + +func (e *MapElementLit) String() string { + return e.Key + ": " + e.Value.String() +} + +// MapLit represents a map literal. +type MapLit struct { + LBrace Pos + Elements []*MapElementLit + RBrace Pos +} + +func (e *MapLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapLit) Pos() Pos { + return e.LBrace +} + +// End returns the position of first character immediately after the node. +func (e *MapLit) End() Pos { + return e.RBrace + 1 +} + +func (e *MapLit) String() string { + var elements []string + for _, m := range e.Elements { + elements = append(elements, m.String()) + } + return "{" + strings.Join(elements, ", ") + "}" +} + +// ParenExpr represents a parenthesis wrapped expression. +type ParenExpr struct { + Expr Expr + LParen Pos + RParen Pos +} + +func (e *ParenExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ParenExpr) Pos() Pos { + return e.LParen +} + +// End returns the position of first character immediately after the node. +func (e *ParenExpr) End() Pos { + return e.RParen + 1 +} + +func (e *ParenExpr) String() string { + return "(" + e.Expr.String() + ")" +} + +// SelectorExpr represents a selector expression. +type SelectorExpr struct { + Expr Expr + Sel Expr +} + +func (e *SelectorExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *SelectorExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SelectorExpr) End() Pos { + return e.Sel.End() +} + +func (e *SelectorExpr) String() string { + return e.Expr.String() + "." + e.Sel.String() +} + +// SliceExpr represents a slice expression. +type SliceExpr struct { + Expr Expr + LBrack Pos + Low Expr + High Expr + RBrack Pos +} + +func (e *SliceExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *SliceExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SliceExpr) End() Pos { + return e.RBrack + 1 +} + +func (e *SliceExpr) String() string { + var low, high string + if e.Low != nil { + low = e.Low.String() + } + if e.High != nil { + high = e.High.String() + } + return e.Expr.String() + "[" + low + ":" + high + "]" +} + +// StringLit represents a string literal. +type StringLit struct { + Value string + ValuePos Pos + Literal string +} + +func (e *StringLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *StringLit) Pos() Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *StringLit) End() Pos { + return Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *StringLit) String() string { + return e.Literal +} + +// UnaryExpr represents an unary operator expression. +type UnaryExpr struct { + Expr Expr + Token token.Token + TokenPos Pos +} + +func (e *UnaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UnaryExpr) Pos() Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *UnaryExpr) End() Pos { + return e.Expr.End() +} + +func (e *UnaryExpr) String() string { + return "(" + e.Token.String() + e.Expr.String() + ")" +} + +// UndefinedLit represents an undefined literal. +type UndefinedLit struct { + TokenPos Pos +} + +func (e *UndefinedLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UndefinedLit) Pos() Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *UndefinedLit) End() Pos { + return e.TokenPos + 9 // len(undefined) == 9 +} + +func (e *UndefinedLit) String() string { + return "undefined" +} diff --git a/vendor/github.com/d5/tengo/v2/parser/file.go b/vendor/github.com/d5/tengo/v2/parser/file.go new file mode 100644 index 00000000..7cf50fea --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/file.go @@ -0,0 +1,29 @@ +package parser + +import ( + "strings" +) + +// File represents a file unit. +type File struct { + InputFile *SourceFile + Stmts []Stmt +} + +// Pos returns the position of first character belonging to the node. +func (n *File) Pos() Pos { + return Pos(n.InputFile.Base) +} + +// End returns the position of first character immediately after the node. +func (n *File) End() Pos { + return Pos(n.InputFile.Base + n.InputFile.Size) +} + +func (n *File) String() string { + var stmts []string + for _, e := range n.Stmts { + stmts = append(stmts, e.String()) + } + return strings.Join(stmts, "; ") +} diff --git a/vendor/github.com/d5/tengo/v2/parser/opcodes.go b/vendor/github.com/d5/tengo/v2/parser/opcodes.go new file mode 100644 index 00000000..a4fbfbaf --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/opcodes.go @@ -0,0 +1,156 @@ +package parser + +// Opcode represents a single byte operation code. +type Opcode = byte + +// List of opcodes +const ( + 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 + OpSuspend // Suspend VM +) + +// OpcodeNames are string representation of opcodes. +var OpcodeNames = [...]string{ + 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", + OpSuspend: "SUSPEND", +} + +// OpcodeOperands is the number of operands. +var OpcodeOperands = [...][]int{ + 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}, + OpSuspend: {}, +} + +// ReadOperands reads operands from the bytecode. +func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) { + for _, width := range numOperands { + switch width { + case 1: + operands = append(operands, int(ins[offset])) + case 2: + operands = append(operands, int(ins[offset+1])|int(ins[offset])<<8) + } + offset += width + } + return +} diff --git a/vendor/github.com/d5/tengo/v2/parser/parser.go b/vendor/github.com/d5/tengo/v2/parser/parser.go new file mode 100644 index 00000000..501a9106 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/parser.go @@ -0,0 +1,1196 @@ +package parser + +import ( + "fmt" + "io" + "sort" + "strconv" + + "github.com/d5/tengo/v2/token" +) + +type bailout struct{} + +var stmtStart = map[token.Token]bool{ + token.Break: true, + token.Continue: true, + token.For: true, + token.If: true, + token.Return: true, + token.Export: true, +} + +// Error represents a parser error. +type Error struct { + Pos SourceFilePos + Msg string +} + +func (e Error) Error() string { + if e.Pos.Filename != "" || e.Pos.IsValid() { + return fmt.Sprintf("Parse Error: %s\n\tat %s", e.Msg, e.Pos) + } + return fmt.Sprintf("Parse Error: %s", e.Msg) +} + +// ErrorList is a collection of parser errors. +type ErrorList []*Error + +// Add adds a new parser error to the collection. +func (p *ErrorList) Add(pos SourceFilePos, msg string) { + *p = append(*p, &Error{pos, msg}) +} + +// Len returns the number of elements in the collection. +func (p ErrorList) Len() int { + return len(p) +} + +func (p ErrorList) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func (p ErrorList) Less(i, j int) bool { + e := &p[i].Pos + f := &p[j].Pos + + if e.Filename != f.Filename { + return e.Filename < f.Filename + } + if e.Line != f.Line { + return e.Line < f.Line + } + if e.Column != f.Column { + return e.Column < f.Column + } + return p[i].Msg < p[j].Msg +} + +// Sort sorts the collection. +func (p ErrorList) Sort() { + sort.Sort(p) +} + +func (p ErrorList) Error() string { + switch len(p) { + case 0: + return "no errors" + case 1: + return p[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) +} + +// Err returns an error. +func (p ErrorList) Err() error { + if len(p) == 0 { + return nil + } + return p +} + +// Parser parses the Tengo source files. It's based on Go's parser +// implementation. +type Parser struct { + file *SourceFile + errors ErrorList + scanner *Scanner + pos Pos + token token.Token + tokenLit string + exprLevel int // < 0: in control clause, >= 0: in expression + syncPos Pos // last sync position + syncCount int // number of advance calls without progress + trace bool + indent int + traceOut io.Writer +} + +// NewParser creates a Parser. +func NewParser(file *SourceFile, src []byte, trace io.Writer) *Parser { + p := &Parser{ + file: file, + trace: trace != nil, + traceOut: trace, + } + p.scanner = NewScanner(p.file, src, + func(pos SourceFilePos, msg string) { + p.errors.Add(pos, msg) + }, 0) + p.next() + return p +} + +// ParseFile parses the source and returns an AST file unit. +func (p *Parser) ParseFile() (file *File, err error) { + defer func() { + if e := recover(); e != nil { + if _, ok := e.(bailout); !ok { + panic(e) + } + } + + p.errors.Sort() + err = p.errors.Err() + }() + + if p.trace { + defer untracep(tracep(p, "File")) + } + + if p.errors.Len() > 0 { + return nil, p.errors.Err() + } + + stmts := p.parseStmtList() + if p.errors.Len() > 0 { + return nil, p.errors.Err() + } + + file = &File{ + InputFile: p.file, + Stmts: stmts, + } + return +} + +func (p *Parser) parseExpr() Expr { + if p.trace { + defer untracep(tracep(p, "Expression")) + } + + expr := p.parseBinaryExpr(token.LowestPrec + 1) + + // ternary conditional expression + if p.token == token.Question { + return p.parseCondExpr(expr) + } + return expr +} + +func (p *Parser) parseBinaryExpr(prec1 int) Expr { + if p.trace { + defer untracep(tracep(p, "BinaryExpression")) + } + + x := p.parseUnaryExpr() + + for { + op, prec := p.token, p.token.Precedence() + if prec < prec1 { + return x + } + + pos := p.expect(op) + + y := p.parseBinaryExpr(prec + 1) + + x = &BinaryExpr{ + LHS: x, + RHS: y, + Token: op, + TokenPos: pos, + } + } +} + +func (p *Parser) parseCondExpr(cond Expr) Expr { + questionPos := p.expect(token.Question) + trueExpr := p.parseExpr() + colonPos := p.expect(token.Colon) + falseExpr := p.parseExpr() + + return &CondExpr{ + Cond: cond, + True: trueExpr, + False: falseExpr, + QuestionPos: questionPos, + ColonPos: colonPos, + } +} + +func (p *Parser) parseUnaryExpr() Expr { + if p.trace { + defer untracep(tracep(p, "UnaryExpression")) + } + + switch p.token { + case token.Add, token.Sub, token.Not, token.Xor: + pos, op := p.pos, p.token + p.next() + x := p.parseUnaryExpr() + return &UnaryExpr{ + Token: op, + TokenPos: pos, + Expr: x, + } + } + return p.parsePrimaryExpr() +} + +func (p *Parser) parsePrimaryExpr() Expr { + if p.trace { + defer untracep(tracep(p, "PrimaryExpression")) + } + + x := p.parseOperand() + +L: + for { + switch p.token { + case token.Period: + p.next() + + switch p.token { + case token.Ident: + x = p.parseSelector(x) + default: + pos := p.pos + p.errorExpected(pos, "selector") + p.advance(stmtStart) + return &BadExpr{From: pos, To: p.pos} + } + case token.LBrack: + x = p.parseIndexOrSlice(x) + case token.LParen: + x = p.parseCall(x) + default: + break L + } + } + return x +} + +func (p *Parser) parseCall(x Expr) *CallExpr { + if p.trace { + defer untracep(tracep(p, "Call")) + } + + lparen := p.expect(token.LParen) + p.exprLevel++ + + var list []Expr + for p.token != token.RParen && p.token != token.EOF { + list = append(list, p.parseExpr()) + + if !p.expectComma(token.RParen, "call argument") { + break + } + } + + p.exprLevel-- + rparen := p.expect(token.RParen) + return &CallExpr{ + Func: x, + LParen: lparen, + RParen: rparen, + Args: list, + } +} + +func (p *Parser) expectComma(closing token.Token, want string) bool { + if p.token == token.Comma { + p.next() + + if p.token == closing { + p.errorExpected(p.pos, want) + return false + } + return true + } + + if p.token == token.Semicolon && p.tokenLit == "\n" { + p.next() + } + return false +} + +func (p *Parser) parseIndexOrSlice(x Expr) Expr { + if p.trace { + defer untracep(tracep(p, "IndexOrSlice")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var index [2]Expr + if p.token != token.Colon { + index[0] = p.parseExpr() + } + numColons := 0 + if p.token == token.Colon { + numColons++ + p.next() + + if p.token != token.RBrack && p.token != token.EOF { + index[1] = p.parseExpr() + } + } + + p.exprLevel-- + rbrack := p.expect(token.RBrack) + + if numColons > 0 { + // slice expression + return &SliceExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Low: index[0], + High: index[1], + } + } + return &IndexExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Index: index[0], + } +} + +func (p *Parser) parseSelector(x Expr) Expr { + if p.trace { + defer untracep(tracep(p, "Selector")) + } + + sel := p.parseIdent() + return &SelectorExpr{Expr: x, Sel: &StringLit{ + Value: sel.Name, + ValuePos: sel.NamePos, + Literal: sel.Name, + }} +} + +func (p *Parser) parseOperand() Expr { + if p.trace { + defer untracep(tracep(p, "Operand")) + } + + switch p.token { + case token.Ident: + return p.parseIdent() + case token.Int: + v, _ := strconv.ParseInt(p.tokenLit, 10, 64) + x := &IntLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + case token.Float: + v, _ := strconv.ParseFloat(p.tokenLit, 64) + x := &FloatLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + case token.Char: + return p.parseCharLit() + case token.String: + v, _ := strconv.Unquote(p.tokenLit) + x := &StringLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + case token.True: + x := &BoolLit{ + Value: true, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + case token.False: + x := &BoolLit{ + Value: false, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + case token.Undefined: + x := &UndefinedLit{TokenPos: p.pos} + p.next() + return x + case token.Import: + return p.parseImportExpr() + case token.LParen: + lparen := p.pos + p.next() + p.exprLevel++ + x := p.parseExpr() + p.exprLevel-- + rparen := p.expect(token.RParen) + return &ParenExpr{ + LParen: lparen, + Expr: x, + RParen: rparen, + } + case token.LBrack: // array literal + return p.parseArrayLit() + case token.LBrace: // map literal + return p.parseMapLit() + case token.Func: // function literal + return p.parseFuncLit() + case token.Error: // error expression + return p.parseErrorExpr() + case token.Immutable: // immutable expression + return p.parseImmutableExpr() + } + + pos := p.pos + p.errorExpected(pos, "operand") + p.advance(stmtStart) + return &BadExpr{From: pos, To: p.pos} +} + +func (p *Parser) parseImportExpr() Expr { + pos := p.pos + p.next() + p.expect(token.LParen) + if p.token != token.String { + p.errorExpected(p.pos, "module name") + p.advance(stmtStart) + return &BadExpr{From: pos, To: p.pos} + } + + // module name + moduleName, _ := strconv.Unquote(p.tokenLit) + expr := &ImportExpr{ + ModuleName: moduleName, + Token: token.Import, + TokenPos: pos, + } + + p.next() + p.expect(token.RParen) + return expr +} + +func (p *Parser) parseCharLit() Expr { + if n := len(p.tokenLit); n >= 3 { + code, _, _, err := strconv.UnquoteChar(p.tokenLit[1:n-1], '\'') + if err == nil { + x := &CharLit{ + Value: code, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + } + } + + pos := p.pos + p.error(pos, "illegal char literal") + p.next() + return &BadExpr{ + From: pos, + To: p.pos, + } +} + +func (p *Parser) parseFuncLit() Expr { + if p.trace { + defer untracep(tracep(p, "FuncLit")) + } + + typ := p.parseFuncType() + p.exprLevel++ + body := p.parseBody() + p.exprLevel-- + return &FuncLit{ + Type: typ, + Body: body, + } +} + +func (p *Parser) parseArrayLit() Expr { + if p.trace { + defer untracep(tracep(p, "ArrayLit")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var elements []Expr + for p.token != token.RBrack && p.token != token.EOF { + elements = append(elements, p.parseExpr()) + + if !p.expectComma(token.RBrack, "array element") { + break + } + } + + p.exprLevel-- + rbrack := p.expect(token.RBrack) + return &ArrayLit{ + Elements: elements, + LBrack: lbrack, + RBrack: rbrack, + } +} + +func (p *Parser) parseErrorExpr() Expr { + pos := p.pos + + p.next() + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + return &ErrorExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } +} + +func (p *Parser) parseImmutableExpr() Expr { + pos := p.pos + + p.next() + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + return &ImmutableExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } +} + +func (p *Parser) parseFuncType() *FuncType { + if p.trace { + defer untracep(tracep(p, "FuncType")) + } + + pos := p.expect(token.Func) + params := p.parseIdentList() + return &FuncType{ + FuncPos: pos, + Params: params, + } +} + +func (p *Parser) parseBody() *BlockStmt { + if p.trace { + defer untracep(tracep(p, "Body")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + return &BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseStmtList() (list []Stmt) { + if p.trace { + defer untracep(tracep(p, "StatementList")) + } + + for p.token != token.RBrace && p.token != token.EOF { + list = append(list, p.parseStmt()) + } + return +} + +func (p *Parser) parseIdent() *Ident { + pos := p.pos + name := "_" + + if p.token == token.Ident { + name = p.tokenLit + p.next() + } else { + p.expect(token.Ident) + } + return &Ident{ + NamePos: pos, + Name: name, + } +} + +func (p *Parser) parseIdentList() *IdentList { + if p.trace { + defer untracep(tracep(p, "IdentList")) + } + + var params []*Ident + lparen := p.expect(token.LParen) + isVarArgs := false + if p.token != token.RParen { + if p.token == token.Ellipsis { + isVarArgs = true + p.next() + } + + params = append(params, p.parseIdent()) + for !isVarArgs && p.token == token.Comma { + p.next() + if p.token == token.Ellipsis { + isVarArgs = true + p.next() + } + params = append(params, p.parseIdent()) + } + } + + rparen := p.expect(token.RParen) + return &IdentList{ + LParen: lparen, + RParen: rparen, + VarArgs: isVarArgs, + List: params, + } +} + +func (p *Parser) parseStmt() (stmt Stmt) { + if p.trace { + defer untracep(tracep(p, "Statement")) + } + + switch p.token { + case // simple statements + token.Func, token.Error, token.Immutable, token.Ident, token.Int, + token.Float, token.Char, token.String, token.True, token.False, + token.Undefined, token.Import, token.LParen, token.LBrace, + token.LBrack, token.Add, token.Sub, token.Mul, token.And, token.Xor, + token.Not: + s := p.parseSimpleStmt(false) + p.expectSemi() + return s + case token.Return: + return p.parseReturnStmt() + case token.Export: + return p.parseExportStmt() + case token.If: + return p.parseIfStmt() + case token.For: + return p.parseForStmt() + case token.Break, token.Continue: + return p.parseBranchStmt(p.token) + case token.Semicolon: + s := &EmptyStmt{Semicolon: p.pos, Implicit: p.tokenLit == "\n"} + p.next() + return s + case token.RBrace: + // semicolon may be omitted before a closing "}" + return &EmptyStmt{Semicolon: p.pos, Implicit: true} + default: + pos := p.pos + p.errorExpected(pos, "statement") + p.advance(stmtStart) + return &BadStmt{From: pos, To: p.pos} + } +} + +func (p *Parser) parseForStmt() Stmt { + if p.trace { + defer untracep(tracep(p, "ForStmt")) + } + + pos := p.expect(token.For) + + // for {} + if p.token == token.LBrace { + body := p.parseBlockStmt() + p.expectSemi() + + return &ForStmt{ + ForPos: pos, + Body: body, + } + } + + prevLevel := p.exprLevel + p.exprLevel = -1 + + var s1 Stmt + if p.token != token.Semicolon { // skipping init + s1 = p.parseSimpleStmt(true) + } + + // for _ in seq {} or + // for value in seq {} or + // for key, value in seq {} + if forInStmt, isForIn := s1.(*ForInStmt); isForIn { + forInStmt.ForPos = pos + p.exprLevel = prevLevel + forInStmt.Body = p.parseBlockStmt() + p.expectSemi() + return forInStmt + } + + // for init; cond; post {} + var s2, s3 Stmt + if p.token == token.Semicolon { + p.next() + if p.token != token.Semicolon { + s2 = p.parseSimpleStmt(false) // cond + } + p.expect(token.Semicolon) + if p.token != token.LBrace { + s3 = p.parseSimpleStmt(false) // post + } + } else { + // for cond {} + s2 = s1 + s1 = nil + } + + // body + p.exprLevel = prevLevel + body := p.parseBlockStmt() + p.expectSemi() + cond := p.makeExpr(s2, "condition expression") + return &ForStmt{ + ForPos: pos, + Init: s1, + Cond: cond, + Post: s3, + Body: body, + } +} + +func (p *Parser) parseBranchStmt(tok token.Token) Stmt { + if p.trace { + defer untracep(tracep(p, "BranchStmt")) + } + + pos := p.expect(tok) + + var label *Ident + if p.token == token.Ident { + label = p.parseIdent() + } + p.expectSemi() + return &BranchStmt{ + Token: tok, + TokenPos: pos, + Label: label, + } +} + +func (p *Parser) parseIfStmt() Stmt { + if p.trace { + defer untracep(tracep(p, "IfStmt")) + } + + pos := p.expect(token.If) + init, cond := p.parseIfHeader() + body := p.parseBlockStmt() + + var elseStmt Stmt + if p.token == token.Else { + p.next() + + switch p.token { + case token.If: + elseStmt = p.parseIfStmt() + case token.LBrace: + elseStmt = p.parseBlockStmt() + p.expectSemi() + default: + p.errorExpected(p.pos, "if or {") + elseStmt = &BadStmt{From: p.pos, To: p.pos} + } + } else { + p.expectSemi() + } + return &IfStmt{ + IfPos: pos, + Init: init, + Cond: cond, + Body: body, + Else: elseStmt, + } +} + +func (p *Parser) parseBlockStmt() *BlockStmt { + if p.trace { + defer untracep(tracep(p, "BlockStmt")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + return &BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseIfHeader() (init Stmt, cond Expr) { + if p.token == token.LBrace { + p.error(p.pos, "missing condition in if statement") + cond = &BadExpr{From: p.pos, To: p.pos} + return + } + + outer := p.exprLevel + p.exprLevel = -1 + if p.token == token.Semicolon { + p.error(p.pos, "missing init in if statement") + return + } + init = p.parseSimpleStmt(false) + + var condStmt Stmt + if p.token == token.LBrace { + condStmt = init + init = nil + } else if p.token == token.Semicolon { + p.next() + + condStmt = p.parseSimpleStmt(false) + } else { + p.error(p.pos, "missing condition in if statement") + } + + if condStmt != nil { + cond = p.makeExpr(condStmt, "boolean expression") + } + if cond == nil { + cond = &BadExpr{From: p.pos, To: p.pos} + } + p.exprLevel = outer + return +} + +func (p *Parser) makeExpr(s Stmt, want string) Expr { + if s == nil { + return nil + } + + if es, isExpr := s.(*ExprStmt); isExpr { + return es.Expr + } + + found := "simple statement" + if _, isAss := s.(*AssignStmt); isAss { + found = "assignment" + } + p.error(s.Pos(), fmt.Sprintf("expected %s, found %s", want, found)) + return &BadExpr{From: s.Pos(), To: p.safePos(s.End())} +} + +func (p *Parser) parseReturnStmt() Stmt { + if p.trace { + defer untracep(tracep(p, "ReturnStmt")) + } + + pos := p.pos + p.expect(token.Return) + + var x Expr + if p.token != token.Semicolon && p.token != token.RBrace { + x = p.parseExpr() + } + p.expectSemi() + return &ReturnStmt{ + ReturnPos: pos, + Result: x, + } +} + +func (p *Parser) parseExportStmt() Stmt { + if p.trace { + defer untracep(tracep(p, "ExportStmt")) + } + + pos := p.pos + p.expect(token.Export) + x := p.parseExpr() + p.expectSemi() + return &ExportStmt{ + ExportPos: pos, + Result: x, + } +} + +func (p *Parser) parseSimpleStmt(forIn bool) Stmt { + if p.trace { + defer untracep(tracep(p, "SimpleStmt")) + } + + x := p.parseExprList() + + switch p.token { + case token.Assign, token.Define: // assignment statement + pos, tok := p.pos, p.token + p.next() + y := p.parseExprList() + return &AssignStmt{ + LHS: x, + RHS: y, + Token: tok, + TokenPos: pos, + } + case token.In: + if forIn { + p.next() + y := p.parseExpr() + + var key, value *Ident + var ok bool + switch len(x) { + case 1: + key = &Ident{Name: "_", NamePos: x[0].Pos()} + + value, ok = x[0].(*Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + value = &Ident{Name: "_", NamePos: x[0].Pos()} + } + case 2: + key, ok = x[0].(*Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + key = &Ident{Name: "_", NamePos: x[0].Pos()} + } + value, ok = x[1].(*Ident) + if !ok { + p.errorExpected(x[1].Pos(), "identifier") + value = &Ident{Name: "_", NamePos: x[1].Pos()} + } + } + return &ForInStmt{ + Key: key, + Value: value, + Iterable: y, + } + } + } + + if len(x) > 1 { + p.errorExpected(x[0].Pos(), "1 expression") + // continue with first expression + } + + switch p.token { + case token.Define, + token.AddAssign, token.SubAssign, token.MulAssign, token.QuoAssign, + token.RemAssign, token.AndAssign, token.OrAssign, token.XorAssign, + token.ShlAssign, token.ShrAssign, token.AndNotAssign: + pos, tok := p.pos, p.token + p.next() + y := p.parseExpr() + return &AssignStmt{ + LHS: []Expr{x[0]}, + RHS: []Expr{y}, + Token: tok, + TokenPos: pos, + } + case token.Inc, token.Dec: + // increment or decrement statement + s := &IncDecStmt{Expr: x[0], Token: p.token, TokenPos: p.pos} + p.next() + return s + } + return &ExprStmt{Expr: x[0]} +} + +func (p *Parser) parseExprList() (list []Expr) { + if p.trace { + defer untracep(tracep(p, "ExpressionList")) + } + + list = append(list, p.parseExpr()) + for p.token == token.Comma { + p.next() + list = append(list, p.parseExpr()) + } + return +} + +func (p *Parser) parseMapElementLit() *MapElementLit { + if p.trace { + defer untracep(tracep(p, "MapElementLit")) + } + + pos := p.pos + name := "_" + if p.token == token.Ident { + name = p.tokenLit + } else if p.token == token.String { + v, _ := strconv.Unquote(p.tokenLit) + name = v + } else { + p.errorExpected(pos, "map key") + } + p.next() + colonPos := p.expect(token.Colon) + valueExpr := p.parseExpr() + return &MapElementLit{ + Key: name, + KeyPos: pos, + ColonPos: colonPos, + Value: valueExpr, + } +} + +func (p *Parser) parseMapLit() *MapLit { + if p.trace { + defer untracep(tracep(p, "MapLit")) + } + + lbrace := p.expect(token.LBrace) + p.exprLevel++ + + var elements []*MapElementLit + for p.token != token.RBrace && p.token != token.EOF { + elements = append(elements, p.parseMapElementLit()) + + if !p.expectComma(token.RBrace, "map element") { + break + } + } + + p.exprLevel-- + rbrace := p.expect(token.RBrace) + return &MapLit{ + LBrace: lbrace, + RBrace: rbrace, + Elements: elements, + } +} + +func (p *Parser) expect(token token.Token) Pos { + pos := p.pos + + if p.token != token { + p.errorExpected(pos, "'"+token.String()+"'") + } + p.next() + return pos +} + +func (p *Parser) expectSemi() { + switch p.token { + case token.RParen, token.RBrace: + // semicolon is optional before a closing ')' or '}' + case token.Comma: + // permit a ',' instead of a ';' but complain + p.errorExpected(p.pos, "';'") + fallthrough + case token.Semicolon: + p.next() + default: + p.errorExpected(p.pos, "';'") + p.advance(stmtStart) + } +} + +func (p *Parser) advance(to map[token.Token]bool) { + for ; p.token != token.EOF; p.next() { + if to[p.token] { + if p.pos == p.syncPos && p.syncCount < 10 { + p.syncCount++ + return + } + if p.pos > p.syncPos { + p.syncPos = p.pos + p.syncCount = 0 + return + } + } + } +} + +func (p *Parser) error(pos Pos, msg string) { + filePos := p.file.Position(pos) + + n := len(p.errors) + if n > 0 && p.errors[n-1].Pos.Line == filePos.Line { + // discard errors reported on the same line + return + } + if n > 10 { + // too many errors; terminate early + panic(bailout{}) + } + p.errors.Add(filePos, msg) +} + +func (p *Parser) errorExpected(pos Pos, msg string) { + msg = "expected " + msg + if pos == p.pos { + // error happened at the current position: provide more specific + switch { + case p.token == token.Semicolon && p.tokenLit == "\n": + msg += ", found newline" + case p.token.IsLiteral(): + msg += ", found " + p.tokenLit + default: + msg += ", found '" + p.token.String() + "'" + } + } + p.error(pos, msg) +} + +func (p *Parser) next() { + if p.trace && p.pos.IsValid() { + s := p.token.String() + switch { + case p.token.IsLiteral(): + p.printTrace(s, p.tokenLit) + case p.token.IsOperator(), p.token.IsKeyword(): + p.printTrace(`"` + s + `"`) + default: + p.printTrace(s) + } + } + p.token, p.tokenLit, p.pos = p.scanner.Scan() +} + +func (p *Parser) printTrace(a ...interface{}) { + const ( + dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + n = len(dots) + ) + + filePos := p.file.Position(p.pos) + _, _ = fmt.Fprintf(p.traceOut, "%5d: %5d:%3d: ", p.pos, filePos.Line, + filePos.Column) + i := 2 * p.indent + for i > n { + _, _ = fmt.Fprint(p.traceOut, dots) + i -= n + } + _, _ = fmt.Fprint(p.traceOut, dots[0:i]) + _, _ = fmt.Fprintln(p.traceOut, a...) +} + +func (p *Parser) safePos(pos Pos) Pos { + fileBase := p.file.Base + fileSize := p.file.Size + + if int(pos) < fileBase || int(pos) > fileBase+fileSize { + return Pos(fileBase + fileSize) + } + return pos +} + +func tracep(p *Parser, msg string) *Parser { + p.printTrace(msg, "(") + p.indent++ + return p +} + +func untracep(p *Parser) { + p.indent-- + p.printTrace(")") +} diff --git a/vendor/github.com/d5/tengo/v2/parser/pos.go b/vendor/github.com/d5/tengo/v2/parser/pos.go new file mode 100644 index 00000000..f8d3898c --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/pos.go @@ -0,0 +1,12 @@ +package parser + +// Pos represents a position in the file set. +type Pos int + +// NoPos represents an invalid position. +const NoPos Pos = 0 + +// IsValid returns true if the position is valid. +func (p Pos) IsValid() bool { + return p != NoPos +} diff --git a/vendor/github.com/d5/tengo/v2/parser/scanner.go b/vendor/github.com/d5/tengo/v2/parser/scanner.go new file mode 100644 index 00000000..f1d820a4 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/scanner.go @@ -0,0 +1,689 @@ +package parser + +import ( + "fmt" + "unicode" + "unicode/utf8" + + "github.com/d5/tengo/v2/token" +) + +// byte order mark +const bom = 0xFEFF + +// ScanMode represents a scanner mode. +type ScanMode int + +// List of scanner modes. +const ( + ScanComments ScanMode = 1 << iota + DontInsertSemis +) + +// ScannerErrorHandler is an error handler for the scanner. +type ScannerErrorHandler func(pos SourceFilePos, msg string) + +// Scanner reads the Tengo source text. It's based on Go's scanner +// implementation. +type Scanner struct { + file *SourceFile // source file handle + src []byte // source + ch rune // current character + offset int // character offset + readOffset int // reading offset (position after current character) + lineOffset int // current line offset + insertSemi bool // insert a semicolon before next newline + errorHandler ScannerErrorHandler // error reporting; or nil + errorCount int // number of errors encountered + mode ScanMode +} + +// NewScanner creates a Scanner. +func NewScanner( + file *SourceFile, + src []byte, + errorHandler ScannerErrorHandler, + mode ScanMode, +) *Scanner { + if file.Size != len(src) { + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", + file.Size, len(src))) + } + + s := &Scanner{ + file: file, + src: src, + errorHandler: errorHandler, + ch: ' ', + mode: mode, + } + + s.next() + if s.ch == bom { + s.next() // ignore BOM at file beginning + } + + return s +} + +// ErrorCount returns the number of errors. +func (s *Scanner) ErrorCount() int { + return s.errorCount +} + +// Scan returns a token, token literal and its position. +func (s *Scanner) Scan() ( + tok token.Token, + literal string, + pos Pos, +) { + s.skipWhitespace() + + pos = s.file.FileSetPos(s.offset) + + insertSemi := false + + // determine token value + switch ch := s.ch; { + case isLetter(ch): + literal = s.scanIdentifier() + tok = token.Lookup(literal) + switch tok { + case token.Ident, token.Break, token.Continue, token.Return, + token.Export, token.True, token.False, token.Undefined: + insertSemi = true + } + case '0' <= ch && ch <= '9': + insertSemi = true + tok, literal = s.scanNumber(false) + default: + s.next() // always make progress + + switch ch { + case -1: // EOF + if s.insertSemi { + s.insertSemi = false // EOF consumed + return token.Semicolon, "\n", pos + } + tok = token.EOF + case '\n': + // we only reach here if s.insertSemi was set in the first place + s.insertSemi = false // newline consumed + return token.Semicolon, "\n", pos + case '"': + insertSemi = true + tok = token.String + literal = s.scanString() + case '\'': + insertSemi = true + tok = token.Char + literal = s.scanRune() + case '`': + insertSemi = true + tok = token.String + literal = s.scanRawString() + case ':': + tok = s.switch2(token.Colon, token.Define) + case '.': + if '0' <= s.ch && s.ch <= '9' { + insertSemi = true + tok, literal = s.scanNumber(true) + } else { + tok = token.Period + if s.ch == '.' && s.peek() == '.' { + s.next() + s.next() // consume last '.' + tok = token.Ellipsis + } + } + case ',': + tok = token.Comma + case '?': + tok = token.Question + case ';': + tok = token.Semicolon + literal = ";" + case '(': + tok = token.LParen + case ')': + insertSemi = true + tok = token.RParen + case '[': + tok = token.LBrack + case ']': + insertSemi = true + tok = token.RBrack + case '{': + tok = token.LBrace + case '}': + insertSemi = true + tok = token.RBrace + case '+': + tok = s.switch3(token.Add, token.AddAssign, '+', token.Inc) + if tok == token.Inc { + insertSemi = true + } + case '-': + tok = s.switch3(token.Sub, token.SubAssign, '-', token.Dec) + if tok == token.Dec { + insertSemi = true + } + case '*': + tok = s.switch2(token.Mul, token.MulAssign) + case '/': + if s.ch == '/' || s.ch == '*' { + // comment + if s.insertSemi && s.findLineEnd() { + // reset position to the beginning of the comment + s.ch = '/' + s.offset = s.file.Offset(pos) + s.readOffset = s.offset + 1 + s.insertSemi = false // newline consumed + return token.Semicolon, "\n", pos + } + comment := s.scanComment() + if s.mode&ScanComments == 0 { + // skip comment + s.insertSemi = false // newline consumed + return s.Scan() + } + tok = token.Comment + literal = comment + } else { + tok = s.switch2(token.Quo, token.QuoAssign) + } + case '%': + tok = s.switch2(token.Rem, token.RemAssign) + case '^': + tok = s.switch2(token.Xor, token.XorAssign) + case '<': + tok = s.switch4(token.Less, token.LessEq, '<', + token.Shl, token.ShlAssign) + case '>': + tok = s.switch4(token.Greater, token.GreaterEq, '>', + token.Shr, token.ShrAssign) + case '=': + tok = s.switch2(token.Assign, token.Equal) + case '!': + tok = s.switch2(token.Not, token.NotEqual) + case '&': + if s.ch == '^' { + s.next() + tok = s.switch2(token.AndNot, token.AndNotAssign) + } else { + tok = s.switch3(token.And, token.AndAssign, '&', token.LAnd) + } + case '|': + tok = s.switch3(token.Or, token.OrAssign, '|', token.LOr) + default: + // next reports unexpected BOMs - don't repeat + if ch != bom { + s.error(s.file.Offset(pos), + fmt.Sprintf("illegal character %#U", ch)) + } + insertSemi = s.insertSemi // preserve insertSemi info + tok = token.Illegal + literal = string(ch) + } + } + if s.mode&DontInsertSemis == 0 { + s.insertSemi = insertSemi + } + return +} + +func (s *Scanner) next() { + if s.readOffset < len(s.src) { + s.offset = s.readOffset + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + r, w := rune(s.src[s.readOffset]), 1 + switch { + case r == 0: + s.error(s.offset, "illegal character NUL") + case r >= utf8.RuneSelf: + // not ASCII + r, w = utf8.DecodeRune(s.src[s.readOffset:]) + if r == utf8.RuneError && w == 1 { + s.error(s.offset, "illegal UTF-8 encoding") + } else if r == bom && s.offset > 0 { + s.error(s.offset, "illegal byte order mark") + } + } + s.readOffset += w + s.ch = r + } else { + s.offset = len(s.src) + if s.ch == '\n' { + s.lineOffset = s.offset + s.file.AddLine(s.offset) + } + s.ch = -1 // eof + } +} + +func (s *Scanner) peek() byte { + if s.readOffset < len(s.src) { + return s.src[s.readOffset] + } + return 0 +} + +func (s *Scanner) error(offset int, msg string) { + if s.errorHandler != nil { + s.errorHandler(s.file.Position(s.file.FileSetPos(offset)), msg) + } + s.errorCount++ +} + +func (s *Scanner) scanComment() string { + // initial '/' already consumed; s.ch == '/' || s.ch == '*' + offs := s.offset - 1 // position of initial '/' + var numCR int + + if s.ch == '/' { + //-style comment + // (the final '\n' is not considered part of the comment) + s.next() + for s.ch != '\n' && s.ch >= 0 { + if s.ch == '\r' { + numCR++ + } + s.next() + } + goto exit + } + + /*-style comment */ + s.next() + for s.ch >= 0 { + ch := s.ch + if ch == '\r' { + numCR++ + } + s.next() + if ch == '*' && s.ch == '/' { + s.next() + goto exit + } + } + + s.error(offs, "comment not terminated") + +exit: + lit := s.src[offs:s.offset] + + // On Windows, a (//-comment) line may end in "\r\n". + // Remove the final '\r' before analyzing the text for line directives (matching the compiler). + // Remove any other '\r' afterwards (matching the pre-existing behavior of the scanner). + if numCR > 0 && len(lit) >= 2 && lit[1] == '/' && lit[len(lit)-1] == '\r' { + lit = lit[:len(lit)-1] + numCR-- + } + if numCR > 0 { + lit = StripCR(lit, lit[1] == '*') + } + return string(lit) +} + +func (s *Scanner) findLineEnd() bool { + // initial '/' already consumed + + defer func(offs int) { + // reset scanner state to where it was upon calling findLineEnd + s.ch = '/' + s.offset = offs + s.readOffset = offs + 1 + s.next() // consume initial '/' again + }(s.offset - 1) + + // read ahead until a newline, EOF, or non-comment tok is found + for s.ch == '/' || s.ch == '*' { + if s.ch == '/' { + //-style comment always contains a newline + return true + } + /*-style comment: look for newline */ + s.next() + for s.ch >= 0 { + ch := s.ch + if ch == '\n' { + return true + } + s.next() + if ch == '*' && s.ch == '/' { + s.next() + break + } + } + s.skipWhitespace() // s.insertSemi is set + if s.ch < 0 || s.ch == '\n' { + return true + } + if s.ch != '/' { + // non-comment tok + return false + } + s.next() // consume '/' + } + return false +} + +func (s *Scanner) scanIdentifier() string { + offs := s.offset + for isLetter(s.ch) || isDigit(s.ch) { + s.next() + } + return string(s.src[offs:s.offset]) +} + +func (s *Scanner) scanMantissa(base int) { + for digitVal(s.ch) < base { + s.next() + } +} + +func (s *Scanner) scanNumber( + seenDecimalPoint bool, +) (tok token.Token, lit string) { + // digitVal(s.ch) < 10 + offs := s.offset + tok = token.Int + + defer func() { + lit = string(s.src[offs:s.offset]) + }() + + if seenDecimalPoint { + offs-- + tok = token.Float + s.scanMantissa(10) + goto exponent + } + + if s.ch == '0' { + // int or float + offs := s.offset + s.next() + if s.ch == 'x' || s.ch == 'X' { + // hexadecimal int + s.next() + s.scanMantissa(16) + if s.offset-offs <= 2 { + // only scanned "0x" or "0X" + s.error(offs, "illegal hexadecimal number") + } + } else { + // octal int or float + seenDecimalDigit := false + s.scanMantissa(8) + if s.ch == '8' || s.ch == '9' { + // illegal octal int or float + seenDecimalDigit = true + s.scanMantissa(10) + } + if s.ch == '.' || s.ch == 'e' || s.ch == 'E' || s.ch == 'i' { + goto fraction + } + // octal int + if seenDecimalDigit { + s.error(offs, "illegal octal number") + } + } + return + } + + // decimal int or float + s.scanMantissa(10) + +fraction: + if s.ch == '.' { + tok = token.Float + s.next() + s.scanMantissa(10) + } + +exponent: + if s.ch == 'e' || s.ch == 'E' { + tok = token.Float + s.next() + if s.ch == '-' || s.ch == '+' { + s.next() + } + if digitVal(s.ch) < 10 { + s.scanMantissa(10) + } else { + s.error(offs, "illegal floating-point exponent") + } + } + return +} + +func (s *Scanner) scanEscape(quote rune) bool { + offs := s.offset + + var n int + var base, max uint32 + switch s.ch { + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote: + s.next() + return true + case '0', '1', '2', '3', '4', '5', '6', '7': + n, base, max = 3, 8, 255 + case 'x': + s.next() + n, base, max = 2, 16, 255 + case 'u': + s.next() + n, base, max = 4, 16, unicode.MaxRune + case 'U': + s.next() + n, base, max = 8, 16, unicode.MaxRune + default: + msg := "unknown escape sequence" + if s.ch < 0 { + msg = "escape sequence not terminated" + } + s.error(offs, msg) + return false + } + + var x uint32 + for n > 0 { + d := uint32(digitVal(s.ch)) + if d >= base { + msg := fmt.Sprintf( + "illegal character %#U in escape sequence", s.ch) + if s.ch < 0 { + msg = "escape sequence not terminated" + } + s.error(s.offset, msg) + return false + } + x = x*base + d + s.next() + n-- + } + + if x > max || 0xD800 <= x && x < 0xE000 { + s.error(offs, "escape sequence is invalid Unicode code point") + return false + } + return true +} + +func (s *Scanner) scanRune() string { + offs := s.offset - 1 // '\'' opening already consumed + + valid := true + n := 0 + for { + ch := s.ch + if ch == '\n' || ch < 0 { + // only report error if we don't have one already + if valid { + s.error(offs, "rune literal not terminated") + valid = false + } + break + } + s.next() + if ch == '\'' { + break + } + n++ + if ch == '\\' { + if !s.scanEscape('\'') { + valid = false + } + // continue to read to closing quote + } + } + + if valid && n != 1 { + s.error(offs, "illegal rune literal") + } + return string(s.src[offs:s.offset]) +} + +func (s *Scanner) scanString() string { + offs := s.offset - 1 // '"' opening already consumed + + for { + ch := s.ch + if ch == '\n' || ch < 0 { + s.error(offs, "string literal not terminated") + break + } + s.next() + if ch == '"' { + break + } + if ch == '\\' { + s.scanEscape('"') + } + } + return string(s.src[offs:s.offset]) +} + +func (s *Scanner) scanRawString() string { + offs := s.offset - 1 // '`' opening already consumed + + hasCR := false + for { + ch := s.ch + if ch < 0 { + s.error(offs, "raw string literal not terminated") + break + } + + s.next() + + if ch == '`' { + break + } + + if ch == '\r' { + hasCR = true + } + } + + lit := s.src[offs:s.offset] + if hasCR { + lit = StripCR(lit, false) + } + return string(lit) +} + +// StripCR removes carriage return characters. +func StripCR(b []byte, comment bool) []byte { + c := make([]byte, len(b)) + i := 0 + for j, ch := range b { + // In a /*-style comment, don't strip \r from *\r/ (incl. sequences of + // \r from *\r\r...\r/) since the resulting */ would terminate the + // comment too early unless the \r is immediately following the opening + // /* in which case it's ok because /*/ is not closed yet. + if ch != '\r' || comment && i > len("/*") && c[i-1] == '*' && + j+1 < len(b) && b[j+1] == '/' { + c[i] = ch + i++ + } + } + return c[:i] +} + +func (s *Scanner) skipWhitespace() { + for s.ch == ' ' || s.ch == '\t' || s.ch == '\n' && !s.insertSemi || + s.ch == '\r' { + s.next() + } +} + +func (s *Scanner) switch2(tok0, tok1 token.Token) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + return tok0 +} + +func (s *Scanner) switch3( + tok0, tok1 token.Token, + ch2 rune, + tok2 token.Token, +) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + if s.ch == ch2 { + s.next() + return tok2 + } + return tok0 +} + +func (s *Scanner) switch4( + tok0, tok1 token.Token, + ch2 rune, + tok2, tok3 token.Token, +) token.Token { + if s.ch == '=' { + s.next() + return tok1 + } + if s.ch == ch2 { + s.next() + if s.ch == '=' { + s.next() + return tok3 + } + return tok2 + } + return tok0 +} + +func isLetter(ch rune) bool { + return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || + ch >= utf8.RuneSelf && unicode.IsLetter(ch) +} + +func isDigit(ch rune) bool { + return '0' <= ch && ch <= '9' || + ch >= utf8.RuneSelf && unicode.IsDigit(ch) +} + +func digitVal(ch rune) int { + switch { + case '0' <= ch && ch <= '9': + return int(ch - '0') + case 'a' <= ch && ch <= 'f': + return int(ch - 'a' + 10) + case 'A' <= ch && ch <= 'F': + return int(ch - 'A' + 10) + } + return 16 // larger than any legal digit val +} diff --git a/vendor/github.com/d5/tengo/v2/parser/source_file.go b/vendor/github.com/d5/tengo/v2/parser/source_file.go new file mode 100644 index 00000000..e9f4b0f5 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/source_file.go @@ -0,0 +1,231 @@ +package parser + +import ( + "fmt" + "sort" +) + +// SourceFilePos represents a position information in the file. +type SourceFilePos struct { + Filename string // filename, if any + Offset int // offset, starting at 0 + Line int // line number, starting at 1 + Column int // column number, starting at 1 (byte count) +} + +// IsValid returns true if the position is valid. +func (p SourceFilePos) IsValid() bool { + return p.Line > 0 +} + +// String returns a string in one of several forms: +// +// file:line:column valid position with file name +// file:line valid position with file name but no column (column == 0) +// line:column valid position without file name +// line valid position without file name and no column (column == 0) +// file invalid position with file name +// - invalid position without file name +// +func (p SourceFilePos) String() string { + s := p.Filename + if p.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d", p.Line) + if p.Column != 0 { + s += fmt.Sprintf(":%d", p.Column) + } + } + if s == "" { + s = "-" + } + return s +} + +// SourceFileSet represents a set of source files. +type SourceFileSet struct { + Base int // base offset for the next file + Files []*SourceFile // list of files in the order added to the set + LastFile *SourceFile // cache of last file looked up +} + +// NewFileSet creates a new file set. +func NewFileSet() *SourceFileSet { + return &SourceFileSet{ + Base: 1, // 0 == NoPos + } +} + +// AddFile adds a new file in the file set. +func (s *SourceFileSet) AddFile(filename string, base, size int) *SourceFile { + if base < 0 { + base = s.Base + } + if base < s.Base || size < 0 { + panic("illegal base or size") + } + f := &SourceFile{ + set: s, + Name: filename, + Base: base, + Size: size, + Lines: []int{0}, + } + base += size + 1 // +1 because EOF also has a position + if base < 0 { + panic("offset overflow (> 2G of source code in file set)") + } + + // add the file to the file set + s.Base = base + s.Files = append(s.Files, f) + s.LastFile = f + return f +} + +// File returns the file that contains the position p. If no such file is +// found (for instance for p == NoPos), the result is nil. +func (s *SourceFileSet) File(p Pos) (f *SourceFile) { + if p != NoPos { + f = s.file(p) + } + return +} + +// Position converts a SourcePos p in the fileset into a SourceFilePos value. +func (s *SourceFileSet) Position(p Pos) (pos SourceFilePos) { + if p != NoPos { + if f := s.file(p); f != nil { + return f.position(p) + } + } + return +} + +func (s *SourceFileSet) file(p Pos) *SourceFile { + // common case: p is in last file + f := s.LastFile + if f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size { + return f + } + + // p is not in last file - search all files + if i := searchFiles(s.Files, int(p)); i >= 0 { + f := s.Files[i] + + // f.base <= int(p) by definition of searchFiles + if int(p) <= f.Base+f.Size { + s.LastFile = f // race is ok - s.last is only a cache + return f + } + } + return nil +} + +func searchFiles(a []*SourceFile, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1 +} + +// SourceFile represents a source file. +type SourceFile struct { + // SourceFile set for the file + set *SourceFileSet + // SourceFile name as provided to AddFile + Name string + // SourcePos value range for this file is [base...base+size] + Base int + // SourceFile size as provided to AddFile + Size int + // Lines contains the offset of the first character for each line + // (the first entry is always 0) + Lines []int +} + +// Set returns SourceFileSet. +func (f *SourceFile) Set() *SourceFileSet { + return f.set +} + +// LineCount returns the current number of lines. +func (f *SourceFile) LineCount() int { + return len(f.Lines) +} + +// AddLine adds a new line. +func (f *SourceFile) AddLine(offset int) { + i := len(f.Lines) + if (i == 0 || f.Lines[i-1] < offset) && offset < f.Size { + f.Lines = append(f.Lines, offset) + } +} + +// LineStart returns the position of the first character in the line. +func (f *SourceFile) LineStart(line int) Pos { + if line < 1 { + panic("illegal line number (line numbering starts at 1)") + } + if line > len(f.Lines) { + panic("illegal line number") + } + return Pos(f.Base + f.Lines[line-1]) +} + +// FileSetPos returns the position in the file set. +func (f *SourceFile) FileSetPos(offset int) Pos { + if offset > f.Size { + panic("illegal file offset") + } + return Pos(f.Base + offset) +} + +// Offset translates the file set position into the file offset. +func (f *SourceFile) Offset(p Pos) int { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal SourcePos value") + } + return int(p) - f.Base +} + +// Position translates the file set position into the file position. +func (f *SourceFile) Position(p Pos) (pos SourceFilePos) { + if p != NoPos { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal SourcePos value") + } + pos = f.position(p) + } + return +} + +func (f *SourceFile) position(p Pos) (pos SourceFilePos) { + offset := int(p) - f.Base + pos.Offset = offset + pos.Filename, pos.Line, pos.Column = f.unpack(offset) + return +} + +func (f *SourceFile) unpack(offset int) (filename string, line, column int) { + filename = f.Name + if i := searchInts(f.Lines, offset); i >= 0 { + line, column = i+1, offset-f.Lines[i]+1 + } + return +} + +func searchInts(a []int, x int) int { + // This function body is a manually inlined version of: + // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 + i, j := 0, len(a) + for i < j { + h := i + (j-i)/2 // avoid overflow when computing h + // i ≤ h < j + if a[h] <= x { + i = h + 1 + } else { + j = h + } + } + return i - 1 +} diff --git a/vendor/github.com/d5/tengo/v2/parser/stmt.go b/vendor/github.com/d5/tengo/v2/parser/stmt.go new file mode 100644 index 00000000..c0848c48 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/parser/stmt.go @@ -0,0 +1,349 @@ +package parser + +import ( + "strings" + + "github.com/d5/tengo/v2/token" +) + +// Stmt represents a statement in the AST. +type Stmt interface { + Node + stmtNode() +} + +// AssignStmt represents an assignment statement. +type AssignStmt struct { + LHS []Expr + RHS []Expr + Token token.Token + TokenPos Pos +} + +func (s *AssignStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *AssignStmt) Pos() Pos { + return s.LHS[0].Pos() +} + +// End returns the position of first character immediately after the node. +func (s *AssignStmt) End() Pos { + return s.RHS[len(s.RHS)-1].End() +} + +func (s *AssignStmt) String() string { + var lhs, rhs []string + for _, e := range s.LHS { + lhs = append(lhs, e.String()) + } + for _, e := range s.RHS { + rhs = append(rhs, e.String()) + } + return strings.Join(lhs, ", ") + " " + s.Token.String() + + " " + strings.Join(rhs, ", ") +} + +// BadStmt represents a bad statement. +type BadStmt struct { + From Pos + To Pos +} + +func (s *BadStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BadStmt) Pos() Pos { + return s.From +} + +// End returns the position of first character immediately after the node. +func (s *BadStmt) End() Pos { + return s.To +} + +func (s *BadStmt) String() string { + return "" +} + +// BlockStmt represents a block statement. +type BlockStmt struct { + Stmts []Stmt + LBrace Pos + RBrace Pos +} + +func (s *BlockStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BlockStmt) Pos() Pos { + return s.LBrace +} + +// End returns the position of first character immediately after the node. +func (s *BlockStmt) End() Pos { + return s.RBrace + 1 +} + +func (s *BlockStmt) String() string { + var list []string + for _, e := range s.Stmts { + list = append(list, e.String()) + } + return "{" + strings.Join(list, "; ") + "}" +} + +// BranchStmt represents a branch statement. +type BranchStmt struct { + Token token.Token + TokenPos Pos + Label *Ident +} + +func (s *BranchStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BranchStmt) Pos() Pos { + return s.TokenPos +} + +// End returns the position of first character immediately after the node. +func (s *BranchStmt) End() Pos { + if s.Label != nil { + return s.Label.End() + } + + return Pos(int(s.TokenPos) + len(s.Token.String())) +} + +func (s *BranchStmt) String() string { + var label string + if s.Label != nil { + label = " " + s.Label.Name + } + return s.Token.String() + label +} + +// EmptyStmt represents an empty statement. +type EmptyStmt struct { + Semicolon Pos + Implicit bool +} + +func (s *EmptyStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *EmptyStmt) Pos() Pos { + return s.Semicolon +} + +// End returns the position of first character immediately after the node. +func (s *EmptyStmt) End() Pos { + if s.Implicit { + return s.Semicolon + } + return s.Semicolon + 1 +} + +func (s *EmptyStmt) String() string { + return ";" +} + +// ExportStmt represents an export statement. +type ExportStmt struct { + ExportPos Pos + Result Expr +} + +func (s *ExportStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ExportStmt) Pos() Pos { + return s.ExportPos +} + +// End returns the position of first character immediately after the node. +func (s *ExportStmt) End() Pos { + return s.Result.End() +} + +func (s *ExportStmt) String() string { + return "export " + s.Result.String() +} + +// ExprStmt represents an expression statement. +type ExprStmt struct { + Expr Expr +} + +func (s *ExprStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ExprStmt) Pos() Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *ExprStmt) End() Pos { + return s.Expr.End() +} + +func (s *ExprStmt) String() string { + return s.Expr.String() +} + +// ForInStmt represents a for-in statement. +type ForInStmt struct { + ForPos Pos + Key *Ident + Value *Ident + Iterable Expr + Body *BlockStmt +} + +func (s *ForInStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ForInStmt) Pos() Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForInStmt) End() Pos { + return s.Body.End() +} + +func (s *ForInStmt) String() string { + if s.Value != nil { + return "for " + s.Key.String() + ", " + s.Value.String() + + " in " + s.Iterable.String() + " " + s.Body.String() + } + return "for " + s.Key.String() + " in " + s.Iterable.String() + + " " + s.Body.String() +} + +// ForStmt represents a for statement. +type ForStmt struct { + ForPos Pos + Init Stmt + Cond Expr + Post Stmt + Body *BlockStmt +} + +func (s *ForStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ForStmt) Pos() Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForStmt) End() Pos { + return s.Body.End() +} + +func (s *ForStmt) String() string { + var init, cond, post string + if s.Init != nil { + init = s.Init.String() + } + if s.Cond != nil { + cond = s.Cond.String() + " " + } + if s.Post != nil { + post = s.Post.String() + } + + if init != "" || post != "" { + return "for " + init + " ; " + cond + " ; " + post + s.Body.String() + } + return "for " + cond + s.Body.String() +} + +// IfStmt represents an if statement. +type IfStmt struct { + IfPos Pos + Init Stmt + Cond Expr + Body *BlockStmt + Else Stmt // else branch; or nil +} + +func (s *IfStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *IfStmt) Pos() Pos { + return s.IfPos +} + +// End returns the position of first character immediately after the node. +func (s *IfStmt) End() Pos { + if s.Else != nil { + return s.Else.End() + } + return s.Body.End() +} + +func (s *IfStmt) String() string { + var initStmt, elseStmt string + if s.Init != nil { + initStmt = s.Init.String() + "; " + } + if s.Else != nil { + elseStmt = " else " + s.Else.String() + } + return "if " + initStmt + s.Cond.String() + " " + + s.Body.String() + elseStmt +} + +// IncDecStmt represents increment or decrement statement. +type IncDecStmt struct { + Expr Expr + Token token.Token + TokenPos Pos +} + +func (s *IncDecStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *IncDecStmt) Pos() Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *IncDecStmt) End() Pos { + return Pos(int(s.TokenPos) + 2) +} + +func (s *IncDecStmt) String() string { + return s.Expr.String() + s.Token.String() +} + +// ReturnStmt represents a return statement. +type ReturnStmt struct { + ReturnPos Pos + Result Expr +} + +func (s *ReturnStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ReturnStmt) Pos() Pos { + return s.ReturnPos +} + +// End returns the position of first character immediately after the node. +func (s *ReturnStmt) End() Pos { + if s.Result != nil { + return s.Result.End() + } + return s.ReturnPos + 6 +} + +func (s *ReturnStmt) String() string { + if s.Result != nil { + return "return " + s.Result.String() + } + return "return" +} diff --git a/vendor/github.com/d5/tengo/v2/script.go b/vendor/github.com/d5/tengo/v2/script.go new file mode 100644 index 00000000..906771d9 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/script.go @@ -0,0 +1,313 @@ +package tengo + +import ( + "context" + "fmt" + "sync" + + "github.com/d5/tengo/v2/parser" +) + +// Script can simplify compilation and execution of embedded scripts. +type Script struct { + variables map[string]*Variable + modules *ModuleMap + input []byte + maxAllocs int64 + maxConstObjects int + enableFileImport bool +} + +// NewScript creates a Script instance with an input script. +func NewScript(input []byte) *Script { + return &Script{ + variables: make(map[string]*Variable), + input: input, + maxAllocs: -1, + maxConstObjects: -1, + } +} + +// Add adds a new variable or updates an existing variable to the script. +func (s *Script) Add(name string, value interface{}) error { + obj, err := FromInterface(value) + if err != nil { + return err + } + s.variables[name] = &Variable{ + name: name, + value: obj, + } + return nil +} + +// Remove removes (undefines) an existing variable for the script. It returns +// false if the variable name is not defined. +func (s *Script) Remove(name string) bool { + if _, ok := s.variables[name]; !ok { + return false + } + delete(s.variables, name) + return true +} + +// SetImports sets import modules. +func (s *Script) SetImports(modules *ModuleMap) { + s.modules = modules +} + +// SetMaxAllocs sets the maximum number of objects allocations during the run +// time. Compiled script will return ErrObjectAllocLimit error if it +// exceeds this limit. +func (s *Script) SetMaxAllocs(n int64) { + s.maxAllocs = n +} + +// 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, globals, err := s.prepCompile() + if err != nil { + return nil, err + } + + fileSet := parser.NewFileSet() + srcFile := fileSet.AddFile("(main)", -1, len(s.input)) + p := parser.NewParser(srcFile, s.input, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + c := 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] + + // global symbol names to indexes + globalIndexes := make(map[string]int, len(globals)) + for _, name := range symbolTable.Names() { + symbol, _, _ := symbolTable.Resolve(name) + if symbol.Scope == ScopeGlobal { + globalIndexes[name] = symbol.Index + } + } + + // 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{ + globalIndexes: globalIndexes, + bytecode: bytecode, + globals: globals, + maxAllocs: s.maxAllocs, + }, nil +} + +// Run compiles and runs the scripts. Use returned compiled object to access +// global variables. +func (s *Script) Run() (compiled *Compiled, err error) { + compiled, err = s.Compile() + if err != nil { + return + } + err = compiled.Run() + return +} + +// RunContext is like Run but includes a context. +func (s *Script) RunContext( + ctx context.Context, +) (compiled *Compiled, err error) { + compiled, err = s.Compile() + if err != nil { + return + } + err = compiled.RunContext(ctx) + return +} + +func (s *Script) prepCompile() ( + symbolTable *SymbolTable, + globals []Object, + err error, +) { + var names []string + for name := range s.variables { + names = append(names, name) + } + + symbolTable = NewSymbolTable() + for idx, fn := range builtinFuncs { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + globals = make([]Object, GlobalsSize) + + for idx, name := range names { + symbol := symbolTable.Define(name) + if symbol.Index != idx { + panic(fmt.Errorf("wrong symbol index: %d != %d", + idx, symbol.Index)) + } + globals[symbol.Index] = s.variables[name].value + } + return +} + +// Compiled is a compiled instance of the user script. Use Script.Compile() to +// create Compiled object. +type Compiled struct { + globalIndexes map[string]int // global symbol name to index + bytecode *Bytecode + globals []Object + maxAllocs int64 + lock sync.RWMutex +} + +// Run executes the compiled script in the virtual machine. +func (c *Compiled) Run() error { + c.lock.Lock() + defer c.lock.Unlock() + + v := 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 := NewVM(c.bytecode, c.globals, c.maxAllocs) + ch := make(chan error, 1) + go func() { + ch <- v.Run() + }() + + select { + case <-ctx.Done(): + v.Abort() + <-ch + err = ctx.Err() + case err = <-ch: + } + 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([]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 { + c.lock.RLock() + defer c.lock.RUnlock() + + idx, ok := c.globalIndexes[name] + if !ok { + return false + } + v := c.globals[idx] + if v == nil { + return false + } + return v != UndefinedValue +} + +// Get returns a variable identified by the name. +func (c *Compiled) Get(name string) *Variable { + c.lock.RLock() + defer c.lock.RUnlock() + + value := UndefinedValue + if idx, ok := c.globalIndexes[name]; ok { + value = c.globals[idx] + if value == nil { + value = UndefinedValue + } + } + return &Variable{ + name: name, + value: value, + } +} + +// 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, idx := range c.globalIndexes { + value := c.globals[idx] + if value == nil { + value = UndefinedValue + } + vars = append(vars, &Variable{ + name: name, + value: value, + }) + } + return vars +} + +// 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 := FromInterface(value) + if err != nil { + return err + } + idx, ok := c.globalIndexes[name] + if !ok { + return fmt.Errorf("'%s' is not defined", name) + } + c.globals[idx] = obj + return nil +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/base64.go b/vendor/github.com/d5/tengo/v2/stdlib/base64.go new file mode 100644 index 00000000..b4c5b56e --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/base64.go @@ -0,0 +1,34 @@ +package stdlib + +import ( + "encoding/base64" + + "github.com/d5/tengo/v2" +) + +var base64Module = map[string]tengo.Object{ + "encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.StdEncoding.EncodeToString), + }, + "decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.StdEncoding.DecodeString), + }, + "raw_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.RawStdEncoding.EncodeToString), + }, + "raw_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.RawStdEncoding.DecodeString), + }, + "url_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.URLEncoding.EncodeToString), + }, + "url_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.URLEncoding.DecodeString), + }, + "raw_url_encode": &tengo.UserFunction{ + Value: FuncAYRS(base64.RawURLEncoding.EncodeToString), + }, + "raw_url_decode": &tengo.UserFunction{ + Value: FuncASRYE(base64.RawURLEncoding.DecodeString), + }, +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/builtin_modules.go b/vendor/github.com/d5/tengo/v2/stdlib/builtin_modules.go new file mode 100644 index 00000000..cf0e9621 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/builtin_modules.go @@ -0,0 +1,18 @@ +package stdlib + +import ( + "github.com/d5/tengo/v2" +) + +// BuiltinModules are builtin type standard library modules. +var BuiltinModules = map[string]map[string]tengo.Object{ + "math": mathModule, + "os": osModule, + "text": textModule, + "times": timesModule, + "rand": randModule, + "fmt": fmtModule, + "json": jsonModule, + "base64": base64Module, + "hex": hexModule, +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/errors.go b/vendor/github.com/d5/tengo/v2/stdlib/errors.go new file mode 100644 index 00000000..ad83c6f8 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/errors.go @@ -0,0 +1,12 @@ +package stdlib + +import ( + "github.com/d5/tengo/v2" +) + +func wrapError(err error) tengo.Object { + if err == nil { + return tengo.TrueValue + } + return &tengo.Error{Value: &tengo.String{Value: err.Error()}} +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/fmt.go b/vendor/github.com/d5/tengo/v2/stdlib/fmt.go new file mode 100644 index 00000000..9945277f --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/fmt.go @@ -0,0 +1,101 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo/v2" +) + +var fmtModule = map[string]tengo.Object{ + "print": &tengo.UserFunction{Name: "print", Value: fmtPrint}, + "printf": &tengo.UserFunction{Name: "printf", Value: fmtPrintf}, + "println": &tengo.UserFunction{Name: "println", Value: fmtPrintln}, + "sprintf": &tengo.UserFunction{Name: "sprintf", Value: fmtSprintf}, +} + +func fmtPrint(args ...tengo.Object) (ret tengo.Object, err error) { + printArgs, err := getPrintArgs(args...) + if err != nil { + return nil, err + } + _, _ = fmt.Print(printArgs...) + return nil, nil +} + +func fmtPrintf(args ...tengo.Object) (ret tengo.Object, err error) { + numArgs := len(args) + if numArgs == 0 { + return nil, tengo.ErrWrongNumArguments + } + + format, ok := args[0].(*tengo.String) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + fmt.Print(format) + return nil, nil + } + + s, err := tengo.Format(format.Value, args[1:]...) + if err != nil { + return nil, err + } + fmt.Print(s) + return nil, nil +} + +func fmtPrintln(args ...tengo.Object) (ret tengo.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 ...tengo.Object) (ret tengo.Object, err error) { + numArgs := len(args) + if numArgs == 0 { + return nil, tengo.ErrWrongNumArguments + } + + format, ok := args[0].(*tengo.String) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + // okay to return 'format' directly as String is immutable + return format, nil + } + s, err := tengo.Format(format.Value, args[1:]...) + if err != nil { + return nil, err + } + return &tengo.String{Value: s}, nil +} + +func getPrintArgs(args ...tengo.Object) ([]interface{}, error) { + var printArgs []interface{} + l := 0 + for _, arg := range args { + s, _ := tengo.ToString(arg) + slen := len(s) + // make sure length does not exceed the limit + if l+slen > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + l += slen + printArgs = append(printArgs, s) + } + return printArgs, nil +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/func_typedefs.go b/vendor/github.com/d5/tengo/v2/stdlib/func_typedefs.go new file mode 100644 index 00000000..fdac933c --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/func_typedefs.go @@ -0,0 +1,1049 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo/v2" +) + +// FuncAR transform a function of 'func()' signature into CallableFunc type. +func FuncAR(fn func()) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + fn() + return tengo.UndefinedValue, nil + } +} + +// FuncARI transform a function of 'func() int' signature into CallableFunc +// type. +func FuncARI(fn func() int) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return &tengo.Int{Value: int64(fn())}, nil + } +} + +// FuncARI64 transform a function of 'func() int64' signature into CallableFunc +// type. +func FuncARI64(fn func() int64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return &tengo.Int{Value: fn()}, nil + } +} + +// FuncAI64RI64 transform a function of 'func(int64) int64' signature into +// CallableFunc type. +func FuncAI64RI64(fn func(int64) int64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + return &tengo.Int{Value: fn(i1)}, nil + } +} + +// FuncAI64R transform a function of 'func(int64)' signature into CallableFunc +// type. +func FuncAI64R(fn func(int64)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + fn(i1) + return tengo.UndefinedValue, nil + } +} + +// FuncARB transform a function of 'func() bool' signature into CallableFunc +// type. +func FuncARB(fn func() bool) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + if fn() { + return tengo.TrueValue, nil + } + return tengo.FalseValue, nil + } +} + +// FuncARE transform a function of 'func() error' signature into CallableFunc +// type. +func FuncARE(fn func() error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return wrapError(fn()), nil + } +} + +// FuncARS transform a function of 'func() string' signature into CallableFunc +// type. +func FuncARS(fn func() string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + s := fn() + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil + } +} + +// FuncARSE transform a function of 'func() (string, error)' signature into +// CallableFunc type. +func FuncARSE(fn func() (string, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + res, err := fn() + if err != nil { + return wrapError(err), nil + } + if len(res) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: res}, nil + } +} + +// FuncARYE transform a function of 'func() ([]byte, error)' signature into +// CallableFunc type. +func FuncARYE(fn func() ([]byte, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + res, err := fn() + if err != nil { + return wrapError(err), nil + } + if len(res) > tengo.MaxBytesLen { + return nil, tengo.ErrBytesLimit + } + return &tengo.Bytes{Value: res}, nil + } +} + +// FuncARF transform a function of 'func() float64' signature into CallableFunc +// type. +func FuncARF(fn func() float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return &tengo.Float{Value: fn()}, nil + } +} + +// FuncARSs transform a function of 'func() []string' signature into +// CallableFunc type. +func FuncARSs(fn func() []string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + arr := &tengo.Array{} + for _, elem := range fn() { + if len(elem) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.String{Value: elem}) + } + return arr, nil + } +} + +// FuncARIsE transform a function of 'func() ([]int, error)' signature into +// CallableFunc type. +func FuncARIsE(fn func() ([]int, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + res, err := fn() + if err != nil { + return wrapError(err), nil + } + arr := &tengo.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + res := fn(i1) + arr := &tengo.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + return &tengo.Float{Value: fn(f1)}, nil + } +} + +// FuncAIR transform a function of 'func(int)' signature into CallableFunc type. +func FuncAIR(fn func(int)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + fn(i1) + return tengo.UndefinedValue, nil + } +} + +// FuncAIRF transform a function of 'func(int) float64' signature into +// CallableFunc type. +func FuncAIRF(fn func(int) float64) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + return &tengo.Float{Value: fn(i1)}, nil + } +} + +// FuncAFRI transform a function of 'func(float64) int' signature into +// CallableFunc type. +func FuncAFRI(fn func(float64) int) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + f2, ok := tengo.ToFloat64(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + f2, ok := tengo.ToFloat64(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + if fn(f1, i2) { + return tengo.TrueValue, nil + } + return tengo.FalseValue, nil + } +} + +// FuncAFRB transform a function of 'func(float64) bool' signature +// into CallableFunc type. +func FuncAFRB(fn func(float64) bool) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + f1, ok := tengo.ToFloat64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + if fn(f1) { + return tengo.TrueValue, nil + } + return tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s := fn(s1) + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil + } +} + +// FuncASRSs transform a function of 'func(string) []string' signature into +// CallableFunc type. +func FuncASRSs(fn func(string) []string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + res := fn(s1) + arr := &tengo.Array{} + for _, elem := range res { + if len(elem) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.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)) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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, tengo.ErrStringLimit + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + arr := &tengo.Array{} + for _, res := range fn(s1, s2) { + if len(res) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 3 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + i3, ok := tengo.ToInt(args[2]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + arr := &tengo.Array{} + for _, res := range fn(s1, s2, i3) { + if len(res) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + s := fn(s1, s2) + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil + } +} + +// FuncASSRB transform a function of 'func(string, string) bool' signature +// into CallableFunc type. +func FuncASSRB(fn func(string, string) bool) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + if fn(s1, s2) { + return tengo.TrueValue, nil + } + return tengo.FalseValue, nil + } +} + +// FuncASsSRS transform a function of 'func([]string, string) string' signature +// into CallableFunc type. +func FuncASsSRS(fn func([]string, string) string) tengo.CallableFunc { + return func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + var ss1 []string + switch arg0 := args[0].(type) { + case *tengo.Array: + for idx, a := range arg0.Value { + as, ok := tengo.ToString(a) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + case *tengo.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := tengo.ToString(a) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + s := fn(ss1, s2) + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil + } +} + +// FuncASI64RE transform a function of 'func(string, int64) error' signature +// into CallableFunc type. +func FuncASI64RE(fn func(string, int64) error) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt64(args[1]) + if !ok { + return nil, tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + s := fn(s1, i2) + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.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) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 3 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + i3, ok := tengo.ToInt(args[2]) + if !ok { + return nil, tengo.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)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + y1, ok := tengo.ToByteSlice(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes(compatible)", + Found: args[0].TypeName(), + } + } + res, err := fn(y1) + if err != nil { + return wrapError(err), nil + } + return &tengo.Int{Value: int64(res)}, nil + } +} + +// FuncAYRS transform a function of 'func([]byte) string' signature into +// CallableFunc type. +func FuncAYRS(fn func([]byte) string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + y1, ok := tengo.ToByteSlice(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes(compatible)", + Found: args[0].TypeName(), + } + } + res := fn(y1) + return &tengo.String{Value: res}, nil + } +} + +// FuncASRIE transform a function of 'func(string) (int, error)' signature +// into CallableFunc type. +func FuncASRIE(fn func(string) (int, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + return &tengo.Int{Value: int64(res)}, nil + } +} + +// FuncASRYE transform a function of 'func(string) ([]byte, error)' signature +// into CallableFunc type. +func FuncASRYE(fn func(string) ([]byte, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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.MaxBytesLen { + return nil, tengo.ErrBytesLimit + } + return &tengo.Bytes{Value: res}, nil + } +} + +// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature +// into CallableFunc type. +func FuncAIRSsE(fn func(int) ([]string, error)) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + res, err := fn(i1) + if err != nil { + return wrapError(err), nil + } + arr := &tengo.Array{} + for _, r := range res { + if len(r) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.String{Value: r}) + } + return arr, nil + } +} + +// FuncAIRS transform a function of 'func(int) string' signature into +// CallableFunc type. +func FuncAIRS(fn func(int) string) tengo.CallableFunc { + return func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + s := fn(i1) + if len(s) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/hex.go b/vendor/github.com/d5/tengo/v2/stdlib/hex.go new file mode 100644 index 00000000..981da696 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/hex.go @@ -0,0 +1,12 @@ +package stdlib + +import ( + "encoding/hex" + + "github.com/d5/tengo/v2" +) + +var hexModule = map[string]tengo.Object{ + "encode": &tengo.UserFunction{Value: FuncAYRS(hex.EncodeToString)}, + "decode": &tengo.UserFunction{Value: FuncASRYE(hex.DecodeString)}, +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/json.go b/vendor/github.com/d5/tengo/v2/stdlib/json.go new file mode 100644 index 00000000..be2185db --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/json.go @@ -0,0 +1,146 @@ +package stdlib + +import ( + "bytes" + gojson "encoding/json" + + "github.com/d5/tengo/v2" + "github.com/d5/tengo/v2/stdlib/json" +) + +var jsonModule = map[string]tengo.Object{ + "decode": &tengo.UserFunction{ + Name: "decode", + Value: jsonDecode, + }, + "encode": &tengo.UserFunction{ + Name: "encode", + Value: jsonEncode, + }, + "indent": &tengo.UserFunction{ + Name: "encode", + Value: jsonIndent, + }, + "html_escape": &tengo.UserFunction{ + Name: "html_escape", + Value: jsonHTMLEscape, + }, +} + +func jsonDecode(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + switch o := args[0].(type) { + case *tengo.Bytes: + v, err := json.Decode(o.Value) + if err != nil { + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil + } + return v, nil + case *tengo.String: + v, err := json.Decode([]byte(o.Value)) + if err != nil { + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil + } + return v, nil + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} + +func jsonEncode(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + b, err := json.Encode(args[0]) + if err != nil { + return &tengo.Error{Value: &tengo.String{Value: err.Error()}}, nil + } + + return &tengo.Bytes{Value: b}, nil +} + +func jsonIndent(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 3 { + return nil, tengo.ErrWrongNumArguments + } + + prefix, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "prefix", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + indent, ok := tengo.ToString(args[2]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "indent", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + switch o := args[0].(type) { + case *tengo.Bytes: + var dst bytes.Buffer + err := gojson.Indent(&dst, o.Value, prefix, indent) + if err != nil { + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil + } + return &tengo.Bytes{Value: dst.Bytes()}, nil + case *tengo.String: + var dst bytes.Buffer + err := gojson.Indent(&dst, []byte(o.Value), prefix, indent) + if err != nil { + return &tengo.Error{ + Value: &tengo.String{Value: err.Error()}, + }, nil + } + return &tengo.Bytes{Value: dst.Bytes()}, nil + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} + +func jsonHTMLEscape(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + switch o := args[0].(type) { + case *tengo.Bytes: + var dst bytes.Buffer + gojson.HTMLEscape(&dst, o.Value) + return &tengo.Bytes{Value: dst.Bytes()}, nil + case *tengo.String: + var dst bytes.Buffer + gojson.HTMLEscape(&dst, []byte(o.Value)) + return &tengo.Bytes{Value: dst.Bytes()}, nil + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/json/decode.go b/vendor/github.com/d5/tengo/v2/stdlib/json/decode.go new file mode 100644 index 00000000..6d468ef0 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/json/decode.go @@ -0,0 +1,358 @@ +// 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/v2" +) + +// Decode parses the JSON-encoded data and returns the result object. +func Decode(data []byte) (tengo.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() (tengo.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() (tengo.Object, error) { + var arr []tengo.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 &tengo.Array{Value: arr}, nil +} + +func (d *decodeState) object() (tengo.Object, error) { + m := make(map[string]tengo.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 &tengo.Map{Value: m}, nil +} + +func (d *decodeState) literal() (tengo.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 tengo.UndefinedValue, nil + + case 't', 'f': // true, false + if c == 't' { + return tengo.TrueValue, nil + } + return tengo.FalseValue, nil + + case '"': // string + s, ok := unquote(item) + if !ok { + panic(phasePanicMsg) + } + return &tengo.String{Value: s}, nil + + default: // number + if c != '-' && (c < '0' || c > '9') { + panic(phasePanicMsg) + } + n, _ := strconv.ParseFloat(string(item), 10) + return &tengo.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:]) + dec := utf16.DecodeRune(rr, rr1) + if 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/v2/stdlib/json/encode.go b/vendor/github.com/d5/tengo/v2/stdlib/json/encode.go new file mode 100644 index 00000000..ab7ca6ff --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/json/encode.go @@ -0,0 +1,146 @@ +// 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/v2" +) + +// Encode returns the JSON encoding of the object. +func Encode(o tengo.Object) ([]byte, error) { + var b []byte + + switch o := o.(type) { + case *tengo.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 *tengo.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 *tengo.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 *tengo.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 *tengo.Bool: + if o.IsFalsy() { + b = strconv.AppendBool(b, false) + } else { + b = strconv.AppendBool(b, true) + } + case *tengo.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 *tengo.Char: + b = strconv.AppendInt(b, int64(o.Value), 10) + case *tengo.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 *tengo.Int: + b = strconv.AppendInt(b, o.Value, 10) + case *tengo.String: + b = strconv.AppendQuote(b, o.Value) + case *tengo.Time: + y, err := o.Value.MarshalJSON() + if err != nil { + return nil, err + } + b = append(b, y...) + case *tengo.Undefined: + b = append(b, "null"...) + default: + // unknown type: ignore + } + return b, nil +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/json/scanner.go b/vendor/github.com/d5/tengo/v2/stdlib/json/scanner.go new file mode 100644 index 00000000..aed15cf5 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/json/scanner.go @@ -0,0 +1,562 @@ +// 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(_ *scanner, _ 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{ + msg: "invalid character " + quoteChar(c) + " " + context, + Offset: 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/v2/stdlib/math.go b/vendor/github.com/d5/tengo/v2/stdlib/math.go new file mode 100644 index 00000000..633ea09f --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/math.go @@ -0,0 +1,233 @@ +package stdlib + +import ( + "math" + + "github.com/d5/tengo/v2" +) + +var mathModule = map[string]tengo.Object{ + "e": &tengo.Float{Value: math.E}, + "pi": &tengo.Float{Value: math.Pi}, + "phi": &tengo.Float{Value: math.Phi}, + "sqrt2": &tengo.Float{Value: math.Sqrt2}, + "sqrtE": &tengo.Float{Value: math.SqrtE}, + "sqrtPi": &tengo.Float{Value: math.SqrtPi}, + "sqrtPhi": &tengo.Float{Value: math.SqrtPhi}, + "ln2": &tengo.Float{Value: math.Ln2}, + "log2E": &tengo.Float{Value: math.Log2E}, + "ln10": &tengo.Float{Value: math.Ln10}, + "log10E": &tengo.Float{Value: math.Log10E}, + "abs": &tengo.UserFunction{ + Name: "abs", + Value: FuncAFRF(math.Abs), + }, + "acos": &tengo.UserFunction{ + Name: "acos", + Value: FuncAFRF(math.Acos), + }, + "acosh": &tengo.UserFunction{ + Name: "acosh", + Value: FuncAFRF(math.Acosh), + }, + "asin": &tengo.UserFunction{ + Name: "asin", + Value: FuncAFRF(math.Asin), + }, + "asinh": &tengo.UserFunction{ + Name: "asinh", + Value: FuncAFRF(math.Asinh), + }, + "atan": &tengo.UserFunction{ + Name: "atan", + Value: FuncAFRF(math.Atan), + }, + "atan2": &tengo.UserFunction{ + Name: "atan2", + Value: FuncAFFRF(math.Atan2), + }, + "atanh": &tengo.UserFunction{ + Name: "atanh", + Value: FuncAFRF(math.Atanh), + }, + "cbrt": &tengo.UserFunction{ + Name: "cbrt", + Value: FuncAFRF(math.Cbrt), + }, + "ceil": &tengo.UserFunction{ + Name: "ceil", + Value: FuncAFRF(math.Ceil), + }, + "copysign": &tengo.UserFunction{ + Name: "copysign", + Value: FuncAFFRF(math.Copysign), + }, + "cos": &tengo.UserFunction{ + Name: "cos", + Value: FuncAFRF(math.Cos), + }, + "cosh": &tengo.UserFunction{ + Name: "cosh", + Value: FuncAFRF(math.Cosh), + }, + "dim": &tengo.UserFunction{ + Name: "dim", + Value: FuncAFFRF(math.Dim), + }, + "erf": &tengo.UserFunction{ + Name: "erf", + Value: FuncAFRF(math.Erf), + }, + "erfc": &tengo.UserFunction{ + Name: "erfc", + Value: FuncAFRF(math.Erfc), + }, + "exp": &tengo.UserFunction{ + Name: "exp", + Value: FuncAFRF(math.Exp), + }, + "exp2": &tengo.UserFunction{ + Name: "exp2", + Value: FuncAFRF(math.Exp2), + }, + "expm1": &tengo.UserFunction{ + Name: "expm1", + Value: FuncAFRF(math.Expm1), + }, + "floor": &tengo.UserFunction{ + Name: "floor", + Value: FuncAFRF(math.Floor), + }, + "gamma": &tengo.UserFunction{ + Name: "gamma", + Value: FuncAFRF(math.Gamma), + }, + "hypot": &tengo.UserFunction{ + Name: "hypot", + Value: FuncAFFRF(math.Hypot), + }, + "ilogb": &tengo.UserFunction{ + Name: "ilogb", + Value: FuncAFRI(math.Ilogb), + }, + "inf": &tengo.UserFunction{ + Name: "inf", + Value: FuncAIRF(math.Inf), + }, + "is_inf": &tengo.UserFunction{ + Name: "is_inf", + Value: FuncAFIRB(math.IsInf), + }, + "is_nan": &tengo.UserFunction{ + Name: "is_nan", + Value: FuncAFRB(math.IsNaN), + }, + "j0": &tengo.UserFunction{ + Name: "j0", + Value: FuncAFRF(math.J0), + }, + "j1": &tengo.UserFunction{ + Name: "j1", + Value: FuncAFRF(math.J1), + }, + "jn": &tengo.UserFunction{ + Name: "jn", + Value: FuncAIFRF(math.Jn), + }, + "ldexp": &tengo.UserFunction{ + Name: "ldexp", + Value: FuncAFIRF(math.Ldexp), + }, + "log": &tengo.UserFunction{ + Name: "log", + Value: FuncAFRF(math.Log), + }, + "log10": &tengo.UserFunction{ + Name: "log10", + Value: FuncAFRF(math.Log10), + }, + "log1p": &tengo.UserFunction{ + Name: "log1p", + Value: FuncAFRF(math.Log1p), + }, + "log2": &tengo.UserFunction{ + Name: "log2", + Value: FuncAFRF(math.Log2), + }, + "logb": &tengo.UserFunction{ + Name: "logb", + Value: FuncAFRF(math.Logb), + }, + "max": &tengo.UserFunction{ + Name: "max", + Value: FuncAFFRF(math.Max), + }, + "min": &tengo.UserFunction{ + Name: "min", + Value: FuncAFFRF(math.Min), + }, + "mod": &tengo.UserFunction{ + Name: "mod", + Value: FuncAFFRF(math.Mod), + }, + "nan": &tengo.UserFunction{ + Name: "nan", + Value: FuncARF(math.NaN), + }, + "nextafter": &tengo.UserFunction{ + Name: "nextafter", + Value: FuncAFFRF(math.Nextafter), + }, + "pow": &tengo.UserFunction{ + Name: "pow", + Value: FuncAFFRF(math.Pow), + }, + "pow10": &tengo.UserFunction{ + Name: "pow10", + Value: FuncAIRF(math.Pow10), + }, + "remainder": &tengo.UserFunction{ + Name: "remainder", + Value: FuncAFFRF(math.Remainder), + }, + "signbit": &tengo.UserFunction{ + Name: "signbit", + Value: FuncAFRB(math.Signbit), + }, + "sin": &tengo.UserFunction{ + Name: "sin", + Value: FuncAFRF(math.Sin), + }, + "sinh": &tengo.UserFunction{ + Name: "sinh", + Value: FuncAFRF(math.Sinh), + }, + "sqrt": &tengo.UserFunction{ + Name: "sqrt", + Value: FuncAFRF(math.Sqrt), + }, + "tan": &tengo.UserFunction{ + Name: "tan", + Value: FuncAFRF(math.Tan), + }, + "tanh": &tengo.UserFunction{ + Name: "tanh", + Value: FuncAFRF(math.Tanh), + }, + "trunc": &tengo.UserFunction{ + Name: "trunc", + Value: FuncAFRF(math.Trunc), + }, + "y0": &tengo.UserFunction{ + Name: "y0", + Value: FuncAFRF(math.Y0), + }, + "y1": &tengo.UserFunction{ + Name: "y1", + Value: FuncAFRF(math.Y1), + }, + "yn": &tengo.UserFunction{ + Name: "yn", + Value: FuncAIFRF(math.Yn), + }, +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/os.go b/vendor/github.com/d5/tengo/v2/stdlib/os.go new file mode 100644 index 00000000..576bc94b --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/os.go @@ -0,0 +1,564 @@ +package stdlib + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + "github.com/d5/tengo/v2" +) + +var osModule = map[string]tengo.Object{ + "o_rdonly": &tengo.Int{Value: int64(os.O_RDONLY)}, + "o_wronly": &tengo.Int{Value: int64(os.O_WRONLY)}, + "o_rdwr": &tengo.Int{Value: int64(os.O_RDWR)}, + "o_append": &tengo.Int{Value: int64(os.O_APPEND)}, + "o_create": &tengo.Int{Value: int64(os.O_CREATE)}, + "o_excl": &tengo.Int{Value: int64(os.O_EXCL)}, + "o_sync": &tengo.Int{Value: int64(os.O_SYNC)}, + "o_trunc": &tengo.Int{Value: int64(os.O_TRUNC)}, + "mode_dir": &tengo.Int{Value: int64(os.ModeDir)}, + "mode_append": &tengo.Int{Value: int64(os.ModeAppend)}, + "mode_exclusive": &tengo.Int{Value: int64(os.ModeExclusive)}, + "mode_temporary": &tengo.Int{Value: int64(os.ModeTemporary)}, + "mode_symlink": &tengo.Int{Value: int64(os.ModeSymlink)}, + "mode_device": &tengo.Int{Value: int64(os.ModeDevice)}, + "mode_named_pipe": &tengo.Int{Value: int64(os.ModeNamedPipe)}, + "mode_socket": &tengo.Int{Value: int64(os.ModeSocket)}, + "mode_setuid": &tengo.Int{Value: int64(os.ModeSetuid)}, + "mode_setgui": &tengo.Int{Value: int64(os.ModeSetgid)}, + "mode_char_device": &tengo.Int{Value: int64(os.ModeCharDevice)}, + "mode_sticky": &tengo.Int{Value: int64(os.ModeSticky)}, + "mode_type": &tengo.Int{Value: int64(os.ModeType)}, + "mode_perm": &tengo.Int{Value: int64(os.ModePerm)}, + "path_separator": &tengo.Char{Value: os.PathSeparator}, + "path_list_separator": &tengo.Char{Value: os.PathListSeparator}, + "dev_null": &tengo.String{Value: os.DevNull}, + "seek_set": &tengo.Int{Value: int64(io.SeekStart)}, + "seek_cur": &tengo.Int{Value: int64(io.SeekCurrent)}, + "seek_end": &tengo.Int{Value: int64(io.SeekEnd)}, + "args": &tengo.UserFunction{ + Name: "args", + Value: osArgs, + }, // args() => array(string) + "chdir": &tengo.UserFunction{ + Name: "chdir", + Value: FuncASRE(os.Chdir), + }, // chdir(dir string) => error + "chmod": osFuncASFmRE("chmod", os.Chmod), // chmod(name string, mode int) => error + "chown": &tengo.UserFunction{ + Name: "chown", + Value: FuncASIIRE(os.Chown), + }, // chown(name string, uid int, gid int) => error + "clearenv": &tengo.UserFunction{ + Name: "clearenv", + Value: FuncAR(os.Clearenv), + }, // clearenv() + "environ": &tengo.UserFunction{ + Name: "environ", + Value: FuncARSs(os.Environ), + }, // environ() => array(string) + "exit": &tengo.UserFunction{ + Name: "exit", + Value: FuncAIR(os.Exit), + }, // exit(code int) + "expand_env": &tengo.UserFunction{ + Name: "expand_env", + Value: osExpandEnv, + }, // expand_env(s string) => string + "getegid": &tengo.UserFunction{ + Name: "getegid", + Value: FuncARI(os.Getegid), + }, // getegid() => int + "getenv": &tengo.UserFunction{ + Name: "getenv", + Value: FuncASRS(os.Getenv), + }, // getenv(s string) => string + "geteuid": &tengo.UserFunction{ + Name: "geteuid", + Value: FuncARI(os.Geteuid), + }, // geteuid() => int + "getgid": &tengo.UserFunction{ + Name: "getgid", + Value: FuncARI(os.Getgid), + }, // getgid() => int + "getgroups": &tengo.UserFunction{ + Name: "getgroups", + Value: FuncARIsE(os.Getgroups), + }, // getgroups() => array(string)/error + "getpagesize": &tengo.UserFunction{ + Name: "getpagesize", + Value: FuncARI(os.Getpagesize), + }, // getpagesize() => int + "getpid": &tengo.UserFunction{ + Name: "getpid", + Value: FuncARI(os.Getpid), + }, // getpid() => int + "getppid": &tengo.UserFunction{ + Name: "getppid", + Value: FuncARI(os.Getppid), + }, // getppid() => int + "getuid": &tengo.UserFunction{ + Name: "getuid", + Value: FuncARI(os.Getuid), + }, // getuid() => int + "getwd": &tengo.UserFunction{ + Name: "getwd", + Value: FuncARSE(os.Getwd), + }, // getwd() => string/error + "hostname": &tengo.UserFunction{ + Name: "hostname", + Value: FuncARSE(os.Hostname), + }, // hostname() => string/error + "lchown": &tengo.UserFunction{ + Name: "lchown", + Value: FuncASIIRE(os.Lchown), + }, // lchown(name string, uid int, gid int) => error + "link": &tengo.UserFunction{ + Name: "link", + Value: FuncASSRE(os.Link), + }, // link(oldname string, newname string) => error + "lookup_env": &tengo.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": &tengo.UserFunction{ + Name: "readlink", + Value: FuncASRSE(os.Readlink), + }, // readlink(name string) => string/error + "remove": &tengo.UserFunction{ + Name: "remove", + Value: FuncASRE(os.Remove), + }, // remove(name string) => error + "remove_all": &tengo.UserFunction{ + Name: "remove_all", + Value: FuncASRE(os.RemoveAll), + }, // remove_all(name string) => error + "rename": &tengo.UserFunction{ + Name: "rename", + Value: FuncASSRE(os.Rename), + }, // rename(oldpath string, newpath string) => error + "setenv": &tengo.UserFunction{ + Name: "setenv", + Value: FuncASSRE(os.Setenv), + }, // setenv(key string, value string) => error + "symlink": &tengo.UserFunction{ + Name: "symlink", + Value: FuncASSRE(os.Symlink), + }, // symlink(oldname string newname string) => error + "temp_dir": &tengo.UserFunction{ + Name: "temp_dir", + Value: FuncARS(os.TempDir), + }, // temp_dir() => string + "truncate": &tengo.UserFunction{ + Name: "truncate", + Value: FuncASI64RE(os.Truncate), + }, // truncate(name string, size int) => error + "unsetenv": &tengo.UserFunction{ + Name: "unsetenv", + Value: FuncASRE(os.Unsetenv), + }, // unsetenv(key string) => error + "create": &tengo.UserFunction{ + Name: "create", + Value: osCreate, + }, // create(name string) => imap(file)/error + "open": &tengo.UserFunction{ + Name: "open", + Value: osOpen, + }, // open(name string) => imap(file)/error + "open_file": &tengo.UserFunction{ + Name: "open_file", + Value: osOpenFile, + }, // open_file(name string, flag int, perm int) => imap(file)/error + "find_process": &tengo.UserFunction{ + Name: "find_process", + Value: osFindProcess, + }, // find_process(pid int) => imap(process)/error + "start_process": &tengo.UserFunction{ + Name: "start_process", + Value: osStartProcess, + }, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "exec_look_path": &tengo.UserFunction{ + Name: "exec_look_path", + Value: FuncASRSE(exec.LookPath), + }, // exec_look_path(file) => string/error + "exec": &tengo.UserFunction{ + Name: "exec", + Value: osExec, + }, // exec(name, args...) => command + "stat": &tengo.UserFunction{ + Name: "stat", + Value: osStat, + }, // stat(name) => imap(fileinfo)/error + "read_file": &tengo.UserFunction{ + Name: "read_file", + Value: osReadFile, + }, // readfile(name) => array(byte)/error +} + +func osReadFile(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + fname, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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, tengo.ErrBytesLimit + } + return &tengo.Bytes{Value: bytes}, nil +} + +func osStat(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + fname, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + stat, err := os.Stat(fname) + if err != nil { + return wrapError(err), nil + } + fstat := &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "name": &tengo.String{Value: stat.Name()}, + "mtime": &tengo.Time{Value: stat.ModTime()}, + "size": &tengo.Int{Value: stat.Size()}, + "mode": &tengo.Int{Value: int64(stat.Mode())}, + }, + } + if stat.IsDir() { + fstat.Value["directory"] = tengo.TrueValue + } else { + fstat.Value["directory"] = tengo.FalseValue + } + return fstat, nil +} + +func osCreate(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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 ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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 ...tengo.Object) (tengo.Object, error) { + if len(args) != 3 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + i3, ok := tengo.ToInt(args[2]) + if !ok { + return nil, tengo.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 ...tengo.Object) (tengo.Object, error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + arr := &tengo.Array{} + for _, osArg := range os.Args { + if len(osArg) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + arr.Value = append(arr.Value, &tengo.String{Value: osArg}) + } + return arr, nil +} + +func osFuncASFmRE( + name string, + fn func(string, os.FileMode) error, +) *tengo.UserFunction { + return &tengo.UserFunction{ + Name: name, + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt64(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + return wrapError(fn(s1, os.FileMode(i2))), nil + }, + } +} + +func osLookupEnv(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + res, ok := os.LookupEnv(s1) + if !ok { + return tengo.FalseValue, nil + } + if len(res) > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + return &tengo.String{Value: res}, nil +} + +func osExpandEnv(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.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, tengo.ErrStringLimit + } + return &tengo.String{Value: s}, nil +} + +func osExec(args ...tengo.Object) (tengo.Object, error) { + if len(args) == 0 { + return nil, tengo.ErrWrongNumArguments + } + name, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + var execArgs []string + for idx, arg := range args[1:] { + execArg, ok := tengo.ToString(arg) + if !ok { + return nil, tengo.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 ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt(args[0]) + if !ok { + return nil, tengo.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 ...tengo.Object) (tengo.Object, error) { + if len(args) != 4 { + return nil, tengo.ErrWrongNumArguments + } + name, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + var argv []string + var err error + switch arg1 := args[1].(type) { + case *tengo.Array: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + case *tengo.ImmutableArray: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "array", + Found: arg1.TypeName(), + } + } + + dir, ok := tengo.ToString(args[2]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + var env []string + switch arg3 := args[3].(type) { + case *tengo.Array: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + case *tengo.ImmutableArray: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + default: + return nil, tengo.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 []tengo.Object, argName string) ([]string, error) { + var sarr []string + for idx, elem := range arr { + str, ok := elem.(*tengo.String) + if !ok { + return nil, tengo.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/v2/stdlib/os_exec.go b/vendor/github.com/d5/tengo/v2/stdlib/os_exec.go new file mode 100644 index 00000000..7ee5c1cd --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/os_exec.go @@ -0,0 +1,119 @@ +package stdlib + +import ( + "os/exec" + + "github.com/d5/tengo/v2" +) + +func makeOSExecCommand(cmd *exec.Cmd) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + // combined_output() => bytes/error + "combined_output": &tengo.UserFunction{ + Name: "combined_output", + Value: FuncARYE(cmd.CombinedOutput), + }, + // output() => bytes/error + "output": &tengo.UserFunction{ + Name: "output", + Value: FuncARYE(cmd.Output), + }, // + // run() => error + "run": &tengo.UserFunction{ + Name: "run", + Value: FuncARE(cmd.Run), + }, // + // start() => error + "start": &tengo.UserFunction{ + Name: "start", + Value: FuncARE(cmd.Start), + }, // + // wait() => error + "wait": &tengo.UserFunction{ + Name: "wait", + Value: FuncARE(cmd.Wait), + }, // + // set_path(path string) + "set_path": &tengo.UserFunction{ + Name: "set_path", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + cmd.Path = s1 + return tengo.UndefinedValue, nil + }, + }, + // set_dir(dir string) + "set_dir": &tengo.UserFunction{ + Name: "set_dir", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + cmd.Dir = s1 + return tengo.UndefinedValue, nil + }, + }, + // set_env(env array(string)) + "set_env": &tengo.UserFunction{ + Name: "set_env", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + + var env []string + var err error + switch arg0 := args[0].(type) { + case *tengo.Array: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + case *tengo.ImmutableArray: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg0.TypeName(), + } + } + cmd.Env = env + return tengo.UndefinedValue, nil + }, + }, + // process() => imap(process) + "process": &tengo.UserFunction{ + Name: "process", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return makeOSProcess(cmd.Process), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/os_file.go b/vendor/github.com/d5/tengo/v2/stdlib/os_file.go new file mode 100644 index 00000000..4f59b4c4 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/os_file.go @@ -0,0 +1,117 @@ +package stdlib + +import ( + "os" + + "github.com/d5/tengo/v2" +) + +func makeOSFile(file *os.File) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + // chdir() => true/error + "chdir": &tengo.UserFunction{ + Name: "chdir", + Value: FuncARE(file.Chdir), + }, // + // chown(uid int, gid int) => true/error + "chown": &tengo.UserFunction{ + Name: "chown", + Value: FuncAIIRE(file.Chown), + }, // + // close() => error + "close": &tengo.UserFunction{ + Name: "close", + Value: FuncARE(file.Close), + }, // + // name() => string + "name": &tengo.UserFunction{ + Name: "name", + Value: FuncARS(file.Name), + }, // + // readdirnames(n int) => array(string)/error + "readdirnames": &tengo.UserFunction{ + Name: "readdirnames", + Value: FuncAIRSsE(file.Readdirnames), + }, // + // sync() => error + "sync": &tengo.UserFunction{ + Name: "sync", + Value: FuncARE(file.Sync), + }, // + // write(bytes) => int/error + "write": &tengo.UserFunction{ + Name: "write", + Value: FuncAYRIE(file.Write), + }, // + // write(string) => int/error + "write_string": &tengo.UserFunction{ + Name: "write_string", + Value: FuncASRIE(file.WriteString), + }, // + // read(bytes) => int/error + "read": &tengo.UserFunction{ + Name: "read", + Value: FuncAYRIE(file.Read), + }, // + // chmod(mode int) => error + "chmod": &tengo.UserFunction{ + Name: "chmod", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.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": &tengo.UserFunction{ + Name: "seek", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + res, err := file.Seek(i1, i2) + if err != nil { + return wrapError(err), nil + } + return &tengo.Int{Value: res}, nil + }, + }, + // stat() => imap(fileinfo)/error + "stat": &tengo.UserFunction{ + Name: "stat", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + return osStat(&tengo.String{Value: file.Name()}) + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/os_process.go b/vendor/github.com/d5/tengo/v2/stdlib/os_process.go new file mode 100644 index 00000000..7fcf27a4 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/os_process.go @@ -0,0 +1,76 @@ +package stdlib + +import ( + "os" + "syscall" + + "github.com/d5/tengo/v2" +) + +func makeOSProcessState(state *os.ProcessState) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "exited": &tengo.UserFunction{ + Name: "exited", + Value: FuncARB(state.Exited), + }, + "pid": &tengo.UserFunction{ + Name: "pid", + Value: FuncARI(state.Pid), + }, + "string": &tengo.UserFunction{ + Name: "string", + Value: FuncARS(state.String), + }, + "success": &tengo.UserFunction{ + Name: "success", + Value: FuncARB(state.Success), + }, + }, + } +} + +func makeOSProcess(proc *os.Process) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "kill": &tengo.UserFunction{ + Name: "kill", + Value: FuncARE(proc.Kill), + }, + "release": &tengo.UserFunction{ + Name: "release", + Value: FuncARE(proc.Release), + }, + "signal": &tengo.UserFunction{ + Name: "signal", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + return wrapError(proc.Signal(syscall.Signal(i1))), nil + }, + }, + "wait": &tengo.UserFunction{ + Name: "wait", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 0 { + return nil, tengo.ErrWrongNumArguments + } + state, err := proc.Wait() + if err != nil { + return wrapError(err), nil + } + return makeOSProcessState(state), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/rand.go b/vendor/github.com/d5/tengo/v2/stdlib/rand.go new file mode 100644 index 00000000..5d21e1df --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/rand.go @@ -0,0 +1,138 @@ +package stdlib + +import ( + "math/rand" + + "github.com/d5/tengo/v2" +) + +var randModule = map[string]tengo.Object{ + "int": &tengo.UserFunction{ + Name: "int", + Value: FuncARI64(rand.Int63), + }, + "float": &tengo.UserFunction{ + Name: "float", + Value: FuncARF(rand.Float64), + }, + "intn": &tengo.UserFunction{ + Name: "intn", + Value: FuncAI64RI64(rand.Int63n), + }, + "exp_float": &tengo.UserFunction{ + Name: "exp_float", + Value: FuncARF(rand.ExpFloat64), + }, + "norm_float": &tengo.UserFunction{ + Name: "norm_float", + Value: FuncARF(rand.NormFloat64), + }, + "perm": &tengo.UserFunction{ + Name: "perm", + Value: FuncAIRIs(rand.Perm), + }, + "seed": &tengo.UserFunction{ + Name: "seed", + Value: FuncAI64R(rand.Seed), + }, + "read": &tengo.UserFunction{ + Name: "read", + Value: func(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + y1, ok := args[0].(*tengo.Bytes) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + res, err := rand.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + return &tengo.Int{Value: int64(res)}, nil + }, + }, + "rand": &tengo.UserFunction{ + Name: "rand", + Value: func(args ...tengo.Object) (tengo.Object, error) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + i1, ok := tengo.ToInt64(args[0]) + if !ok { + return nil, tengo.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) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "int": &tengo.UserFunction{ + Name: "int", + Value: FuncARI64(r.Int63), + }, + "float": &tengo.UserFunction{ + Name: "float", + Value: FuncARF(r.Float64), + }, + "intn": &tengo.UserFunction{ + Name: "intn", + Value: FuncAI64RI64(r.Int63n), + }, + "exp_float": &tengo.UserFunction{ + Name: "exp_float", + Value: FuncARF(r.ExpFloat64), + }, + "norm_float": &tengo.UserFunction{ + Name: "norm_float", + Value: FuncARF(r.NormFloat64), + }, + "perm": &tengo.UserFunction{ + Name: "perm", + Value: FuncAIRIs(r.Perm), + }, + "seed": &tengo.UserFunction{ + Name: "seed", + Value: FuncAI64R(r.Seed), + }, + "read": &tengo.UserFunction{ + Name: "read", + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + if len(args) != 1 { + return nil, tengo.ErrWrongNumArguments + } + y1, ok := args[0].(*tengo.Bytes) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + res, err := r.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + return &tengo.Int{Value: int64(res)}, nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/source_modules.go b/vendor/github.com/d5/tengo/v2/stdlib/source_modules.go new file mode 100644 index 00000000..ca69d7d1 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/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/v2/stdlib/srcmod_enum.tengo b/vendor/github.com/d5/tengo/v2/stdlib/srcmod_enum.tengo new file mode 100644 index 00000000..7a5ea637 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/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/v2/stdlib/stdlib.go b/vendor/github.com/d5/tengo/v2/stdlib/stdlib.go new file mode 100644 index 00000000..16c369a0 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/stdlib.go @@ -0,0 +1,34 @@ +package stdlib + +//go:generate go run gensrcmods.go + +import ( + "github.com/d5/tengo/v2" +) + +// 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) *tengo.ModuleMap { + modules := tengo.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/v2/stdlib/text.go b/vendor/github.com/d5/tengo/v2/stdlib/text.go new file mode 100644 index 00000000..d7d5d1da --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/text.go @@ -0,0 +1,1072 @@ +package stdlib + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "unicode/utf8" + + "github.com/d5/tengo/v2" +) + +var textModule = map[string]tengo.Object{ + "re_match": &tengo.UserFunction{ + Name: "re_match", + Value: textREMatch, + }, // re_match(pattern, text) => bool/error + "re_find": &tengo.UserFunction{ + Name: "re_find", + Value: textREFind, + }, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined + "re_replace": &tengo.UserFunction{ + Name: "re_replace", + Value: textREReplace, + }, // re_replace(pattern, text, repl) => string/error + "re_split": &tengo.UserFunction{ + Name: "re_split", + Value: textRESplit, + }, // re_split(pattern, text, count) => [string]/error + "re_compile": &tengo.UserFunction{ + Name: "re_compile", + Value: textRECompile, + }, // re_compile(pattern) => Regexp/error + "compare": &tengo.UserFunction{ + Name: "compare", + Value: FuncASSRI(strings.Compare), + }, // compare(a, b) => int + "contains": &tengo.UserFunction{ + Name: "contains", + Value: FuncASSRB(strings.Contains), + }, // contains(s, substr) => bool + "contains_any": &tengo.UserFunction{ + Name: "contains_any", + Value: FuncASSRB(strings.ContainsAny), + }, // contains_any(s, chars) => bool + "count": &tengo.UserFunction{ + Name: "count", + Value: FuncASSRI(strings.Count), + }, // count(s, substr) => int + "equal_fold": &tengo.UserFunction{ + Name: "equal_fold", + Value: FuncASSRB(strings.EqualFold), + }, // "equal_fold(s, t) => bool + "fields": &tengo.UserFunction{ + Name: "fields", + Value: FuncASRSs(strings.Fields), + }, // fields(s) => [string] + "has_prefix": &tengo.UserFunction{ + Name: "has_prefix", + Value: FuncASSRB(strings.HasPrefix), + }, // has_prefix(s, prefix) => bool + "has_suffix": &tengo.UserFunction{ + Name: "has_suffix", + Value: FuncASSRB(strings.HasSuffix), + }, // has_suffix(s, suffix) => bool + "index": &tengo.UserFunction{ + Name: "index", + Value: FuncASSRI(strings.Index), + }, // index(s, substr) => int + "index_any": &tengo.UserFunction{ + Name: "index_any", + Value: FuncASSRI(strings.IndexAny), + }, // index_any(s, chars) => int + "join": &tengo.UserFunction{ + Name: "join", + Value: textJoin, + }, // join(arr, sep) => string + "last_index": &tengo.UserFunction{ + Name: "last_index", + Value: FuncASSRI(strings.LastIndex), + }, // last_index(s, substr) => int + "last_index_any": &tengo.UserFunction{ + Name: "last_index_any", + Value: FuncASSRI(strings.LastIndexAny), + }, // last_index_any(s, chars) => int + "repeat": &tengo.UserFunction{ + Name: "repeat", + Value: textRepeat, + }, // repeat(s, count) => string + "replace": &tengo.UserFunction{ + Name: "replace", + Value: textReplace, + }, // replace(s, old, new, n) => string + "substr": &tengo.UserFunction{ + Name: "substr", + Value: textSubstring, + }, // substr(s, lower, upper) => string + "split": &tengo.UserFunction{ + Name: "split", + Value: FuncASSRSs(strings.Split), + }, // split(s, sep) => [string] + "split_after": &tengo.UserFunction{ + Name: "split_after", + Value: FuncASSRSs(strings.SplitAfter), + }, // split_after(s, sep) => [string] + "split_after_n": &tengo.UserFunction{ + Name: "split_after_n", + Value: FuncASSIRSs(strings.SplitAfterN), + }, // split_after_n(s, sep, n) => [string] + "split_n": &tengo.UserFunction{ + Name: "split_n", + Value: FuncASSIRSs(strings.SplitN), + }, // split_n(s, sep, n) => [string] + "title": &tengo.UserFunction{ + Name: "title", + Value: FuncASRS(strings.Title), + }, // title(s) => string + "to_lower": &tengo.UserFunction{ + Name: "to_lower", + Value: FuncASRS(strings.ToLower), + }, // to_lower(s) => string + "to_title": &tengo.UserFunction{ + Name: "to_title", + Value: FuncASRS(strings.ToTitle), + }, // to_title(s) => string + "to_upper": &tengo.UserFunction{ + Name: "to_upper", + Value: FuncASRS(strings.ToUpper), + }, // to_upper(s) => string + "pad_left": &tengo.UserFunction{ + Name: "pad_left", + Value: textPadLeft, + }, // pad_left(s, pad_len, pad_with) => string + "pad_right": &tengo.UserFunction{ + Name: "pad_right", + Value: textPadRight, + }, // pad_right(s, pad_len, pad_with) => string + "trim": &tengo.UserFunction{ + Name: "trim", + Value: FuncASSRS(strings.Trim), + }, // trim(s, cutset) => string + "trim_left": &tengo.UserFunction{ + Name: "trim_left", + Value: FuncASSRS(strings.TrimLeft), + }, // trim_left(s, cutset) => string + "trim_prefix": &tengo.UserFunction{ + Name: "trim_prefix", + Value: FuncASSRS(strings.TrimPrefix), + }, // trim_prefix(s, prefix) => string + "trim_right": &tengo.UserFunction{ + Name: "trim_right", + Value: FuncASSRS(strings.TrimRight), + }, // trim_right(s, cutset) => string + "trim_space": &tengo.UserFunction{ + Name: "trim_space", + Value: FuncASRS(strings.TrimSpace), + }, // trim_space(s) => string + "trim_suffix": &tengo.UserFunction{ + Name: "trim_suffix", + Value: FuncASSRS(strings.TrimSuffix), + }, // trim_suffix(s, suffix) => string + "atoi": &tengo.UserFunction{ + Name: "atoi", + Value: FuncASRIE(strconv.Atoi), + }, // atoi(str) => int/error + "format_bool": &tengo.UserFunction{ + Name: "format_bool", + Value: textFormatBool, + }, // format_bool(b) => string + "format_float": &tengo.UserFunction{ + Name: "format_float", + Value: textFormatFloat, + }, // format_float(f, fmt, prec, bits) => string + "format_int": &tengo.UserFunction{ + Name: "format_int", + Value: textFormatInt, + }, // format_int(i, base) => string + "itoa": &tengo.UserFunction{ + Name: "itoa", + Value: FuncAIRS(strconv.Itoa), + }, // itoa(i) => string + "parse_bool": &tengo.UserFunction{ + Name: "parse_bool", + Value: textParseBool, + }, // parse_bool(str) => bool/error + "parse_float": &tengo.UserFunction{ + Name: "parse_float", + Value: textParseFloat, + }, // parse_float(str, bits) => float/error + "parse_int": &tengo.UserFunction{ + Name: "parse_int", + Value: textParseInt, + }, // parse_int(str, base, bits) => int/error + "quote": &tengo.UserFunction{ + Name: "quote", + Value: FuncASRS(strconv.Quote), + }, // quote(str) => string + "unquote": &tengo.UserFunction{ + Name: "unquote", + Value: FuncASRSE(strconv.Unquote), + }, // unquote(str) => string/error +} + +func textREMatch(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.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 = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return +} + +func textREFind(args ...tengo.Object) (ret tengo.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.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 := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + if numArgs < 3 { + m := re.FindStringSubmatchIndex(s2) + if m == nil { + ret = tengo.UndefinedValue + return + } + + arr := &tengo.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, + &tengo.ImmutableMap{Value: map[string]tengo.Object{ + "text": &tengo.String{Value: s2[m[i]:m[i+1]]}, + "begin": &tengo.Int{Value: int64(m[i])}, + "end": &tengo.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &tengo.Array{Value: []tengo.Object{arr}} + + return + } + + i3, ok := tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s2, i3) + if m == nil { + ret = tengo.UndefinedValue + return + } + + arr := &tengo.Array{} + for _, m := range m { + subMatch := &tengo.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, + &tengo.ImmutableMap{Value: map[string]tengo.Object{ + "text": &tengo.String{Value: s2[m[i]:m[i+1]]}, + "begin": &tengo.Int{Value: int64(m[i])}, + "end": &tengo.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return +} + +func textREReplace(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := tengo.ToString(args[2]) + if !ok { + err = tengo.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, tengo.ErrStringLimit + } + + ret = &tengo.String{Value: s} + } + + return +} + +func textRESplit(args ...tengo.Object) (ret tengo.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + var i3 = -1 + if numArgs > 2 { + i3, ok = tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + arr := &tengo.Array{} + for _, s := range re.Split(s2, i3) { + arr.Value = append(arr.Value, &tengo.String{Value: s}) + } + + ret = arr + + return +} + +func textRECompile(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.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 ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 4 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := tengo.ToString(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := tengo.ToInt(args[3]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + s, ok := doTextReplace(s1, s2, s3, i4) + if !ok { + err = tengo.ErrStringLimit + return + } + + ret = &tengo.String{Value: s} + + return +} + +func textSubstring(args ...tengo.Object) (ret tengo.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + strlen := len(s1) + i3 := strlen + if argslen == 3 { + i3, ok = tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + if i2 > i3 { + err = tengo.ErrInvalidIndexType + return + } + + if i2 < 0 { + i2 = 0 + } else if i2 > strlen { + i2 = strlen + } + + if i3 < 0 { + i3 = 0 + } else if i3 > strlen { + i3 = strlen + } + + ret = &tengo.String{Value: s1[i2:i3]} + + return +} + +func textPadLeft(args ...tengo.Object) (ret tengo.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &tengo.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = tengo.ToString(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &tengo.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := strings.Repeat(s3, padCount) + s1 + ret = &tengo.String{Value: retStr[len(retStr)-i2:]} + + return +} + +func textPadRight(args ...tengo.Object) (ret tengo.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &tengo.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = tengo.ToString(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &tengo.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := s1 + strings.Repeat(s3, padCount) + ret = &tengo.String{Value: retStr[:i2]} + + return +} + +func textRepeat(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + if len(s1)*i2 > tengo.MaxStringLen { + return nil, tengo.ErrStringLimit + } + + return &tengo.String{Value: strings.Repeat(s1, i2)}, nil +} + +func textJoin(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + return nil, tengo.ErrWrongNumArguments + } + + var slen int + var ss1 []string + switch arg0 := args[0].(type) { + case *tengo.Array: + for idx, a := range arg0.Value { + as, ok := tengo.ToString(a) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + slen += len(as) + ss1 = append(ss1, as) + } + case *tengo.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := tengo.ToString(a) + if !ok { + return nil, tengo.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + slen += len(as) + ss1 = append(ss1, as) + } + default: + return nil, tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + return nil, tengo.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, tengo.ErrStringLimit + } + + return &tengo.String{Value: strings.Join(ss1, s2)}, nil +} + +func textFormatBool(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + b1, ok := args[0].(*tengo.Bool) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "bool", + Found: args[0].TypeName(), + } + return + } + + if b1 == tengo.TrueValue { + ret = &tengo.String{Value: "true"} + } else { + ret = &tengo.String{Value: "false"} + } + + return +} + +func textFormatFloat(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 4 { + err = tengo.ErrWrongNumArguments + return + } + + f1, ok := args[0].(*tengo.Float) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "float", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := tengo.ToInt(args[3]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &tengo.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + + return +} + +func textFormatInt(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := args[0].(*tengo.Int) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &tengo.String{Value: strconv.FormatInt(i1.Value, i2)} + + return +} + +func textParseBool(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*tengo.String) + if !ok { + err = tengo.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 = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return +} + +func textParseFloat(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*tengo.String) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.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 = &tengo.Float{Value: parsed} + + return +} + +func textParseInt(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 3 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*tengo.String) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := tengo.ToInt(args[2]) + if !ok { + err = tengo.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 = &tengo.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/v2/stdlib/text_regexp.go b/vendor/github.com/d5/tengo/v2/stdlib/text_regexp.go new file mode 100644 index 00000000..1a7ecf07 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/text_regexp.go @@ -0,0 +1,251 @@ +package stdlib + +import ( + "regexp" + + "github.com/d5/tengo/v2" +) + +func makeTextRegexp(re *regexp.Regexp) *tengo.ImmutableMap { + return &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + // match(text) => bool + "match": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if re.MatchString(s1) { + ret = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return + }, + }, + + // find(text) => array(array({text:,begin:,end:}))/undefined + // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined + "find": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if numArgs == 1 { + m := re.FindStringSubmatchIndex(s1) + if m == nil { + ret = tengo.UndefinedValue + return + } + + arr := &tengo.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, + &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "text": &tengo.String{ + Value: s1[m[i]:m[i+1]], + }, + "begin": &tengo.Int{ + Value: int64(m[i]), + }, + "end": &tengo.Int{ + Value: int64(m[i+1]), + }, + }}) + } + + ret = &tengo.Array{Value: []tengo.Object{arr}} + + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s1, i2) + if m == nil { + ret = tengo.UndefinedValue + return + } + + arr := &tengo.Array{} + for _, m := range m { + subMatch := &tengo.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, + &tengo.ImmutableMap{ + Value: map[string]tengo.Object{ + "text": &tengo.String{ + Value: s1[m[i]:m[i+1]], + }, + "begin": &tengo.Int{ + Value: int64(m[i]), + }, + "end": &tengo.Int{ + Value: int64(m[i+1]), + }, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return + }, + }, + + // replace(src, repl) => string + "replace": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s, ok := doTextRegexpReplace(re, s1, s2) + if !ok { + return nil, tengo.ErrStringLimit + } + + ret = &tengo.String{Value: s} + + return + }, + }, + + // split(text) => array(string) + // split(text, maxCount) => array(string) + "split": &tengo.UserFunction{ + Value: func(args ...tengo.Object) ( + ret tengo.Object, + err error, + ) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + var i2 = -1 + if numArgs > 1 { + i2, ok = tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + } + + arr := &tengo.Array{} + for _, s := range re.Split(s1, i2) { + arr.Value = append(arr.Value, + &tengo.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 out, true +} diff --git a/vendor/github.com/d5/tengo/v2/stdlib/times.go b/vendor/github.com/d5/tengo/v2/stdlib/times.go new file mode 100644 index 00000000..0b6f7bd4 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/stdlib/times.go @@ -0,0 +1,1135 @@ +package stdlib + +import ( + "time" + + "github.com/d5/tengo/v2" +) + +var timesModule = map[string]tengo.Object{ + "format_ansic": &tengo.String{Value: time.ANSIC}, + "format_unix_date": &tengo.String{Value: time.UnixDate}, + "format_ruby_date": &tengo.String{Value: time.RubyDate}, + "format_rfc822": &tengo.String{Value: time.RFC822}, + "format_rfc822z": &tengo.String{Value: time.RFC822Z}, + "format_rfc850": &tengo.String{Value: time.RFC850}, + "format_rfc1123": &tengo.String{Value: time.RFC1123}, + "format_rfc1123z": &tengo.String{Value: time.RFC1123Z}, + "format_rfc3339": &tengo.String{Value: time.RFC3339}, + "format_rfc3339_nano": &tengo.String{Value: time.RFC3339Nano}, + "format_kitchen": &tengo.String{Value: time.Kitchen}, + "format_stamp": &tengo.String{Value: time.Stamp}, + "format_stamp_milli": &tengo.String{Value: time.StampMilli}, + "format_stamp_micro": &tengo.String{Value: time.StampMicro}, + "format_stamp_nano": &tengo.String{Value: time.StampNano}, + "nanosecond": &tengo.Int{Value: int64(time.Nanosecond)}, + "microsecond": &tengo.Int{Value: int64(time.Microsecond)}, + "millisecond": &tengo.Int{Value: int64(time.Millisecond)}, + "second": &tengo.Int{Value: int64(time.Second)}, + "minute": &tengo.Int{Value: int64(time.Minute)}, + "hour": &tengo.Int{Value: int64(time.Hour)}, + "january": &tengo.Int{Value: int64(time.January)}, + "february": &tengo.Int{Value: int64(time.February)}, + "march": &tengo.Int{Value: int64(time.March)}, + "april": &tengo.Int{Value: int64(time.April)}, + "may": &tengo.Int{Value: int64(time.May)}, + "june": &tengo.Int{Value: int64(time.June)}, + "july": &tengo.Int{Value: int64(time.July)}, + "august": &tengo.Int{Value: int64(time.August)}, + "september": &tengo.Int{Value: int64(time.September)}, + "october": &tengo.Int{Value: int64(time.October)}, + "november": &tengo.Int{Value: int64(time.November)}, + "december": &tengo.Int{Value: int64(time.December)}, + "sleep": &tengo.UserFunction{ + Name: "sleep", + Value: timesSleep, + }, // sleep(int) + "parse_duration": &tengo.UserFunction{ + Name: "parse_duration", + Value: timesParseDuration, + }, // parse_duration(str) => int + "since": &tengo.UserFunction{ + Name: "since", + Value: timesSince, + }, // since(time) => int + "until": &tengo.UserFunction{ + Name: "until", + Value: timesUntil, + }, // until(time) => int + "duration_hours": &tengo.UserFunction{ + Name: "duration_hours", + Value: timesDurationHours, + }, // duration_hours(int) => float + "duration_minutes": &tengo.UserFunction{ + Name: "duration_minutes", + Value: timesDurationMinutes, + }, // duration_minutes(int) => float + "duration_nanoseconds": &tengo.UserFunction{ + Name: "duration_nanoseconds", + Value: timesDurationNanoseconds, + }, // duration_nanoseconds(int) => int + "duration_seconds": &tengo.UserFunction{ + Name: "duration_seconds", + Value: timesDurationSeconds, + }, // duration_seconds(int) => float + "duration_string": &tengo.UserFunction{ + Name: "duration_string", + Value: timesDurationString, + }, // duration_string(int) => string + "month_string": &tengo.UserFunction{ + Name: "month_string", + Value: timesMonthString, + }, // month_string(int) => string + "date": &tengo.UserFunction{ + Name: "date", + Value: timesDate, + }, // date(year, month, day, hour, min, sec, nsec) => time + "now": &tengo.UserFunction{ + Name: "now", + Value: timesNow, + }, // now() => time + "parse": &tengo.UserFunction{ + Name: "parse", + Value: timesParse, + }, // parse(format, str) => time + "unix": &tengo.UserFunction{ + Name: "unix", + Value: timesUnix, + }, // unix(sec, nsec) => time + "add": &tengo.UserFunction{ + Name: "add", + Value: timesAdd, + }, // add(time, int) => time + "add_date": &tengo.UserFunction{ + Name: "add_date", + Value: timesAddDate, + }, // add_date(time, years, months, days) => time + "sub": &tengo.UserFunction{ + Name: "sub", + Value: timesSub, + }, // sub(t time, u time) => int + "after": &tengo.UserFunction{ + Name: "after", + Value: timesAfter, + }, // after(t time, u time) => bool + "before": &tengo.UserFunction{ + Name: "before", + Value: timesBefore, + }, // before(t time, u time) => bool + "time_year": &tengo.UserFunction{ + Name: "time_year", + Value: timesTimeYear, + }, // time_year(time) => int + "time_month": &tengo.UserFunction{ + Name: "time_month", + Value: timesTimeMonth, + }, // time_month(time) => int + "time_day": &tengo.UserFunction{ + Name: "time_day", + Value: timesTimeDay, + }, // time_day(time) => int + "time_weekday": &tengo.UserFunction{ + Name: "time_weekday", + Value: timesTimeWeekday, + }, // time_weekday(time) => int + "time_hour": &tengo.UserFunction{ + Name: "time_hour", + Value: timesTimeHour, + }, // time_hour(time) => int + "time_minute": &tengo.UserFunction{ + Name: "time_minute", + Value: timesTimeMinute, + }, // time_minute(time) => int + "time_second": &tengo.UserFunction{ + Name: "time_second", + Value: timesTimeSecond, + }, // time_second(time) => int + "time_nanosecond": &tengo.UserFunction{ + Name: "time_nanosecond", + Value: timesTimeNanosecond, + }, // time_nanosecond(time) => int + "time_unix": &tengo.UserFunction{ + Name: "time_unix", + Value: timesTimeUnix, + }, // time_unix(time) => int + "time_unix_nano": &tengo.UserFunction{ + Name: "time_unix_nano", + Value: timesTimeUnixNano, + }, // time_unix_nano(time) => int + "time_format": &tengo.UserFunction{ + Name: "time_format", + Value: timesTimeFormat, + }, // time_format(time, format) => string + "time_location": &tengo.UserFunction{ + Name: "time_location", + Value: timesTimeLocation, + }, // time_location(time) => string + "time_string": &tengo.UserFunction{ + Name: "time_string", + Value: timesTimeString, + }, // time_string(time) => string + "is_zero": &tengo.UserFunction{ + Name: "is_zero", + Value: timesIsZero, + }, // is_zero(time) => bool + "to_local": &tengo.UserFunction{ + Name: "to_local", + Value: timesToLocal, + }, // to_local(time) => time + "to_utc": &tengo.UserFunction{ + Name: "to_utc", + Value: timesToUTC, + }, // to_utc(time) => time +} + +func timesSleep(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + time.Sleep(time.Duration(i1)) + ret = tengo.UndefinedValue + + return +} + +func timesParseDuration(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + dur, err := time.ParseDuration(s1) + if err != nil { + ret = wrapError(err) + return + } + + ret = &tengo.Int{Value: int64(dur)} + + return +} + +func timesSince(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(time.Since(t1))} + + return +} + +func timesUntil(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(time.Until(t1))} + + return +} + +func timesDurationHours(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Float{Value: time.Duration(i1).Hours()} + + return +} + +func timesDurationMinutes(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Float{Value: time.Duration(i1).Minutes()} + + return +} + +func timesDurationNanoseconds(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: time.Duration(i1).Nanoseconds()} + + return +} + +func timesDurationSeconds(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Float{Value: time.Duration(i1).Seconds()} + + return +} + +func timesDurationString(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.String{Value: time.Duration(i1).String()} + + return +} + +func timesMonthString(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.String{Value: time.Month(i1).String()} + + return +} + +func timesDate(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 7 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + i3, ok := tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + i4, ok := tengo.ToInt(args[3]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + i5, ok := tengo.ToInt(args[4]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "fifth", + Expected: "int(compatible)", + Found: args[4].TypeName(), + } + return + } + i6, ok := tengo.ToInt(args[5]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "sixth", + Expected: "int(compatible)", + Found: args[5].TypeName(), + } + return + } + i7, ok := tengo.ToInt(args[6]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "seventh", + Expected: "int(compatible)", + Found: args[6].TypeName(), + } + return + } + + ret = &tengo.Time{ + Value: time.Date(i1, + time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location()), + } + + return +} + +func timesNow(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 0 { + err = tengo.ErrWrongNumArguments + return + } + + ret = &tengo.Time{Value: time.Now()} + + return +} + +func timesParse(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + s1, ok := tengo.ToString(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.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 = &tengo.Time{Value: parsed} + + return +} + +func timesUnix(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + i1, ok := tengo.ToInt64(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt64(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &tengo.Time{Value: time.Unix(i1, i2)} + + return +} + +func timesAdd(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt64(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &tengo.Time{Value: t1.Add(time.Duration(i2))} + + return +} + +func timesSub(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := tengo.ToTime(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Sub(t2))} + + return +} + +func timesAddDate(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 4 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := tengo.ToInt(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := tengo.ToInt(args[2]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := tengo.ToInt(args[3]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &tengo.Time{Value: t1.AddDate(i2, i3, i4)} + + return +} + +func timesAfter(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := tengo.ToTime(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + if t1.After(t2) { + ret = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return +} + +func timesBefore(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := tengo.ToTime(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.Before(t2) { + ret = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return +} + +func timesTimeYear(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Year())} + + return +} + +func timesTimeMonth(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Month())} + + return +} + +func timesTimeDay(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Day())} + + return +} + +func timesTimeWeekday(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Weekday())} + + return +} + +func timesTimeHour(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Hour())} + + return +} + +func timesTimeMinute(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Minute())} + + return +} + +func timesTimeSecond(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Second())} + + return +} + +func timesTimeNanosecond(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: int64(t1.Nanosecond())} + + return +} + +func timesTimeUnix(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: t1.Unix()} + + return +} + +func timesTimeUnixNano(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Int{Value: t1.UnixNano()} + + return +} + +func timesTimeFormat(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 2 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := tengo.ToString(args[1]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s := t1.Format(s2) + if len(s) > tengo.MaxStringLen { + + return nil, tengo.ErrStringLimit + } + + ret = &tengo.String{Value: s} + + return +} + +func timesIsZero(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.IsZero() { + ret = tengo.TrueValue + } else { + ret = tengo.FalseValue + } + + return +} + +func timesToLocal(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Time{Value: t1.Local()} + + return +} + +func timesToUTC(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.Time{Value: t1.UTC()} + + return +} + +func timesTimeLocation(args ...tengo.Object) ( + ret tengo.Object, + err error, +) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.String{Value: t1.Location().String()} + + return +} + +func timesTimeString(args ...tengo.Object) (ret tengo.Object, err error) { + if len(args) != 1 { + err = tengo.ErrWrongNumArguments + return + } + + t1, ok := tengo.ToTime(args[0]) + if !ok { + err = tengo.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &tengo.String{Value: t1.String()} + + return +} diff --git a/vendor/github.com/d5/tengo/v2/symbol_table.go b/vendor/github.com/d5/tengo/v2/symbol_table.go new file mode 100644 index 00000000..6ae5d7d3 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/symbol_table.go @@ -0,0 +1,165 @@ +package tengo + +// SymbolScope represents a symbol scope. +type SymbolScope string + +// List of symbol scopes +const ( + ScopeGlobal SymbolScope = "GLOBAL" + ScopeLocal SymbolScope = "LOCAL" + ScopeBuiltin SymbolScope = "BUILTIN" + ScopeFree SymbolScope = "FREE" +) + +// Symbol represents a symbol in the symbol table. +type Symbol struct { + Name string + Scope SymbolScope + Index int + LocalAssigned bool // if the local symbol is assigned at least once +} + +// SymbolTable represents a symbol table. +type SymbolTable struct { + parent *SymbolTable + block bool + store map[string]*Symbol + numDefinition int + maxDefinition int + freeSymbols []*Symbol + builtinSymbols []*Symbol +} + +// NewSymbolTable creates a SymbolTable. +func NewSymbolTable() *SymbolTable { + return &SymbolTable{ + store: make(map[string]*Symbol), + } +} + +// Define adds a new symbol in the current scope. +func (t *SymbolTable) Define(name string) *Symbol { + symbol := &Symbol{Name: name, Index: t.nextIndex()} + t.numDefinition++ + + if t.Parent(true) == nil { + symbol.Scope = ScopeGlobal + } else { + symbol.Scope = ScopeLocal + } + t.store[name] = symbol + t.updateMaxDefs(symbol.Index + 1) + return symbol +} + +// DefineBuiltin adds a symbol for builtin function. +func (t *SymbolTable) DefineBuiltin(index int, name string) *Symbol { + if t.parent != nil { + return t.parent.DefineBuiltin(index, name) + } + + symbol := &Symbol{ + Name: name, + Index: index, + Scope: ScopeBuiltin, + } + t.store[name] = symbol + t.builtinSymbols = append(t.builtinSymbols, symbol) + return symbol +} + +// Resolve resolves a symbol with a given name. +func (t *SymbolTable) Resolve( + name string, +) (symbol *Symbol, depth int, ok bool) { + symbol, ok = t.store[name] + if !ok && t.parent != nil { + symbol, depth, ok = t.parent.Resolve(name) + if !ok { + return + } + depth++ + + // if symbol is defined in parent table and if it's not global/builtin + // then it's free variable. + if !t.block && depth > 0 && + symbol.Scope != ScopeGlobal && + symbol.Scope != ScopeBuiltin { + return t.defineFree(symbol), depth, true + } + return + } + return +} + +// Fork creates a new symbol table for a new scope. +func (t *SymbolTable) Fork(block bool) *SymbolTable { + return &SymbolTable{ + store: make(map[string]*Symbol), + parent: t, + block: block, + } +} + +// Parent returns the outer scope of the current symbol table. +func (t *SymbolTable) Parent(skipBlock bool) *SymbolTable { + if skipBlock && t.block { + return t.parent.Parent(skipBlock) + } + return t.parent +} + +// MaxSymbols returns the total number of symbols defined in the scope. +func (t *SymbolTable) MaxSymbols() int { + return t.maxDefinition +} + +// FreeSymbols returns free symbols for the scope. +func (t *SymbolTable) FreeSymbols() []*Symbol { + return t.freeSymbols +} + +// BuiltinSymbols returns builtin symbols for the scope. +func (t *SymbolTable) BuiltinSymbols() []*Symbol { + if t.parent != nil { + return t.parent.BuiltinSymbols() + } + return t.builtinSymbols +} + +// Names returns the name of all the symbols. +func (t *SymbolTable) Names() []string { + var names []string + for name := range t.store { + names = append(names, name) + } + return names +} + +func (t *SymbolTable) nextIndex() int { + if t.block { + return t.parent.nextIndex() + t.numDefinition + } + return t.numDefinition +} + +func (t *SymbolTable) updateMaxDefs(numDefs int) { + if numDefs > t.maxDefinition { + t.maxDefinition = numDefs + } + if t.block { + t.parent.updateMaxDefs(numDefs) + } +} + +func (t *SymbolTable) defineFree(original *Symbol) *Symbol { + // TODO: should we check duplicates? + t.freeSymbols = append(t.freeSymbols, original) + symbol := &Symbol{ + Name: original.Name, + Index: len(t.freeSymbols) - 1, + Scope: ScopeFree, + } + t.store[original.Name] = symbol + return symbol +} diff --git a/vendor/github.com/d5/tengo/v2/tengo.go b/vendor/github.com/d5/tengo/v2/tengo.go new file mode 100644 index 00000000..098a1970 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/tengo.go @@ -0,0 +1,306 @@ +package tengo + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +var ( + // MaxStringLen is the maximum byte-length for string value. Note this + // limit applies to all compiler/VM instances in the process. + MaxStringLen = 2147483647 + + // MaxBytesLen is the maximum length for bytes value. Note this limit + // applies to all compiler/VM instances in the process. + MaxBytesLen = 2147483647 +) + +const ( + // GlobalsSize is the maximum number of global variables for a VM. + GlobalsSize = 1024 + + // StackSize is the maximum stack size for a VM. + StackSize = 2048 + + // MaxFrames is the maximum number of function frames for a VM. + MaxFrames = 1024 +) + +// CallableFunc is a function signature for the callable functions. +type CallableFunc = func(args ...Object) (ret Object, err error) + +// 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 +} + +// ToString will try to convert object o to string value. +func ToString(o Object) (v string, ok bool) { + if o == UndefinedValue { + return + } + ok = true + if str, isStr := o.(*String); isStr { + v = str.Value + } else { + v = o.String() + } + return +} + +// ToInt will try to convert object o to int value. +func ToInt(o Object) (v int, ok bool) { + switch o := o.(type) { + case *Int: + v = int(o.Value) + ok = true + case *Float: + v = int(o.Value) + ok = true + case *Char: + v = int(o.Value) + ok = true + case *Bool: + if o == TrueValue { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = int(c) + ok = true + } + } + return +} + +// ToInt64 will try to convert object o to int64 value. +func ToInt64(o Object) (v int64, ok bool) { + switch o := o.(type) { + case *Int: + v = o.Value + ok = true + case *Float: + v = int64(o.Value) + ok = true + case *Char: + v = int64(o.Value) + ok = true + case *Bool: + if o == TrueValue { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = c + ok = true + } + } + return +} + +// ToFloat64 will try to convert object o to float64 value. +func ToFloat64(o Object) (v float64, ok bool) { + switch o := o.(type) { + case *Int: + v = float64(o.Value) + ok = true + case *Float: + v = o.Value + ok = true + case *String: + c, err := strconv.ParseFloat(o.Value, 64) + if err == nil { + v = c + ok = true + } + } + return +} + +// ToBool will try to convert object o to bool value. +func ToBool(o Object) (v bool, ok bool) { + ok = true + v = !o.IsFalsy() + return +} + +// ToRune will try to convert object o to rune value. +func ToRune(o Object) (v rune, ok bool) { + switch o := o.(type) { + case *Int: + v = rune(o.Value) + ok = true + case *Char: + v = o.Value + ok = true + } + return +} + +// ToByteSlice will try to convert object o to []byte value. +func ToByteSlice(o Object) (v []byte, ok bool) { + switch o := o.(type) { + case *Bytes: + v = o.Value + ok = true + case *String: + v = []byte(o.Value) + ok = true + } + return +} + +// ToTime will try to convert object o to time.Time value. +func ToTime(o Object) (v time.Time, ok bool) { + switch o := o.(type) { + case *Time: + v = o.Value + ok = true + case *Int: + v = time.Unix(o.Value, 0) + ok = true + } + return +} + +// 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 + case *String: + res = o.Value + case *Float: + res = o.Value + case *Bool: + res = o == TrueValue + case *Char: + res = o.Value + case *Bytes: + res = o.Value + case *Array: + res = make([]interface{}, len(o.Value)) + for i, val := range o.Value { + 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] = 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 + } + return +} + +// FromInterface will attempt to convert an interface{} v to a Tengo Object +func FromInterface(v interface{}) (Object, error) { + switch v := v.(type) { + case nil: + return UndefinedValue, nil + case string: + if len(v) > MaxStringLen { + return nil, ErrStringLimit + } + return &String{Value: v}, nil + case int64: + return &Int{Value: v}, nil + case int: + return &Int{Value: int64(v)}, nil + case bool: + if v { + return TrueValue, nil + } + return FalseValue, nil + case rune: + return &Char{Value: v}, nil + case byte: + return &Char{Value: rune(v)}, nil + case float64: + return &Float{Value: v}, nil + case []byte: + if len(v) > MaxBytesLen { + return nil, ErrBytesLimit + } + return &Bytes{Value: v}, nil + case error: + return &Error{Value: &String{Value: v.Error()}}, nil + case map[string]Object: + return &Map{Value: v}, nil + case map[string]interface{}: + kv := make(map[string]Object) + for vk, vv := range v { + vo, err := FromInterface(vv) + if err != nil { + return nil, err + } + kv[vk] = vo + } + return &Map{Value: kv}, nil + case []Object: + return &Array{Value: v}, nil + case []interface{}: + arr := make([]Object, len(v)) + for i, e := range v { + vo, err := FromInterface(e) + if err != nil { + return nil, err + } + arr[i] = vo + } + return &Array{Value: arr}, nil + case time.Time: + return &Time{Value: v}, nil + case Object: + return v, nil + case CallableFunc: + return &UserFunction{Value: v}, nil + } + return nil, fmt.Errorf("cannot convert to object: %T", v) +} diff --git a/vendor/github.com/d5/tengo/v2/token/token.go b/vendor/github.com/d5/tengo/v2/token/token.go new file mode 100644 index 00000000..4e6aa80a --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/token/token.go @@ -0,0 +1,225 @@ +package token + +import "strconv" + +var keywords map[string]Token + +// Token represents a token. +type Token int + +// List of tokens +const ( + Illegal Token = iota + EOF + Comment + _literalBeg + Ident + Int + Float + Char + String + _literalEnd + _operatorBeg + Add // + + Sub // - + Mul // * + Quo // / + Rem // % + And // & + Or // | + Xor // ^ + Shl // << + Shr // >> + AndNot // &^ + AddAssign // += + SubAssign // -= + MulAssign // *= + QuoAssign // /= + RemAssign // %= + AndAssign // &= + OrAssign // |= + XorAssign // ^= + ShlAssign // <<= + ShrAssign // >>= + AndNotAssign // &^= + LAnd // && + LOr // || + Inc // ++ + Dec // -- + Equal // == + Less // < + Greater // > + Assign // = + Not // ! + NotEqual // != + LessEq // <= + GreaterEq // >= + Define // := + Ellipsis // ... + LParen // ( + LBrack // [ + LBrace // { + Comma // , + Period // . + RParen // ) + RBrack // ] + RBrace // } + Semicolon // ; + Colon // : + Question // ? + _operatorEnd + _keywordBeg + Break + Continue + Else + For + Func + Error + Immutable + If + Return + Export + True + False + In + Undefined + Import + _keywordEnd +) + +var tokens = [...]string{ + Illegal: "ILLEGAL", + EOF: "EOF", + Comment: "COMMENT", + Ident: "IDENT", + Int: "INT", + Float: "FLOAT", + Char: "CHAR", + String: "STRING", + Add: "+", + Sub: "-", + Mul: "*", + Quo: "/", + Rem: "%", + And: "&", + Or: "|", + Xor: "^", + Shl: "<<", + Shr: ">>", + AndNot: "&^", + AddAssign: "+=", + SubAssign: "-=", + MulAssign: "*=", + QuoAssign: "/=", + RemAssign: "%=", + AndAssign: "&=", + OrAssign: "|=", + XorAssign: "^=", + ShlAssign: "<<=", + ShrAssign: ">>=", + AndNotAssign: "&^=", + LAnd: "&&", + LOr: "||", + Inc: "++", + Dec: "--", + Equal: "==", + Less: "<", + Greater: ">", + Assign: "=", + Not: "!", + NotEqual: "!=", + LessEq: "<=", + GreaterEq: ">=", + Define: ":=", + Ellipsis: "...", + LParen: "(", + LBrack: "[", + LBrace: "{", + Comma: ",", + Period: ".", + RParen: ")", + RBrack: "]", + RBrace: "}", + Semicolon: ";", + Colon: ":", + Question: "?", + Break: "break", + Continue: "continue", + Else: "else", + For: "for", + Func: "func", + Error: "error", + Immutable: "immutable", + If: "if", + Return: "return", + Export: "export", + True: "true", + False: "false", + In: "in", + Undefined: "undefined", + Import: "import", +} + +func (tok Token) String() string { + s := "" + + if 0 <= tok && tok < Token(len(tokens)) { + s = tokens[tok] + } + + if s == "" { + s = "token(" + strconv.Itoa(int(tok)) + ")" + } + + return s +} + +// LowestPrec represents lowest operator precedence. +const LowestPrec = 0 + +// Precedence returns the precedence for the operator token. +func (tok Token) Precedence() int { + switch tok { + case LOr: + return 1 + case LAnd: + return 2 + case Equal, NotEqual, Less, LessEq, Greater, GreaterEq: + return 3 + case Add, Sub, Or, Xor: + return 4 + case Mul, Quo, Rem, Shl, Shr, And, AndNot: + return 5 + } + return LowestPrec +} + +// IsLiteral returns true if the token is a literal. +func (tok Token) IsLiteral() bool { + return _literalBeg < tok && tok < _literalEnd +} + +// IsOperator returns true if the token is an operator. +func (tok Token) IsOperator() bool { + return _operatorBeg < tok && tok < _operatorEnd +} + +// IsKeyword returns true if the token is a keyword. +func (tok Token) IsKeyword() bool { + return _keywordBeg < tok && tok < _keywordEnd +} + +// Lookup returns corresponding keyword if ident is a keyword. +func Lookup(ident string) Token { + if tok, isKeyword := keywords[ident]; isKeyword { + return tok + } + return Ident +} + +func init() { + keywords = make(map[string]Token) + for i := _keywordBeg + 1; i < _keywordEnd; i++ { + keywords[tokens[i]] = i + } +} diff --git a/vendor/github.com/d5/tengo/v2/variable.go b/vendor/github.com/d5/tengo/v2/variable.go new file mode 100644 index 00000000..481b36b8 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/variable.go @@ -0,0 +1,136 @@ +package tengo + +import ( + "errors" +) + +// Variable is a user-defined variable for the script. +type Variable struct { + name string + value Object +} + +// NewVariable creates a Variable. +func NewVariable(name string, value interface{}) (*Variable, error) { + obj, err := FromInterface(value) + if err != nil { + return nil, err + } + return &Variable{ + name: name, + value: obj, + }, nil +} + +// Name returns the name of the variable. +func (v *Variable) Name() string { + return v.name +} + +// Value returns an empty interface of the variable value. +func (v *Variable) Value() interface{} { + return ToInterface(v.value) +} + +// ValueType returns the name of the value type. +func (v *Variable) ValueType() string { + 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, _ := ToInt(v.value) + return c +} + +// 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, _ := ToInt64(v.value) + return c +} + +// 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, _ := ToFloat64(v.value) + return c +} + +// 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, _ := ToRune(v.value) + return c +} + +// 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, _ := ToBool(v.value) + return c +} + +// 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) { + case *Array: + var arr []interface{} + for _, e := range val.Value { + arr = append(arr, ToInterface(e)) + } + return arr + } + return nil +} + +// 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) { + case *Map: + kv := make(map[string]interface{}) + for mk, mv := range val.Value { + kv[mk] = ToInterface(mv) + } + return kv + } + return nil +} + +// 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, _ := ToString(v.value) + return c +} + +// 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, _ := ToByteSlice(v.value) + return c +} + +// 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.(*Error) + if ok { + return errors.New(err.String()) + } + return nil +} + +// 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() Object { + return v.value +} + +// IsUndefined returns true if the underlying value is undefined. +func (v *Variable) IsUndefined() bool { + return v.value == UndefinedValue +} diff --git a/vendor/github.com/d5/tengo/v2/vm.go b/vendor/github.com/d5/tengo/v2/vm.go new file mode 100644 index 00000000..783a54a9 --- /dev/null +++ b/vendor/github.com/d5/tengo/v2/vm.go @@ -0,0 +1,883 @@ +package tengo + +import ( + "fmt" + "sync/atomic" + + "github.com/d5/tengo/v2/parser" + "github.com/d5/tengo/v2/token" +) + +// frame represents a function call frame. +type frame struct { + fn *CompiledFunction + freeVars []*ObjectPtr + ip int + basePointer int +} + +// VM is a virtual machine that executes the bytecode compiled by Compiler. +type VM struct { + constants []Object + stack [StackSize]Object + sp int + globals []Object + fileSet *parser.SourceFileSet + 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 *Bytecode, + globals []Object, + maxAllocs int64, +) *VM { + if globals == nil { + globals = make([]Object, GlobalsSize) + } + v := &VM{ + constants: bytecode.Constants, + sp: 0, + globals: globals, + fileSet: bytecode.FileSet, + framesIndex: 1, + ip: -1, + maxAllocs: maxAllocs, + } + v.frames[0].fn = bytecode.MainFunction + v.frames[0].ip = -1 + v.curFrame = &v.frames[0] + v.curInsts = v.curFrame.fn.Instructions + return v +} + +// Abort aborts the execution. +func (v *VM) Abort() { + atomic.StoreInt64(&v.aborting, 1) +} + +// Run starts the execution. +func (v *VM) Run() (err error) { + // reset VM states + v.sp = 0 + v.curFrame = &(v.frames[0]) + v.curInsts = v.curFrame.fn.Instructions + v.framesIndex = 1 + v.ip = -1 + 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.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.SourcePos(v.curFrame.ip - 1)) + err = fmt.Errorf("%s\n\tat %s", err.Error(), filePos) + } + return err + } + return nil +} + +func (v *VM) run() { + for atomic.LoadInt64(&v.aborting) == 0 { + v.ip++ + + switch v.curInsts[v.ip] { + case parser.OpConstant: + v.ip += 2 + cidx := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + + v.stack[v.sp] = v.constants[cidx] + v.sp++ + case parser.OpNull: + v.stack[v.sp] = UndefinedValue + v.sp++ + case parser.OpBinaryOp: + v.ip++ + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + tok := token.Token(v.curInsts[v.ip]) + res, e := left.BinaryOp(tok, right) + if e != nil { + v.sp -= 2 + if e == ErrInvalidOperator { + v.err = fmt.Errorf("invalid operation: %s %s %s", + left.TypeName(), tok.String(), right.TypeName()) + return + } + v.err = e + return + } + + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp-2] = res + v.sp-- + case parser.OpEqual: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + if left.Equals(right) { + v.stack[v.sp] = TrueValue + } else { + v.stack[v.sp] = FalseValue + } + v.sp++ + case parser.OpNotEqual: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + if left.Equals(right) { + v.stack[v.sp] = FalseValue + } else { + v.stack[v.sp] = TrueValue + } + v.sp++ + case parser.OpPop: + v.sp-- + case parser.OpTrue: + v.stack[v.sp] = TrueValue + v.sp++ + case parser.OpFalse: + v.stack[v.sp] = FalseValue + v.sp++ + case parser.OpLNot: + operand := v.stack[v.sp-1] + v.sp-- + if operand.IsFalsy() { + v.stack[v.sp] = TrueValue + } else { + v.stack[v.sp] = FalseValue + } + v.sp++ + case parser.OpBComplement: + operand := v.stack[v.sp-1] + v.sp-- + + switch x := operand.(type) { + case *Int: + var res Object = &Int{Value: ^x.Value} + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = res + v.sp++ + default: + v.err = fmt.Errorf("invalid operation: ^%s", + operand.TypeName()) + return + } + case parser.OpMinus: + operand := v.stack[v.sp-1] + v.sp-- + + switch x := operand.(type) { + case *Int: + var res Object = &Int{Value: -x.Value} + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = res + v.sp++ + case *Float: + var res Object = &Float{Value: -x.Value} + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = res + v.sp++ + default: + v.err = fmt.Errorf("invalid operation: -%s", + operand.TypeName()) + return + } + case parser.OpJumpFalsy: + v.ip += 2 + v.sp-- + if v.stack[v.sp].IsFalsy() { + pos := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + v.ip = pos - 1 + } + case parser.OpAndJump: + v.ip += 2 + 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 parser.OpOrJump: + v.ip += 2 + 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 parser.OpJump: + pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip = pos - 1 + case parser.OpSetGlobal: + 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 parser.OpSetSelGlobal: + 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 := make([]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 + e := indexAssign(v.globals[globalIndex], val, selectors) + if e != nil { + v.err = e + return + } + case parser.OpGetGlobal: + v.ip += 2 + globalIndex := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + val := v.globals[globalIndex] + v.stack[v.sp] = val + v.sp++ + case parser.OpArray: + v.ip += 2 + numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + + var elements []Object + for i := v.sp - numElements; i < v.sp; i++ { + elements = append(elements, v.stack[i]) + } + v.sp -= numElements + + var arr Object = &Array{Value: elements} + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + + v.stack[v.sp] = arr + v.sp++ + case parser.OpMap: + v.ip += 2 + numElements := int(v.curInsts[v.ip]) | int(v.curInsts[v.ip-1])<<8 + kv := make(map[string]Object) + for i := v.sp - numElements; i < v.sp; i += 2 { + key := v.stack[i] + value := v.stack[i+1] + kv[key.(*String).Value] = value + } + v.sp -= numElements + + var m Object = &Map{Value: kv} + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = m + v.sp++ + case parser.OpError: + value := v.stack[v.sp-1] + var e Object = &Error{ + Value: value, + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp-1] = e + case parser.OpImmutable: + value := v.stack[v.sp-1] + switch value := value.(type) { + case *Array: + var immutableArray Object = &ImmutableArray{ + Value: value.Value, + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp-1] = immutableArray + case *Map: + var immutableMap Object = &ImmutableMap{ + Value: value.Value, + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp-1] = immutableMap + } + case parser.OpIndex: + index := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + val, err := left.IndexGet(index) + if err != nil { + if err == ErrNotIndexable { + v.err = fmt.Errorf("not indexable: %s", index.TypeName()) + return + } + if err == ErrInvalidIndexType { + v.err = fmt.Errorf("invalid index type: %s", + index.TypeName()) + return + } + v.err = err + return + } + if val == nil { + val = UndefinedValue + } + v.stack[v.sp] = val + v.sp++ + case parser.OpSliceIndex: + high := v.stack[v.sp-1] + low := v.stack[v.sp-2] + left := v.stack[v.sp-3] + v.sp -= 3 + + var lowIdx int64 + if low != UndefinedValue { + if low, ok := low.(*Int); ok { + lowIdx = low.Value + } else { + v.err = fmt.Errorf("invalid slice index type: %s", + low.TypeName()) + return + } + } + + switch left := left.(type) { + case *Array: + numElements := int64(len(left.Value)) + var highIdx int64 + if high == UndefinedValue { + highIdx = numElements + } else if high, ok := high.(*Int); ok { + highIdx = high.Value + } else { + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) + return + } + if lowIdx > highIdx { + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) + return + } + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + var val Object = &Array{ + Value: left.Value[lowIdx:highIdx], + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = val + v.sp++ + case *ImmutableArray: + numElements := int64(len(left.Value)) + var highIdx int64 + if high == UndefinedValue { + highIdx = numElements + } else if high, ok := high.(*Int); ok { + highIdx = high.Value + } else { + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) + return + } + if lowIdx > highIdx { + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) + return + } + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + var val Object = &Array{ + Value: left.Value[lowIdx:highIdx], + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = val + v.sp++ + case *String: + numElements := int64(len(left.Value)) + var highIdx int64 + if high == UndefinedValue { + highIdx = numElements + } else if high, ok := high.(*Int); ok { + highIdx = high.Value + } else { + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) + return + } + if lowIdx > highIdx { + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) + return + } + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + var val Object = &String{ + Value: left.Value[lowIdx:highIdx], + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = val + v.sp++ + case *Bytes: + numElements := int64(len(left.Value)) + var highIdx int64 + if high == UndefinedValue { + highIdx = numElements + } else if high, ok := high.(*Int); ok { + highIdx = high.Value + } else { + v.err = fmt.Errorf("invalid slice index type: %s", + high.TypeName()) + return + } + if lowIdx > highIdx { + v.err = fmt.Errorf("invalid slice index: %d > %d", + lowIdx, highIdx) + return + } + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + var val Object = &Bytes{ + Value: left.Value[lowIdx:highIdx], + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = val + v.sp++ + } + case parser.OpCall: + numArgs := int(v.curInsts[v.ip+1]) + v.ip++ + value := v.stack[v.sp-1-numArgs] + if !value.CanCall() { + v.err = fmt.Errorf("not callable: %s", value.TypeName()) + return + } + if callee, ok := value.(*CompiledFunction); ok { + if callee.VarArgs { + // if the closure is variadic, + // roll up all variadic parameters into an array + realArgs := callee.NumParameters - 1 + varArgs := numArgs - realArgs + if varArgs >= 0 { + numArgs = realArgs + 1 + args := make([]Object, varArgs) + spStart := v.sp - varArgs + for i := spStart; i < v.sp; i++ { + args[i-spStart] = v.stack[i] + } + v.stack[spStart] = &Array{Value: args} + v.sp = spStart + 1 + } + } + if numArgs != callee.NumParameters { + if callee.VarArgs { + v.err = fmt.Errorf( + "wrong number of arguments: want>=%d, got=%d", + callee.NumParameters-1, numArgs) + } else { + v.err = fmt.Errorf( + "wrong number of arguments: want=%d, got=%d", + callee.NumParameters, numArgs) + } + return + } + + // test if it's tail-call + if callee == v.curFrame.fn { // recursion + nextOp := v.curInsts[v.ip+1] + if nextOp == parser.OpReturn || + (nextOp == parser.OpPop && + parser.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 + } + } + if v.framesIndex >= MaxFrames { + v.err = ErrStackOverflow + return + } + + // update call frame + v.curFrame.ip = v.ip // store current ip before call + v.curFrame = &(v.frames[v.framesIndex]) + v.curFrame.fn = callee + v.curFrame.freeVars = callee.Free + v.curFrame.basePointer = v.sp - numArgs + v.curInsts = callee.Instructions + v.ip = -1 + v.framesIndex++ + v.sp = v.sp - numArgs + callee.NumLocals + } else { + var args []Object + args = append(args, v.stack[v.sp-numArgs:v.sp]...) + ret, e := value.Call(args...) + v.sp -= numArgs + 1 + + // runtime error + if e != nil { + if e == ErrWrongNumArguments { + v.err = fmt.Errorf( + "wrong number of arguments in call to '%s'", + value.TypeName()) + return + } + if e, ok := e.(ErrInvalidArgumentType); ok { + v.err = fmt.Errorf( + "invalid type for argument '%s' in call to '%s': "+ + "expected %s, found %s", + e.Name, value.TypeName(), e.Expected, e.Found) + return + } + v.err = e + return + } + + // nil return -> undefined + if ret == nil { + ret = UndefinedValue + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = ret + v.sp++ + } + case parser.OpReturn: + v.ip++ + var retVal Object + if int(v.curInsts[v.ip]) == 1 { + retVal = v.stack[v.sp-1] + } else { + retVal = UndefinedValue + } + //v.sp-- + v.framesIndex-- + v.curFrame = &v.frames[v.framesIndex-1] + v.curInsts = v.curFrame.fn.Instructions + v.ip = v.curFrame.ip + //v.sp = lastFrame.basePointer - 1 + v.sp = v.frames[v.framesIndex].basePointer + // skip stack overflow check because (newSP) <= (oldSP) + v.stack[v.sp-1] = retVal + //v.sp++ + case parser.OpDefineLocal: + 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] + v.sp-- + v.stack[sp] = val + case parser.OpSetLocal: + localIndex := int(v.curInsts[v.ip+1]) + v.ip++ + sp := v.curFrame.basePointer + localIndex + + // update pointee of v.stack[sp] instead of replacing the pointer + // itself. this is needed because there can be free variables + // referencing the same local variables. + val := v.stack[v.sp-1] + v.sp-- + if obj, ok := v.stack[sp].(*ObjectPtr); ok { + *obj.Value = val + val = obj + } + v.stack[sp] = val // also use a copy of popped value + case parser.OpSetSelLocal: + localIndex := int(v.curInsts[v.ip+1]) + numSelectors := int(v.curInsts[v.ip+2]) + v.ip += 2 + + // selectors and RHS value + selectors := make([]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 + dst := v.stack[v.curFrame.basePointer+localIndex] + if obj, ok := dst.(*ObjectPtr); ok { + dst = *obj.Value + } + if e := indexAssign(dst, val, selectors); e != nil { + v.err = e + return + } + case parser.OpGetLocal: + v.ip++ + localIndex := int(v.curInsts[v.ip]) + val := v.stack[v.curFrame.basePointer+localIndex] + if obj, ok := val.(*ObjectPtr); ok { + val = *obj.Value + } + v.stack[v.sp] = val + v.sp++ + case parser.OpGetBuiltin: + v.ip++ + builtinIndex := int(v.curInsts[v.ip]) + v.stack[v.sp] = builtinFuncs[builtinIndex] + v.sp++ + case parser.OpClosure: + 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].(*CompiledFunction) + if !ok { + v.err = fmt.Errorf("not function: %s", fn.TypeName()) + return + } + free := make([]*ObjectPtr, numFree) + for i := 0; i < numFree; i++ { + switch freeVar := (v.stack[v.sp-numFree+i]).(type) { + case *ObjectPtr: + free[i] = freeVar + default: + free[i] = &ObjectPtr{ + Value: &v.stack[v.sp-numFree+i], + } + } + } + v.sp -= numFree + cl := &CompiledFunction{ + Instructions: fn.Instructions, + NumLocals: fn.NumLocals, + NumParameters: fn.NumParameters, + VarArgs: fn.VarArgs, + Free: free, + } + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = cl + v.sp++ + case parser.OpGetFreePtr: + v.ip++ + freeIndex := int(v.curInsts[v.ip]) + val := v.curFrame.freeVars[freeIndex] + v.stack[v.sp] = val + v.sp++ + case parser.OpGetFree: + v.ip++ + freeIndex := int(v.curInsts[v.ip]) + val := *v.curFrame.freeVars[freeIndex].Value + v.stack[v.sp] = val + v.sp++ + case parser.OpSetFree: + v.ip++ + freeIndex := int(v.curInsts[v.ip]) + *v.curFrame.freeVars[freeIndex].Value = v.stack[v.sp-1] + v.sp-- + case parser.OpGetLocalPtr: + v.ip++ + localIndex := int(v.curInsts[v.ip]) + sp := v.curFrame.basePointer + localIndex + val := v.stack[sp] + var freeVar *ObjectPtr + if obj, ok := val.(*ObjectPtr); ok { + freeVar = obj + } else { + freeVar = &ObjectPtr{Value: &val} + v.stack[sp] = freeVar + } + v.stack[v.sp] = freeVar + v.sp++ + case parser.OpSetSelFree: + v.ip += 2 + freeIndex := int(v.curInsts[v.ip-1]) + numSelectors := int(v.curInsts[v.ip]) + + // selectors and RHS value + selectors := make([]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 + e := indexAssign(*v.curFrame.freeVars[freeIndex].Value, + val, selectors) + if e != nil { + v.err = e + return + } + case parser.OpIteratorInit: + var iterator Object + dst := v.stack[v.sp-1] + v.sp-- + if !dst.CanIterate() { + v.err = fmt.Errorf("not iterable: %s", dst.TypeName()) + return + } + iterator = dst.Iterate() + v.allocs-- + if v.allocs == 0 { + v.err = ErrObjectAllocLimit + return + } + v.stack[v.sp] = iterator + v.sp++ + case parser.OpIteratorNext: + iterator := v.stack[v.sp-1] + v.sp-- + hasMore := iterator.(Iterator).Next() + if hasMore { + v.stack[v.sp] = TrueValue + } else { + v.stack[v.sp] = FalseValue + } + v.sp++ + case parser.OpIteratorKey: + iterator := v.stack[v.sp-1] + v.sp-- + val := iterator.(Iterator).Key() + v.stack[v.sp] = val + v.sp++ + case parser.OpIteratorValue: + iterator := v.stack[v.sp-1] + v.sp-- + val := iterator.(Iterator).Value() + v.stack[v.sp] = val + v.sp++ + case parser.OpSuspend: + return + default: + v.err = fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip]) + return + } + } +} + +// IsStackEmpty tests if the stack is empty or not. +func (v *VM) IsStackEmpty() bool { + return v.sp == 0 +} + +func indexAssign(dst, src Object, selectors []Object) error { + numSel := len(selectors) + for sidx := numSel - 1; sidx > 0; sidx-- { + next, err := dst.IndexGet(selectors[sidx]) + if err != nil { + if err == ErrNotIndexable { + return fmt.Errorf("not indexable: %s", dst.TypeName()) + } + if err == ErrInvalidIndexType { + return fmt.Errorf("invalid index type: %s", + selectors[sidx].TypeName()) + } + return err + } + dst = next + } + + if err := dst.IndexSet(selectors[0], src); err != nil { + if err == ErrNotIndexAssignable { + return fmt.Errorf("not index-assignable: %s", dst.TypeName()) + } + if err == ErrInvalidIndexValueType { + return fmt.Errorf("invaid index value type: %s", src.TypeName()) + } + return err + } + return nil +} -- cgit v1.2.3