From 1bb39eba8717f62336cc98c5bb7cfbef194f3626 Mon Sep 17 00:00:00 2001 From: Wim Date: Sat, 23 Feb 2019 16:39:44 +0100 Subject: [PATCH] Add scripting (tengo) support for every incoming message (#731) TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script. This script will receive every incoming message and can be used to modify the Username and the Text of that message. The script will have the following global variables: to modify: msgUsername and msgText to read: msgChannel and msgAccount The script is reloaded on every message, so you can modify the script on the fly. Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo The example below will check if the text contains blah and if so, it'll replace the text and the username of that message. text := import("text") if text.re_match("blah",msgText) { msgText="replaced by this" msgUsername="fakeuser" } More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and https://github.com/d5/tengo/blob/master/docs/stdlib.md --- README.md | 1 + bridge/config/config.go | 1 + contrib/example.tengo | 2 + gateway/bench.tengo | 5 + gateway/gateway.go | 31 + gateway/gateway_test.go | 10 + go.mod | 1 + go.sum | 2 + matterbridge.toml.sample | 23 + vendor/github.com/d5/tengo/LICENSE | 21 + .../d5/tengo/compiler/ast/array_lit.go | 35 + .../d5/tengo/compiler/ast/assign_stmt.go | 40 + .../github.com/d5/tengo/compiler/ast/ast.go | 5 + .../d5/tengo/compiler/ast/bad_expr.go | 25 + .../d5/tengo/compiler/ast/bad_stmt.go | 25 + .../d5/tengo/compiler/ast/binary_expr.go | 30 + .../d5/tengo/compiler/ast/block_stmt.go | 35 + .../d5/tengo/compiler/ast/bool_lit.go | 26 + .../d5/tengo/compiler/ast/branch_stmt.go | 38 + .../d5/tengo/compiler/ast/call_expr.go | 36 + .../d5/tengo/compiler/ast/char_lit.go | 26 + .../d5/tengo/compiler/ast/cond_expr.go | 30 + .../d5/tengo/compiler/ast/empty_stmt.go | 29 + .../d5/tengo/compiler/ast/error_expr.go | 29 + .../d5/tengo/compiler/ast/export_stmt.go | 27 + .../github.com/d5/tengo/compiler/ast/expr.go | 7 + .../d5/tengo/compiler/ast/expr_stmt.go | 24 + .../github.com/d5/tengo/compiler/ast/file.go | 32 + .../d5/tengo/compiler/ast/float_lit.go | 26 + .../d5/tengo/compiler/ast/for_in_stmt.go | 32 + .../d5/tengo/compiler/ast/for_stmt.go | 43 + .../d5/tengo/compiler/ast/func_lit.go | 25 + .../d5/tengo/compiler/ast/func_type.go | 25 + .../github.com/d5/tengo/compiler/ast/ident.go | 29 + .../d5/tengo/compiler/ast/ident_list.go | 58 + .../d5/tengo/compiler/ast/if_stmt.go | 40 + .../d5/tengo/compiler/ast/immutable_expr.go | 29 + .../d5/tengo/compiler/ast/import_expr.go | 29 + .../d5/tengo/compiler/ast/inc_dec_stmt.go | 29 + .../d5/tengo/compiler/ast/index_expr.go | 32 + .../d5/tengo/compiler/ast/int_lit.go | 26 + .../d5/tengo/compiler/ast/map_element_lit.go | 27 + .../d5/tengo/compiler/ast/map_lit.go | 35 + .../github.com/d5/tengo/compiler/ast/node.go | 13 + .../d5/tengo/compiler/ast/paren_expr.go | 26 + .../d5/tengo/compiler/ast/return_stmt.go | 35 + .../d5/tengo/compiler/ast/selector_expr.go | 25 + .../d5/tengo/compiler/ast/slice_expr.go | 36 + .../github.com/d5/tengo/compiler/ast/stmt.go | 7 + .../d5/tengo/compiler/ast/string_lit.go | 26 + .../d5/tengo/compiler/ast/unary_expr.go | 29 + .../d5/tengo/compiler/ast/undefined_lit.go | 24 + .../github.com/d5/tengo/compiler/bytecode.go | 134 ++ .../d5/tengo/compiler/compilation_scope.go | 12 + .../github.com/d5/tengo/compiler/compiler.go | 731 +++++++++ .../d5/tengo/compiler/compiler_assign.go | 133 ++ .../d5/tengo/compiler/compiler_for.go | 181 +++ .../d5/tengo/compiler/compiler_logical.go | 30 + .../d5/tengo/compiler/compiler_loops.go | 31 + .../d5/tengo/compiler/compiler_module.go | 123 ++ .../d5/tengo/compiler/compiler_scopes.go | 43 + .../d5/tengo/compiler/emitted_instruction.go | 8 + vendor/github.com/d5/tengo/compiler/error.go | 20 + .../d5/tengo/compiler/instructions.go | 59 + vendor/github.com/d5/tengo/compiler/loop.go | 8 + .../d5/tengo/compiler/module_loader.go | 4 + .../github.com/d5/tengo/compiler/opcodes.go | 191 +++ .../d5/tengo/compiler/parser/error.go | 21 + .../d5/tengo/compiler/parser/error_list.go | 68 + .../d5/tengo/compiler/parser/parse_file.go | 28 + .../d5/tengo/compiler/parser/parse_source.go | 16 + .../d5/tengo/compiler/parser/parser.go | 1181 ++++++++++++++ .../d5/tengo/compiler/parser/sync.go | 12 + .../tengo/compiler/scanner/error_handler.go | 6 + .../d5/tengo/compiler/scanner/mode.go | 10 + .../d5/tengo/compiler/scanner/scanner.go | 680 ++++++++ .../d5/tengo/compiler/source/file.go | 110 ++ .../d5/tengo/compiler/source/file_pos.go | 47 + .../d5/tengo/compiler/source/file_set.go | 96 ++ .../d5/tengo/compiler/source/pos.go | 12 + vendor/github.com/d5/tengo/compiler/symbol.go | 9 + .../d5/tengo/compiler/symbol_scopes.go | 12 + .../d5/tengo/compiler/symbol_table.go | 145 ++ .../d5/tengo/compiler/token/keywords.go | 19 + .../d5/tengo/compiler/token/tokens.go | 208 +++ vendor/github.com/d5/tengo/objects/array.go | 130 ++ .../d5/tengo/objects/array_iterator.go | 57 + vendor/github.com/d5/tengo/objects/bool.go | 64 + vendor/github.com/d5/tengo/objects/break.go | 37 + .../d5/tengo/objects/builtin_append.go | 21 + .../d5/tengo/objects/builtin_convert.go | 155 ++ .../d5/tengo/objects/builtin_copy.go | 9 + .../d5/tengo/objects/builtin_function.go | 47 + .../d5/tengo/objects/builtin_json.go | 54 + .../d5/tengo/objects/builtin_len.go | 29 + .../d5/tengo/objects/builtin_print.go | 75 + .../d5/tengo/objects/builtin_type.go | 9 + .../d5/tengo/objects/builtin_type_checks.go | 183 +++ .../github.com/d5/tengo/objects/builtins.go | 135 ++ vendor/github.com/d5/tengo/objects/bytes.go | 76 + .../github.com/d5/tengo/objects/callable.go | 9 + .../d5/tengo/objects/callable_func.go | 4 + vendor/github.com/d5/tengo/objects/char.go | 119 ++ vendor/github.com/d5/tengo/objects/closure.go | 45 + .../d5/tengo/objects/compiled_function.go | 49 + .../github.com/d5/tengo/objects/continue.go | 38 + .../github.com/d5/tengo/objects/conversion.go | 249 +++ vendor/github.com/d5/tengo/objects/error.go | 47 + vendor/github.com/d5/tengo/objects/errors.go | 32 + vendor/github.com/d5/tengo/objects/float.go | 146 ++ .../d5/tengo/objects/immautable_array.go | 109 ++ .../d5/tengo/objects/immutable_map.go | 105 ++ .../d5/tengo/objects/index_assignable.go | 9 + .../github.com/d5/tengo/objects/indexable.go | 9 + vendor/github.com/d5/tengo/objects/int.go | 198 +++ .../github.com/d5/tengo/objects/iterable.go | 7 + .../github.com/d5/tengo/objects/iterator.go | 15 + vendor/github.com/d5/tengo/objects/map.go | 118 ++ .../d5/tengo/objects/map_iterator.go | 62 + vendor/github.com/d5/tengo/objects/object.go | 30 + vendor/github.com/d5/tengo/objects/objects.go | 12 + .../d5/tengo/objects/return_value.go | 39 + vendor/github.com/d5/tengo/objects/string.go | 95 ++ .../d5/tengo/objects/string_iterator.go | 57 + vendor/github.com/d5/tengo/objects/time.go | 89 ++ .../github.com/d5/tengo/objects/undefined.go | 42 + .../d5/tengo/objects/user_function.go | 47 + vendor/github.com/d5/tengo/runtime/errors.go | 8 + vendor/github.com/d5/tengo/runtime/frame.go | 13 + vendor/github.com/d5/tengo/runtime/vm.go | 1424 +++++++++++++++++ vendor/github.com/d5/tengo/script/compiled.go | 113 ++ .../github.com/d5/tengo/script/conversion.go | 33 + vendor/github.com/d5/tengo/script/script.go | 180 +++ vendor/github.com/d5/tengo/script/variable.go | 149 ++ vendor/github.com/d5/tengo/stdlib/errors.go | 11 + .../d5/tengo/stdlib/func_typedefs.go | 1057 ++++++++++++ vendor/github.com/d5/tengo/stdlib/math.go | 74 + vendor/github.com/d5/tengo/stdlib/os.go | 437 +++++ vendor/github.com/d5/tengo/stdlib/os_exec.go | 109 ++ vendor/github.com/d5/tengo/stdlib/os_file.go | 93 ++ .../github.com/d5/tengo/stdlib/os_process.go | 60 + vendor/github.com/d5/tengo/stdlib/rand.go | 99 ++ vendor/github.com/d5/tengo/stdlib/stdlib.go | 16 + vendor/github.com/d5/tengo/stdlib/text.go | 585 +++++++ .../github.com/d5/tengo/stdlib/text_regexp.go | 195 +++ vendor/github.com/d5/tengo/stdlib/times.go | 982 ++++++++++++ vendor/modules.txt | 11 + 147 files changed, 14201 insertions(+) create mode 100644 contrib/example.tengo create mode 100644 gateway/bench.tengo create mode 100644 vendor/github.com/d5/tengo/LICENSE create mode 100644 vendor/github.com/d5/tengo/compiler/ast/array_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/ast.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/bad_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/binary_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/block_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/bool_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/call_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/char_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/cond_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/error_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/export_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/file.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/float_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/for_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/func_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/func_type.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/ident.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/ident_list.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/if_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/immutable_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/import_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/inc_dec_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/index_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/int_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/map_element_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/map_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/node.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/paren_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/return_stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/selector_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/slice_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/stmt.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/string_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/unary_expr.go create mode 100644 vendor/github.com/d5/tengo/compiler/ast/undefined_lit.go create mode 100644 vendor/github.com/d5/tengo/compiler/bytecode.go create mode 100644 vendor/github.com/d5/tengo/compiler/compilation_scope.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_assign.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_for.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_logical.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_loops.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_module.go create mode 100644 vendor/github.com/d5/tengo/compiler/compiler_scopes.go create mode 100644 vendor/github.com/d5/tengo/compiler/emitted_instruction.go create mode 100644 vendor/github.com/d5/tengo/compiler/error.go create mode 100644 vendor/github.com/d5/tengo/compiler/instructions.go create mode 100644 vendor/github.com/d5/tengo/compiler/loop.go create mode 100644 vendor/github.com/d5/tengo/compiler/module_loader.go create mode 100644 vendor/github.com/d5/tengo/compiler/opcodes.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/error.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/error_list.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/parse_file.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/parse_source.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/parser.go create mode 100644 vendor/github.com/d5/tengo/compiler/parser/sync.go create mode 100644 vendor/github.com/d5/tengo/compiler/scanner/error_handler.go create mode 100644 vendor/github.com/d5/tengo/compiler/scanner/mode.go create mode 100644 vendor/github.com/d5/tengo/compiler/scanner/scanner.go create mode 100644 vendor/github.com/d5/tengo/compiler/source/file.go create mode 100644 vendor/github.com/d5/tengo/compiler/source/file_pos.go create mode 100644 vendor/github.com/d5/tengo/compiler/source/file_set.go create mode 100644 vendor/github.com/d5/tengo/compiler/source/pos.go create mode 100644 vendor/github.com/d5/tengo/compiler/symbol.go create mode 100644 vendor/github.com/d5/tengo/compiler/symbol_scopes.go create mode 100644 vendor/github.com/d5/tengo/compiler/symbol_table.go create mode 100644 vendor/github.com/d5/tengo/compiler/token/keywords.go create mode 100644 vendor/github.com/d5/tengo/compiler/token/tokens.go create mode 100644 vendor/github.com/d5/tengo/objects/array.go create mode 100644 vendor/github.com/d5/tengo/objects/array_iterator.go create mode 100644 vendor/github.com/d5/tengo/objects/bool.go create mode 100644 vendor/github.com/d5/tengo/objects/break.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_append.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_convert.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_copy.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_function.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_json.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_len.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_print.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_type.go create mode 100644 vendor/github.com/d5/tengo/objects/builtin_type_checks.go create mode 100644 vendor/github.com/d5/tengo/objects/builtins.go create mode 100644 vendor/github.com/d5/tengo/objects/bytes.go create mode 100644 vendor/github.com/d5/tengo/objects/callable.go create mode 100644 vendor/github.com/d5/tengo/objects/callable_func.go create mode 100644 vendor/github.com/d5/tengo/objects/char.go create mode 100644 vendor/github.com/d5/tengo/objects/closure.go create mode 100644 vendor/github.com/d5/tengo/objects/compiled_function.go create mode 100644 vendor/github.com/d5/tengo/objects/continue.go create mode 100644 vendor/github.com/d5/tengo/objects/conversion.go create mode 100644 vendor/github.com/d5/tengo/objects/error.go create mode 100644 vendor/github.com/d5/tengo/objects/errors.go create mode 100644 vendor/github.com/d5/tengo/objects/float.go create mode 100644 vendor/github.com/d5/tengo/objects/immautable_array.go create mode 100644 vendor/github.com/d5/tengo/objects/immutable_map.go create mode 100644 vendor/github.com/d5/tengo/objects/index_assignable.go create mode 100644 vendor/github.com/d5/tengo/objects/indexable.go create mode 100644 vendor/github.com/d5/tengo/objects/int.go create mode 100644 vendor/github.com/d5/tengo/objects/iterable.go create mode 100644 vendor/github.com/d5/tengo/objects/iterator.go create mode 100644 vendor/github.com/d5/tengo/objects/map.go create mode 100644 vendor/github.com/d5/tengo/objects/map_iterator.go create mode 100644 vendor/github.com/d5/tengo/objects/object.go create mode 100644 vendor/github.com/d5/tengo/objects/objects.go create mode 100644 vendor/github.com/d5/tengo/objects/return_value.go create mode 100644 vendor/github.com/d5/tengo/objects/string.go create mode 100644 vendor/github.com/d5/tengo/objects/string_iterator.go create mode 100644 vendor/github.com/d5/tengo/objects/time.go create mode 100644 vendor/github.com/d5/tengo/objects/undefined.go create mode 100644 vendor/github.com/d5/tengo/objects/user_function.go create mode 100644 vendor/github.com/d5/tengo/runtime/errors.go create mode 100644 vendor/github.com/d5/tengo/runtime/frame.go create mode 100644 vendor/github.com/d5/tengo/runtime/vm.go create mode 100644 vendor/github.com/d5/tengo/script/compiled.go create mode 100644 vendor/github.com/d5/tengo/script/conversion.go create mode 100644 vendor/github.com/d5/tengo/script/script.go create mode 100644 vendor/github.com/d5/tengo/script/variable.go create mode 100644 vendor/github.com/d5/tengo/stdlib/errors.go create mode 100644 vendor/github.com/d5/tengo/stdlib/func_typedefs.go create mode 100644 vendor/github.com/d5/tengo/stdlib/math.go create mode 100644 vendor/github.com/d5/tengo/stdlib/os.go create mode 100644 vendor/github.com/d5/tengo/stdlib/os_exec.go create mode 100644 vendor/github.com/d5/tengo/stdlib/os_file.go create mode 100644 vendor/github.com/d5/tengo/stdlib/os_process.go create mode 100644 vendor/github.com/d5/tengo/stdlib/rand.go create mode 100644 vendor/github.com/d5/tengo/stdlib/stdlib.go create mode 100644 vendor/github.com/d5/tengo/stdlib/text.go create mode 100644 vendor/github.com/d5/tengo/stdlib/text_regexp.go create mode 100644 vendor/github.com/d5/tengo/stdlib/times.go diff --git a/README.md b/README.md index 4ccfd9db..4923e360 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ Matterbridge wouldn't exist without these libraries: * xmpp - https://github.com/mattn/go-xmpp * whatsapp - https://github.com/Rhymen/go-whatsapp/ * zulip - https://github.com/ifo/gozulipbot +* tengo - https://github.com/d5/tengo diff --git a/bridge/config/config.go b/bridge/config/config.go index 7ab6aef4..47914951 100644 --- a/bridge/config/config.go +++ b/bridge/config/config.go @@ -129,6 +129,7 @@ type Protocol struct { SkipTLSVerify bool // IRC, mattermost StripNick bool // all protocols SyncTopic bool // slack + TengoModifyMessage string // general Team string // mattermost Token string // gitter, slack, discord, api Topic string // zulip diff --git a/contrib/example.tengo b/contrib/example.tengo new file mode 100644 index 00000000..da4eede6 --- /dev/null +++ b/contrib/example.tengo @@ -0,0 +1,2 @@ +text := import("text") +msgText=text.re_replace("matterbridge",msgText,"matterbridge (https://github.com/42wim/matterbridge)") diff --git a/gateway/bench.tengo b/gateway/bench.tengo new file mode 100644 index 00000000..879d17aa --- /dev/null +++ b/gateway/bench.tengo @@ -0,0 +1,5 @@ +text := import("text") +if text.re_match("blah",msgText) { + msgText="replaced by this" + msgUsername="fakeuser" +} diff --git a/gateway/gateway.go b/gateway/gateway.go index 2f797734..72d7c72d 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -1,6 +1,7 @@ package gateway import ( + "io/ioutil" "os" "regexp" "strings" @@ -8,6 +9,7 @@ import ( "github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge/config" + "github.com/d5/tengo/script" "github.com/hashicorp/golang-lru" "github.com/peterhellberg/emojilib" "github.com/sirupsen/logrus" @@ -334,6 +336,10 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string } func (gw *Gateway) modifyMessage(msg *config.Message) { + if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil { + flog.Errorf("TengoModifyMessage failed: %s", err) + } + // replace :emoji: to unicode msg.Text = emojilib.Replace(msg.Text) @@ -458,3 +464,28 @@ func getProtocol(msg *config.Message) string { p := strings.Split(msg.Account, ".") return p[0] } + +func modifyMessageTengo(filename string, msg *config.Message) error { + if filename == "" { + return nil + } + res, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + s := script.New(res) + _ = s.Add("msgText", msg.Text) + _ = s.Add("msgUsername", msg.Username) + _ = s.Add("msgAccount", msg.Account) + _ = s.Add("msgChannel", msg.Channel) + c, err := s.Compile() + if err != nil { + return err + } + if err := c.Run(); err != nil { + return err + } + msg.Text = c.Get("msgText").String() + msg.Username = c.Get("msgUsername").String() + return nil +} diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index 9621ab7d..677afde4 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -499,3 +499,13 @@ func TestIgnoreNicks(t *testing.T) { assert.Equalf(t, testcase.output, output, "case '%s' failed", testname) } } + +func BenchmarkTengo(b *testing.B) { + msg := &config.Message{Username: "user", Text: "blah testing", Account: "protocol.account", Channel: "mychannel"} + for n := 0; n < b.N; n++ { + err := modifyMessageTengo("bench.tengo", msg) + if err != nil { + return + } + } +} diff --git a/go.mod b/go.mod index 0d79c67f..3a590135 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884 github.com/bwmarrin/discordgo v0.19.0 + github.com/d5/tengo v1.9.2 github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec github.com/fsnotify/fsnotify v1.4.7 github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible diff --git a/go.sum b/go.sum index 4aa24acd..92b03710 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/d5/tengo v1.9.2 h1:UE/X8PYl7bLS4Ww2zGeh91nq5PTnkhe8ncgNeA5PK7k= +github.com/d5/tengo v1.9.2/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample index 51faa199..19cb09b7 100644 --- a/matterbridge.toml.sample +++ b/matterbridge.toml.sample @@ -1527,6 +1527,29 @@ MediaDownloadBlacklist=[".html$",".htm$"] #OPTIONAL (default false) IgnoreFailureOnStart=false + +#TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script. +#This script will receive every incoming message and can be used to modify the Username and the Text of that message. +#The script will have the following global variables: +#to modify: msgUsername and msgText +#to read: msgChannel and msgAccount +# +#The script is reloaded on every message, so you can modify the script on the fly. +# +#Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo +#and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo +# +#The example below will check if the text contains blah and if so, it'll replace the text and the username of that message. +#text := import("text") +#if text.re_match("blah",msgText) { +# msgText="replaced by this" +# msgUsername="fakeuser" +#} +#More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and +#https://github.com/d5/tengo/blob/master/docs/stdlib.md +#OPTIONAL (default empty) +TengoModifyMessage="example.tengo" + ################################################################### #Gateway configuration ################################################################### diff --git a/vendor/github.com/d5/tengo/LICENSE b/vendor/github.com/d5/tengo/LICENSE new file mode 100644 index 00000000..2516341a --- /dev/null +++ b/vendor/github.com/d5/tengo/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/compiler/ast/array_lit.go b/vendor/github.com/d5/tengo/compiler/ast/array_lit.go new file mode 100644 index 00000000..9fb4ed67 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/array_lit.go @@ -0,0 +1,35 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// ArrayLit represents an array literal. +type ArrayLit struct { + Elements []Expr + LBrack source.Pos + RBrack source.Pos +} + +func (e *ArrayLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ArrayLit) Pos() source.Pos { + return e.LBrack +} + +// End returns the position of first character immediately after the node. +func (e *ArrayLit) End() source.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, ", ") + "]" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go new file mode 100644 index 00000000..e129114e --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go @@ -0,0 +1,40 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// AssignStmt represents an assignment statement. +type AssignStmt struct { + LHS []Expr + RHS []Expr + Token token.Token + TokenPos source.Pos +} + +func (s *AssignStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *AssignStmt) Pos() source.Pos { + return s.LHS[0].Pos() +} + +// End returns the position of first character immediately after the node. +func (s *AssignStmt) End() source.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, ", ") +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/ast.go b/vendor/github.com/d5/tengo/compiler/ast/ast.go new file mode 100644 index 00000000..9fd06728 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/ast.go @@ -0,0 +1,5 @@ +package ast + +const ( + nullRep = "" +) diff --git a/vendor/github.com/d5/tengo/compiler/ast/bad_expr.go b/vendor/github.com/d5/tengo/compiler/ast/bad_expr.go new file mode 100644 index 00000000..771f26fd --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/bad_expr.go @@ -0,0 +1,25 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// BadExpr represents a bad expression. +type BadExpr struct { + From source.Pos + To source.Pos +} + +func (e *BadExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BadExpr) Pos() source.Pos { + return e.From +} + +// End returns the position of first character immediately after the node. +func (e *BadExpr) End() source.Pos { + return e.To +} + +func (e *BadExpr) String() string { + return "" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go new file mode 100644 index 00000000..c2d0ae9a --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go @@ -0,0 +1,25 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// BadStmt represents a bad statement. +type BadStmt struct { + From source.Pos + To source.Pos +} + +func (s *BadStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BadStmt) Pos() source.Pos { + return s.From +} + +// End returns the position of first character immediately after the node. +func (s *BadStmt) End() source.Pos { + return s.To +} + +func (s *BadStmt) String() string { + return "" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/binary_expr.go b/vendor/github.com/d5/tengo/compiler/ast/binary_expr.go new file mode 100644 index 00000000..0cc5bba8 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/binary_expr.go @@ -0,0 +1,30 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// BinaryExpr represents a binary operator expression. +type BinaryExpr struct { + LHS Expr + RHS Expr + Token token.Token + TokenPos source.Pos +} + +func (e *BinaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BinaryExpr) Pos() source.Pos { + return e.LHS.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *BinaryExpr) End() source.Pos { + return e.RHS.End() +} + +func (e *BinaryExpr) String() string { + return "(" + e.LHS.String() + " " + e.Token.String() + " " + e.RHS.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/block_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/block_stmt.go new file mode 100644 index 00000000..9bde9fa3 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/block_stmt.go @@ -0,0 +1,35 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// BlockStmt represents a block statement. +type BlockStmt struct { + Stmts []Stmt + LBrace source.Pos + RBrace source.Pos +} + +func (s *BlockStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BlockStmt) Pos() source.Pos { + return s.LBrace +} + +// End returns the position of first character immediately after the node. +func (s *BlockStmt) End() source.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, "; ") + "}" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/bool_lit.go b/vendor/github.com/d5/tengo/compiler/ast/bool_lit.go new file mode 100644 index 00000000..e667a5c8 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/bool_lit.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// BoolLit represents a boolean literal. +type BoolLit struct { + Value bool + ValuePos source.Pos + Literal string +} + +func (e *BoolLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *BoolLit) Pos() source.Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *BoolLit) End() source.Pos { + return source.Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *BoolLit) String() string { + return e.Literal +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go new file mode 100644 index 00000000..f6c7fdea --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go @@ -0,0 +1,38 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// BranchStmt represents a branch statement. +type BranchStmt struct { + Token token.Token + TokenPos source.Pos + Label *Ident +} + +func (s *BranchStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *BranchStmt) Pos() source.Pos { + return s.TokenPos +} + +// End returns the position of first character immediately after the node. +func (s *BranchStmt) End() source.Pos { + if s.Label != nil { + return s.Label.End() + } + + return source.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 +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/call_expr.go b/vendor/github.com/d5/tengo/compiler/ast/call_expr.go new file mode 100644 index 00000000..0219d7c9 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/call_expr.go @@ -0,0 +1,36 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// CallExpr represents a function call expression. +type CallExpr struct { + Func Expr + LParen source.Pos + Args []Expr + RParen source.Pos +} + +func (e *CallExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CallExpr) Pos() source.Pos { + return e.Func.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CallExpr) End() source.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, ", ") + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/char_lit.go b/vendor/github.com/d5/tengo/compiler/ast/char_lit.go new file mode 100644 index 00000000..592f8744 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/char_lit.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// CharLit represents a character literal. +type CharLit struct { + Value rune + ValuePos source.Pos + Literal string +} + +func (e *CharLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CharLit) Pos() source.Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *CharLit) End() source.Pos { + return source.Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *CharLit) String() string { + return e.Literal +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/cond_expr.go b/vendor/github.com/d5/tengo/compiler/ast/cond_expr.go new file mode 100644 index 00000000..bb1db849 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/cond_expr.go @@ -0,0 +1,30 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// CondExpr represents a ternary conditional expression. +type CondExpr struct { + Cond Expr + True Expr + False Expr + QuestionPos source.Pos + ColonPos source.Pos +} + +func (e *CondExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *CondExpr) Pos() source.Pos { + return e.Cond.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *CondExpr) End() source.Pos { + return e.False.End() +} + +func (e *CondExpr) String() string { + return "(" + e.Cond.String() + " ? " + e.True.String() + " : " + e.False.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go new file mode 100644 index 00000000..a2ac6ffe --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go @@ -0,0 +1,29 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// EmptyStmt represents an empty statement. +type EmptyStmt struct { + Semicolon source.Pos + Implicit bool +} + +func (s *EmptyStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *EmptyStmt) Pos() source.Pos { + return s.Semicolon +} + +// End returns the position of first character immediately after the node. +func (s *EmptyStmt) End() source.Pos { + if s.Implicit { + return s.Semicolon + } + + return s.Semicolon + 1 +} + +func (s *EmptyStmt) String() string { + return ";" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/error_expr.go b/vendor/github.com/d5/tengo/compiler/ast/error_expr.go new file mode 100644 index 00000000..7ce5667e --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/error_expr.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// ErrorExpr represents an error expression +type ErrorExpr struct { + Expr Expr + ErrorPos source.Pos + LParen source.Pos + RParen source.Pos +} + +func (e *ErrorExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ErrorExpr) Pos() source.Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ErrorExpr) End() source.Pos { + return e.RParen +} + +func (e *ErrorExpr) String() string { + return "error(" + e.Expr.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/export_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/export_stmt.go new file mode 100644 index 00000000..64eb7606 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/export_stmt.go @@ -0,0 +1,27 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// ExportStmt represents an export statement. +type ExportStmt struct { + ExportPos source.Pos + Result Expr +} + +func (s *ExportStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ExportStmt) Pos() source.Pos { + return s.ExportPos +} + +// End returns the position of first character immediately after the node. +func (s *ExportStmt) End() source.Pos { + return s.Result.End() +} + +func (s *ExportStmt) String() string { + return "export " + s.Result.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/expr.go b/vendor/github.com/d5/tengo/compiler/ast/expr.go new file mode 100644 index 00000000..764bacec --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/expr.go @@ -0,0 +1,7 @@ +package ast + +// Expr represents an expression node in the AST. +type Expr interface { + Node + exprNode() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go new file mode 100644 index 00000000..095a3ad5 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go @@ -0,0 +1,24 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// 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() source.Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *ExprStmt) End() source.Pos { + return s.Expr.End() +} + +func (s *ExprStmt) String() string { + return s.Expr.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/file.go b/vendor/github.com/d5/tengo/compiler/ast/file.go new file mode 100644 index 00000000..fc18b2d7 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/file.go @@ -0,0 +1,32 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// File represents a file unit. +type File struct { + InputFile *source.File + Stmts []Stmt +} + +// Pos returns the position of first character belonging to the node. +func (n *File) Pos() source.Pos { + return source.Pos(n.InputFile.Base) +} + +// End returns the position of first character immediately after the node. +func (n *File) End() source.Pos { + return source.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/compiler/ast/float_lit.go b/vendor/github.com/d5/tengo/compiler/ast/float_lit.go new file mode 100644 index 00000000..670f744b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/float_lit.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// FloatLit represents a floating point literal. +type FloatLit struct { + Value float64 + ValuePos source.Pos + Literal string +} + +func (e *FloatLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FloatLit) Pos() source.Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *FloatLit) End() source.Pos { + return source.Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *FloatLit) String() string { + return e.Literal +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go new file mode 100644 index 00000000..18020b56 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go @@ -0,0 +1,32 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// ForInStmt represents a for-in statement. +type ForInStmt struct { + ForPos source.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() source.Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForInStmt) End() source.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() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/for_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/for_stmt.go new file mode 100644 index 00000000..4b5a0a17 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/for_stmt.go @@ -0,0 +1,43 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// ForStmt represents a for statement. +type ForStmt struct { + ForPos source.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() source.Pos { + return s.ForPos +} + +// End returns the position of first character immediately after the node. +func (s *ForStmt) End() source.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() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/func_lit.go b/vendor/github.com/d5/tengo/compiler/ast/func_lit.go new file mode 100644 index 00000000..2e90ed2b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/func_lit.go @@ -0,0 +1,25 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// 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() source.Pos { + return e.Type.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *FuncLit) End() source.Pos { + return e.Body.End() +} + +func (e *FuncLit) String() string { + return "func" + e.Type.Params.String() + " " + e.Body.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/func_type.go b/vendor/github.com/d5/tengo/compiler/ast/func_type.go new file mode 100644 index 00000000..2afaabb1 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/func_type.go @@ -0,0 +1,25 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// FuncType represents a function type definition. +type FuncType struct { + FuncPos source.Pos + Params *IdentList +} + +func (e *FuncType) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *FuncType) Pos() source.Pos { + return e.FuncPos +} + +// End returns the position of first character immediately after the node. +func (e *FuncType) End() source.Pos { + return e.Params.End() +} + +func (e *FuncType) String() string { + return "func" + e.Params.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/ident.go b/vendor/github.com/d5/tengo/compiler/ast/ident.go new file mode 100644 index 00000000..33b7ff76 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/ident.go @@ -0,0 +1,29 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// Ident represents an identifier. +type Ident struct { + Name string + NamePos source.Pos +} + +func (e *Ident) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *Ident) Pos() source.Pos { + return e.NamePos +} + +// End returns the position of first character immediately after the node. +func (e *Ident) End() source.Pos { + return source.Pos(int(e.NamePos) + len(e.Name)) +} + +func (e *Ident) String() string { + if e != nil { + return e.Name + } + + return nullRep +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/ident_list.go b/vendor/github.com/d5/tengo/compiler/ast/ident_list.go new file mode 100644 index 00000000..ee8f7db2 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/ident_list.go @@ -0,0 +1,58 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// IdentList represents a list of identifiers. +type IdentList struct { + LParen source.Pos + List []*Ident + RParen source.Pos +} + +// Pos returns the position of first character belonging to the node. +func (n *IdentList) Pos() source.Pos { + if n.LParen.IsValid() { + return n.LParen + } + + if len(n.List) > 0 { + return n.List[0].Pos() + } + + return source.NoPos +} + +// End returns the position of first character immediately after the node. +func (n *IdentList) End() source.Pos { + if n.RParen.IsValid() { + return n.RParen + 1 + } + + if l := len(n.List); l > 0 { + return n.List[l-1].End() + } + + return source.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 _, e := range n.List { + list = append(list, e.String()) + } + + return "(" + strings.Join(list, ", ") + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/if_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/if_stmt.go new file mode 100644 index 00000000..b3d65606 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/if_stmt.go @@ -0,0 +1,40 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// IfStmt represents an if statement. +type IfStmt struct { + IfPos source.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() source.Pos { + return s.IfPos +} + +// End returns the position of first character immediately after the node. +func (s *IfStmt) End() source.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 +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/immutable_expr.go b/vendor/github.com/d5/tengo/compiler/ast/immutable_expr.go new file mode 100644 index 00000000..f9843b50 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/immutable_expr.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// ImmutableExpr represents an immutable expression +type ImmutableExpr struct { + Expr Expr + ErrorPos source.Pos + LParen source.Pos + RParen source.Pos +} + +func (e *ImmutableExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImmutableExpr) Pos() source.Pos { + return e.ErrorPos +} + +// End returns the position of first character immediately after the node. +func (e *ImmutableExpr) End() source.Pos { + return e.RParen +} + +func (e *ImmutableExpr) String() string { + return "immutable(" + e.Expr.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/import_expr.go b/vendor/github.com/d5/tengo/compiler/ast/import_expr.go new file mode 100644 index 00000000..6eff74a9 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/import_expr.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// ImportExpr represents an import expression +type ImportExpr struct { + ModuleName string + Token token.Token + TokenPos source.Pos +} + +func (e *ImportExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ImportExpr) Pos() source.Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *ImportExpr) End() source.Pos { + return source.Pos(int(e.TokenPos) + 10 + len(e.ModuleName)) // import("moduleName") +} + +func (e *ImportExpr) String() string { + return `import("` + e.ModuleName + `")"` +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/inc_dec_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/inc_dec_stmt.go new file mode 100644 index 00000000..e4e7f92c --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/inc_dec_stmt.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// IncDecStmt represents increment or decrement statement. +type IncDecStmt struct { + Expr Expr + Token token.Token + TokenPos source.Pos +} + +func (s *IncDecStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *IncDecStmt) Pos() source.Pos { + return s.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (s *IncDecStmt) End() source.Pos { + return source.Pos(int(s.TokenPos) + 2) +} + +func (s *IncDecStmt) String() string { + return s.Expr.String() + s.Token.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/index_expr.go b/vendor/github.com/d5/tengo/compiler/ast/index_expr.go new file mode 100644 index 00000000..bc0992a3 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/index_expr.go @@ -0,0 +1,32 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// IndexExpr represents an index expression. +type IndexExpr struct { + Expr Expr + LBrack source.Pos + Index Expr + RBrack source.Pos +} + +func (e *IndexExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IndexExpr) Pos() source.Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *IndexExpr) End() source.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 + "]" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/int_lit.go b/vendor/github.com/d5/tengo/compiler/ast/int_lit.go new file mode 100644 index 00000000..3e1fd98b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/int_lit.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// IntLit represents an integer literal. +type IntLit struct { + Value int64 + ValuePos source.Pos + Literal string +} + +func (e *IntLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *IntLit) Pos() source.Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *IntLit) End() source.Pos { + return source.Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *IntLit) String() string { + return e.Literal +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/map_element_lit.go b/vendor/github.com/d5/tengo/compiler/ast/map_element_lit.go new file mode 100644 index 00000000..3d7fca9e --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/map_element_lit.go @@ -0,0 +1,27 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// MapElementLit represents a map element. +type MapElementLit struct { + Key string + KeyPos source.Pos + ColonPos source.Pos + Value Expr +} + +func (e *MapElementLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapElementLit) Pos() source.Pos { + return e.KeyPos +} + +// End returns the position of first character immediately after the node. +func (e *MapElementLit) End() source.Pos { + return e.Value.End() +} + +func (e *MapElementLit) String() string { + return e.Key + ": " + e.Value.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/map_lit.go b/vendor/github.com/d5/tengo/compiler/ast/map_lit.go new file mode 100644 index 00000000..a228224d --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/map_lit.go @@ -0,0 +1,35 @@ +package ast + +import ( + "strings" + + "github.com/d5/tengo/compiler/source" +) + +// MapLit represents a map literal. +type MapLit struct { + LBrace source.Pos + Elements []*MapElementLit + RBrace source.Pos +} + +func (e *MapLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *MapLit) Pos() source.Pos { + return e.LBrace +} + +// End returns the position of first character immediately after the node. +func (e *MapLit) End() source.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, ", ") + "}" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/node.go b/vendor/github.com/d5/tengo/compiler/ast/node.go new file mode 100644 index 00000000..44677b47 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/node.go @@ -0,0 +1,13 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// Node represents a node in the AST. +type Node interface { + // Pos returns the position of first character belonging to the node. + Pos() source.Pos + // End returns the position of first character immediately after the node. + End() source.Pos + // String returns a string representation of the node. + String() string +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/paren_expr.go b/vendor/github.com/d5/tengo/compiler/ast/paren_expr.go new file mode 100644 index 00000000..8db4ac02 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/paren_expr.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// ParenExpr represents a parenthesis wrapped expression. +type ParenExpr struct { + Expr Expr + LParen source.Pos + RParen source.Pos +} + +func (e *ParenExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *ParenExpr) Pos() source.Pos { + return e.LParen +} + +// End returns the position of first character immediately after the node. +func (e *ParenExpr) End() source.Pos { + return e.RParen + 1 +} + +func (e *ParenExpr) String() string { + return "(" + e.Expr.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/return_stmt.go b/vendor/github.com/d5/tengo/compiler/ast/return_stmt.go new file mode 100644 index 00000000..592d45b8 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/return_stmt.go @@ -0,0 +1,35 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" +) + +// ReturnStmt represents a return statement. +type ReturnStmt struct { + ReturnPos source.Pos + Result Expr +} + +func (s *ReturnStmt) stmtNode() {} + +// Pos returns the position of first character belonging to the node. +func (s *ReturnStmt) Pos() source.Pos { + return s.ReturnPos +} + +// End returns the position of first character immediately after the node. +func (s *ReturnStmt) End() source.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/compiler/ast/selector_expr.go b/vendor/github.com/d5/tengo/compiler/ast/selector_expr.go new file mode 100644 index 00000000..31d2e6d1 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/selector_expr.go @@ -0,0 +1,25 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// 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() source.Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SelectorExpr) End() source.Pos { + return e.Sel.End() +} + +func (e *SelectorExpr) String() string { + return e.Expr.String() + "." + e.Sel.String() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/slice_expr.go b/vendor/github.com/d5/tengo/compiler/ast/slice_expr.go new file mode 100644 index 00000000..e7e2e05b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/slice_expr.go @@ -0,0 +1,36 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// SliceExpr represents a slice expression. +type SliceExpr struct { + Expr Expr + LBrack source.Pos + Low Expr + High Expr + RBrack source.Pos +} + +func (e *SliceExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *SliceExpr) Pos() source.Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *SliceExpr) End() source.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 + "]" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/stmt.go b/vendor/github.com/d5/tengo/compiler/ast/stmt.go new file mode 100644 index 00000000..6b26ba88 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/stmt.go @@ -0,0 +1,7 @@ +package ast + +// Stmt represents a statement in the AST. +type Stmt interface { + Node + stmtNode() +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/string_lit.go b/vendor/github.com/d5/tengo/compiler/ast/string_lit.go new file mode 100644 index 00000000..2119d34b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/string_lit.go @@ -0,0 +1,26 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// StringLit represents a string literal. +type StringLit struct { + Value string + ValuePos source.Pos + Literal string +} + +func (e *StringLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *StringLit) Pos() source.Pos { + return e.ValuePos +} + +// End returns the position of first character immediately after the node. +func (e *StringLit) End() source.Pos { + return source.Pos(int(e.ValuePos) + len(e.Literal)) +} + +func (e *StringLit) String() string { + return e.Literal +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/unary_expr.go b/vendor/github.com/d5/tengo/compiler/ast/unary_expr.go new file mode 100644 index 00000000..53236146 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/unary_expr.go @@ -0,0 +1,29 @@ +package ast + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// UnaryExpr represents an unary operator expression. +type UnaryExpr struct { + Expr Expr + Token token.Token + TokenPos source.Pos +} + +func (e *UnaryExpr) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UnaryExpr) Pos() source.Pos { + return e.Expr.Pos() +} + +// End returns the position of first character immediately after the node. +func (e *UnaryExpr) End() source.Pos { + return e.Expr.End() +} + +func (e *UnaryExpr) String() string { + return "(" + e.Token.String() + e.Expr.String() + ")" +} diff --git a/vendor/github.com/d5/tengo/compiler/ast/undefined_lit.go b/vendor/github.com/d5/tengo/compiler/ast/undefined_lit.go new file mode 100644 index 00000000..8e51b113 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/ast/undefined_lit.go @@ -0,0 +1,24 @@ +package ast + +import "github.com/d5/tengo/compiler/source" + +// UndefinedLit represents an undefined literal. +type UndefinedLit struct { + TokenPos source.Pos +} + +func (e *UndefinedLit) exprNode() {} + +// Pos returns the position of first character belonging to the node. +func (e *UndefinedLit) Pos() source.Pos { + return e.TokenPos +} + +// End returns the position of first character immediately after the node. +func (e *UndefinedLit) End() source.Pos { + return e.TokenPos + 9 // len(undefined) == 9 +} + +func (e *UndefinedLit) String() string { + return "undefined" +} diff --git a/vendor/github.com/d5/tengo/compiler/bytecode.go b/vendor/github.com/d5/tengo/compiler/bytecode.go new file mode 100644 index 00000000..42527731 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/bytecode.go @@ -0,0 +1,134 @@ +package compiler + +import ( + "encoding/gob" + "fmt" + "io" + "reflect" + + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/objects" +) + +// Bytecode is a compiled instructions and constants. +type Bytecode struct { + FileSet *source.FileSet + MainFunction *objects.CompiledFunction + Constants []objects.Object +} + +// Decode reads Bytecode data from the reader. +func (b *Bytecode) Decode(r io.Reader) error { + dec := gob.NewDecoder(r) + + if err := dec.Decode(&b.FileSet); err != nil { + return err + } + // TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet + // as it's private field and not serialized by gob encoder/decoder. + + if err := dec.Decode(&b.MainFunction); err != nil { + return err + } + + if err := dec.Decode(&b.Constants); err != nil { + return err + } + + // replace Bool and Undefined with known value + for i, v := range b.Constants { + b.Constants[i] = cleanupObjects(v) + } + + return nil +} + +// Encode writes Bytecode data to the writer. +func (b *Bytecode) Encode(w io.Writer) error { + enc := gob.NewEncoder(w) + + if err := enc.Encode(b.FileSet); err != nil { + return err + } + + if err := enc.Encode(b.MainFunction); err != nil { + return err + } + + // constants + return enc.Encode(b.Constants) +} + +// 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 *objects.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 +} + +func cleanupObjects(o objects.Object) objects.Object { + switch o := o.(type) { + case *objects.Bool: + if o.IsFalsy() { + return objects.FalseValue + } + return objects.TrueValue + case *objects.Undefined: + return objects.UndefinedValue + case *objects.Array: + for i, v := range o.Value { + o.Value[i] = cleanupObjects(v) + } + case *objects.Map: + for k, v := range o.Value { + o.Value[k] = cleanupObjects(v) + } + } + + return o +} + +func init() { + gob.Register(&source.FileSet{}) + gob.Register(&source.File{}) + gob.Register(&objects.Array{}) + gob.Register(&objects.ArrayIterator{}) + gob.Register(&objects.Bool{}) + gob.Register(&objects.Break{}) + gob.Register(&objects.BuiltinFunction{}) + gob.Register(&objects.Bytes{}) + gob.Register(&objects.Char{}) + gob.Register(&objects.Closure{}) + gob.Register(&objects.CompiledFunction{}) + gob.Register(&objects.Continue{}) + gob.Register(&objects.Error{}) + gob.Register(&objects.Float{}) + gob.Register(&objects.ImmutableArray{}) + gob.Register(&objects.ImmutableMap{}) + gob.Register(&objects.Int{}) + gob.Register(&objects.Map{}) + gob.Register(&objects.MapIterator{}) + gob.Register(&objects.ReturnValue{}) + gob.Register(&objects.String{}) + gob.Register(&objects.StringIterator{}) + gob.Register(&objects.Time{}) + gob.Register(&objects.Undefined{}) + gob.Register(&objects.UserFunction{}) +} diff --git a/vendor/github.com/d5/tengo/compiler/compilation_scope.go b/vendor/github.com/d5/tengo/compiler/compilation_scope.go new file mode 100644 index 00000000..dd198ae9 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compilation_scope.go @@ -0,0 +1,12 @@ +package compiler + +import "github.com/d5/tengo/compiler/source" + +// CompilationScope represents a compiled instructions +// and the last two instructions that were emitted. +type CompilationScope struct { + instructions []byte + lastInstructions [2]EmittedInstruction + symbolInit map[string]bool + sourceMap map[int]source.Pos +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler.go b/vendor/github.com/d5/tengo/compiler/compiler.go new file mode 100644 index 00000000..141ea8fd --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler.go @@ -0,0 +1,731 @@ +package compiler + +import ( + "fmt" + "io" + "reflect" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" + "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" +) + +// Compiler compiles the AST into a bytecode. +type Compiler struct { + file *source.File + parent *Compiler + moduleName string + constants []objects.Object + symbolTable *SymbolTable + scopes []CompilationScope + scopeIndex int + moduleLoader ModuleLoader + builtinModules map[string]bool + compiledModules map[string]*objects.CompiledFunction + loops []*Loop + loopIndex int + trace io.Writer + indent int +} + +// NewCompiler creates a Compiler. +// User can optionally provide the symbol table if one wants to add or remove +// some global- or builtin- scope symbols. If not (nil), Compile will create +// a new symbol table and use the default builtin functions. Likewise, standard +// modules can be explicitly provided if user wants to add or remove some modules. +// By default, Compile will use all the standard modules otherwise. +func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, builtinModules map[string]bool, trace io.Writer) *Compiler { + mainScope := CompilationScope{ + symbolInit: make(map[string]bool), + sourceMap: make(map[int]source.Pos), + } + + // symbol table + if symbolTable == nil { + symbolTable = NewSymbolTable() + + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } + } + + // builtin modules + if builtinModules == nil { + builtinModules = make(map[string]bool) + for name := range stdlib.Modules { + builtinModules[name] = true + } + } + + return &Compiler{ + file: file, + symbolTable: symbolTable, + constants: constants, + scopes: []CompilationScope{mainScope}, + scopeIndex: 0, + loopIndex: -1, + trace: trace, + builtinModules: builtinModules, + compiledModules: make(map[string]*objects.CompiledFunction), + } +} + +// Compile compiles the AST node. +func (c *Compiler) Compile(node ast.Node) error { + if c.trace != nil { + if node != nil { + defer un(trace(c, fmt.Sprintf("%s (%s)", node.String(), reflect.TypeOf(node).Elem().Name()))) + } else { + defer un(trace(c, "")) + } + } + + switch node := node.(type) { + case *ast.File: + for _, stmt := range node.Stmts { + if err := c.Compile(stmt); err != nil { + return err + } + } + + case *ast.ExprStmt: + if err := c.Compile(node.Expr); err != nil { + return err + } + c.emit(node, OpPop) + + case *ast.IncDecStmt: + op := token.AddAssign + if node.Token == token.Dec { + op = token.SubAssign + } + + return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op) + + case *ast.ParenExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + case *ast.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, OpGreaterThan) + + 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, OpGreaterThanEqual) + + 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, OpAdd) + case token.Sub: + c.emit(node, OpSub) + case token.Mul: + c.emit(node, OpMul) + case token.Quo: + c.emit(node, OpDiv) + case token.Rem: + c.emit(node, OpRem) + case token.Greater: + c.emit(node, OpGreaterThan) + case token.GreaterEq: + c.emit(node, OpGreaterThanEqual) + case token.Equal: + c.emit(node, OpEqual) + case token.NotEqual: + c.emit(node, OpNotEqual) + case token.And: + c.emit(node, OpBAnd) + case token.Or: + c.emit(node, OpBOr) + case token.Xor: + c.emit(node, OpBXor) + case token.AndNot: + c.emit(node, OpBAndNot) + case token.Shl: + c.emit(node, OpBShiftLeft) + case token.Shr: + c.emit(node, OpBShiftRight) + default: + return c.errorf(node, "invalid binary operator: %s", node.Token.String()) + } + + case *ast.IntLit: + c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value})) + + case *ast.FloatLit: + c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value})) + + case *ast.BoolLit: + if node.Value { + c.emit(node, OpTrue) + } else { + c.emit(node, OpFalse) + } + + case *ast.StringLit: + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value})) + + case *ast.CharLit: + c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value})) + + case *ast.UndefinedLit: + c.emit(node, OpNull) + + case *ast.UnaryExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + switch node.Token { + case token.Not: + c.emit(node, OpLNot) + case token.Sub: + c.emit(node, OpMinus) + case token.Xor: + c.emit(node, OpBComplement) + case token.Add: + // do nothing? + default: + return c.errorf(node, "invalid unary operator: %s", node.Token.String()) + } + + case *ast.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, OpJumpFalsy, 0) + + if err := c.Compile(node.Body); err != nil { + return err + } + + if node.Else != nil { + // second jump placeholder + jumpPos2 := c.emit(node, 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 *ast.ForStmt: + return c.compileForStmt(node) + + case *ast.ForInStmt: + return c.compileForInStmt(node) + + case *ast.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, 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, OpJump, 0) + curLoop.Continues = append(curLoop.Continues, pos) + } else { + panic(fmt.Errorf("invalid branch statement: %s", node.Token.String())) + } + + case *ast.BlockStmt: + for _, stmt := range node.Stmts { + if err := c.Compile(stmt); err != nil { + return err + } + } + + case *ast.AssignStmt: + if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil { + return err + } + + case *ast.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, OpGetGlobal, symbol.Index) + case ScopeLocal: + c.emit(node, OpGetLocal, symbol.Index) + case ScopeBuiltin: + c.emit(node, OpGetBuiltin, symbol.Index) + case ScopeFree: + c.emit(node, OpGetFree, symbol.Index) + } + + case *ast.ArrayLit: + for _, elem := range node.Elements { + if err := c.Compile(elem); err != nil { + return err + } + } + + c.emit(node, OpArray, len(node.Elements)) + + case *ast.MapLit: + for _, elt := range node.Elements { + // key + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key})) + + // value + if err := c.Compile(elt.Value); err != nil { + return err + } + } + + c.emit(node, OpMap, len(node.Elements)*2) + + case *ast.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, OpIndex) + + case *ast.IndexExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + if err := c.Compile(node.Index); err != nil { + return err + } + + c.emit(node, OpIndex) + + case *ast.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, OpNull) + } + + if node.High != nil { + if err := c.Compile(node.High); err != nil { + return err + } + } else { + c.emit(node, OpNull) + } + + c.emit(node, OpSliceIndex) + + case *ast.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 + } + + // add OpReturn if function returns nothing + if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) { + c.emit(node, OpReturn) + } + + 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, OpNull) + c.emit(node, OpDefineLocal, s.Index) + + s.LocalAssigned = true + } + + c.emit(node, OpGetLocal, s.Index) + case ScopeFree: + c.emit(node, OpGetFree, s.Index) + } + } + + compiledFunction := &objects.CompiledFunction{ + Instructions: instructions, + NumLocals: numLocals, + NumParameters: len(node.Type.Params.List), + SourceMap: sourceMap, + } + + if len(freeSymbols) > 0 { + c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols)) + } else { + c.emit(node, OpConstant, c.addConstant(compiledFunction)) + } + + case *ast.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, OpReturn) + } else { + if err := c.Compile(node.Result); err != nil { + return err + } + + c.emit(node, OpReturnValue) + } + + case *ast.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, OpCall, len(node.Args)) + + case *ast.ImportExpr: + if c.builtinModules[node.ModuleName] { + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName})) + c.emit(node, OpGetBuiltinModule) + } else { + userMod, err := c.compileModule(node) + if err != nil { + return err + } + + c.emit(node, OpConstant, c.addConstant(userMod)) + c.emit(node, OpCall, 0) + } + + case *ast.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, OpImmutable) + c.emit(node, OpReturnValue) + + case *ast.ErrorExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + c.emit(node, OpError) + + case *ast.ImmutableExpr: + if err := c.Compile(node.Expr); err != nil { + return err + } + + c.emit(node, OpImmutable) + + case *ast.CondExpr: + if err := c.Compile(node.Cond); err != nil { + return err + } + + // first jump placeholder + jumpPos1 := c.emit(node, OpJumpFalsy, 0) + + if err := c.Compile(node.True); err != nil { + return err + } + + // second jump placeholder + jumpPos2 := c.emit(node, 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: &objects.CompiledFunction{ + Instructions: c.currentInstructions(), + SourceMap: c.currentSourceMap(), + }, + Constants: c.constants, + } +} + +// SetModuleLoader sets or replaces the current module loader. +// Note that the module loader is used for user modules, +// not for the standard modules. +func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { + c.moduleLoader = moduleLoader +} + +func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { + child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) + child.moduleName = moduleName // name of the module to compile + child.parent = c // parent to set to current compiler + child.moduleLoader = c.moduleLoader // share module loader + + return child +} + +func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error { + return &Error{ + fileSet: c.file.Set(), + node: node, + error: fmt.Errorf(format, args...), + } +} + +func (c *Compiler) addConstant(o objects.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) setLastInstruction(op Opcode, pos int) { + c.scopes[c.scopeIndex].lastInstructions[1] = c.scopes[c.scopeIndex].lastInstructions[0] + + c.scopes[c.scopeIndex].lastInstructions[0].Opcode = op + c.scopes[c.scopeIndex].lastInstructions[0].Position = pos +} + +func (c *Compiler) lastInstructionIs(op Opcode) bool { + if len(c.currentInstructions()) == 0 { + return false + } + + return c.scopes[c.scopeIndex].lastInstructions[0].Opcode == op +} + +func (c *Compiler) removeLastInstruction() { + lastPos := c.scopes[c.scopeIndex].lastInstructions[0].Position + + if c.trace != nil { + c.printTrace(fmt.Sprintf("DELET %s", + FormatInstructions(c.scopes[c.scopeIndex].instructions[lastPos:], lastPos)[0])) + } + + c.scopes[c.scopeIndex].instructions = c.currentInstructions()[:lastPos] + c.scopes[c.scopeIndex].lastInstructions[0] = c.scopes[c.scopeIndex].lastInstructions[1] +} + +func (c *Compiler) replaceInstruction(pos int, inst []byte) { + copy(c.currentInstructions()[pos:], inst) + + 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 := Opcode(c.currentInstructions()[opPos]) + inst := MakeInstruction(op, operand...) + + c.replaceInstruction(opPos, inst) +} + +func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { + filePos := source.NoPos + if node != nil { + filePos = node.Pos() + } + + inst := MakeInstruction(opcode, operands...) + pos := c.addInstruction(inst) + c.scopes[c.scopeIndex].sourceMap[pos] = filePos + c.setLastInstruction(opcode, pos) + + if c.trace != nil { + c.printTrace(fmt.Sprintf("EMIT %s", + 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 trace(c *Compiler, msg string) *Compiler { + c.printTrace(msg, "{") + c.indent++ + + return c +} + +func un(c *Compiler) { + c.indent-- + c.printTrace("}") +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_assign.go b/vendor/github.com/d5/tengo/compiler/compiler_assign.go new file mode 100644 index 00000000..0e086c83 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_assign.go @@ -0,0 +1,133 @@ +package compiler + +import ( + "fmt" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/token" +) + +func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.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, OpAdd) + case token.SubAssign: + c.emit(node, OpSub) + case token.MulAssign: + c.emit(node, OpMul) + case token.QuoAssign: + c.emit(node, OpDiv) + case token.RemAssign: + c.emit(node, OpRem) + case token.AndAssign: + c.emit(node, OpBAnd) + case token.OrAssign: + c.emit(node, OpBOr) + case token.AndNotAssign: + c.emit(node, OpBAndNot) + case token.XorAssign: + c.emit(node, OpBXor) + case token.ShlAssign: + c.emit(node, OpBShiftLeft) + case token.ShrAssign: + c.emit(node, OpBShiftRight) + } + + // 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, OpSetSelGlobal, symbol.Index, numSel) + } else { + c.emit(node, OpSetGlobal, symbol.Index) + } + case ScopeLocal: + if numSel > 0 { + c.emit(node, OpSetSelLocal, symbol.Index, numSel) + } else { + if op == token.Define && !symbol.LocalAssigned { + c.emit(node, OpDefineLocal, symbol.Index) + } else { + c.emit(node, OpSetLocal, symbol.Index) + } + } + + // mark the symbol as local-assigned + symbol.LocalAssigned = true + case ScopeFree: + if numSel > 0 { + c.emit(node, OpSetSelFree, symbol.Index, numSel) + } else { + c.emit(node, OpSetFree, symbol.Index) + } + default: + panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope)) + } + + return nil +} + +func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) { + switch term := expr.(type) { + case *ast.SelectorExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Sel) + return + + case *ast.IndexExpr: + name, selectors = resolveAssignLHS(term.Expr) + selectors = append(selectors, term.Index) + + case *ast.Ident: + name = term.Name + } + + return +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_for.go b/vendor/github.com/d5/tengo/compiler/compiler_for.go new file mode 100644 index 00000000..e7b7b5f1 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_for.go @@ -0,0 +1,181 @@ +package compiler + +import ( + "github.com/d5/tengo/compiler/ast" +) + +func (c *Compiler) compileForStmt(stmt *ast.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, 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, 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 *ast.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, OpIteratorInit) + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, OpSetGlobal, itSymbol.Index) + } else { + c.emit(stmt, OpDefineLocal, itSymbol.Index) + } + + // pre-condition position + preCondPos := len(c.currentInstructions()) + + // condition + // :it.HasMore() + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, OpGetLocal, itSymbol.Index) + } + c.emit(stmt, OpIteratorNext) + + // condition jump position + postCondPos := c.emit(stmt, 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, OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, OpGetLocal, itSymbol.Index) + } + c.emit(stmt, OpIteratorKey) + if keySymbol.Scope == ScopeGlobal { + c.emit(stmt, OpSetGlobal, keySymbol.Index) + } else { + c.emit(stmt, OpDefineLocal, keySymbol.Index) + } + } + + // assign value variable + if stmt.Value.Name != "_" { + valueSymbol := c.symbolTable.Define(stmt.Value.Name) + if itSymbol.Scope == ScopeGlobal { + c.emit(stmt, OpGetGlobal, itSymbol.Index) + } else { + c.emit(stmt, OpGetLocal, itSymbol.Index) + } + c.emit(stmt, OpIteratorValue) + if valueSymbol.Scope == ScopeGlobal { + c.emit(stmt, OpSetGlobal, valueSymbol.Index) + } else { + c.emit(stmt, 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, 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 +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_logical.go b/vendor/github.com/d5/tengo/compiler/compiler_logical.go new file mode 100644 index 00000000..68c96759 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_logical.go @@ -0,0 +1,30 @@ +package compiler + +import ( + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/token" +) + +func (c *Compiler) compileLogical(node *ast.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, OpAndJump, 0) + } else { + jumpPos = c.emit(node, OpOrJump, 0) + } + + // right side term + if err := c.Compile(node.RHS); err != nil { + return err + } + + c.changeOperand(jumpPos, len(c.currentInstructions())) + + return nil +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_loops.go b/vendor/github.com/d5/tengo/compiler/compiler_loops.go new file mode 100644 index 00000000..0659ce73 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_loops.go @@ -0,0 +1,31 @@ +package compiler + +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 +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_module.go b/vendor/github.com/d5/tengo/compiler/compiler_module.go new file mode 100644 index 00000000..8f63abb3 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_module.go @@ -0,0 +1,123 @@ +package compiler + +import ( + "io/ioutil" + "strings" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/parser" + "github.com/d5/tengo/objects" +) + +func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { + compiledModule, exists := c.loadCompiledModule(expr.ModuleName) + if exists { + return compiledModule, nil + } + + moduleName := expr.ModuleName + + // read module source from loader + var moduleSrc []byte + if c.moduleLoader == nil { + // default loader: read from local file + if !strings.HasSuffix(moduleName, ".tengo") { + moduleName += ".tengo" + } + + if err := c.checkCyclicImports(expr, moduleName); err != nil { + return nil, err + } + + var err error + moduleSrc, err = ioutil.ReadFile(moduleName) + if err != nil { + return nil, c.errorf(expr, "module file read error: %s", err.Error()) + } + } else { + if err := c.checkCyclicImports(expr, moduleName); err != nil { + return nil, err + } + + var err error + moduleSrc, err = c.moduleLoader(moduleName) + if err != nil { + return nil, err + } + } + + compiledModule, err := c.doCompileModule(moduleName, moduleSrc) + if err != nil { + return nil, err + } + + c.storeCompiledModule(moduleName, compiledModule) + + return compiledModule, nil +} + +func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error { + if c.moduleName == moduleName { + return c.errorf(node, "cyclic module import: %s", moduleName) + } else if c.parent != nil { + return c.parent.checkCyclicImports(node, moduleName) + } + + return nil +} + +func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { + modFile := c.file.Set().AddFile(moduleName, -1, len(src)) + p := parser.NewParser(modFile, src, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + symbolTable := NewSymbolTable() + + // inherit builtin functions + for idx, fn := range objects.Builtins { + s, _, ok := c.symbolTable.Resolve(fn.Name) + if ok && s.Scope == ScopeBuiltin { + symbolTable.DefineBuiltin(idx, fn.Name) + } + } + + // no global scope for the module + symbolTable = symbolTable.Fork(false) + + // compile module + moduleCompiler := c.fork(modFile, moduleName, symbolTable) + if err := moduleCompiler.Compile(file); err != nil { + return nil, err + } + + // add OpReturn (== export undefined) if export is missing + if !moduleCompiler.lastInstructionIs(OpReturnValue) { + moduleCompiler.emit(nil, OpReturn) + } + + compiledFunc := moduleCompiler.Bytecode().MainFunction + compiledFunc.NumLocals = symbolTable.MaxSymbols() + + return compiledFunc, nil +} + +func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) { + if c.parent != nil { + return c.parent.loadCompiledModule(moduleName) + } + + mod, ok = c.compiledModules[moduleName] + + return +} + +func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) { + if c.parent != nil { + c.parent.storeCompiledModule(moduleName, module) + } + + c.compiledModules[moduleName] = module +} diff --git a/vendor/github.com/d5/tengo/compiler/compiler_scopes.go b/vendor/github.com/d5/tengo/compiler/compiler_scopes.go new file mode 100644 index 00000000..b63f915a --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/compiler_scopes.go @@ -0,0 +1,43 @@ +package compiler + +import "github.com/d5/tengo/compiler/source" + +func (c *Compiler) currentInstructions() []byte { + return c.scopes[c.scopeIndex].instructions +} + +func (c *Compiler) currentSourceMap() map[int]source.Pos { + return c.scopes[c.scopeIndex].sourceMap +} + +func (c *Compiler) enterScope() { + scope := CompilationScope{ + symbolInit: make(map[string]bool), + sourceMap: make(map[int]source.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]source.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 +} diff --git a/vendor/github.com/d5/tengo/compiler/emitted_instruction.go b/vendor/github.com/d5/tengo/compiler/emitted_instruction.go new file mode 100644 index 00000000..8572fb0a --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/emitted_instruction.go @@ -0,0 +1,8 @@ +package compiler + +// EmittedInstruction represents an opcode +// with its emitted position. +type EmittedInstruction struct { + Opcode Opcode + Position int +} diff --git a/vendor/github.com/d5/tengo/compiler/error.go b/vendor/github.com/d5/tengo/compiler/error.go new file mode 100644 index 00000000..6ac33d42 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/error.go @@ -0,0 +1,20 @@ +package compiler + +import ( + "fmt" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// Error represents a compiler error. +type Error struct { + fileSet *source.FileSet + node ast.Node + error error +} + +func (e *Error) Error() string { + filePos := e.fileSet.Position(e.node.Pos()) + return fmt.Sprintf("Compile Error: %s\n\tat %s", e.error.Error(), filePos) +} diff --git a/vendor/github.com/d5/tengo/compiler/instructions.go b/vendor/github.com/d5/tengo/compiler/instructions.go new file mode 100644 index 00000000..b04b2826 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/instructions.go @@ -0,0 +1,59 @@ +package compiler + +import ( + "fmt" +) + +// MakeInstruction returns a bytecode for an opcode and the operands. +func MakeInstruction(opcode Opcode, operands ...int) []byte { + numOperands := OpcodeOperands[opcode] + + totalLen := 1 + for _, w := range numOperands { + totalLen += w + } + + instruction := make([]byte, totalLen, totalLen) + instruction[0] = byte(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 := OpcodeOperands[Opcode(b[i])] + operands, read := ReadOperands(numOperands, b[i+1:]) + + switch len(numOperands) { + case 0: + out = append(out, fmt.Sprintf("%04d %-7s", posOffset+i, OpcodeNames[Opcode(b[i])])) + case 1: + out = append(out, fmt.Sprintf("%04d %-7s %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0])) + case 2: + out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0], operands[1])) + } + + i += 1 + read + } + + return out +} diff --git a/vendor/github.com/d5/tengo/compiler/loop.go b/vendor/github.com/d5/tengo/compiler/loop.go new file mode 100644 index 00000000..e27cb096 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/loop.go @@ -0,0 +1,8 @@ +package compiler + +// Loop represents a loop construct that +// the compiler uses to track the current loop. +type Loop struct { + Continues []int + Breaks []int +} diff --git a/vendor/github.com/d5/tengo/compiler/module_loader.go b/vendor/github.com/d5/tengo/compiler/module_loader.go new file mode 100644 index 00000000..b050474d --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/module_loader.go @@ -0,0 +1,4 @@ +package compiler + +// ModuleLoader should take a module name and return the module data. +type ModuleLoader func(moduleName string) ([]byte, error) diff --git a/vendor/github.com/d5/tengo/compiler/opcodes.go b/vendor/github.com/d5/tengo/compiler/opcodes.go new file mode 100644 index 00000000..d4cf1ba2 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/opcodes.go @@ -0,0 +1,191 @@ +package compiler + +// Opcode represents a single byte operation code. +type Opcode = byte + +// List of opcodes +const ( + OpConstant Opcode = iota // Load constant + OpAdd // Add + OpSub // Sub + OpMul // Multiply + OpDiv // Divide + OpRem // Remainder + OpBAnd // bitwise AND + OpBOr // bitwise OR + OpBXor // bitwise XOR + OpBShiftLeft // bitwise shift left + OpBShiftRight // bitwise shift right + OpBAndNot // bitwise AND NOT + OpBComplement // bitwise complement + OpPop // Pop + OpTrue // Push true + OpFalse // Push false + OpEqual // Equal == + OpNotEqual // Not equal != + OpGreaterThan // Greater than >= + OpGreaterThanEqual // Greater than or equal to >= + OpMinus // Minus - + OpLNot // Logical not ! + OpJumpFalsy // Jump if falsy + OpAndJump // Logical AND jump + OpOrJump // Logical OR jump + OpJump // Jump + OpNull // Push null + OpArray // Array object + OpMap // Map object + OpError // Error object + OpImmutable // Immutable object + OpIndex // Index operation + OpSliceIndex // Slice operation + OpCall // Call function + OpReturn // Return + OpReturnValue // Return value + OpGetGlobal // Get global variable + OpSetGlobal // Set global variable + OpSetSelGlobal // Set global variable using selectors + OpGetLocal // Get local variable + OpSetLocal // Set local variable + OpDefineLocal // Define local variable + OpSetSelLocal // Set local variable using selectors + OpGetFree // Get free variables + OpSetFree // Set free variables + OpSetSelFree // Set free variables using selectors + OpGetBuiltin // Get builtin function + OpGetBuiltinModule // Get builtin module + OpClosure // Push closure + OpIteratorInit // Iterator init + OpIteratorNext // Iterator next + OpIteratorKey // Iterator key + OpIteratorValue // Iterator value +) + +// OpcodeNames is opcode names. +var OpcodeNames = [...]string{ + OpConstant: "CONST", + OpPop: "POP", + OpTrue: "TRUE", + OpFalse: "FALSE", + OpAdd: "ADD", + OpSub: "SUB", + OpMul: "MUL", + OpDiv: "DIV", + OpRem: "REM", + OpBAnd: "AND", + OpBOr: "OR", + OpBXor: "XOR", + OpBAndNot: "ANDN", + OpBShiftLeft: "SHL", + OpBShiftRight: "SHR", + OpBComplement: "NEG", + OpEqual: "EQL", + OpNotEqual: "NEQ", + OpGreaterThan: "GTR", + OpGreaterThanEqual: "GEQ", + OpMinus: "NEG", + OpLNot: "NOT", + OpJumpFalsy: "JMPF", + OpAndJump: "ANDJMP", + OpOrJump: "ORJMP", + OpJump: "JMP", + OpNull: "NULL", + OpGetGlobal: "GETG", + OpSetGlobal: "SETG", + OpSetSelGlobal: "SETSG", + OpArray: "ARR", + OpMap: "MAP", + OpError: "ERROR", + OpImmutable: "IMMUT", + OpIndex: "INDEX", + OpSliceIndex: "SLICE", + OpCall: "CALL", + OpReturn: "RET", + OpReturnValue: "RETVAL", + OpGetLocal: "GETL", + OpSetLocal: "SETL", + OpDefineLocal: "DEFL", + OpSetSelLocal: "SETSL", + OpGetBuiltin: "BUILTIN", + OpGetBuiltinModule: "BLTMOD", + OpClosure: "CLOSURE", + OpGetFree: "GETF", + OpSetFree: "SETF", + OpSetSelFree: "SETSF", + OpIteratorInit: "ITER", + OpIteratorNext: "ITNXT", + OpIteratorKey: "ITKEY", + OpIteratorValue: "ITVAL", +} + +// OpcodeOperands is the number of operands. +var OpcodeOperands = [...][]int{ + OpConstant: {2}, + OpPop: {}, + OpTrue: {}, + OpFalse: {}, + OpAdd: {}, + OpSub: {}, + OpMul: {}, + OpDiv: {}, + OpRem: {}, + OpBAnd: {}, + OpBOr: {}, + OpBXor: {}, + OpBAndNot: {}, + OpBShiftLeft: {}, + OpBShiftRight: {}, + OpBComplement: {}, + OpEqual: {}, + OpNotEqual: {}, + OpGreaterThan: {}, + OpGreaterThanEqual: {}, + OpMinus: {}, + OpLNot: {}, + OpJumpFalsy: {2}, + OpAndJump: {2}, + OpOrJump: {2}, + OpJump: {2}, + OpNull: {}, + OpGetGlobal: {2}, + OpSetGlobal: {2}, + OpSetSelGlobal: {2, 1}, + OpArray: {2}, + OpMap: {2}, + OpError: {}, + OpImmutable: {}, + OpIndex: {}, + OpSliceIndex: {}, + OpCall: {1}, + OpReturn: {}, + OpReturnValue: {}, + OpGetLocal: {1}, + OpSetLocal: {1}, + OpDefineLocal: {1}, + OpSetSelLocal: {1, 1}, + OpGetBuiltin: {1}, + OpGetBuiltinModule: {}, + OpClosure: {2, 1}, + OpGetFree: {1}, + OpSetFree: {1}, + OpSetSelFree: {1, 1}, + OpIteratorInit: {}, + OpIteratorNext: {}, + OpIteratorKey: {}, + OpIteratorValue: {}, +} + +// 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/compiler/parser/error.go b/vendor/github.com/d5/tengo/compiler/parser/error.go new file mode 100644 index 00000000..80544fbd --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/error.go @@ -0,0 +1,21 @@ +package parser + +import ( + "fmt" + + "github.com/d5/tengo/compiler/source" +) + +// Error represents a parser error. +type Error struct { + Pos source.FilePos + 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) +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/error_list.go b/vendor/github.com/d5/tengo/compiler/parser/error_list.go new file mode 100644 index 00000000..599eaf7d --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/error_list.go @@ -0,0 +1,68 @@ +package parser + +import ( + "fmt" + "sort" + + "github.com/d5/tengo/compiler/source" +) + +// ErrorList is a collection of parser errors. +type ErrorList []*Error + +// Add adds a new parser error to the collection. +func (p *ErrorList) Add(pos source.FilePos, 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 +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parse_file.go b/vendor/github.com/d5/tengo/compiler/parser/parse_file.go new file mode 100644 index 00000000..0482c775 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parse_file.go @@ -0,0 +1,28 @@ +package parser + +import ( + "io" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// ParseFile parses a file with a given src. +func ParseFile(file *source.File, src []byte, trace io.Writer) (res *ast.File, err error) { + p := NewParser(file, src, trace) + + defer func() { + if e := recover(); e != nil { + if _, ok := e.(bailout); !ok { + panic(e) + } + } + + p.errors.Sort() + err = p.errors.Err() + }() + + res, err = p.ParseFile() + + return +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parse_source.go b/vendor/github.com/d5/tengo/compiler/parser/parse_source.go new file mode 100644 index 00000000..5d242db4 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parse_source.go @@ -0,0 +1,16 @@ +package parser + +import ( + "io" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// ParseSource parses source code 'src' and builds an AST. +func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) { + fileSet := source.NewFileSet() + file := fileSet.AddFile(filename, -1, len(src)) + + return ParseFile(file, src, trace) +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parser.go b/vendor/github.com/d5/tengo/compiler/parser/parser.go new file mode 100644 index 00000000..93f04f7c --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parser.go @@ -0,0 +1,1181 @@ +/* + Parser parses the Tengo source files. + + Parser is a modified version of Go's parser implementation. + + Copyright 2009 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 parser + +import ( + "fmt" + "io" + "strconv" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/scanner" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +type bailout struct{} + +// Parser parses the Tengo source files. +type Parser struct { + file *source.File + errors ErrorList + scanner *scanner.Scanner + pos source.Pos + token token.Token + tokenLit string + exprLevel int // < 0: in control clause, >= 0: in expression + syncPos source.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 *source.File, src []byte, trace io.Writer) *Parser { + p := &Parser{ + file: file, + trace: trace != nil, + traceOut: trace, + } + + p.scanner = scanner.NewScanner(p.file, src, func(pos source.FilePos, 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() (*ast.File, error) { + if p.trace { + defer un(trace(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() + } + + return &ast.File{ + InputFile: p.file, + Stmts: stmts, + }, nil +} + +func (p *Parser) parseExpr() ast.Expr { + if p.trace { + defer un(trace(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) ast.Expr { + if p.trace { + defer un(trace(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 = &ast.BinaryExpr{ + LHS: x, + RHS: y, + Token: op, + TokenPos: pos, + } + } +} + +func (p *Parser) parseCondExpr(cond ast.Expr) ast.Expr { + questionPos := p.expect(token.Question) + + trueExpr := p.parseExpr() + + colonPos := p.expect(token.Colon) + + falseExpr := p.parseExpr() + + return &ast.CondExpr{ + Cond: cond, + True: trueExpr, + False: falseExpr, + QuestionPos: questionPos, + ColonPos: colonPos, + } +} + +func (p *Parser) parseUnaryExpr() ast.Expr { + if p.trace { + defer un(trace(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 &ast.UnaryExpr{ + Token: op, + TokenPos: pos, + Expr: x, + } + } + + return p.parsePrimaryExpr() +} + +func (p *Parser) parsePrimaryExpr() ast.Expr { + if p.trace { + defer un(trace(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 &ast.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 ast.Expr) *ast.CallExpr { + if p.trace { + defer un(trace(p, "Call")) + } + + lparen := p.expect(token.LParen) + p.exprLevel++ + + var list []ast.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 &ast.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 ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "IndexOrSlice")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var index [2]ast.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 &ast.SliceExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Low: index[0], + High: index[1], + } + } + + return &ast.IndexExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Index: index[0], + } +} + +func (p *Parser) parseSelector(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "Selector")) + } + + sel := p.parseIdent() + + return &ast.SelectorExpr{Expr: x, Sel: &ast.StringLit{ + Value: sel.Name, + ValuePos: sel.NamePos, + Literal: sel.Name, + }} +} + +func (p *Parser) parseOperand() ast.Expr { + if p.trace { + defer un(trace(p, "Operand")) + } + + switch p.token { + case token.Ident: + return p.parseIdent() + + case token.Int: + v, _ := strconv.ParseInt(p.tokenLit, 10, 64) + x := &ast.IntLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.Float: + v, _ := strconv.ParseFloat(p.tokenLit, 64) + x := &ast.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 := &ast.StringLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.True: + x := &ast.BoolLit{ + Value: true, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.False: + x := &ast.BoolLit{ + Value: false, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.Undefined: + x := &ast.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 &ast.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 &ast.BadExpr{From: pos, To: p.pos} +} + +func (p *Parser) parseImportExpr() ast.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 &ast.BadExpr{From: pos, To: p.pos} + } + + // module name + moduleName, _ := strconv.Unquote(p.tokenLit) + + expr := &ast.ImportExpr{ + ModuleName: moduleName, + Token: token.Import, + TokenPos: pos, + } + + p.next() + + p.expect(token.RParen) + + return expr +} + +func (p *Parser) parseCharLit() ast.Expr { + if n := len(p.tokenLit); n >= 3 { + if code, _, _, err := strconv.UnquoteChar(p.tokenLit[1:n-1], '\''); err == nil { + x := &ast.CharLit{ + Value: rune(code), + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + } + } + + pos := p.pos + p.error(pos, "illegal char literal") + p.next() + return &ast.BadExpr{ + From: pos, + To: p.pos, + } +} + +func (p *Parser) parseFuncLit() ast.Expr { + if p.trace { + defer un(trace(p, "FuncLit")) + } + + typ := p.parseFuncType() + + p.exprLevel++ + body := p.parseBody() + p.exprLevel-- + + return &ast.FuncLit{ + Type: typ, + Body: body, + } +} + +func (p *Parser) parseArrayLit() ast.Expr { + if p.trace { + defer un(trace(p, "ArrayLit")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var elements []ast.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 &ast.ArrayLit{ + Elements: elements, + LBrack: lbrack, + RBrack: rbrack, + } +} + +func (p *Parser) parseErrorExpr() ast.Expr { + pos := p.pos + + p.next() + + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + + expr := &ast.ErrorExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } + + return expr +} + +func (p *Parser) parseImmutableExpr() ast.Expr { + pos := p.pos + + p.next() + + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + + expr := &ast.ImmutableExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } + + return expr +} + +func (p *Parser) parseFuncType() *ast.FuncType { + if p.trace { + defer un(trace(p, "FuncType")) + } + + pos := p.expect(token.Func) + params := p.parseIdentList() + + return &ast.FuncType{ + FuncPos: pos, + Params: params, + } +} + +func (p *Parser) parseBody() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "Body")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + + return &ast.BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseStmtList() (list []ast.Stmt) { + if p.trace { + defer un(trace(p, "StatementList")) + } + + for p.token != token.RBrace && p.token != token.EOF { + list = append(list, p.parseStmt()) + } + + return +} + +func (p *Parser) parseIdent() *ast.Ident { + pos := p.pos + name := "_" + + if p.token == token.Ident { + name = p.tokenLit + p.next() + } else { + p.expect(token.Ident) + } + + return &ast.Ident{ + NamePos: pos, + Name: name, + } +} + +func (p *Parser) parseIdentList() *ast.IdentList { + if p.trace { + defer un(trace(p, "IdentList")) + } + + var params []*ast.Ident + lparen := p.expect(token.LParen) + if p.token != token.RParen { + params = append(params, p.parseIdent()) + for p.token == token.Comma { + p.next() + params = append(params, p.parseIdent()) + } + } + rparen := p.expect(token.RParen) + + return &ast.IdentList{ + LParen: lparen, + RParen: rparen, + List: params, + } +} + +func (p *Parser) parseStmt() (stmt ast.Stmt) { + if p.trace { + defer un(trace(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 := &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.tokenLit == "\n"} + p.next() + return s + case token.RBrace: + // semicolon may be omitted before a closing "}" + return &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} + default: + pos := p.pos + p.errorExpected(pos, "statement") + p.advance(stmtStart) + return &ast.BadStmt{From: pos, To: p.pos} + } +} + +func (p *Parser) parseForStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ForStmt")) + } + + pos := p.expect(token.For) + + // for {} + if p.token == token.LBrace { + body := p.parseBlockStmt() + p.expectSemi() + + return &ast.ForStmt{ + ForPos: pos, + Body: body, + } + } + + prevLevel := p.exprLevel + p.exprLevel = -1 + + var s1 ast.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.(*ast.ForInStmt); isForIn { + forInStmt.ForPos = pos + p.exprLevel = prevLevel + forInStmt.Body = p.parseBlockStmt() + p.expectSemi() + return forInStmt + } + + // for init; cond; post {} + var s2, s3 ast.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 &ast.ForStmt{ + ForPos: pos, + Init: s1, + Cond: cond, + Post: s3, + Body: body, + } + +} + +func (p *Parser) parseBranchStmt(tok token.Token) ast.Stmt { + if p.trace { + defer un(trace(p, "BranchStmt")) + } + + pos := p.expect(tok) + + var label *ast.Ident + if p.token == token.Ident { + label = p.parseIdent() + } + p.expectSemi() + + return &ast.BranchStmt{ + Token: tok, + TokenPos: pos, + Label: label, + } +} + +func (p *Parser) parseIfStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "IfStmt")) + } + + pos := p.expect(token.If) + + init, cond := p.parseIfHeader() + body := p.parseBlockStmt() + + var elseStmt ast.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 = &ast.BadStmt{From: p.pos, To: p.pos} + } + } else { + p.expectSemi() + } + + return &ast.IfStmt{ + IfPos: pos, + Init: init, + Cond: cond, + Body: body, + Else: elseStmt, + } +} + +func (p *Parser) parseBlockStmt() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "BlockStmt")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + + return &ast.BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { + if p.token == token.LBrace { + p.error(p.pos, "missing condition in if statement") + cond = &ast.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 ast.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 = &ast.BadExpr{From: p.pos, To: p.pos} + } + + p.exprLevel = outer + + return +} + +func (p *Parser) makeExpr(s ast.Stmt, want string) ast.Expr { + if s == nil { + return nil + } + + if es, isExpr := s.(*ast.ExprStmt); isExpr { + return es.Expr + } + + found := "simple statement" + if _, isAss := s.(*ast.AssignStmt); isAss { + found = "assignment" + } + + p.error(s.Pos(), fmt.Sprintf("expected %s, found %s", want, found)) + + return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} +} + +func (p *Parser) parseReturnStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ReturnStmt")) + } + + pos := p.pos + p.expect(token.Return) + + var x ast.Expr + if p.token != token.Semicolon && p.token != token.RBrace { + x = p.parseExpr() + } + p.expectSemi() + + return &ast.ReturnStmt{ + ReturnPos: pos, + Result: x, + } +} + +func (p *Parser) parseExportStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ExportStmt")) + } + + pos := p.pos + p.expect(token.Export) + + x := p.parseExpr() + p.expectSemi() + + return &ast.ExportStmt{ + ExportPos: pos, + Result: x, + } +} + +func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { + if p.trace { + defer un(trace(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 &ast.AssignStmt{ + LHS: x, + RHS: y, + Token: tok, + TokenPos: pos, + } + case token.In: + if forIn { + p.next() + + y := p.parseExpr() + + var key, value *ast.Ident + var ok bool + + switch len(x) { + case 1: + key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + + value, ok = x[0].(*ast.Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + value = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + } + case 2: + key, ok = x[0].(*ast.Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + } + value, ok = x[1].(*ast.Ident) + if !ok { + p.errorExpected(x[1].Pos(), "identifier") + value = &ast.Ident{Name: "_", NamePos: x[1].Pos()} + } + } + + return &ast.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 &ast.AssignStmt{ + LHS: []ast.Expr{x[0]}, + RHS: []ast.Expr{y}, + Token: tok, + TokenPos: pos, + } + case token.Inc, token.Dec: + // increment or decrement statement + s := &ast.IncDecStmt{Expr: x[0], Token: p.token, TokenPos: p.pos} + p.next() + return s + } + + // expression statement + return &ast.ExprStmt{Expr: x[0]} +} + +func (p *Parser) parseExprList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ExpressionList")) + } + + list = append(list, p.parseExpr()) + for p.token == token.Comma { + p.next() + list = append(list, p.parseExpr()) + } + + return +} + +func (p *Parser) parseMapElementLit() *ast.MapElementLit { + if p.trace { + defer un(trace(p, "MapElementLit")) + } + + // key: read identifier token but it's not actually an identifier + ident := p.parseIdent() + + colonPos := p.expect(token.Colon) + + valueExpr := p.parseExpr() + + return &ast.MapElementLit{ + Key: ident.Name, + KeyPos: ident.NamePos, + ColonPos: colonPos, + Value: valueExpr, + } +} + +func (p *Parser) parseMapLit() *ast.MapLit { + if p.trace { + defer un(trace(p, "MapLit")) + } + + lbrace := p.expect(token.LBrace) + p.exprLevel++ + + var elements []*ast.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 &ast.MapLit{ + LBrace: lbrace, + RBrace: rbrace, + Elements: elements, + } +} + +func (p *Parser) expect(token token.Token) source.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 source.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 source.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 source.Pos) source.Pos { + fileBase := p.file.Base + fileSize := p.file.Size + + if int(pos) < fileBase || int(pos) > fileBase+fileSize { + return source.Pos(fileBase + fileSize) + } + + return pos +} + +func trace(p *Parser, msg string) *Parser { + p.printTrace(msg, "(") + p.indent++ + + return p +} + +func un(p *Parser) { + p.indent-- + p.printTrace(")") +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/sync.go b/vendor/github.com/d5/tengo/compiler/parser/sync.go new file mode 100644 index 00000000..e68d623a --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/sync.go @@ -0,0 +1,12 @@ +package parser + +import "github.com/d5/tengo/compiler/token" + +var stmtStart = map[token.Token]bool{ + token.Break: true, + token.Continue: true, + token.For: true, + token.If: true, + token.Return: true, + token.Export: true, +} diff --git a/vendor/github.com/d5/tengo/compiler/scanner/error_handler.go b/vendor/github.com/d5/tengo/compiler/scanner/error_handler.go new file mode 100644 index 00000000..379f0196 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/scanner/error_handler.go @@ -0,0 +1,6 @@ +package scanner + +import "github.com/d5/tengo/compiler/source" + +// ErrorHandler is an error handler for the scanner. +type ErrorHandler func(pos source.FilePos, msg string) diff --git a/vendor/github.com/d5/tengo/compiler/scanner/mode.go b/vendor/github.com/d5/tengo/compiler/scanner/mode.go new file mode 100644 index 00000000..f67ceaf8 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/scanner/mode.go @@ -0,0 +1,10 @@ +package scanner + +// Mode represents a scanner mode. +type Mode int + +// List of scanner modes. +const ( + ScanComments Mode = 1 << iota + DontInsertSemis +) diff --git a/vendor/github.com/d5/tengo/compiler/scanner/scanner.go b/vendor/github.com/d5/tengo/compiler/scanner/scanner.go new file mode 100644 index 00000000..387cd8ee --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/scanner/scanner.go @@ -0,0 +1,680 @@ +/* + Scanner reads the Tengo source text and tokenize them. + + Scanner is a modified version of Go's scanner implementation. + + Copyright 2009 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 scanner + +import ( + "fmt" + "unicode" + "unicode/utf8" + + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// byte order mark +const bom = 0xFEFF + +// Scanner reads the Tengo source text. +type Scanner struct { + file *source.File // 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 ErrorHandler // error reporting; or nil + errorCount int // number of errors encountered + mode Mode +} + +// NewScanner creates a Scanner. +func NewScanner(file *source.File, src []byte, errorHandler ErrorHandler, mode Mode) *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 source.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/compiler/source/file.go b/vendor/github.com/d5/tengo/compiler/source/file.go new file mode 100644 index 00000000..9e51c9a4 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/source/file.go @@ -0,0 +1,110 @@ +package source + +// File represents a source file. +type File struct { + // File set for the file + set *FileSet + // File name as provided to AddFile + Name string + // Pos value range for this file is [base...base+size] + Base int + // File 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 FileSet. +func (f *File) Set() *FileSet { + return f.set +} + +// LineCount returns the current number of lines. +func (f *File) LineCount() int { + return len(f.Lines) +} + +// AddLine adds a new line. +func (f *File) AddLine(offset int) { + if i := len(f.Lines); (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 *File) 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 *File) 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 *File) Offset(p Pos) int { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal Pos value") + } + + return int(p) - f.Base +} + +// Position translates the file set position into the file position. +func (f *File) Position(p Pos) (pos FilePos) { + if p != NoPos { + if int(p) < f.Base || int(p) > f.Base+f.Size { + panic("illegal Pos value") + } + + pos = f.position(p) + } + + return +} + +func (f *File) position(p Pos) (pos FilePos) { + offset := int(p) - f.Base + pos.Offset = offset + pos.Filename, pos.Line, pos.Column = f.unpack(offset) + + return +} + +func (f *File) 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/compiler/source/file_pos.go b/vendor/github.com/d5/tengo/compiler/source/file_pos.go new file mode 100644 index 00000000..4055fe6d --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/source/file_pos.go @@ -0,0 +1,47 @@ +package source + +import "fmt" + +// FilePos represents a position information in the file. +type FilePos 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 FilePos) 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 FilePos) 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 +} diff --git a/vendor/github.com/d5/tengo/compiler/source/file_set.go b/vendor/github.com/d5/tengo/compiler/source/file_set.go new file mode 100644 index 00000000..da342364 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/source/file_set.go @@ -0,0 +1,96 @@ +package source + +import ( + "sort" +) + +// FileSet represents a set of source files. +type FileSet struct { + Base int // base offset for the next file + Files []*File // list of files in the order added to the set + LastFile *File // cache of last file looked up +} + +// NewFileSet creates a new file set. +func NewFileSet() *FileSet { + return &FileSet{ + Base: 1, // 0 == NoPos + } +} + +// AddFile adds a new file in the file set. +func (s *FileSet) AddFile(filename string, base, size int) *File { + if base < 0 { + base = s.Base + } + if base < s.Base || size < 0 { + panic("illegal base or size") + } + + f := &File{ + 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 *FileSet) File(p Pos) (f *File) { + if p != NoPos { + f = s.file(p) + } + + return +} + +// Position converts a Pos p in the fileset into a FilePos value. +func (s *FileSet) Position(p Pos) (pos FilePos) { + if p != NoPos { + if f := s.file(p); f != nil { + return f.position(p) + } + } + + return +} + +func (s *FileSet) file(p Pos) *File { + // common case: p is in last file + if f := s.LastFile; 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 []*File, x int) int { + return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1 +} diff --git a/vendor/github.com/d5/tengo/compiler/source/pos.go b/vendor/github.com/d5/tengo/compiler/source/pos.go new file mode 100644 index 00000000..72128b13 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/source/pos.go @@ -0,0 +1,12 @@ +package source + +// 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/compiler/symbol.go b/vendor/github.com/d5/tengo/compiler/symbol.go new file mode 100644 index 00000000..bcd53234 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/symbol.go @@ -0,0 +1,9 @@ +package compiler + +// 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 +} diff --git a/vendor/github.com/d5/tengo/compiler/symbol_scopes.go b/vendor/github.com/d5/tengo/compiler/symbol_scopes.go new file mode 100644 index 00000000..15204b35 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/symbol_scopes.go @@ -0,0 +1,12 @@ +package compiler + +// SymbolScope represents a symbol scope. +type SymbolScope string + +// List of symbol scopes +const ( + ScopeGlobal SymbolScope = "GLOBAL" + ScopeLocal = "LOCAL" + ScopeBuiltin = "BUILTIN" + ScopeFree = "FREE" +) diff --git a/vendor/github.com/d5/tengo/compiler/symbol_table.go b/vendor/github.com/d5/tengo/compiler/symbol_table.go new file mode 100644 index 00000000..da55a826 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/symbol_table.go @@ -0,0 +1,145 @@ +package compiler + +// SymbolTable represents a symbol table. +type SymbolTable struct { + parent *SymbolTable + block bool + store map[string]*Symbol + numDefinition int + maxDefinition int + freeSymbols []*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 { + symbol := &Symbol{ + Name: name, + Index: index, + Scope: ScopeBuiltin, + } + + t.store[name] = 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 + } + + if !t.block { + 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 +} + +// 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/compiler/token/keywords.go b/vendor/github.com/d5/tengo/compiler/token/keywords.go new file mode 100644 index 00000000..fd6e9d0b --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/token/keywords.go @@ -0,0 +1,19 @@ +package token + +var keywords map[string]Token + +func init() { + keywords = make(map[string]Token) + for i := _keywordBeg + 1; i < _keywordEnd; i++ { + keywords[tokens[i]] = i + } +} + +// Lookup returns corresponding keyword if ident is a keyword. +func Lookup(ident string) Token { + if tok, isKeyword := keywords[ident]; isKeyword { + return tok + } + + return Ident +} diff --git a/vendor/github.com/d5/tengo/compiler/token/tokens.go b/vendor/github.com/d5/tengo/compiler/token/tokens.go new file mode 100644 index 00000000..b32d36ee --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/token/tokens.go @@ -0,0 +1,208 @@ +package token + +import "strconv" + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/array.go b/vendor/github.com/d5/tengo/objects/array.go new file mode 100644 index 00000000..1e917c59 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/array.go @@ -0,0 +1,130 @@ +package objects + +import ( + "fmt" + "strings" + + "github.com/d5/tengo/compiler/token" +) + +// Array represents an array of objects. +type Array struct { + 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), + } +} diff --git a/vendor/github.com/d5/tengo/objects/array_iterator.go b/vendor/github.com/d5/tengo/objects/array_iterator.go new file mode 100644 index 00000000..204faa41 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/array_iterator.go @@ -0,0 +1,57 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// ArrayIterator is an iterator for an array. +type ArrayIterator struct { + 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 "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (i *ArrayIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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] +} diff --git a/vendor/github.com/d5/tengo/objects/bool.go b/vendor/github.com/d5/tengo/objects/bool.go new file mode 100644 index 00000000..ac9949e4 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/bool.go @@ -0,0 +1,64 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// Bool represents a boolean value. +type Bool struct { + // 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" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/break.go b/vendor/github.com/d5/tengo/objects/break.go new file mode 100644 index 00000000..cd473a87 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/break.go @@ -0,0 +1,37 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// Break represents a break statement. +type Break struct{} + +// TypeName returns the name of the type. +func (o *Break) TypeName() string { + return "break" +} + +func (o *Break) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Break) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Break) Copy() Object { + return &Break{} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Break) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *Break) Equals(x Object) bool { + return false +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_append.go b/vendor/github.com/d5/tengo/objects/builtin_append.go new file mode 100644 index 00000000..9fb14b82 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_append.go @@ -0,0 +1,21 @@ +package objects + +// 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/objects/builtin_convert.go b/vendor/github.com/d5/tengo/objects/builtin_convert.go new file mode 100644 index 00000000..7d9a8733 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_convert.go @@ -0,0 +1,155 @@ +package objects + +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 { + 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 { + return &Bytes{Value: make([]byte, int(n.Value))}, nil + } + + v, ok := ToByteSlice(args[0]) + if ok { + 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 +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_copy.go b/vendor/github.com/d5/tengo/objects/builtin_copy.go new file mode 100644 index 00000000..4b254b2b --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_copy.go @@ -0,0 +1,9 @@ +package objects + +func builtinCopy(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + return args[0].Copy(), nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_function.go b/vendor/github.com/d5/tengo/objects/builtin_function.go new file mode 100644 index 00000000..1d021617 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_function.go @@ -0,0 +1,47 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// BuiltinFunction represents a builtin function. +type BuiltinFunction struct { + 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 "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *BuiltinFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *BuiltinFunction) Copy() Object { + return &BuiltinFunction{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *BuiltinFunction) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *BuiltinFunction) Equals(x Object) bool { + return false +} + +// Call executes a builtin function. +func (o *BuiltinFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_json.go b/vendor/github.com/d5/tengo/objects/builtin_json.go new file mode 100644 index 00000000..c0810f7d --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_json.go @@ -0,0 +1,54 @@ +package objects + +import ( + "encoding/json" +) + +// to_json(v object) => bytes +func builtinToJSON(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + res, err := json.Marshal(objectToInterface(args[0])) + if err != nil { + return &Error{Value: &String{Value: err.Error()}}, nil + } + + return &Bytes{Value: res}, nil +} + +// from_json(data string/bytes) => object +func builtinFromJSON(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + var target interface{} + + switch o := args[0].(type) { + case *Bytes: + err := json.Unmarshal(o.Value, &target) + if err != nil { + return &Error{Value: &String{Value: err.Error()}}, nil + } + case *String: + err := json.Unmarshal([]byte(o.Value), &target) + if err != nil { + return &Error{Value: &String{Value: err.Error()}}, nil + } + default: + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } + + res, err := FromInterface(target) + if err != nil { + return nil, err + } + + return res, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_len.go b/vendor/github.com/d5/tengo/objects/builtin_len.go new file mode 100644 index 00000000..39fbedd8 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_len.go @@ -0,0 +1,29 @@ +package objects + +// 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(), + } + } +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_print.go b/vendor/github.com/d5/tengo/objects/builtin_print.go new file mode 100644 index 00000000..c5fe36db --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_print.go @@ -0,0 +1,75 @@ +package objects + +import ( + "fmt" +) + +// print(args...) +func builtinPrint(args ...Object) (Object, error) { + for _, arg := range args { + if str, ok := arg.(*String); ok { + fmt.Println(str.Value) + } else { + fmt.Println(arg.String()) + } + } + + return nil, nil +} + +// printf("format", args...) +func builtinPrintf(args ...Object) (Object, error) { + numArgs := len(args) + if numArgs == 0 { + return nil, ErrWrongNumArguments + } + + format, ok := args[0].(*String) + if !ok { + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + fmt.Print(format) + return nil, nil + } + + formatArgs := make([]interface{}, numArgs-1, numArgs-1) + for idx, arg := range args[1:] { + formatArgs[idx] = objectToInterface(arg) + } + + fmt.Printf(format.Value, formatArgs...) + + return nil, nil +} + +// sprintf("format", args...) +func builtinSprintf(args ...Object) (Object, error) { + numArgs := len(args) + if numArgs == 0 { + return nil, ErrWrongNumArguments + } + + format, ok := args[0].(*String) + if !ok { + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } + } + if numArgs == 1 { + return format, nil // okay to return 'format' directly as String is immutable + } + + formatArgs := make([]interface{}, numArgs-1, numArgs-1) + for idx, arg := range args[1:] { + formatArgs[idx] = objectToInterface(arg) + } + + return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_type.go b/vendor/github.com/d5/tengo/objects/builtin_type.go new file mode 100644 index 00000000..376c26bb --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_type.go @@ -0,0 +1,9 @@ +package objects + +func builtinTypeName(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + return &String{Value: args[0].TypeName()}, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtin_type_checks.go b/vendor/github.com/d5/tengo/objects/builtin_type_checks.go new file mode 100644 index 00000000..960f7828 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtin_type_checks.go @@ -0,0 +1,183 @@ +package objects + +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, *Closure: + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsCallable(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + switch args[0].(type) { + case *CompiledFunction, *Closure, Callable: // BuiltinFunction is Callable + return TrueValue, nil + } + + return FalseValue, nil +} diff --git a/vendor/github.com/d5/tengo/objects/builtins.go b/vendor/github.com/d5/tengo/objects/builtins.go new file mode 100644 index 00000000..67553932 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/builtins.go @@ -0,0 +1,135 @@ +package objects + +// NamedBuiltinFunc is a named builtin function. +type NamedBuiltinFunc struct { + Name string + Func CallableFunc +} + +// Builtins contains all default builtin functions. +var Builtins = []NamedBuiltinFunc{ + { + Name: "print", + Func: builtinPrint, + }, + { + Name: "printf", + Func: builtinPrintf, + }, + { + Name: "sprintf", + Func: builtinSprintf, + }, + { + Name: "len", + Func: builtinLen, + }, + { + Name: "copy", + Func: builtinCopy, + }, + { + Name: "append", + Func: builtinAppend, + }, + { + Name: "string", + Func: builtinString, + }, + { + Name: "int", + Func: builtinInt, + }, + { + Name: "bool", + Func: builtinBool, + }, + { + Name: "float", + Func: builtinFloat, + }, + { + Name: "char", + Func: builtinChar, + }, + { + Name: "bytes", + Func: builtinBytes, + }, + { + Name: "time", + Func: builtinTime, + }, + { + Name: "is_int", + Func: builtinIsInt, + }, + { + Name: "is_float", + Func: builtinIsFloat, + }, + { + Name: "is_string", + Func: builtinIsString, + }, + { + Name: "is_bool", + Func: builtinIsBool, + }, + { + Name: "is_char", + Func: builtinIsChar, + }, + { + Name: "is_bytes", + Func: builtinIsBytes, + }, + { + Name: "is_array", + Func: builtinIsArray, + }, + { + Name: "is_immutable_array", + Func: builtinIsImmutableArray, + }, + { + Name: "is_map", + Func: builtinIsMap, + }, + { + Name: "is_immutable_map", + Func: builtinIsImmutableMap, + }, + { + Name: "is_time", + Func: builtinIsTime, + }, + { + Name: "is_error", + Func: builtinIsError, + }, + { + Name: "is_undefined", + Func: builtinIsUndefined, + }, + { + Name: "is_function", + Func: builtinIsFunction, + }, + { + Name: "is_callable", + Func: builtinIsCallable, + }, + { + Name: "to_json", + Func: builtinToJSON, + }, + { + Name: "from_json", + Func: builtinFromJSON, + }, + { + Name: "type_name", + Func: builtinTypeName, + }, +} diff --git a/vendor/github.com/d5/tengo/objects/bytes.go b/vendor/github.com/d5/tengo/objects/bytes.go new file mode 100644 index 00000000..7d8d6694 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/bytes.go @@ -0,0 +1,76 @@ +package objects + +import ( + "bytes" + + "github.com/d5/tengo/compiler/token" +) + +// Bytes represents a byte array. +type Bytes struct { + 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: + 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.Compare(o.Value, t.Value) == 0 +} + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/callable.go b/vendor/github.com/d5/tengo/objects/callable.go new file mode 100644 index 00000000..a066e1b9 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/callable.go @@ -0,0 +1,9 @@ +package objects + +// Callable represents an object that can be called like a function. +type Callable interface { + // 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) +} diff --git a/vendor/github.com/d5/tengo/objects/callable_func.go b/vendor/github.com/d5/tengo/objects/callable_func.go new file mode 100644 index 00000000..cf9b43aa --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/callable_func.go @@ -0,0 +1,4 @@ +package objects + +// CallableFunc is a function signature for the callable functions. +type CallableFunc func(args ...Object) (ret Object, err error) diff --git a/vendor/github.com/d5/tengo/objects/char.go b/vendor/github.com/d5/tengo/objects/char.go new file mode 100644 index 00000000..4458bd12 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/char.go @@ -0,0 +1,119 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// Char represents a character value. +type Char struct { + 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 +} diff --git a/vendor/github.com/d5/tengo/objects/closure.go b/vendor/github.com/d5/tengo/objects/closure.go new file mode 100644 index 00000000..d4915a52 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/closure.go @@ -0,0 +1,45 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// Closure represents a function closure. +type Closure struct { + Fn *CompiledFunction + Free []*Object +} + +// TypeName returns the name of the type. +func (o *Closure) TypeName() string { + return "closure" +} + +func (o *Closure) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Closure) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Closure) Copy() Object { + return &Closure{ + Fn: o.Fn.Copy().(*CompiledFunction), + Free: append([]*Object{}, o.Free...), // DO NOT Copy() of elements; these are variable pointers + } +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Closure) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *Closure) Equals(x Object) bool { + return false +} diff --git a/vendor/github.com/d5/tengo/objects/compiled_function.go b/vendor/github.com/d5/tengo/objects/compiled_function.go new file mode 100644 index 00000000..d20f2375 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/compiled_function.go @@ -0,0 +1,49 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +// CompiledFunction represents a compiled function. +type CompiledFunction struct { + Instructions []byte + NumLocals int // number of local variables (including function parameters) + NumParameters int + SourceMap map[int]source.Pos +} + +// TypeName returns the name of the type. +func (o *CompiledFunction) TypeName() string { + return "compiled-function" +} + +func (o *CompiledFunction) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *CompiledFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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, + } +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *CompiledFunction) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *CompiledFunction) Equals(x Object) bool { + return false +} diff --git a/vendor/github.com/d5/tengo/objects/continue.go b/vendor/github.com/d5/tengo/objects/continue.go new file mode 100644 index 00000000..8094e686 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/continue.go @@ -0,0 +1,38 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// Continue represents a continue statement. +type Continue struct { +} + +// TypeName returns the name of the type. +func (o *Continue) TypeName() string { + return "continue" +} + +func (o *Continue) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Continue) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Continue) Copy() Object { + return &Continue{} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Continue) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *Continue) Equals(x Object) bool { + return false +} diff --git a/vendor/github.com/d5/tengo/objects/conversion.go b/vendor/github.com/d5/tengo/objects/conversion.go new file mode 100644 index 00000000..3c17546f --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/conversion.go @@ -0,0 +1,249 @@ +package objects + +import ( + "fmt" + "strconv" + "time" +) + +// ToString will try to convert object o to string value. +func ToString(o Object) (v string, ok bool) { + if o == UndefinedValue { + //ok = false + 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 + } + } + + //ok = false + 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 + } + } + + //ok = false + 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 + } + } + + //ok = false + 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 = rune(o.Value) + ok = true + } + + //ok = false + 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 + } + + //ok = false + 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 + } + + //ok = false + return +} + +// objectToInterface attempts to convert an object o to an interface{} value +func objectToInterface(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] = objectToInterface(val) + } + case *Map: + res = make(map[string]interface{}) + for key, v := range o.Value { + res.(map[string]interface{})[key] = objectToInterface(v) + } + 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: + 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: + 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), 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 + } + + return nil, fmt.Errorf("cannot convert to object: %T", v) +} diff --git a/vendor/github.com/d5/tengo/objects/error.go b/vendor/github.com/d5/tengo/objects/error.go new file mode 100644 index 00000000..be21de03 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/error.go @@ -0,0 +1,47 @@ +package objects + +import ( + "fmt" + + "github.com/d5/tengo/compiler/token" +) + +// Error represents a string value. +type Error struct { + 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" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Error) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/errors.go b/vendor/github.com/d5/tengo/objects/errors.go new file mode 100644 index 00000000..e4012314 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/errors.go @@ -0,0 +1,32 @@ +package objects + +import ( + "errors" + "fmt" +) + +// ErrIndexOutOfBounds is an error where a given index is out of the bounds. +var ErrIndexOutOfBounds = errors.New("index out of bounds") + +// ErrInvalidIndexType represents an invalid index type. +var ErrInvalidIndexType = errors.New("invalid index type") + +// ErrInvalidIndexValueType represents an invalid index value type. +var ErrInvalidIndexValueType = errors.New("invalid index value type") + +// ErrInvalidOperator represents an error for invalid operator usage. +var ErrInvalidOperator = errors.New("invalid operator") + +// ErrWrongNumArguments represents a wrong number of arguments error. +var ErrWrongNumArguments = errors.New("wrong number of arguments") + +// 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/objects/float.go b/vendor/github.com/d5/tengo/objects/float.go new file mode 100644 index 00000000..65997303 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/float.go @@ -0,0 +1,146 @@ +package objects + +import ( + "math" + "strconv" + + "github.com/d5/tengo/compiler/token" +) + +// Float represents a floating point number value. +type Float struct { + 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 +} diff --git a/vendor/github.com/d5/tengo/objects/immautable_array.go b/vendor/github.com/d5/tengo/objects/immautable_array.go new file mode 100644 index 00000000..f3621e29 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/immautable_array.go @@ -0,0 +1,109 @@ +package objects + +import ( + "fmt" + "strings" + + "github.com/d5/tengo/compiler/token" +) + +// ImmutableArray represents an immutable array of objects. +type ImmutableArray struct { + 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), + } +} diff --git a/vendor/github.com/d5/tengo/objects/immutable_map.go b/vendor/github.com/d5/tengo/objects/immutable_map.go new file mode 100644 index 00000000..8f58701b --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/immutable_map.go @@ -0,0 +1,105 @@ +package objects + +import ( + "fmt" + "strings" + + "github.com/d5/tengo/compiler/token" +) + +// ImmutableMap represents an immutable map object. +type ImmutableMap struct { + 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, ", ")) +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *ImmutableMap) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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 + } + + val, ok := o.Value[strIdx] + if !ok { + val = UndefinedValue + } + + return val, nil +} + +// 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), + } +} diff --git a/vendor/github.com/d5/tengo/objects/index_assignable.go b/vendor/github.com/d5/tengo/objects/index_assignable.go new file mode 100644 index 00000000..a1c6cbff --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/index_assignable.go @@ -0,0 +1,9 @@ +package objects + +// IndexAssignable is an object that can take an index and a value +// on the left-hand side of the assignment statement. +type IndexAssignable interface { + // IndexSet should take an index Object and a value Object. + // If an error is returned, it will be treated as a run-time error. + IndexSet(index, value Object) error +} diff --git a/vendor/github.com/d5/tengo/objects/indexable.go b/vendor/github.com/d5/tengo/objects/indexable.go new file mode 100644 index 00000000..bbc81633 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/indexable.go @@ -0,0 +1,9 @@ +package objects + +// Indexable is an object that can take an index and return an object. +type Indexable interface { + // IndexGet should take an index Object and return a result Object or an error. + // If error is returned, the runtime will treat it as a run-time error and ignore returned value. + // If nil is returned as value, it will be converted to Undefined value by the runtime. + IndexGet(index Object) (value Object, err error) +} diff --git a/vendor/github.com/d5/tengo/objects/int.go b/vendor/github.com/d5/tengo/objects/int.go new file mode 100644 index 00000000..e902c93a --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/int.go @@ -0,0 +1,198 @@ +package objects + +import ( + "strconv" + + "github.com/d5/tengo/compiler/token" +) + +// Int represents an integer value. +type Int struct { + 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{float64(o.Value) + rhs.Value}, nil + case token.Sub: + return &Float{float64(o.Value) - rhs.Value}, nil + case token.Mul: + return &Float{float64(o.Value) * rhs.Value}, nil + case token.Quo: + return &Float{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{rune(o.Value) + rhs.Value}, nil + case token.Sub: + return &Char{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 +} diff --git a/vendor/github.com/d5/tengo/objects/iterable.go b/vendor/github.com/d5/tengo/objects/iterable.go new file mode 100644 index 00000000..e431d3d7 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/iterable.go @@ -0,0 +1,7 @@ +package objects + +// Iterable represents an object that has iterator. +type Iterable interface { + // Iterate should return an Iterator for the type. + Iterate() Iterator +} diff --git a/vendor/github.com/d5/tengo/objects/iterator.go b/vendor/github.com/d5/tengo/objects/iterator.go new file mode 100644 index 00000000..01522ba5 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/iterator.go @@ -0,0 +1,15 @@ +package objects + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/map.go b/vendor/github.com/d5/tengo/objects/map.go new file mode 100644 index 00000000..c42ffe93 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/map.go @@ -0,0 +1,118 @@ +package objects + +import ( + "fmt" + "strings" + + "github.com/d5/tengo/compiler/token" +) + +// Map represents a map of objects. +type Map struct { + 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, ", ")) +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Map) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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 := index.(*String) + if !ok { + err = ErrInvalidIndexType + return + } + + val, ok := o.Value[strIdx.Value] + if !ok { + val = UndefinedValue + } + + return val, nil +} + +// 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), + } +} diff --git a/vendor/github.com/d5/tengo/objects/map_iterator.go b/vendor/github.com/d5/tengo/objects/map_iterator.go new file mode 100644 index 00000000..d60dd0e1 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/map_iterator.go @@ -0,0 +1,62 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// MapIterator represents an iterator for the map. +type MapIterator struct { + 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 "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (i *MapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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] +} diff --git a/vendor/github.com/d5/tengo/objects/object.go b/vendor/github.com/d5/tengo/objects/object.go new file mode 100644 index 00000000..4c5aa7ae --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/object.go @@ -0,0 +1,30 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// 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 +} diff --git a/vendor/github.com/d5/tengo/objects/objects.go b/vendor/github.com/d5/tengo/objects/objects.go new file mode 100644 index 00000000..f3878b11 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/objects.go @@ -0,0 +1,12 @@ +package objects + +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{} +) diff --git a/vendor/github.com/d5/tengo/objects/return_value.go b/vendor/github.com/d5/tengo/objects/return_value.go new file mode 100644 index 00000000..f7ef1dc4 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/return_value.go @@ -0,0 +1,39 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// ReturnValue represents a value that is being returned. +type ReturnValue struct { + Value Object +} + +// TypeName returns the name of the type. +func (o *ReturnValue) TypeName() string { + return "return-value" +} + +func (o *ReturnValue) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *ReturnValue) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *ReturnValue) Copy() Object { + return &ReturnValue{Value: o.Copy()} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *ReturnValue) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *ReturnValue) Equals(x Object) bool { + return false +} diff --git a/vendor/github.com/d5/tengo/objects/string.go b/vendor/github.com/d5/tengo/objects/string.go new file mode 100644 index 00000000..6a53b44d --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/string.go @@ -0,0 +1,95 @@ +package objects + +import ( + "strconv" + + "github.com/d5/tengo/compiler/token" +) + +// String represents a string value. +type String struct { + 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: + return &String{Value: o.Value + rhs.Value}, nil + default: + return &String{Value: o.Value + rhs.String()}, 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), + } +} diff --git a/vendor/github.com/d5/tengo/objects/string_iterator.go b/vendor/github.com/d5/tengo/objects/string_iterator.go new file mode 100644 index 00000000..8bc95eb5 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/string_iterator.go @@ -0,0 +1,57 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// StringIterator represents an iterator for a string. +type StringIterator struct { + 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 "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (i *StringIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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/objects/time.go b/vendor/github.com/d5/tengo/objects/time.go new file mode 100644 index 00000000..4e783cc8 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/time.go @@ -0,0 +1,89 @@ +package objects + +import ( + "time" + + "github.com/d5/tengo/compiler/token" +) + +// Time represents a time value. +type Time struct { + 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) +} diff --git a/vendor/github.com/d5/tengo/objects/undefined.go b/vendor/github.com/d5/tengo/objects/undefined.go new file mode 100644 index 00000000..79a380f5 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/undefined.go @@ -0,0 +1,42 @@ +package objects + +import "github.com/d5/tengo/compiler/token" + +// Undefined represents an undefined value. +type Undefined struct{} + +// TypeName returns the name of the type. +func (o *Undefined) TypeName() string { + return "undefined" +} + +func (o *Undefined) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Undefined) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// 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(index Object) (Object, error) { + return UndefinedValue, nil +} diff --git a/vendor/github.com/d5/tengo/objects/user_function.go b/vendor/github.com/d5/tengo/objects/user_function.go new file mode 100644 index 00000000..1d9bb4f7 --- /dev/null +++ b/vendor/github.com/d5/tengo/objects/user_function.go @@ -0,0 +1,47 @@ +package objects + +import ( + "github.com/d5/tengo/compiler/token" +) + +// UserFunction represents a user function. +type UserFunction struct { + Name string + Value CallableFunc +} + +// TypeName returns the name of the type. +func (o *UserFunction) TypeName() string { + return "user-function:" + o.Name +} + +func (o *UserFunction) String() string { + return "" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *UserFunction) BinaryOp(op token.Token, rhs Object) (Object, error) { + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *UserFunction) Copy() Object { + return &UserFunction{Value: o.Value} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *UserFunction) IsFalsy() bool { + return false +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *UserFunction) Equals(x Object) bool { + return false +} + +// Call invokes a user function. +func (o *UserFunction) Call(args ...Object) (Object, error) { + return o.Value(args...) +} diff --git a/vendor/github.com/d5/tengo/runtime/errors.go b/vendor/github.com/d5/tengo/runtime/errors.go new file mode 100644 index 00000000..f5f201ce --- /dev/null +++ b/vendor/github.com/d5/tengo/runtime/errors.go @@ -0,0 +1,8 @@ +package runtime + +import ( + "errors" +) + +// ErrStackOverflow is a stack overflow error. +var ErrStackOverflow = errors.New("stack overflow") diff --git a/vendor/github.com/d5/tengo/runtime/frame.go b/vendor/github.com/d5/tengo/runtime/frame.go new file mode 100644 index 00000000..90151a1e --- /dev/null +++ b/vendor/github.com/d5/tengo/runtime/frame.go @@ -0,0 +1,13 @@ +package runtime + +import ( + "github.com/d5/tengo/objects" +) + +// Frame represents a function call frame. +type Frame struct { + fn *objects.CompiledFunction + freeVars []*objects.Object + ip int + basePointer int +} diff --git a/vendor/github.com/d5/tengo/runtime/vm.go b/vendor/github.com/d5/tengo/runtime/vm.go new file mode 100644 index 00000000..2708fde7 --- /dev/null +++ b/vendor/github.com/d5/tengo/runtime/vm.go @@ -0,0 +1,1424 @@ +package runtime + +import ( + "fmt" + "sync/atomic" + + "github.com/d5/tengo/compiler" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" + "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" +) + +const ( + // StackSize is the maximum stack size. + StackSize = 2048 + + // GlobalsSize is the maximum number of global variables. + GlobalsSize = 1024 + + // MaxFrames is the maximum number of function frames. + MaxFrames = 1024 +) + +var ( + truePtr = &objects.TrueValue + falsePtr = &objects.FalseValue + undefinedPtr = &objects.UndefinedValue + builtinFuncs []objects.Object +) + +// VM is a virtual machine that executes the bytecode compiled by Compiler. +type VM struct { + constants []objects.Object + stack []*objects.Object + sp int + globals []*objects.Object + fileSet *source.FileSet + frames []Frame + framesIndex int + curFrame *Frame + curInsts []byte + curIPLimit int + ip int + aborting int64 + builtinModules map[string]*objects.Object +} + +// NewVM creates a VM. +func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object, builtinModules map[string]*objects.Object) *VM { + if globals == nil { + globals = make([]*objects.Object, GlobalsSize) + } + + if builtinModules == nil { + builtinModules = stdlib.Modules + } + + frames := make([]Frame, MaxFrames) + frames[0].fn = bytecode.MainFunction + frames[0].freeVars = nil + frames[0].ip = -1 + frames[0].basePointer = 0 + + return &VM{ + constants: bytecode.Constants, + stack: make([]*objects.Object, StackSize), + sp: 0, + globals: globals, + fileSet: bytecode.FileSet, + frames: frames, + framesIndex: 1, + curFrame: &(frames[0]), + curInsts: frames[0].fn.Instructions, + curIPLimit: len(frames[0].fn.Instructions) - 1, + ip: -1, + builtinModules: builtinModules, + } +} + +// 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.curIPLimit = len(v.curInsts) - 1 + v.framesIndex = 1 + v.ip = -1 + atomic.StoreInt64(&v.aborting, 0) + + var filePos source.FilePos + +mainloop: + for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) { + v.ip++ + + switch v.curInsts[v.ip] { + case compiler.OpConstant: + cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &v.constants[cidx] + v.sp++ + + case compiler.OpNull: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = undefinedPtr + v.sp++ + + case compiler.OpAdd: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Add, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s + %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpSub: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Sub, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s - %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpMul: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Mul, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s * %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpDiv: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Quo, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s / %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpRem: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Rem, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s %% %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBAnd: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.And, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s & %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBOr: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Or, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s | %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBXor: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Xor, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s ^ %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBAndNot: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.AndNot, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s &^ %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBShiftLeft: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Shl, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s << %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpBShiftRight: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Shr, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s >> %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpEqual: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + if (*left).Equals(*right) { + v.stack[v.sp] = truePtr + } else { + v.stack[v.sp] = falsePtr + } + v.sp++ + + case compiler.OpNotEqual: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + if (*left).Equals(*right) { + v.stack[v.sp] = falsePtr + } else { + v.stack[v.sp] = truePtr + } + v.sp++ + + case compiler.OpGreaterThan: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.Greater, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s > %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpGreaterThanEqual: + right := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + res, e := (*left).BinaryOp(token.GreaterEq, *right) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if e == objects.ErrInvalidOperator { + err = fmt.Errorf("invalid operation: %s >= %s", + (*left).TypeName(), (*right).TypeName()) + break mainloop + } + + err = e + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &res + v.sp++ + + case compiler.OpPop: + v.sp-- + + case compiler.OpTrue: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = truePtr + v.sp++ + + case compiler.OpFalse: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = falsePtr + v.sp++ + + case compiler.OpLNot: + operand := v.stack[v.sp-1] + v.sp-- + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + if (*operand).IsFalsy() { + v.stack[v.sp] = truePtr + } else { + v.stack[v.sp] = falsePtr + } + v.sp++ + + case compiler.OpBComplement: + operand := v.stack[v.sp-1] + v.sp-- + + switch x := (*operand).(type) { + case *objects.Int: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var res objects.Object = &objects.Int{Value: ^x.Value} + + v.stack[v.sp] = &res + v.sp++ + default: + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid operation: ^%s", (*operand).TypeName()) + break mainloop + } + + case compiler.OpMinus: + operand := v.stack[v.sp-1] + v.sp-- + + switch x := (*operand).(type) { + case *objects.Int: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var res objects.Object = &objects.Int{Value: -x.Value} + + v.stack[v.sp] = &res + v.sp++ + case *objects.Float: + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var res objects.Object = &objects.Float{Value: -x.Value} + + v.stack[v.sp] = &res + v.sp++ + default: + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid operation: -%s", (*operand).TypeName()) + break mainloop + } + + case compiler.OpJumpFalsy: + pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + condition := v.stack[v.sp-1] + v.sp-- + + if (*condition).IsFalsy() { + v.ip = pos - 1 + } + + case compiler.OpAndJump: + pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + condition := *v.stack[v.sp-1] + if condition.IsFalsy() { + v.ip = pos - 1 + } else { + v.sp-- + } + + case compiler.OpOrJump: + pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + condition := *v.stack[v.sp-1] + if !condition.IsFalsy() { + v.ip = pos - 1 + } else { + v.sp-- + } + + case compiler.OpJump: + pos := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip = pos - 1 + + case compiler.OpSetGlobal: + globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + v.sp-- + + v.globals[globalIndex] = v.stack[v.sp] + + case compiler.OpSetSelGlobal: + globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + numSelectors := int(v.curInsts[v.ip+3]) + v.ip += 3 + + // selectors and RHS value + selectors := v.stack[v.sp-numSelectors : v.sp] + val := v.stack[v.sp-numSelectors-1] + v.sp -= numSelectors + 1 + + if e := indexAssign(v.globals[globalIndex], val, selectors); e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + err = e + break mainloop + } + + case compiler.OpGetGlobal: + globalIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + val := v.globals[globalIndex] + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = val + v.sp++ + + case compiler.OpArray: + numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + var elements []objects.Object + for i := v.sp - numElements; i < v.sp; i++ { + elements = append(elements, *v.stack[i]) + } + v.sp -= numElements + + var arr objects.Object = &objects.Array{Value: elements} + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &arr + v.sp++ + + case compiler.OpMap: + numElements := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + v.ip += 2 + + kv := make(map[string]objects.Object) + for i := v.sp - numElements; i < v.sp; i += 2 { + key := *v.stack[i] + value := *v.stack[i+1] + kv[key.(*objects.String).Value] = value + } + v.sp -= numElements + + var m objects.Object = &objects.Map{Value: kv} + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &m + v.sp++ + + case compiler.OpError: + value := v.stack[v.sp-1] + + var e objects.Object = &objects.Error{ + Value: *value, + } + + v.stack[v.sp-1] = &e + + case compiler.OpImmutable: + value := v.stack[v.sp-1] + + switch value := (*value).(type) { + case *objects.Array: + var immutableArray objects.Object = &objects.ImmutableArray{ + Value: value.Value, + } + v.stack[v.sp-1] = &immutableArray + case *objects.Map: + var immutableMap objects.Object = &objects.ImmutableMap{ + Value: value.Value, + } + v.stack[v.sp-1] = &immutableMap + } + + case compiler.OpIndex: + index := v.stack[v.sp-1] + left := v.stack[v.sp-2] + v.sp -= 2 + + switch left := (*left).(type) { + case objects.Indexable: + val, e := left.IndexGet(*index) + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + + if e == objects.ErrInvalidIndexType { + err = fmt.Errorf("invalid index type: %s", (*index).TypeName()) + break mainloop + } + + err = e + break mainloop + } + if val == nil { + val = objects.UndefinedValue + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &val + v.sp++ + + case *objects.Error: // e.value + key, ok := (*index).(*objects.String) + if !ok || key.Value != "value" { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid index on error") + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &left.Value + v.sp++ + + default: + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("not indexable: %s", left.TypeName()) + break mainloop + } + + case compiler.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 != objects.UndefinedValue { + if low, ok := (*low).(*objects.Int); ok { + lowIdx = low.Value + } else { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index type: %s", low.TypeName()) + break mainloop + } + } + + switch left := (*left).(type) { + case *objects.Array: + numElements := int64(len(left.Value)) + var highIdx int64 + if *high == objects.UndefinedValue { + highIdx = numElements + } else if high, ok := (*high).(*objects.Int); ok { + highIdx = high.Value + } else { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + break mainloop + } + + if lowIdx > highIdx { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + break mainloop + } + + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} + v.stack[v.sp] = &val + v.sp++ + + case *objects.ImmutableArray: + numElements := int64(len(left.Value)) + var highIdx int64 + if *high == objects.UndefinedValue { + highIdx = numElements + } else if high, ok := (*high).(*objects.Int); ok { + highIdx = high.Value + } else { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + break mainloop + } + + if lowIdx > highIdx { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + break mainloop + } + + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var val objects.Object = &objects.Array{Value: left.Value[lowIdx:highIdx]} + + v.stack[v.sp] = &val + v.sp++ + + case *objects.String: + numElements := int64(len(left.Value)) + var highIdx int64 + if *high == objects.UndefinedValue { + highIdx = numElements + } else if high, ok := (*high).(*objects.Int); ok { + highIdx = high.Value + } else { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + break mainloop + } + + if lowIdx > highIdx { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + break mainloop + } + + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var val objects.Object = &objects.String{Value: left.Value[lowIdx:highIdx]} + + v.stack[v.sp] = &val + v.sp++ + + case *objects.Bytes: + numElements := int64(len(left.Value)) + var highIdx int64 + if *high == objects.UndefinedValue { + highIdx = numElements + } else if high, ok := (*high).(*objects.Int); ok { + highIdx = high.Value + } else { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index type: %s", high.TypeName()) + break mainloop + } + + if lowIdx > highIdx { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + break mainloop + } + + if lowIdx < 0 { + lowIdx = 0 + } else if lowIdx > numElements { + lowIdx = numElements + } + + if highIdx < 0 { + highIdx = 0 + } else if highIdx > numElements { + highIdx = numElements + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]} + + v.stack[v.sp] = &val + v.sp++ + } + + case compiler.OpCall: + numArgs := int(v.curInsts[v.ip+1]) + v.ip++ + + value := *v.stack[v.sp-1-numArgs] + + switch callee := value.(type) { + case *objects.Closure: + if numArgs != callee.Fn.NumParameters { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", + callee.Fn.NumParameters, numArgs) + break mainloop + } + + // test if it's tail-call + if callee.Fn == v.curFrame.fn { // recursion + nextOp := v.curInsts[v.ip+1] + if nextOp == compiler.OpReturnValue || + (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { + for p := 0; p < numArgs; p++ { + v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] + } + v.sp -= numArgs + 1 + v.ip = -1 // reset IP to beginning of the frame + continue mainloop + } + } + + // update call frame + v.curFrame.ip = v.ip // store current ip before call + v.curFrame = &(v.frames[v.framesIndex]) + v.curFrame.fn = callee.Fn + v.curFrame.freeVars = callee.Free + v.curFrame.basePointer = v.sp - numArgs + v.curInsts = callee.Fn.Instructions + v.ip = -1 + v.curIPLimit = len(v.curInsts) - 1 + v.framesIndex++ + v.sp = v.sp - numArgs + callee.Fn.NumLocals + + case *objects.CompiledFunction: + if numArgs != callee.NumParameters { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + err = fmt.Errorf("wrong number of arguments: want=%d, got=%d", + callee.NumParameters, numArgs) + break mainloop + } + + // test if it's tail-call + if callee == v.curFrame.fn { // recursion + nextOp := v.curInsts[v.ip+1] + if nextOp == compiler.OpReturnValue || + (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { + for p := 0; p < numArgs; p++ { + v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] + } + v.sp -= numArgs + 1 + v.ip = -1 // reset IP to beginning of the frame + continue mainloop + } + } + + // 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 = nil + v.curFrame.basePointer = v.sp - numArgs + v.curInsts = callee.Instructions + v.ip = -1 + v.curIPLimit = len(v.curInsts) - 1 + v.framesIndex++ + v.sp = v.sp - numArgs + callee.NumLocals + + case objects.Callable: + var args []objects.Object + for _, arg := range v.stack[v.sp-numArgs : v.sp] { + args = append(args, *arg) + } + + ret, e := callee.Call(args...) + v.sp -= numArgs + 1 + + // runtime error + if e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + + if e == objects.ErrWrongNumArguments { + err = fmt.Errorf("wrong number of arguments in call to '%s'", + value.TypeName()) + break mainloop + } + + if e, ok := e.(objects.ErrInvalidArgumentType); ok { + err = fmt.Errorf("invalid type for argument '%s' in call to '%s': expected %s, found %s", + e.Name, value.TypeName(), e.Expected, e.Found) + break mainloop + } + + err = e + break mainloop + } + + // nil return -> undefined + if ret == nil { + ret = objects.UndefinedValue + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &ret + v.sp++ + + default: + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + err = fmt.Errorf("not callable: %s", callee.TypeName()) + break mainloop + } + + case compiler.OpReturnValue: + retVal := v.stack[v.sp-1] + //v.sp-- + + v.framesIndex-- + lastFrame := v.frames[v.framesIndex] + v.curFrame = &v.frames[v.framesIndex-1] + v.curInsts = v.curFrame.fn.Instructions + v.curIPLimit = len(v.curInsts) - 1 + v.ip = v.curFrame.ip + + //v.sp = lastFrame.basePointer - 1 + v.sp = lastFrame.basePointer + + // skip stack overflow check because (newSP) <= (oldSP) + v.stack[v.sp-1] = retVal + //v.sp++ + + case compiler.OpReturn: + v.framesIndex-- + lastFrame := v.frames[v.framesIndex] + v.curFrame = &v.frames[v.framesIndex-1] + v.curInsts = v.curFrame.fn.Instructions + v.curIPLimit = len(v.curInsts) - 1 + v.ip = v.curFrame.ip + + //v.sp = lastFrame.basePointer - 1 + v.sp = lastFrame.basePointer + + // skip stack overflow check because (newSP) <= (oldSP) + v.stack[v.sp-1] = undefinedPtr + //v.sp++ + + case compiler.OpDefineLocal: + localIndex := int(v.curInsts[v.ip+1]) + v.ip++ + + 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 compiler.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-- + + *v.stack[sp] = *val // also use a copy of popped value + + case compiler.OpSetSelLocal: + localIndex := int(v.curInsts[v.ip+1]) + numSelectors := int(v.curInsts[v.ip+2]) + v.ip += 2 + + // selectors and RHS value + selectors := v.stack[v.sp-numSelectors : v.sp] + val := v.stack[v.sp-numSelectors-1] + v.sp -= numSelectors + 1 + + sp := v.curFrame.basePointer + localIndex + + if e := indexAssign(v.stack[sp], val, selectors); e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2]) + err = e + break mainloop + } + + case compiler.OpGetLocal: + localIndex := int(v.curInsts[v.ip+1]) + v.ip++ + + val := v.stack[v.curFrame.basePointer+localIndex] + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = val + v.sp++ + + case compiler.OpGetBuiltin: + builtinIndex := int(v.curInsts[v.ip+1]) + v.ip++ + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &builtinFuncs[builtinIndex] + v.sp++ + + case compiler.OpGetBuiltinModule: + val := v.stack[v.sp-1] + v.sp-- + + moduleName := (*val).(*objects.String).Value + + module, ok := v.builtinModules[moduleName] + if !ok { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + err = fmt.Errorf("module '%s' not found", moduleName) + break mainloop + } + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = module + v.sp++ + + case compiler.OpClosure: + constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 + numFree := int(v.curInsts[v.ip+3]) + v.ip += 3 + + fn, ok := v.constants[constIndex].(*objects.CompiledFunction) + if !ok { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + err = fmt.Errorf("not function: %s", fn.TypeName()) + break mainloop + } + + free := make([]*objects.Object, numFree) + for i := 0; i < numFree; i++ { + free[i] = v.stack[v.sp-numFree+i] + } + v.sp -= numFree + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + var cl objects.Object = &objects.Closure{ + Fn: fn, + Free: free, + } + + v.stack[v.sp] = &cl + v.sp++ + + case compiler.OpGetFree: + freeIndex := int(v.curInsts[v.ip+1]) + v.ip++ + + val := v.curFrame.freeVars[freeIndex] + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = val + v.sp++ + + case compiler.OpSetSelFree: + freeIndex := int(v.curInsts[v.ip+1]) + numSelectors := int(v.curInsts[v.ip+2]) + v.ip += 2 + + // selectors and RHS value + selectors := v.stack[v.sp-numSelectors : v.sp] + val := v.stack[v.sp-numSelectors-1] + v.sp -= numSelectors + 1 + + if e := indexAssign(v.curFrame.freeVars[freeIndex], val, selectors); e != nil { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2]) + err = e + break mainloop + } + + case compiler.OpSetFree: + freeIndex := int(v.curInsts[v.ip+1]) + v.ip++ + + val := v.stack[v.sp-1] + v.sp-- + + *v.curFrame.freeVars[freeIndex] = *val + + case compiler.OpIteratorInit: + var iterator objects.Object + + dst := v.stack[v.sp-1] + v.sp-- + + iterable, ok := (*dst).(objects.Iterable) + if !ok { + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + err = fmt.Errorf("not iterable: %s", (*dst).TypeName()) + break mainloop + } + + iterator = iterable.Iterate() + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &iterator + v.sp++ + + case compiler.OpIteratorNext: + iterator := v.stack[v.sp-1] + v.sp-- + + hasMore := (*iterator).(objects.Iterator).Next() + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + if hasMore { + v.stack[v.sp] = truePtr + } else { + v.stack[v.sp] = falsePtr + } + v.sp++ + + case compiler.OpIteratorKey: + iterator := v.stack[v.sp-1] + v.sp-- + + val := (*iterator).(objects.Iterator).Key() + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &val + v.sp++ + + case compiler.OpIteratorValue: + iterator := v.stack[v.sp-1] + v.sp-- + + val := (*iterator).(objects.Iterator).Value() + + if v.sp >= StackSize { + err = ErrStackOverflow + break mainloop + } + + v.stack[v.sp] = &val + v.sp++ + + default: + panic(fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])) + } + } + + if err != nil { + err = fmt.Errorf("Runtime Error: %s\n\tat %s", err.Error(), filePos) + for v.framesIndex > 1 { + v.framesIndex-- + v.curFrame = &v.frames[v.framesIndex-1] + + filePos = v.fileSet.Position(v.curFrame.fn.SourceMap[v.curFrame.ip-1]) + err = fmt.Errorf("%s\n\tat %s", err.Error(), filePos) + } + return err + } + + // check if stack still has some objects left + if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 { + panic(fmt.Errorf("non empty stack after execution: %d", v.sp)) + } + + return nil +} + +// Globals returns the global variables. +func (v *VM) Globals() []*objects.Object { + return v.globals +} + +func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error { + numSel := len(selectors) + + for sidx := numSel - 1; sidx > 0; sidx-- { + indexable, ok := (*dst).(objects.Indexable) + if !ok { + return fmt.Errorf("not indexable: %s", (*dst).TypeName()) + } + + next, err := indexable.IndexGet(*selectors[sidx]) + if err != nil { + if err == objects.ErrInvalidIndexType { + return fmt.Errorf("invalid index type: %s", (*selectors[sidx]).TypeName()) + } + + return err + } + + dst = &next + } + + indexAssignable, ok := (*dst).(objects.IndexAssignable) + if !ok { + return fmt.Errorf("not index-assignable: %s", (*dst).TypeName()) + } + + if err := indexAssignable.IndexSet(*selectors[0], *src); err != nil { + if err == objects.ErrInvalidIndexValueType { + return fmt.Errorf("invaid index value type: %s", (*src).TypeName()) + } + + return err + } + + return nil +} + +func init() { + builtinFuncs = make([]objects.Object, len(objects.Builtins)) + for i, b := range objects.Builtins { + builtinFuncs[i] = &objects.BuiltinFunction{ + Name: b.Name, + Value: b.Func, + } + } +} diff --git a/vendor/github.com/d5/tengo/script/compiled.go b/vendor/github.com/d5/tengo/script/compiled.go new file mode 100644 index 00000000..4acc46ee --- /dev/null +++ b/vendor/github.com/d5/tengo/script/compiled.go @@ -0,0 +1,113 @@ +package script + +import ( + "context" + "fmt" + + "github.com/d5/tengo/compiler" + "github.com/d5/tengo/objects" + "github.com/d5/tengo/runtime" +) + +// Compiled is a compiled instance of the user script. +// Use Script.Compile() to create Compiled object. +type Compiled struct { + symbolTable *compiler.SymbolTable + machine *runtime.VM +} + +// Run executes the compiled script in the virtual machine. +func (c *Compiled) Run() error { + return c.machine.Run() +} + +// RunContext is like Run but includes a context. +func (c *Compiled) RunContext(ctx context.Context) (err error) { + ch := make(chan error, 1) + + go func() { + ch <- c.machine.Run() + }() + + select { + case <-ctx.Done(): + c.machine.Abort() + <-ch + err = ctx.Err() + case err = <-ch: + } + + return +} + +// IsDefined returns true if the variable name is defined (has value) before or after the execution. +func (c *Compiled) IsDefined(name string) bool { + symbol, _, ok := c.symbolTable.Resolve(name) + if !ok { + return false + } + + v := c.machine.Globals()[symbol.Index] + if v == nil { + return false + } + + return *v != objects.UndefinedValue +} + +// Get returns a variable identified by the name. +func (c *Compiled) Get(name string) *Variable { + value := &objects.UndefinedValue + + symbol, _, ok := c.symbolTable.Resolve(name) + if ok && symbol.Scope == compiler.ScopeGlobal { + value = c.machine.Globals()[symbol.Index] + if value == nil { + value = &objects.UndefinedValue + } + } + + return &Variable{ + name: name, + value: value, + } +} + +// GetAll returns all the variables that are defined by the compiled script. +func (c *Compiled) GetAll() []*Variable { + var vars []*Variable + for _, name := range c.symbolTable.Names() { + symbol, _, ok := c.symbolTable.Resolve(name) + if ok && symbol.Scope == compiler.ScopeGlobal { + value := c.machine.Globals()[symbol.Index] + if value == nil { + value = &objects.UndefinedValue + } + + vars = append(vars, &Variable{ + name: name, + value: value, + }) + } + } + + 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 { + obj, err := objects.FromInterface(value) + if err != nil { + return err + } + + symbol, _, ok := c.symbolTable.Resolve(name) + if !ok || symbol.Scope != compiler.ScopeGlobal { + return fmt.Errorf("'%s' is not defined", name) + } + + c.machine.Globals()[symbol.Index] = &obj + + return nil +} diff --git a/vendor/github.com/d5/tengo/script/conversion.go b/vendor/github.com/d5/tengo/script/conversion.go new file mode 100644 index 00000000..c35c1411 --- /dev/null +++ b/vendor/github.com/d5/tengo/script/conversion.go @@ -0,0 +1,33 @@ +package script + +import ( + "github.com/d5/tengo/objects" +) + +func objectToInterface(o objects.Object) interface{} { + switch val := o.(type) { + case *objects.Array: + return val.Value + case *objects.Map: + return val.Value + case *objects.Int: + return val.Value + case *objects.Float: + return val.Value + case *objects.Bool: + if val == objects.TrueValue { + return true + } + return false + case *objects.Char: + return val.Value + case *objects.String: + return val.Value + case *objects.Bytes: + return val.Value + case *objects.Undefined: + return nil + } + + return o +} diff --git a/vendor/github.com/d5/tengo/script/script.go b/vendor/github.com/d5/tengo/script/script.go new file mode 100644 index 00000000..0b810278 --- /dev/null +++ b/vendor/github.com/d5/tengo/script/script.go @@ -0,0 +1,180 @@ +package script + +import ( + "context" + "fmt" + + "github.com/d5/tengo/compiler" + "github.com/d5/tengo/compiler/parser" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/objects" + "github.com/d5/tengo/runtime" + "github.com/d5/tengo/stdlib" +) + +// Script can simplify compilation and execution of embedded scripts. +type Script struct { + variables map[string]*Variable + removedBuiltins map[string]bool + removedStdModules map[string]bool + userModuleLoader compiler.ModuleLoader + input []byte +} + +// New creates a Script instance with an input script. +func New(input []byte) *Script { + return &Script{ + variables: make(map[string]*Variable), + input: input, + } +} + +// Add adds a new variable or updates an existing variable to the script. +func (s *Script) Add(name string, value interface{}) error { + obj, err := objects.FromInterface(value) + if err != nil { + return err + } + + s.variables[name] = &Variable{ + name: name, + value: &obj, + } + + return nil +} + +// Remove removes (undefines) an existing variable for the script. +// It returns false if the variable name is not defined. +func (s *Script) Remove(name string) bool { + if _, ok := s.variables[name]; !ok { + return false + } + + delete(s.variables, name) + + return true +} + +// DisableBuiltinFunction disables a builtin function. +func (s *Script) DisableBuiltinFunction(name string) { + if s.removedBuiltins == nil { + s.removedBuiltins = make(map[string]bool) + } + + s.removedBuiltins[name] = true +} + +// DisableStdModule disables a standard library module. +func (s *Script) DisableStdModule(name string) { + if s.removedStdModules == nil { + s.removedStdModules = make(map[string]bool) + } + + s.removedStdModules[name] = true +} + +// SetUserModuleLoader sets the user module loader for the compiler. +func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) { + s.userModuleLoader = loader +} + +// Compile compiles the script with all the defined variables, and, returns Compiled object. +func (s *Script) Compile() (*Compiled, error) { + symbolTable, stdModules, globals, err := s.prepCompile() + if err != nil { + return nil, err + } + + fileSet := source.NewFileSet() + srcFile := fileSet.AddFile("(main)", -1, len(s.input)) + + p := parser.NewParser(srcFile, s.input, nil) + file, err := p.ParseFile() + if err != nil { + return nil, fmt.Errorf("parse error: %s", err.Error()) + } + + c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil) + + if s.userModuleLoader != nil { + c.SetModuleLoader(s.userModuleLoader) + } + + if err := c.Compile(file); err != nil { + return nil, err + } + + return &Compiled{ + symbolTable: symbolTable, + machine: runtime.NewVM(c.Bytecode(), globals, nil), + }, 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 *compiler.SymbolTable, stdModules map[string]bool, globals []*objects.Object, err error) { + var names []string + for name := range s.variables { + names = append(names, name) + } + + symbolTable = compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + if !s.removedBuiltins[fn.Name] { + symbolTable.DefineBuiltin(idx, fn.Name) + } + } + + stdModules = make(map[string]bool) + for name := range stdlib.Modules { + if !s.removedStdModules[name] { + stdModules[name] = true + } + } + + globals = make([]*objects.Object, runtime.GlobalsSize, runtime.GlobalsSize) + + for idx, name := range names { + symbol := symbolTable.Define(name) + if symbol.Index != idx { + panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index)) + } + + globals[symbol.Index] = s.variables[name].value + } + + return +} + +func (s *Script) copyVariables() map[string]*Variable { + vars := make(map[string]*Variable) + for n, v := range s.variables { + vars[n] = v + } + + return vars +} diff --git a/vendor/github.com/d5/tengo/script/variable.go b/vendor/github.com/d5/tengo/script/variable.go new file mode 100644 index 00000000..c5e01bd9 --- /dev/null +++ b/vendor/github.com/d5/tengo/script/variable.go @@ -0,0 +1,149 @@ +package script + +import ( + "errors" + + "github.com/d5/tengo/objects" +) + +// Variable is a user-defined variable for the script. +type Variable struct { + name string + value *objects.Object +} + +// NewVariable creates a Variable. +func NewVariable(name string, value interface{}) (*Variable, error) { + obj, err := objects.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 objectToInterface(*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, _ := objects.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, _ := objects.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, _ := objects.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, _ := objects.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, _ := objects.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 *objects.Array: + var arr []interface{} + for _, e := range val.Value { + arr = append(arr, objectToInterface(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 *objects.Map: + kv := make(map[string]interface{}) + for mk, mv := range val.Value { + kv[mk] = objectToInterface(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, _ := objects.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, _ := objects.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).(*objects.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() objects.Object { + return *v.value +} + +// IsUndefined returns true if the underlying value is undefined. +func (v *Variable) IsUndefined() bool { + return *v.value == objects.UndefinedValue +} diff --git a/vendor/github.com/d5/tengo/stdlib/errors.go b/vendor/github.com/d5/tengo/stdlib/errors.go new file mode 100644 index 00000000..a2942bb0 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/errors.go @@ -0,0 +1,11 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +func wrapError(err error) objects.Object { + if err == nil { + return objects.TrueValue + } + + return &objects.Error{Value: &objects.String{Value: err.Error()}} +} diff --git a/vendor/github.com/d5/tengo/stdlib/func_typedefs.go b/vendor/github.com/d5/tengo/stdlib/func_typedefs.go new file mode 100644 index 00000000..a85619fa --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/func_typedefs.go @@ -0,0 +1,1057 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo/objects" +) + +// FuncAR transform a function of 'func()' signature +// into CallableFunc type. +func FuncAR(fn func()) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + fn() + + return objects.UndefinedValue, nil + } +} + +// FuncARI transform a function of 'func() int' signature +// into CallableFunc type. +func FuncARI(fn func() int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: int64(fn())}, nil + } +} + +// FuncARI64 transform a function of 'func() int64' signature +// into CallableFunc type. +func FuncARI64(fn func() int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: fn()}, nil + } +} + +// FuncAI64RI64 transform a function of 'func(int64) int64' signature +// into CallableFunc type. +func FuncAI64RI64(fn func(int64) int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: fn(i1)}, nil + } +} + +// FuncAI64R transform a function of 'func(int64)' signature +// into CallableFunc type. +func FuncAI64R(fn func(int64)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncARB transform a function of 'func() bool' signature +// into CallableFunc type. +func FuncARB(fn func() bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + if fn() { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncARE transform a function of 'func() error' signature +// into CallableFunc type. +func FuncARE(fn func() error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return wrapError(fn()), nil + } +} + +// FuncARS transform a function of 'func() string' signature +// into CallableFunc type. +func FuncARS(fn func() string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.String{Value: fn()}, nil + } +} + +// FuncARSE transform a function of 'func() (string, error)' signature +// into CallableFunc type. +func FuncARSE(fn func() (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + } +} + +// FuncARYE transform a function of 'func() ([]byte, error)' signature +// into CallableFunc type. +func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.Bytes{Value: res}, nil + } +} + +// FuncARF transform a function of 'func() float64' signature +// into CallableFunc type. +func FuncARF(fn func() float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Float{Value: fn()}, nil + } +} + +// FuncARSs transform a function of 'func() []string' signature +// into CallableFunc type. +func FuncARSs(fn func() []string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range fn() { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + } +} + +// FuncARIsE transform a function of 'func() ([]int, error)' signature +// into CallableFunc type. +func FuncARIsE(fn func() ([]int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAIRIs transform a function of 'func(int) []int' signature +// into CallableFunc type. +func FuncAIRIs(fn func(int) []int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(i1) + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAFRF transform a function of 'func(float64) float64' signature +// into CallableFunc type. +func FuncAFRF(fn func(float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(f1)}, nil + } +} + +// FuncAIR transform a function of 'func(int)' signature +// into CallableFunc type. +func FuncAIR(fn func(int)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncAIRF transform a function of 'func(int) float64' signature +// into CallableFunc type. +func FuncAIRF(fn func(int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(i1)}, nil + } +} + +// FuncAFRI transform a function of 'func(float64) int' signature +// into CallableFunc type. +func FuncAFRI(fn func(float64) int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(f1))}, nil + } +} + +// FuncAFFRF transform a function of 'func(float64, float64) float64' signature +// into CallableFunc type. +func FuncAFFRF(fn func(float64, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, f2)}, nil + } +} + +// FuncAIFRF transform a function of 'func(int, float64) float64' signature +// into CallableFunc type. +func FuncAIFRF(fn func(int, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(i1, f2)}, nil + } +} + +// FuncAFIRF transform a function of 'func(float64, int) float64' signature +// into CallableFunc type. +func FuncAFIRF(fn func(float64, int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, i2)}, nil + } +} + +// FuncAFIRB transform a function of 'func(float64, int) bool' signature +// into CallableFunc type. +func FuncAFIRB(fn func(float64, int) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(f1, i2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncAFRB transform a function of 'func(float64) bool' signature +// into CallableFunc type. +func FuncAFRB(fn func(float64) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + if fn(f1) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASRS transform a function of 'func(string) string' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRS(fn func(string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.String{Value: fn(s1)}, nil + } +} + +// FuncASRSs transform a function of 'func(string) []string' signature into CallableFunc type. +func FuncASRSs(fn func(string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(s1) + + arr := &objects.Array{} + for _, osArg := range res { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + } +} + +// FuncASRSE transform a function of 'func(string) (string, error)' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + } +} + +// FuncASRE transform a function of 'func(string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRE(fn func(string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(fn(s1)), nil + } +} + +// FuncASSRE transform a function of 'func(string, string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASSRE(fn func(string, string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, s2)), nil + } +} + +// FuncASSRSs transform a function of 'func(string, string) []string' signature into CallableFunc type. +func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into CallableFunc type. +func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSRI transform a function of 'func(string, string) int' signature into CallableFunc type. +func FuncASSRI(fn func(string, string) int) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(s1, s2))}, nil + } +} + +// FuncASSRS transform a function of 'func(string, string) string' signature into CallableFunc type. +func FuncASSRS(fn func(string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(s1, s2)}, nil + } +} + +// FuncASSRB transform a function of 'func(string, string) bool' signature into CallableFunc type. +func FuncASSRB(fn func(string, string) bool) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(s1, s2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASsSRS transform a function of 'func([]string, string) string' signature into CallableFunc type. +func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + var ss1 []string + switch arg0 := args[0].(type) { + case *objects.Array: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + case *objects.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(ss1, s2)}, nil + } +} + +// FuncASI64RE transform a function of 'func(string, int64) error' signature +// into CallableFunc type. +func FuncASI64RE(fn func(string, int64) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, i2)), nil + } +} + +// FuncAIIRE transform a function of 'func(int, int) error' signature +// into CallableFunc type. +func FuncAIIRE(fn func(int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(i1, i2)), nil + } +} + +// FuncASIRS transform a function of 'func(string, int) string' signature +// into CallableFunc type. +func FuncASIRS(fn func(string, int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(s1, i2)}, nil + } +} + +// FuncASIIRE transform a function of 'func(string, int, int) error' signature +// into CallableFunc type. +func FuncASIIRE(fn func(string, int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + return wrapError(fn(s1, i2, i3)), nil + } +} + +// FuncAYRIE transform a function of 'func([]byte) (int, error)' signature +// into CallableFunc type. +func FuncAYRIE(fn func([]byte) (int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := objects.ToByteSlice(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(y1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncASRIE transform a function of 'func(string) (int, error)' signature +// into CallableFunc type. +func FuncASRIE(fn func(string) (int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature +// into CallableFunc type. +func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(i1) + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, r := range res { + arr.Value = append(arr.Value, &objects.String{Value: r}) + } + + return arr, nil + } +} + +// FuncAIRS transform a function of 'func(int) string' signature +// into CallableFunc type. +func FuncAIRS(fn func(int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.String{Value: fn(i1)}, nil + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/math.go b/vendor/github.com/d5/tengo/stdlib/math.go new file mode 100644 index 00000000..08d82bdf --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/math.go @@ -0,0 +1,74 @@ +package stdlib + +import ( + "math" + + "github.com/d5/tengo/objects" +) + +var mathModule = map[string]objects.Object{ + "e": &objects.Float{Value: math.E}, + "pi": &objects.Float{Value: math.Pi}, + "phi": &objects.Float{Value: math.Phi}, + "sqrt2": &objects.Float{Value: math.Sqrt2}, + "sqrtE": &objects.Float{Value: math.SqrtE}, + "sqrtPi": &objects.Float{Value: math.SqrtPi}, + "sqrtPhi": &objects.Float{Value: math.SqrtPhi}, + "ln2": &objects.Float{Value: math.Ln2}, + "log2E": &objects.Float{Value: math.Log2E}, + "ln10": &objects.Float{Value: math.Ln10}, + "log10E": &objects.Float{Value: math.Log10E}, + "abs": &objects.UserFunction{Name: "abs", Value: FuncAFRF(math.Abs)}, + "acos": &objects.UserFunction{Name: "acos", Value: FuncAFRF(math.Acos)}, + "acosh": &objects.UserFunction{Name: "acosh", Value: FuncAFRF(math.Acosh)}, + "asin": &objects.UserFunction{Name: "asin", Value: FuncAFRF(math.Asin)}, + "asinh": &objects.UserFunction{Name: "asinh", Value: FuncAFRF(math.Asinh)}, + "atan": &objects.UserFunction{Name: "atan", Value: FuncAFRF(math.Atan)}, + "atan2": &objects.UserFunction{Name: "atan2", Value: FuncAFFRF(math.Atan2)}, + "atanh": &objects.UserFunction{Name: "atanh", Value: FuncAFRF(math.Atanh)}, + "cbrt": &objects.UserFunction{Name: "cbrt", Value: FuncAFRF(math.Cbrt)}, + "ceil": &objects.UserFunction{Name: "ceil", Value: FuncAFRF(math.Ceil)}, + "copysign": &objects.UserFunction{Name: "copysign", Value: FuncAFFRF(math.Copysign)}, + "cos": &objects.UserFunction{Name: "cos", Value: FuncAFRF(math.Cos)}, + "cosh": &objects.UserFunction{Name: "cosh", Value: FuncAFRF(math.Cosh)}, + "dim": &objects.UserFunction{Name: "dim", Value: FuncAFFRF(math.Dim)}, + "erf": &objects.UserFunction{Name: "erf", Value: FuncAFRF(math.Erf)}, + "erfc": &objects.UserFunction{Name: "erfc", Value: FuncAFRF(math.Erfc)}, + "exp": &objects.UserFunction{Name: "exp", Value: FuncAFRF(math.Exp)}, + "exp2": &objects.UserFunction{Name: "exp2", Value: FuncAFRF(math.Exp2)}, + "expm1": &objects.UserFunction{Name: "expm1", Value: FuncAFRF(math.Expm1)}, + "floor": &objects.UserFunction{Name: "floor", Value: FuncAFRF(math.Floor)}, + "gamma": &objects.UserFunction{Name: "gamma", Value: FuncAFRF(math.Gamma)}, + "hypot": &objects.UserFunction{Name: "hypot", Value: FuncAFFRF(math.Hypot)}, + "ilogb": &objects.UserFunction{Name: "ilogb", Value: FuncAFRI(math.Ilogb)}, + "inf": &objects.UserFunction{Name: "inf", Value: FuncAIRF(math.Inf)}, + "is_inf": &objects.UserFunction{Name: "is_inf", Value: FuncAFIRB(math.IsInf)}, + "is_nan": &objects.UserFunction{Name: "is_nan", Value: FuncAFRB(math.IsNaN)}, + "j0": &objects.UserFunction{Name: "j0", Value: FuncAFRF(math.J0)}, + "j1": &objects.UserFunction{Name: "j1", Value: FuncAFRF(math.J1)}, + "jn": &objects.UserFunction{Name: "jn", Value: FuncAIFRF(math.Jn)}, + "ldexp": &objects.UserFunction{Name: "ldexp", Value: FuncAFIRF(math.Ldexp)}, + "log": &objects.UserFunction{Name: "log", Value: FuncAFRF(math.Log)}, + "log10": &objects.UserFunction{Name: "log10", Value: FuncAFRF(math.Log10)}, + "log1p": &objects.UserFunction{Name: "log1p", Value: FuncAFRF(math.Log1p)}, + "log2": &objects.UserFunction{Name: "log2", Value: FuncAFRF(math.Log2)}, + "logb": &objects.UserFunction{Name: "logb", Value: FuncAFRF(math.Logb)}, + "max": &objects.UserFunction{Name: "max", Value: FuncAFFRF(math.Max)}, + "min": &objects.UserFunction{Name: "min", Value: FuncAFFRF(math.Min)}, + "mod": &objects.UserFunction{Name: "mod", Value: FuncAFFRF(math.Mod)}, + "nan": &objects.UserFunction{Name: "nan", Value: FuncARF(math.NaN)}, + "nextafter": &objects.UserFunction{Name: "nextafter", Value: FuncAFFRF(math.Nextafter)}, + "pow": &objects.UserFunction{Name: "pow", Value: FuncAFFRF(math.Pow)}, + "pow10": &objects.UserFunction{Name: "pow10", Value: FuncAIRF(math.Pow10)}, + "remainder": &objects.UserFunction{Name: "remainder", Value: FuncAFFRF(math.Remainder)}, + "signbit": &objects.UserFunction{Name: "signbit", Value: FuncAFRB(math.Signbit)}, + "sin": &objects.UserFunction{Name: "sin", Value: FuncAFRF(math.Sin)}, + "sinh": &objects.UserFunction{Name: "sinh", Value: FuncAFRF(math.Sinh)}, + "sqrt": &objects.UserFunction{Name: "sqrt", Value: FuncAFRF(math.Sqrt)}, + "tan": &objects.UserFunction{Name: "tan", Value: FuncAFRF(math.Tan)}, + "tanh": &objects.UserFunction{Name: "tanh", Value: FuncAFRF(math.Tanh)}, + "trunc": &objects.UserFunction{Name: "trunc", Value: FuncAFRF(math.Trunc)}, + "y0": &objects.UserFunction{Name: "y0", Value: FuncAFRF(math.Y0)}, + "y1": &objects.UserFunction{Name: "y1", Value: FuncAFRF(math.Y1)}, + "yn": &objects.UserFunction{Name: "yn", Value: FuncAIFRF(math.Yn)}, +} diff --git a/vendor/github.com/d5/tengo/stdlib/os.go b/vendor/github.com/d5/tengo/stdlib/os.go new file mode 100644 index 00000000..e68d5101 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os.go @@ -0,0 +1,437 @@ +package stdlib + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + "github.com/d5/tengo/objects" +) + +var osModule = map[string]objects.Object{ + "o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)}, + "o_wronly": &objects.Int{Value: int64(os.O_WRONLY)}, + "o_rdwr": &objects.Int{Value: int64(os.O_RDWR)}, + "o_append": &objects.Int{Value: int64(os.O_APPEND)}, + "o_create": &objects.Int{Value: int64(os.O_CREATE)}, + "o_excl": &objects.Int{Value: int64(os.O_EXCL)}, + "o_sync": &objects.Int{Value: int64(os.O_SYNC)}, + "o_trunc": &objects.Int{Value: int64(os.O_TRUNC)}, + "mode_dir": &objects.Int{Value: int64(os.ModeDir)}, + "mode_append": &objects.Int{Value: int64(os.ModeAppend)}, + "mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)}, + "mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)}, + "mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)}, + "mode_device": &objects.Int{Value: int64(os.ModeDevice)}, + "mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)}, + "mode_socket": &objects.Int{Value: int64(os.ModeSocket)}, + "mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)}, + "mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)}, + "mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)}, + "mode_sticky": &objects.Int{Value: int64(os.ModeSticky)}, + "mode_type": &objects.Int{Value: int64(os.ModeType)}, + "mode_perm": &objects.Int{Value: int64(os.ModePerm)}, + "path_separator": &objects.Char{Value: os.PathSeparator}, + "path_list_separator": &objects.Char{Value: os.PathListSeparator}, + "dev_null": &objects.String{Value: os.DevNull}, + "seek_set": &objects.Int{Value: int64(io.SeekStart)}, + "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, + "seek_end": &objects.Int{Value: int64(io.SeekEnd)}, + "args": &objects.UserFunction{Value: osArgs}, // args() => array(string) + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error + "chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error + "chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error + "clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv() + "environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string) + "exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int) + "expand_env": &objects.UserFunction{Name: "expand_env", Value: FuncASRS(os.ExpandEnv)}, // expand_env(s string) => string + "getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int + "getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string + "geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int + "getgid": &objects.UserFunction{Name: "getgid", Value: FuncARI(os.Getgid)}, // getgid() => int + "getgroups": &objects.UserFunction{Name: "getgroups", Value: FuncARIsE(os.Getgroups)}, // getgroups() => array(string)/error + "getpagesize": &objects.UserFunction{Name: "getpagesize", Value: FuncARI(os.Getpagesize)}, // getpagesize() => int + "getpid": &objects.UserFunction{Name: "getpid", Value: FuncARI(os.Getpid)}, // getpid() => int + "getppid": &objects.UserFunction{Name: "getppid", Value: FuncARI(os.Getppid)}, // getppid() => int + "getuid": &objects.UserFunction{Name: "getuid", Value: FuncARI(os.Getuid)}, // getuid() => int + "getwd": &objects.UserFunction{Name: "getwd", Value: FuncARSE(os.Getwd)}, // getwd() => string/error + "hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error + "lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error + "link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error + "lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false + "mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error + "mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error + "readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error + "remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error + "remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error + "rename": &objects.UserFunction{Name: "rename", Value: FuncASSRE(os.Rename)}, // rename(oldpath string, newpath string) => error + "setenv": &objects.UserFunction{Name: "setenv", Value: FuncASSRE(os.Setenv)}, // setenv(key string, value string) => error + "symlink": &objects.UserFunction{Name: "symlink", Value: FuncASSRE(os.Symlink)}, // symlink(oldname string newname string) => error + "temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string + "truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error + "unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error + "create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error + "open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error + "open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error + "find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error + "start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error + "exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command + "stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error + "read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error +} + +func osReadFile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + bytes, err := ioutil.ReadFile(fname) + if err != nil { + return wrapError(err), nil + } + + return &objects.Bytes{Value: bytes}, nil +} + +func osStat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + stat, err := os.Stat(fname) + if err != nil { + return wrapError(err), nil + } + + fstat := &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "name": &objects.String{Value: stat.Name()}, + "mtime": &objects.Time{Value: stat.ModTime()}, + "size": &objects.Int{Value: stat.Size()}, + "mode": &objects.Int{Value: int64(stat.Mode())}, + }, + } + + if stat.IsDir() { + fstat.Value["directory"] = objects.TrueValue + } else { + fstat.Value["directory"] = objects.FalseValue + } + + return fstat, nil +} + +func osCreate(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Create(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpen(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Open(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpenFile(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + res, err := os.OpenFile(s1, i2, os.FileMode(i3)) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osArgs(args ...objects.Object) (objects.Object, error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range os.Args { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil +} + +func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, os.FileMode(i2))), nil + }, + } +} + +func osLookupEnv(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, ok := os.LookupEnv(s1) + if !ok { + return objects.FalseValue, nil + } + + return &objects.String{Value: res}, nil +} + +func osExec(args ...objects.Object) (objects.Object, error) { + if len(args) == 0 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var execArgs []string + for idx, arg := range args[1:] { + execArg, ok := objects.ToString(arg) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("args[%d]", idx), + Expected: "string(compatible)", + Found: args[1+idx].TypeName(), + } + } + + execArgs = append(execArgs, execArg) + } + + return makeOSExecCommand(exec.Command(name, execArgs...)), nil +} + +func osFindProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + proc, err := os.FindProcess(i1) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func osStartProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 4 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var argv []string + var err error + switch arg1 := args[1].(type) { + case *objects.Array: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "array", + Found: arg1.TypeName(), + } + } + + dir, ok := objects.ToString(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + var env []string + switch arg3 := args[3].(type) { + case *objects.Array: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "array", + Found: arg3.TypeName(), + } + } + + proc, err := os.StartProcess(name, argv, &os.ProcAttr{ + Dir: dir, + Env: env, + }) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func stringArray(arr []objects.Object, argName string) ([]string, error) { + var sarr []string + for idx, elem := range arr { + str, ok := elem.(*objects.String) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("%s[%d]", argName, idx), + Expected: "string", + Found: elem.TypeName(), + } + } + + sarr = append(sarr, str.Value) + } + + return sarr, nil +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_exec.go b/vendor/github.com/d5/tengo/stdlib/os_exec.go new file mode 100644 index 00000000..809c5810 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_exec.go @@ -0,0 +1,109 @@ +package stdlib + +import ( + "os/exec" + + "github.com/d5/tengo/objects" +) + +func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // combined_output() => bytes/error + "combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, // + // output() => bytes/error + "output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, // + // run() => error + "run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, // + // start() => error + "start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, // + // wait() => error + "wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, // + // set_path(path string) + "set_path": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + cmd.Path = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_dir(dir string) + "set_dir": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + cmd.Dir = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_env(env array(string)) + "set_env": &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + var env []string + var err error + switch arg0 := args[0].(type) { + case *objects.Array: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg0.TypeName(), + } + } + + cmd.Env = env + + return objects.UndefinedValue, nil + }, + }, + // process() => imap(process) + "process": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return makeOSProcess(cmd.Process), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_file.go b/vendor/github.com/d5/tengo/stdlib/os_file.go new file mode 100644 index 00000000..4fc41dd8 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_file.go @@ -0,0 +1,93 @@ +package stdlib + +import ( + "os" + + "github.com/d5/tengo/objects" +) + +func makeOSFile(file *os.File) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // chdir() => true/error + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, // + // chown(uid int, gid int) => true/error + "chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, // + // close() => error + "close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, // + // name() => string + "name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, // + // readdirnames(n int) => array(string)/error + "readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, // + // sync() => error + "sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, // + // write(bytes) => int/error + "write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, // + // write(string) => int/error + "write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, // + // read(bytes) => int/error + "read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, // + // chmod(mode int) => error + "chmod": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(file.Chmod(os.FileMode(i1))), nil + }, + }, + // seek(offset int, whence int) => int/error + "seek": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + res, err := file.Seek(i1, i2) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: res}, nil + }, + }, + // stat() => imap(fileinfo)/error + "stat": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return osStat(&objects.String{Value: file.Name()}) + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/os_process.go b/vendor/github.com/d5/tengo/stdlib/os_process.go new file mode 100644 index 00000000..0f4a9f77 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/os_process.go @@ -0,0 +1,60 @@ +package stdlib + +import ( + "os" + "syscall" + + "github.com/d5/tengo/objects" +) + +func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, // + "pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, // + "string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, // + "success": &objects.UserFunction{Name: "success", Value: FuncARB(state.Success)}, // + }, + } +} + +func makeOSProcess(proc *os.Process) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, // + "release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // + "signal": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(proc.Signal(syscall.Signal(i1))), nil + }, + }, + "wait": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + state, err := proc.Wait() + if err != nil { + return wrapError(err), nil + } + + return makeOSProcessState(state), nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/rand.go b/vendor/github.com/d5/tengo/stdlib/rand.go new file mode 100644 index 00000000..248d8e79 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/rand.go @@ -0,0 +1,99 @@ +package stdlib + +import ( + "math/rand" + + "github.com/d5/tengo/objects" +) + +var randModule = map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(rand.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(rand.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(rand.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(rand.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(rand.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, + "read": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := rand.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + "rand": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + src := rand.NewSource(i1) + + return randRand(rand.New(src)), nil + }, + }, +} + +func randRand(r *rand.Rand) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(r.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(r.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(r.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(r.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(r.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, + "read": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := r.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/stdlib.go b/vendor/github.com/d5/tengo/stdlib/stdlib.go new file mode 100644 index 00000000..d34fbc82 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/stdlib.go @@ -0,0 +1,16 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +// Modules contain the standard modules. +var Modules = map[string]*objects.Object{ + "math": objectPtr(&objects.ImmutableMap{Value: mathModule}), + "os": objectPtr(&objects.ImmutableMap{Value: osModule}), + "text": objectPtr(&objects.ImmutableMap{Value: textModule}), + "times": objectPtr(&objects.ImmutableMap{Value: timesModule}), + "rand": objectPtr(&objects.ImmutableMap{Value: randModule}), +} + +func objectPtr(o objects.Object) *objects.Object { + return &o +} diff --git a/vendor/github.com/d5/tengo/stdlib/text.go b/vendor/github.com/d5/tengo/stdlib/text.go new file mode 100644 index 00000000..053bebf5 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/text.go @@ -0,0 +1,585 @@ +package stdlib + +import ( + "regexp" + "strconv" + "strings" + + "github.com/d5/tengo/objects" +) + +var textModule = map[string]objects.Object{ + "re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error + "re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined + "re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error + "re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error + "re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error + "compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int + "contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool + "contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool + "count": &objects.UserFunction{Name: "count", Value: FuncASSRI(strings.Count)}, // count(s, substr) => int + "equal_fold": &objects.UserFunction{Name: "equal_fold", Value: FuncASSRB(strings.EqualFold)}, // "equal_fold(s, t) => bool + "fields": &objects.UserFunction{Name: "fields", Value: FuncASRSs(strings.Fields)}, // fields(s) => [string] + "has_prefix": &objects.UserFunction{Name: "has_prefix", Value: FuncASSRB(strings.HasPrefix)}, // has_prefix(s, prefix) => bool + "has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool + "index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int + "index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int + "join": &objects.UserFunction{Name: "join", Value: FuncASsSRS(strings.Join)}, // join(arr, sep) => string + "last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int + "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int + "repeat": &objects.UserFunction{Name: "repeat", Value: FuncASIRS(strings.Repeat)}, // repeat(s, count) => string + "replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string + "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] + "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] + "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] + "split_n": &objects.UserFunction{Name: "split_n", Value: FuncASSIRSs(strings.SplitN)}, // split_n(s, sep, n) => [string] + "title": &objects.UserFunction{Name: "title", Value: FuncASRS(strings.Title)}, // title(s) => string + "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string + "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string + "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string + "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string + "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string + "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string + "trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string + "trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string + "atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error + "format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string + "format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string + "format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string + "itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string + "parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error + "parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error + "parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error + "quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string + "unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error +} + +func textREMatch(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + matched, err := regexp.MatchString(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + if matched { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textREFind(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + if numArgs < 3 { + m := re.FindStringSubmatchIndex(s2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s2, i3) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return +} + +func textREReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = &objects.String{Value: re.ReplaceAllString(s2, s3)} + } + + return +} + +func textRESplit(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + var i3 = -1 + if numArgs > 2 { + i3, ok = objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + arr := &objects.Array{} + for _, s := range re.Split(s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return +} + +func textRECompile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = makeTextRegexp(re) + } + + return +} + +func textReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)} + + return +} + +func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + b1, ok := args[0].(*objects.Bool) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bool", + Found: args[0].TypeName(), + } + return + } + + if b1 == objects.TrueValue { + ret = &objects.String{Value: "true"} + } else { + ret = &objects.String{Value: "false"} + } + + return +} + +func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + f1, ok := args[0].(*objects.Float) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + + return +} + +func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := args[0].(*objects.Int) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)} + + return +} + +func textParseBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + parsed, err := strconv.ParseBool(s1.Value) + if err != nil { + ret = wrapError(err) + return + } + + if parsed { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + parsed, err := strconv.ParseFloat(s1.Value, i2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Float{Value: parsed} + + return +} + +func textParseInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + parsed, err := strconv.ParseInt(s1.Value, i2, i3) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Int{Value: parsed} + + return +} diff --git a/vendor/github.com/d5/tengo/stdlib/text_regexp.go b/vendor/github.com/d5/tengo/stdlib/text_regexp.go new file mode 100644 index 00000000..3fb8b3b3 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/text_regexp.go @@ -0,0 +1,195 @@ +package stdlib + +import ( + "regexp" + + "github.com/d5/tengo/objects" +) + +func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // match(text) => bool + "match": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if re.MatchString(s1) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return + }, + }, + + // find(text) => array(array({text:,begin:,end:}))/undefined + // find(text, maxCount) => array(array({text:,begin:,end:}))/undefined + "find": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + if numArgs == 1 { + m := re.FindStringSubmatchIndex(s1) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s1, i2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s1[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return + }, + }, + + // replace(src, repl) => string + "replace": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.String{Value: re.ReplaceAllString(s1, s2)} + + return + }, + }, + + // split(text) => array(string) + // split(text, maxCount) => array(string) + "split": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 1 && numArgs != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + var i2 = -1 + if numArgs > 1 { + i2, ok = objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + } + + arr := &objects.Array{} + for _, s := range re.Split(s1, i2) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return + }, + }, + }, + } +} diff --git a/vendor/github.com/d5/tengo/stdlib/times.go b/vendor/github.com/d5/tengo/stdlib/times.go new file mode 100644 index 00000000..16d6d146 --- /dev/null +++ b/vendor/github.com/d5/tengo/stdlib/times.go @@ -0,0 +1,982 @@ +package stdlib + +import ( + "time" + + "github.com/d5/tengo/objects" +) + +var timesModule = map[string]objects.Object{ + "format_ansic": &objects.String{Value: time.ANSIC}, + "format_unix_date": &objects.String{Value: time.UnixDate}, + "format_ruby_date": &objects.String{Value: time.RubyDate}, + "format_rfc822": &objects.String{Value: time.RFC822}, + "format_rfc822z": &objects.String{Value: time.RFC822Z}, + "format_rfc850": &objects.String{Value: time.RFC850}, + "format_rfc1123": &objects.String{Value: time.RFC1123}, + "format_rfc1123z": &objects.String{Value: time.RFC1123Z}, + "format_rfc3339": &objects.String{Value: time.RFC3339}, + "format_rfc3339_nano": &objects.String{Value: time.RFC3339Nano}, + "format_kitchen": &objects.String{Value: time.Kitchen}, + "format_stamp": &objects.String{Value: time.Stamp}, + "format_stamp_milli": &objects.String{Value: time.StampMilli}, + "format_stamp_micro": &objects.String{Value: time.StampMicro}, + "format_stamp_nano": &objects.String{Value: time.StampNano}, + "nanosecond": &objects.Int{Value: int64(time.Nanosecond)}, + "microsecond": &objects.Int{Value: int64(time.Microsecond)}, + "millisecond": &objects.Int{Value: int64(time.Millisecond)}, + "second": &objects.Int{Value: int64(time.Second)}, + "minute": &objects.Int{Value: int64(time.Minute)}, + "hour": &objects.Int{Value: int64(time.Hour)}, + "january": &objects.Int{Value: int64(time.January)}, + "february": &objects.Int{Value: int64(time.February)}, + "march": &objects.Int{Value: int64(time.March)}, + "april": &objects.Int{Value: int64(time.April)}, + "may": &objects.Int{Value: int64(time.May)}, + "june": &objects.Int{Value: int64(time.June)}, + "july": &objects.Int{Value: int64(time.July)}, + "august": &objects.Int{Value: int64(time.August)}, + "september": &objects.Int{Value: int64(time.September)}, + "october": &objects.Int{Value: int64(time.October)}, + "november": &objects.Int{Value: int64(time.November)}, + "december": &objects.Int{Value: int64(time.December)}, + "sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int) + "parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int + "since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int + "until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int + "duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float + "duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float + "duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int + "duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float + "duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string + "month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string + "date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time + "now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time + "parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time + "unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time + "add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time + "add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time + "sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int + "after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool + "before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool + "time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int + "time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int + "time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int + "time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int + "time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int + "time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int + "time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int + "time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int + "time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int + "time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int + "time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string + "time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string + "time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string + "is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool + "to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time + "to_utc": &objects.UserFunction{Name: "to_utc", Value: timesToUTC}, // to_utc(time) => time +} + +func timesSleep(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + time.Sleep(time.Duration(i1)) + ret = objects.UndefinedValue + + return +} + +func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + dur, err := time.ParseDuration(s1) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Int{Value: int64(dur)} + + return +} + +func timesSince(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(time.Since(t1))} + + return +} + +func timesUntil(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(time.Until(t1))} + + return +} + +func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Hours()} + + return +} + +func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Minutes()} + + return +} + +func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: time.Duration(i1).Nanoseconds()} + + return +} + +func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Float{Value: time.Duration(i1).Seconds()} + + return +} + +func timesDurationString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: time.Duration(i1).String()} + + return +} + +func timesMonthString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: time.Month(i1).String()} + + return +} + +func timesDate(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 7 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + i5, ok := objects.ToInt(args[4]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fifth", + Expected: "int(compatible)", + Found: args[4].TypeName(), + } + return + } + i6, ok := objects.ToInt(args[5]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "sixth", + Expected: "int(compatible)", + Found: args[5].TypeName(), + } + return + } + i7, ok := objects.ToInt(args[6]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "seventh", + Expected: "int(compatible)", + Found: args[6].TypeName(), + } + return + } + + ret = &objects.Time{Value: time.Date(i1, time.Month(i2), i3, i4, i5, i6, i7, time.Now().Location())} + + return +} + +func timesNow(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + err = objects.ErrWrongNumArguments + return + } + + ret = &objects.Time{Value: time.Now()} + + return +} + +func timesParse(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + parsed, err := time.Parse(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Time{Value: parsed} + + return +} + +func timesUnix(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Time{Value: time.Unix(i1, i2)} + + return +} + +func timesAdd(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.Add(time.Duration(i2))} + + return +} + +func timesSub(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Sub(t2))} + + return +} + +func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.AddDate(i2, i3, i4)} + + return +} + +func timesAfter(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } + return + } + + if t1.After(t2) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesBefore(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + t2, ok := objects.ToTime(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.Before(t2) { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Year())} + + return +} + +func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Month())} + + return +} + +func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Day())} + + return +} + +func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Weekday())} + + return +} + +func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Hour())} + + return +} + +func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Minute())} + + return +} + +func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Second())} + + return +} + +func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Nanosecond())} + + return +} + +func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.Unix())} + + return +} + +func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Int{Value: int64(t1.UnixNano())} + + return +} + +func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.String{Value: t1.Format(s2)} + + return +} + +func timesIsZero(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + if t1.IsZero() { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func timesToLocal(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.Local()} + + return +} + +func timesToUTC(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.Time{Value: t1.UTC()} + + return +} + +func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: t1.Location().String()} + + return +} + +func timesTimeString(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + t1, ok := objects.ToTime(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } + return + } + + ret = &objects.String{Value: t1.String()} + + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 1546cf3b..740bc5dc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -25,6 +25,17 @@ github.com/Rhymen/go-whatsapp/crypto/hkdf github.com/Rhymen/go-whatsapp/binary/token # github.com/bwmarrin/discordgo v0.19.0 github.com/bwmarrin/discordgo +# github.com/d5/tengo v1.9.2 +github.com/d5/tengo/script +github.com/d5/tengo/compiler +github.com/d5/tengo/compiler/parser +github.com/d5/tengo/compiler/source +github.com/d5/tengo/objects +github.com/d5/tengo/runtime +github.com/d5/tengo/stdlib +github.com/d5/tengo/compiler/ast +github.com/d5/tengo/compiler/token +github.com/d5/tengo/compiler/scanner # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec