summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--bridge/config/config.go1
-rw-r--r--contrib/example.tengo2
-rw-r--r--gateway/bench.tengo5
-rw-r--r--gateway/gateway.go31
-rw-r--r--gateway/gateway_test.go10
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--matterbridge.toml.sample23
-rw-r--r--vendor/github.com/d5/tengo/LICENSE21
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/array_lit.go35
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go40
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/ast.go5
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/bad_expr.go25
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go25
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/binary_expr.go30
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/block_stmt.go35
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/bool_lit.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go38
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/call_expr.go36
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/char_lit.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/cond_expr.go30
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/error_expr.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/export_stmt.go27
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/expr.go7
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go24
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/file.go32
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/float_lit.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go32
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/for_stmt.go43
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/func_lit.go25
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/func_type.go25
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/ident.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/ident_list.go58
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/if_stmt.go40
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/immutable_expr.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/import_expr.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/inc_dec_stmt.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/index_expr.go32
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/int_lit.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/map_element_lit.go27
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/map_lit.go35
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/node.go13
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/paren_expr.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/return_stmt.go35
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/selector_expr.go25
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/slice_expr.go36
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/stmt.go7
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/string_lit.go26
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/unary_expr.go29
-rw-r--r--vendor/github.com/d5/tengo/compiler/ast/undefined_lit.go24
-rw-r--r--vendor/github.com/d5/tengo/compiler/bytecode.go134
-rw-r--r--vendor/github.com/d5/tengo/compiler/compilation_scope.go12
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler.go731
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_assign.go133
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_for.go181
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_logical.go30
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_loops.go31
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_module.go123
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler_scopes.go43
-rw-r--r--vendor/github.com/d5/tengo/compiler/emitted_instruction.go8
-rw-r--r--vendor/github.com/d5/tengo/compiler/error.go20
-rw-r--r--vendor/github.com/d5/tengo/compiler/instructions.go59
-rw-r--r--vendor/github.com/d5/tengo/compiler/loop.go8
-rw-r--r--vendor/github.com/d5/tengo/compiler/module_loader.go4
-rw-r--r--vendor/github.com/d5/tengo/compiler/opcodes.go191
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/error.go21
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/error_list.go68
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/parse_file.go28
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/parse_source.go16
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/parser.go1181
-rw-r--r--vendor/github.com/d5/tengo/compiler/parser/sync.go12
-rw-r--r--vendor/github.com/d5/tengo/compiler/scanner/error_handler.go6
-rw-r--r--vendor/github.com/d5/tengo/compiler/scanner/mode.go10
-rw-r--r--vendor/github.com/d5/tengo/compiler/scanner/scanner.go680
-rw-r--r--vendor/github.com/d5/tengo/compiler/source/file.go110
-rw-r--r--vendor/github.com/d5/tengo/compiler/source/file_pos.go47
-rw-r--r--vendor/github.com/d5/tengo/compiler/source/file_set.go96
-rw-r--r--vendor/github.com/d5/tengo/compiler/source/pos.go12
-rw-r--r--vendor/github.com/d5/tengo/compiler/symbol.go9
-rw-r--r--vendor/github.com/d5/tengo/compiler/symbol_scopes.go12
-rw-r--r--vendor/github.com/d5/tengo/compiler/symbol_table.go145
-rw-r--r--vendor/github.com/d5/tengo/compiler/token/keywords.go19
-rw-r--r--vendor/github.com/d5/tengo/compiler/token/tokens.go208
-rw-r--r--vendor/github.com/d5/tengo/objects/array.go130
-rw-r--r--vendor/github.com/d5/tengo/objects/array_iterator.go57
-rw-r--r--vendor/github.com/d5/tengo/objects/bool.go64
-rw-r--r--vendor/github.com/d5/tengo/objects/break.go37
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_append.go21
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_convert.go155
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_copy.go9
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_function.go47
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_json.go54
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_len.go29
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_print.go75
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_type.go9
-rw-r--r--vendor/github.com/d5/tengo/objects/builtin_type_checks.go183
-rw-r--r--vendor/github.com/d5/tengo/objects/builtins.go135
-rw-r--r--vendor/github.com/d5/tengo/objects/bytes.go76
-rw-r--r--vendor/github.com/d5/tengo/objects/callable.go9
-rw-r--r--vendor/github.com/d5/tengo/objects/callable_func.go4
-rw-r--r--vendor/github.com/d5/tengo/objects/char.go119
-rw-r--r--vendor/github.com/d5/tengo/objects/closure.go45
-rw-r--r--vendor/github.com/d5/tengo/objects/compiled_function.go49
-rw-r--r--vendor/github.com/d5/tengo/objects/continue.go38
-rw-r--r--vendor/github.com/d5/tengo/objects/conversion.go249
-rw-r--r--vendor/github.com/d5/tengo/objects/error.go47
-rw-r--r--vendor/github.com/d5/tengo/objects/errors.go32
-rw-r--r--vendor/github.com/d5/tengo/objects/float.go146
-rw-r--r--vendor/github.com/d5/tengo/objects/immautable_array.go109
-rw-r--r--vendor/github.com/d5/tengo/objects/immutable_map.go105
-rw-r--r--vendor/github.com/d5/tengo/objects/index_assignable.go9
-rw-r--r--vendor/github.com/d5/tengo/objects/indexable.go9
-rw-r--r--vendor/github.com/d5/tengo/objects/int.go198
-rw-r--r--vendor/github.com/d5/tengo/objects/iterable.go7
-rw-r--r--vendor/github.com/d5/tengo/objects/iterator.go15
-rw-r--r--vendor/github.com/d5/tengo/objects/map.go118
-rw-r--r--vendor/github.com/d5/tengo/objects/map_iterator.go62
-rw-r--r--vendor/github.com/d5/tengo/objects/object.go30
-rw-r--r--vendor/github.com/d5/tengo/objects/objects.go12
-rw-r--r--vendor/github.com/d5/tengo/objects/return_value.go39
-rw-r--r--vendor/github.com/d5/tengo/objects/string.go95
-rw-r--r--vendor/github.com/d5/tengo/objects/string_iterator.go57
-rw-r--r--vendor/github.com/d5/tengo/objects/time.go89
-rw-r--r--vendor/github.com/d5/tengo/objects/undefined.go42
-rw-r--r--vendor/github.com/d5/tengo/objects/user_function.go47
-rw-r--r--vendor/github.com/d5/tengo/runtime/errors.go8
-rw-r--r--vendor/github.com/d5/tengo/runtime/frame.go13
-rw-r--r--vendor/github.com/d5/tengo/runtime/vm.go1424
-rw-r--r--vendor/github.com/d5/tengo/script/compiled.go113
-rw-r--r--vendor/github.com/d5/tengo/script/conversion.go33
-rw-r--r--vendor/github.com/d5/tengo/script/script.go180
-rw-r--r--vendor/github.com/d5/tengo/script/variable.go149
-rw-r--r--vendor/github.com/d5/tengo/stdlib/errors.go11
-rw-r--r--vendor/github.com/d5/tengo/stdlib/func_typedefs.go1057
-rw-r--r--vendor/github.com/d5/tengo/stdlib/math.go74
-rw-r--r--vendor/github.com/d5/tengo/stdlib/os.go437
-rw-r--r--vendor/github.com/d5/tengo/stdlib/os_exec.go109
-rw-r--r--vendor/github.com/d5/tengo/stdlib/os_file.go93
-rw-r--r--vendor/github.com/d5/tengo/stdlib/os_process.go60
-rw-r--r--vendor/github.com/d5/tengo/stdlib/rand.go99
-rw-r--r--vendor/github.com/d5/tengo/stdlib/stdlib.go16
-rw-r--r--vendor/github.com/d5/tengo/stdlib/text.go585
-rw-r--r--vendor/github.com/d5/tengo/stdlib/text_regexp.go195
-rw-r--r--vendor/github.com/d5/tengo/stdlib/times.go982
-rw-r--r--vendor/modules.txt11
147 files changed, 14201 insertions, 0 deletions
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
<!-- Links -->
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 = "<null>"
+)
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 "<bad expression>"
+}
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 "<bad statement>"
+}
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, "<nil>"))
+ }
+ }
+
+ 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 "<array-iterator>"
+}
+
+// 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 "<break>"
+}
+
+// BinaryOp returns another object that is the result of
+// a given binary operator and a right-hand side object.
+func (o *Break) BinaryOp(op token.Token, rhs Object) (Object, error) {
+ return nil, ErrInvalidOperator
+}
+
+// Copy returns a copy of the type.
+func (o *Break) Copy() Object {
+ return &Break{}
+}
+
+// IsFalsy returns true if the value of the type is falsy.
+func (o *Break) IsFalsy() bool {
+ return false
+}
+
+// Equals returns true if the value of the type
+// is equal to the value of another object.
+func (o *Break) Equals(x Object) bool {
+ return false
+}
diff --git a/vendor/github.com/d5/tengo/objects/builtin_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 "<builtin-function>"
+}
+
+// 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 "<closure>"
+}
+
+// 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 "<compiled-function>"
+}
+
+// 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 "<continue>"
+}
+
+// BinaryOp returns another object that is the result of
+// a given binary operator and a right-hand side object.
+func (o *Continue) BinaryOp(op token.Token, rhs Object) (Object, error) {
+ return nil, ErrInvalidOperator
+}
+
+// Copy returns a copy of the type.
+func (o *Continue) Copy() Object {
+ return &Continue{}
+}
+
+// IsFalsy returns true if the value of the type is falsy.
+func (o *Continue) IsFalsy() bool {
+ return false
+}
+
+// Equals returns true if the value of the type
+// is equal to the value of another object.
+func (o *Continue) Equals(x Object) bool {
+ return false
+}
diff --git a/vendor/github.com/d5/tengo/objects/conversion.go b/vendor/github.com/d5/tengo/objects/conversion.go
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 "<map-iterator>"
+}
+
+// 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 "<return-value>"
+}
+
+// BinaryOp returns another object that is the result of
+// a given binary operator and a right-hand side object.
+func (o *ReturnValue) BinaryOp(op token.Token, rhs Object) (Object, error) {
+ return nil, ErrInvalidOperator
+}
+
+// Copy returns a copy of the type.
+func (o *ReturnValue) Copy() Object {
+ return &ReturnValue{Value: o.Copy()}
+}
+
+// IsFalsy returns true if the value of the type is falsy.
+func (o *ReturnValue) IsFalsy() bool {
+ return false
+}
+
+// Equals returns true if the value of the type
+// is equal to the value of another object.
+func (o *ReturnValue) Equals(x Object) bool {
+ return false
+}
diff --git a/vendor/github.com/d5/tengo/objects/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 "<string-iterator>"
+}
+
+// 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 "<undefined>"
+}
+
+// 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 "<user-function>"
+}
+
+// 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