summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/d5/tengo/compiler
diff options
context:
space:
mode:
authorWim <wim@42.be>2019-02-23 16:39:44 +0100
committerGitHub <noreply@github.com>2019-02-23 16:39:44 +0100
commit1bb39eba8717f62336cc98c5bb7cfbef194f3626 (patch)
tree0437ae89473b8e25ad1c9597e1049a23a7933f6a /vendor/github.com/d5/tengo/compiler
parent3190703dc8618896c932a23d8ca155fbbf6fab13 (diff)
downloadmatterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.tar.gz
matterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.tar.bz2
matterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.zip
Add scripting (tengo) support for every incoming message (#731)
TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script. This script will receive every incoming message and can be used to modify the Username and the Text of that message. The script will have the following global variables: to modify: msgUsername and msgText to read: msgChannel and msgAccount The script is reloaded on every message, so you can modify the script on the fly. Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo The example below will check if the text contains blah and if so, it'll replace the text and the username of that message. text := import("text") if text.re_match("blah",msgText) { msgText="replaced by this" msgUsername="fakeuser" } More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and https://github.com/d5/tengo/blob/master/docs/stdlib.md
Diffstat (limited to 'vendor/github.com/d5/tengo/compiler')
-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
75 files changed, 5593 insertions, 0 deletions
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
+}