4
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2025-06-29 00:16:20 +00:00

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
This commit is contained in:
Wim
2019-02-23 16:39:44 +01:00
committed by GitHub
parent 3190703dc8
commit 1bb39eba87
147 changed files with 14201 additions and 0 deletions

35
vendor/github.com/d5/tengo/compiler/ast/array_lit.go generated vendored Normal file
View File

@ -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, ", ") + "]"
}

40
vendor/github.com/d5/tengo/compiler/ast/assign_stmt.go generated vendored Normal file
View File

@ -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, ", ")
}

5
vendor/github.com/d5/tengo/compiler/ast/ast.go generated vendored Normal file
View File

@ -0,0 +1,5 @@
package ast
const (
nullRep = "<null>"
)

25
vendor/github.com/d5/tengo/compiler/ast/bad_expr.go generated vendored Normal file
View File

@ -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>"
}

25
vendor/github.com/d5/tengo/compiler/ast/bad_stmt.go generated vendored Normal file
View File

@ -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>"
}

30
vendor/github.com/d5/tengo/compiler/ast/binary_expr.go generated vendored Normal file
View File

@ -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() + ")"
}

35
vendor/github.com/d5/tengo/compiler/ast/block_stmt.go generated vendored Normal file
View File

@ -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, "; ") + "}"
}

26
vendor/github.com/d5/tengo/compiler/ast/bool_lit.go generated vendored Normal file
View File

@ -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
}

38
vendor/github.com/d5/tengo/compiler/ast/branch_stmt.go generated vendored Normal file
View File

@ -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
}

36
vendor/github.com/d5/tengo/compiler/ast/call_expr.go generated vendored Normal file
View File

@ -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, ", ") + ")"
}

26
vendor/github.com/d5/tengo/compiler/ast/char_lit.go generated vendored Normal file
View File

@ -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
}

30
vendor/github.com/d5/tengo/compiler/ast/cond_expr.go generated vendored Normal file
View File

@ -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() + ")"
}

29
vendor/github.com/d5/tengo/compiler/ast/empty_stmt.go generated vendored Normal file
View File

@ -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 ";"
}

29
vendor/github.com/d5/tengo/compiler/ast/error_expr.go generated vendored Normal file
View File

@ -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() + ")"
}

27
vendor/github.com/d5/tengo/compiler/ast/export_stmt.go generated vendored Normal file
View File

@ -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()
}

7
vendor/github.com/d5/tengo/compiler/ast/expr.go generated vendored Normal file
View File

@ -0,0 +1,7 @@
package ast
// Expr represents an expression node in the AST.
type Expr interface {
Node
exprNode()
}

24
vendor/github.com/d5/tengo/compiler/ast/expr_stmt.go generated vendored Normal file
View File

@ -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()
}

32
vendor/github.com/d5/tengo/compiler/ast/file.go generated vendored Normal file
View File

@ -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, "; ")
}

26
vendor/github.com/d5/tengo/compiler/ast/float_lit.go generated vendored Normal file
View File

@ -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
}

32
vendor/github.com/d5/tengo/compiler/ast/for_in_stmt.go generated vendored Normal file
View File

@ -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()
}

43
vendor/github.com/d5/tengo/compiler/ast/for_stmt.go generated vendored Normal file
View File

@ -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()
}

25
vendor/github.com/d5/tengo/compiler/ast/func_lit.go generated vendored Normal file
View File

@ -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()
}

25
vendor/github.com/d5/tengo/compiler/ast/func_type.go generated vendored Normal file
View File

@ -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()
}

29
vendor/github.com/d5/tengo/compiler/ast/ident.go generated vendored Normal file
View File

@ -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
}

58
vendor/github.com/d5/tengo/compiler/ast/ident_list.go generated vendored Normal file
View File

@ -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, ", ") + ")"
}

40
vendor/github.com/d5/tengo/compiler/ast/if_stmt.go generated vendored Normal file
View File

@ -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
}

View File

@ -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() + ")"
}

29
vendor/github.com/d5/tengo/compiler/ast/import_expr.go generated vendored Normal file
View File

@ -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 + `")"`
}

View File

@ -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()
}

32
vendor/github.com/d5/tengo/compiler/ast/index_expr.go generated vendored Normal file
View File

@ -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 + "]"
}

26
vendor/github.com/d5/tengo/compiler/ast/int_lit.go generated vendored Normal file
View File

@ -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
}

View File

@ -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()
}

35
vendor/github.com/d5/tengo/compiler/ast/map_lit.go generated vendored Normal file
View File

@ -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, ", ") + "}"
}

