diff options
author | Wim <wim@42.be> | 2019-02-23 16:39:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-23 16:39:44 +0100 |
commit | 1bb39eba8717f62336cc98c5bb7cfbef194f3626 (patch) | |
tree | 0437ae89473b8e25ad1c9597e1049a23a7933f6a /vendor/github.com/d5/tengo/compiler | |
parent | 3190703dc8618896c932a23d8ca155fbbf6fab13 (diff) | |
download | matterbridge-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')
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 +} |