summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/d5/tengo/v2
diff options
context:
space:
mode:
authorWim <wim@42.be>2020-01-09 21:52:19 +0100
committerGitHub <noreply@github.com>2020-01-09 21:52:19 +0100
commit9d84d6dd643c4017074e81465671cd9b25f9539a (patch)
tree8a767f91d655a6cf21d476e4fb7aa6fd8a952df8 /vendor/github.com/d5/tengo/v2
parent0f708daf2d14dcca261ef98cc698a1b1f2a6aa74 (diff)
downloadmatterbridge-msglm-9d84d6dd643c4017074e81465671cd9b25f9539a.tar.gz
matterbridge-msglm-9d84d6dd643c4017074e81465671cd9b25f9539a.tar.bz2
matterbridge-msglm-9d84d6dd643c4017074e81465671cd9b25f9539a.zip
Update to tengo v2 (#976)
Diffstat (limited to 'vendor/github.com/d5/tengo/v2')
-rw-r--r--vendor/github.com/d5/tengo/v2/.gitignore1
-rw-r--r--vendor/github.com/d5/tengo/v2/.goreleaser.yml20
-rw-r--r--vendor/github.com/d5/tengo/v2/LICENSE21
-rw-r--r--vendor/github.com/d5/tengo/v2/Makefile11
-rw-r--r--vendor/github.com/d5/tengo/v2/README.md135
-rw-r--r--vendor/github.com/d5/tengo/v2/builtins.go502
-rw-r--r--vendor/github.com/d5/tengo/v2/bytecode.go292
-rw-r--r--vendor/github.com/d5/tengo/v2/compiler.go1312
-rw-r--r--vendor/github.com/d5/tengo/v2/doc.go3
-rw-r--r--vendor/github.com/d5/tengo/v2/errors.go64
-rw-r--r--vendor/github.com/d5/tengo/v2/formatter.go1245
-rw-r--r--vendor/github.com/d5/tengo/v2/go.mod3
-rw-r--r--vendor/github.com/d5/tengo/v2/go.sum0
-rw-r--r--vendor/github.com/d5/tengo/v2/instructions.go61
-rw-r--r--vendor/github.com/d5/tengo/v2/iterator.go209
-rw-r--r--vendor/github.com/d5/tengo/v2/modules.go93
-rw-r--r--vendor/github.com/d5/tengo/v2/objects.go1581
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/ast.go69
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/expr.go597
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/file.go29
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/opcodes.go156
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/parser.go1196
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/pos.go12
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/scanner.go689
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/source_file.go231
-rw-r--r--vendor/github.com/d5/tengo/v2/parser/stmt.go349
-rw-r--r--vendor/github.com/d5/tengo/v2/script.go313
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/base64.go34
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/builtin_modules.go18
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/errors.go12
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/fmt.go101
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/func_typedefs.go1049
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/hex.go12
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/json.go146
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/json/decode.go358
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/json/encode.go146
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/json/scanner.go562
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/math.go233
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/os.go564
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/os_exec.go119
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/os_file.go117
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/os_process.go76
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/rand.go138
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/source_modules.go8
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/srcmod_enum.tengo128
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/stdlib.go34
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/text.go1072
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/text_regexp.go251
-rw-r--r--vendor/github.com/d5/tengo/v2/stdlib/times.go1135
-rw-r--r--vendor/github.com/d5/tengo/v2/symbol_table.go165
-rw-r--r--vendor/github.com/d5/tengo/v2/tengo.go306
-rw-r--r--vendor/github.com/d5/tengo/v2/token/token.go225
-rw-r--r--vendor/github.com/d5/tengo/v2/variable.go136
-rw-r--r--vendor/github.com/d5/tengo/v2/vm.go883
54 files changed, 17222 insertions, 0 deletions
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 @@
+<p align="center">
+ <img src="https://raw.githubusercontent.com/d5/tengolang-share/master/logo_400.png" width="200" height="200">
+</p>
+
+# 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, "<nil>"))
+ }
+ }
+
+ 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
--- /dev/null
+++ b/vendor/github.com/d5/tengo/v2/go.sum
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 "<array-iterator>"
+}
+
+// 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 "<bytes-iterator>"
+}
+
+// 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 "<map-iterator>"
+}
+
+// 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 "<string-iterator>"
+}
+
+// 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 "<builtin-function>"
+}
+
+// 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 "<compiled-function>"
+}
+
+// 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 "<free-var>"
+}
+
+// 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 "<undefined>"
+}
+
+// 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 "<user-function>"
+}
+
+// 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 = "<null>"
+)
+
+// 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 "<bad expression>"
+}
+
+// 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 "<bad statement>"
+}
+
+// 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
+}