13
vendor/github.com/d5/tengo/compiler/ast/node.go generated vendored Normal file
View File

@ -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
}

26
vendor/github.com/d5/tengo/compiler/ast/paren_expr.go generated vendored Normal file
View File

@ -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() + ")"
}

35
vendor/github.com/d5/tengo/compiler/ast/return_stmt.go generated vendored Normal file
View File

@ -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"
}

View File

@ -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()
}

36
vendor/github.com/d5/tengo/compiler/ast/slice_expr.go generated vendored Normal file
View File

@ -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 + "]"
}

7
vendor/github.com/d5/tengo/compiler/ast/stmt.go generated vendored Normal file
View File

@ -0,0 +1,7 @@
package ast
// Stmt represents a statement in the AST.
type Stmt interface {
Node
stmtNode()
}

26
vendor/github.com/d5/tengo/compiler/ast/string_lit.go generated vendored Normal file
View File

@ -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
}

29
vendor/github.com/d5/tengo/compiler/ast/unary_expr.go generated vendored Normal file
View File

@ -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() + ")"
}

View File

@ -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"
}

134
vendor/github.com/d5/tengo/compiler/bytecode.go generated vendored Normal file
View File

@ -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{})
}

View File

@ -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
}

731
vendor/github.com/d5/tengo/compiler/compiler.go generated vendored Normal file
View File

@ -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("}")
}

133
vendor/github.com/d5/tengo/compiler/compiler_assign.go generated vendored Normal file
View File

@ -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
}

181
vendor/github.com/d5/tengo/compiler/compiler_for.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

31
vendor/github.com/d5/tengo/compiler/compiler_loops.go generated vendored Normal file
View File

@ -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
}

123
vendor/github.com/d5/tengo/compiler/compiler_module.go generated vendored Normal file
View File

@ -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
}

43
vendor/github.com/d5/tengo/compiler/compiler_scopes.go generated vendored Normal file
View File

@ -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
}

View File

@ -0,0 +1,8 @@
package compiler
// EmittedInstruction represents an opcode
// with its emitted position.
type EmittedInstruction struct {
Opcode Opcode
Position int
}

20
vendor/github.com/d5/tengo/compiler/error.go generated vendored Normal file
View File

@ -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)
}

59
vendor/github.com/d5/tengo/compiler/instructions.go generated vendored Normal file
View File

@ -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
}

8
vendor/github.com/d5/tengo/compiler/loop.go generated vendored Normal file
View File

@ -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
}

4
vendor/github.com/d5/tengo/compiler/module_loader.go generated vendored Normal file
View File

@ -0,0 +1,4 @@
package compiler
// ModuleLoader should take a module name and return the module data.
type ModuleLoader func(moduleName string) ([]byte, error)

191
vendor/github.com/d5/tengo/compiler/opcodes.go generated vendored Normal file
View File

@ -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
}

21
vendor/github.com/d5/tengo/compiler/parser/error.go generated vendored Normal file
View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

1181
vendor/github.com/d5/tengo/compiler/parser/parser.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

12
vendor/github.com/d5/tengo/compiler/parser/sync.go generated vendored Normal file
View File

@ -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,
}

View File

@ -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)

10
vendor/github.com/d5/tengo/compiler/scanner/mode.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package scanner
// Mode represents a scanner mode.
type Mode int
// List of scanner modes.
const (
ScanComments Mode = 1 << iota
DontInsertSemis
)

680
vendor/github.com/d5/tengo/compiler/scanner/scanner.go generated vendored Normal file
View File

@ -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
}

110
vendor/github.com/d5/tengo/compiler/source/file.go generated vendored Normal file
View File

@ -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
}

47
vendor/github.com/d5/tengo/compiler/source/file_pos.go generated vendored Normal file
View File

@ -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
}

96
vendor/github.com/d5/tengo/compiler/source/file_set.go generated vendored Normal file
View File

@ -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
}

12
vendor/github.com/d5/tengo/compiler/source/pos.go generated vendored Normal file
View File

@ -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
}

9
vendor/github.com/d5/tengo/compiler/symbol.go generated vendored Normal file
View File

@ -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
}

12
vendor/github.com/d5/tengo/compiler/symbol_scopes.go generated vendored Normal file
View File

@ -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"
)

145
vendor/github.com/d5/tengo/compiler/symbol_table.go generated vendored Normal file
View File

@ -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
}

19
vendor/github.com/d5/tengo/compiler/token/keywords.go generated vendored Normal file
View File

@ -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
}

208
vendor/github.com/d5/tengo/compiler/token/tokens.go generated vendored Normal file
View File

@ -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
}