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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
147 changed files with 14201 additions and 0 deletions

View File

@ -282,6 +282,7 @@ Matterbridge wouldn't exist without these libraries:
* xmpp - https://github.com/mattn/go-xmpp * xmpp - https://github.com/mattn/go-xmpp
* whatsapp - https://github.com/Rhymen/go-whatsapp/ * whatsapp - https://github.com/Rhymen/go-whatsapp/
* zulip - https://github.com/ifo/gozulipbot * zulip - https://github.com/ifo/gozulipbot
* tengo - https://github.com/d5/tengo
<!-- Links --> <!-- Links -->

View File

@ -129,6 +129,7 @@ type Protocol struct {
SkipTLSVerify bool // IRC, mattermost SkipTLSVerify bool // IRC, mattermost
StripNick bool // all protocols StripNick bool // all protocols
SyncTopic bool // slack SyncTopic bool // slack
TengoModifyMessage string // general
Team string // mattermost Team string // mattermost
Token string // gitter, slack, discord, api Token string // gitter, slack, discord, api
Topic string // zulip Topic string // zulip

2
contrib/example.tengo Normal file
View File

@ -0,0 +1,2 @@
text := import("text")
msgText=text.re_replace("matterbridge",msgText,"matterbridge (https://github.com/42wim/matterbridge)")

5
gateway/bench.tengo Normal file
View File

@ -0,0 +1,5 @@
text := import("text")
if text.re_match("blah",msgText) {
msgText="replaced by this"
msgUsername="fakeuser"
}

View File

@ -1,6 +1,7 @@
package gateway package gateway
import ( import (
"io/ioutil"
"os" "os"
"regexp" "regexp"
"strings" "strings"
@ -8,6 +9,7 @@ import (
"github.com/42wim/matterbridge/bridge" "github.com/42wim/matterbridge/bridge"
"github.com/42wim/matterbridge/bridge/config" "github.com/42wim/matterbridge/bridge/config"
"github.com/d5/tengo/script"
"github.com/hashicorp/golang-lru" "github.com/hashicorp/golang-lru"
"github.com/peterhellberg/emojilib" "github.com/peterhellberg/emojilib"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -334,6 +336,10 @@ func (gw *Gateway) modifyAvatar(msg config.Message, dest *bridge.Bridge) string
} }
func (gw *Gateway) modifyMessage(msg *config.Message) { func (gw *Gateway) modifyMessage(msg *config.Message) {
if err := modifyMessageTengo(gw.BridgeValues().General.TengoModifyMessage, msg); err != nil {
flog.Errorf("TengoModifyMessage failed: %s", err)
}
// replace :emoji: to unicode // replace :emoji: to unicode
msg.Text = emojilib.Replace(msg.Text) msg.Text = emojilib.Replace(msg.Text)
@ -458,3 +464,28 @@ func getProtocol(msg *config.Message) string {
p := strings.Split(msg.Account, ".") p := strings.Split(msg.Account, ".")
return p[0] return p[0]
} }
func modifyMessageTengo(filename string, msg *config.Message) error {
if filename == "" {
return nil
}
res, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
s := script.New(res)
_ = s.Add("msgText", msg.Text)
_ = s.Add("msgUsername", msg.Username)
_ = s.Add("msgAccount", msg.Account)
_ = s.Add("msgChannel", msg.Channel)
c, err := s.Compile()
if err != nil {
return err
}
if err := c.Run(); err != nil {
return err
}
msg.Text = c.Get("msgText").String()
msg.Username = c.Get("msgUsername").String()
return nil
}

View File

@ -499,3 +499,13 @@ func TestIgnoreNicks(t *testing.T) {
assert.Equalf(t, testcase.output, output, "case '%s' failed", testname) assert.Equalf(t, testcase.output, output, "case '%s' failed", testname)
} }
} }
func BenchmarkTengo(b *testing.B) {
msg := &config.Message{Username: "user", Text: "blah testing", Account: "protocol.account", Channel: "mychannel"}
for n := 0; n < b.N; n++ {
err := modifyMessageTengo("bench.tengo", msg)
if err != nil {
return
}
}
}

1
go.mod
View File

@ -8,6 +8,7 @@ require (
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884 github.com/Rhymen/go-whatsapp v0.0.0-20190208184307-c9a81e957884
github.com/bwmarrin/discordgo v0.19.0 github.com/bwmarrin/discordgo v0.19.0
github.com/d5/tengo v1.9.2
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible

2
go.sum
View File

@ -20,6 +20,8 @@ github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVO
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/d5/tengo v1.9.2 h1:UE/X8PYl7bLS4Ww2zGeh91nq5PTnkhe8ncgNeA5PK7k=
github.com/d5/tengo v1.9.2/go.mod h1:gsbjo7lBXzBIWBd6NQp1lRKqqiDDANqBOyhW8rTlFsY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@ -1527,6 +1527,29 @@ MediaDownloadBlacklist=[".html$",".htm$"]
#OPTIONAL (default false) #OPTIONAL (default false)
IgnoreFailureOnStart=false IgnoreFailureOnStart=false
#TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
#This script will receive every incoming message and can be used to modify the Username and the Text of that message.
#The script will have the following global variables:
#to modify: msgUsername and msgText
#to read: msgChannel and msgAccount
#
#The script is reloaded on every message, so you can modify the script on the fly.
#
#Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo
#and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo
#
#The example below will check if the text contains blah and if so, it'll replace the text and the username of that message.
#text := import("text")
#if text.re_match("blah",msgText) {
# msgText="replaced by this"
# msgUsername="fakeuser"
#}
#More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and
#https://github.com/d5/tengo/blob/master/docs/stdlib.md
#OPTIONAL (default empty)
TengoModifyMessage="example.tengo"
################################################################### ###################################################################
#Gateway configuration #Gateway configuration
################################################################### ###################################################################

21
vendor/github.com/d5/tengo/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Daniel Kang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

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
}

130
vendor/github.com/d5/tengo/objects/array.go generated vendored Normal file
View File

@ -0,0 +1,130 @@
package objects
import (
"fmt"
"strings"
"github.com/d5/tengo/compiler/token"
)
// Array represents an array of objects.
type Array struct {
Value []Object
}
// TypeName returns the name of the type.
func (o *Array) TypeName() string {
return "array"
}
func (o *Array) String() string {
var elements []string
for _, e := range o.Value {
elements = append(elements, e.String())
}
return fmt.Sprintf("[%s]", strings.Join(elements, ", "))
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Array) BinaryOp(op token.Token, rhs Object) (Object, error) {
if rhs, ok := rhs.(*Array); ok {
switch op {
case token.Add:
if len(rhs.Value) == 0 {
return o, nil
}
return &Array{Value: append(o.Value, rhs.Value...)}, nil
}
}
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Array) Copy() Object {
var c []Object
for _, elem := range o.Value {
c = append(c, elem.Copy())
}
return &Array{Value: c}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Array) IsFalsy() bool {
return len(o.Value) == 0
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Array) Equals(x Object) bool {
var xVal []Object
switch x := x.(type) {
case *Array:
xVal = x.Value
case *ImmutableArray:
xVal = x.Value
default:
return false
}
if len(o.Value) != len(xVal) {
return false
}
for i, e := range o.Value {
if !e.Equals(xVal[i]) {
return false
}
}
return true
}
// IndexGet returns an element at a given index.
func (o *Array) IndexGet(index Object) (res Object, err error) {
intIdx, ok := index.(*Int)
if !ok {
err = ErrInvalidIndexType
return
}
idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) {
res = UndefinedValue
return
}
res = o.Value[idxVal]
return
}
// IndexSet sets an element at a given index.
func (o *Array) IndexSet(index, value Object) (err error) {
intIdx, ok := ToInt(index)
if !ok {
err = ErrInvalidIndexType
return
}
if intIdx < 0 || intIdx >= len(o.Value) {
err = ErrIndexOutOfBounds
return
}
o.Value[intIdx] = value
return nil
}
// Iterate creates an array iterator.
func (o *Array) Iterate() Iterator {
return &ArrayIterator{
v: o.Value,
l: len(o.Value),
}
}

57
vendor/github.com/d5/tengo/objects/array_iterator.go generated vendored Normal file
View File

@ -0,0 +1,57 @@
package objects
import "github.com/d5/tengo/compiler/token"
// ArrayIterator is an iterator for an array.
type ArrayIterator struct {
v []Object
i int
l int
}
// TypeName returns the name of the type.
func (i *ArrayIterator) TypeName() string {
return "array-iterator"
}
func (i *ArrayIterator) String() string {
return "<array-iterator>"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (i *ArrayIterator) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// IsFalsy returns true if the value of the type is falsy.
func (i *ArrayIterator) IsFalsy() bool {
return true
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (i *ArrayIterator) Equals(Object) bool {
return false
}
// Copy returns a copy of the type.
func (i *ArrayIterator) Copy() Object {
return &ArrayIterator{v: i.v, i: i.i, l: i.l}
}
// Next returns true if there are more elements to iterate.
func (i *ArrayIterator) Next() bool {
i.i++
return i.i <= i.l
}
// Key returns the key or index value of the current element.
func (i *ArrayIterator) Key() Object {
return &Int{Value: int64(i.i - 1)}
}
// Value returns the value of the current element.
func (i *ArrayIterator) Value() Object {
return i.v[i.i-1]
}

64
vendor/github.com/d5/tengo/objects/bool.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package objects
import (
"github.com/d5/tengo/compiler/token"
)
// Bool represents a boolean value.
type Bool struct {
// this is intentionally non-public to force using objects.TrueValue and FalseValue always
value bool
}
func (o *Bool) String() string {
if o.value {
return "true"
}
return "false"
}
// TypeName returns the name of the type.
func (o *Bool) TypeName() string {
return "bool"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Bool) Copy() Object {
return o
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Bool) IsFalsy() bool {
return !o.value
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Bool) Equals(x Object) bool {
return o == x
}
// GobDecode decodes bool value from input bytes.
func (o *Bool) GobDecode(b []byte) (err error) {
o.value = b[0] == 1
return
}
// GobEncode encodes bool values into bytes.
func (o *Bool) GobEncode() (b []byte, err error) {
if o.value {
b = []byte{1}
} else {
b = []byte{0}
}
return
}

37
vendor/github.com/d5/tengo/objects/break.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
package objects
import "github.com/d5/tengo/compiler/token"
// Break represents a break statement.
type Break struct{}
// TypeName returns the name of the type.
func (o *Break) TypeName() string {
return "break"
}
func (o *Break) String() string {
return "<break>"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Break) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Break) Copy() Object {
return &Break{}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Break) IsFalsy() bool {
return false
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Break) Equals(x Object) bool {
return false
}

21
vendor/github.com/d5/tengo/objects/builtin_append.go generated vendored Normal file
View File

@ -0,0 +1,21 @@
package objects
// append(arr, items...)
func builtinAppend(args ...Object) (Object, error) {
if len(args) < 2 {
return nil, ErrWrongNumArguments
}
switch arg := args[0].(type) {
case *Array:
return &Array{Value: append(arg.Value, args[1:]...)}, nil
case *ImmutableArray:
return &Array{Value: append(arg.Value, args[1:]...)}, nil
default:
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "array",
Found: arg.TypeName(),
}
}
}

155
vendor/github.com/d5/tengo/objects/builtin_convert.go generated vendored Normal file
View File

@ -0,0 +1,155 @@
package objects
func builtinString(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*String); ok {
return args[0], nil
}
v, ok := ToString(args[0])
if ok {
return &String{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}
func builtinInt(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Int); ok {
return args[0], nil
}
v, ok := ToInt64(args[0])
if ok {
return &Int{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}
func builtinFloat(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Float); ok {
return args[0], nil
}
v, ok := ToFloat64(args[0])
if ok {
return &Float{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}
func builtinBool(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Bool); ok {
return args[0], nil
}
v, ok := ToBool(args[0])
if ok {
if v {
return TrueValue, nil
}
return FalseValue, nil
}
return UndefinedValue, nil
}
func builtinChar(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Char); ok {
return args[0], nil
}
v, ok := ToRune(args[0])
if ok {
return &Char{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}
func builtinBytes(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
// bytes(N) => create a new bytes with given size N
if n, ok := args[0].(*Int); ok {
return &Bytes{Value: make([]byte, int(n.Value))}, nil
}
v, ok := ToByteSlice(args[0])
if ok {
return &Bytes{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}
func builtinTime(args ...Object) (Object, error) {
argsLen := len(args)
if !(argsLen == 1 || argsLen == 2) {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Time); ok {
return args[0], nil
}
v, ok := ToTime(args[0])
if ok {
return &Time{Value: v}, nil
}
if argsLen == 2 {
return args[1], nil
}
return UndefinedValue, nil
}

9
vendor/github.com/d5/tengo/objects/builtin_copy.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package objects
func builtinCopy(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
return args[0].Copy(), nil
}

47
vendor/github.com/d5/tengo/objects/builtin_function.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package objects
import (
"github.com/d5/tengo/compiler/token"
)
// BuiltinFunction represents a builtin function.
type BuiltinFunction struct {
Name string
Value CallableFunc
}
// TypeName returns the name of the type.
func (o *BuiltinFunction) TypeName() string {
return "builtin-function:" + o.Name
}
func (o *BuiltinFunction) String() string {
return "<builtin-function>"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *BuiltinFunction) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *BuiltinFunction) Copy() Object {
return &BuiltinFunction{Value: o.Value}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *BuiltinFunction) IsFalsy() bool {
return false
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *BuiltinFunction) Equals(x Object) bool {
return false
}
// Call executes a builtin function.
func (o *BuiltinFunction) Call(args ...Object) (Object, error) {
return o.Value(args...)
}

54
vendor/github.com/d5/tengo/objects/builtin_json.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
package objects
import (
"encoding/json"
)
// to_json(v object) => bytes
func builtinToJSON(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
res, err := json.Marshal(objectToInterface(args[0]))
if err != nil {
return &Error{Value: &String{Value: err.Error()}}, nil
}
return &Bytes{Value: res}, nil
}
// from_json(data string/bytes) => object
func builtinFromJSON(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
var target interface{}
switch o := args[0].(type) {
case *Bytes:
err := json.Unmarshal(o.Value, &target)
if err != nil {
return &Error{Value: &String{Value: err.Error()}}, nil
}
case *String:
err := json.Unmarshal([]byte(o.Value), &target)
if err != nil {
return &Error{Value: &String{Value: err.Error()}}, nil
}
default:
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "bytes/string",
Found: args[0].TypeName(),
}
}
res, err := FromInterface(target)
if err != nil {
return nil, err
}
return res, nil
}

29
vendor/github.com/d5/tengo/objects/builtin_len.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package objects
// len(obj object) => int
func builtinLen(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
switch arg := args[0].(type) {
case *Array:
return &Int{Value: int64(len(arg.Value))}, nil
case *ImmutableArray:
return &Int{Value: int64(len(arg.Value))}, nil
case *String:
return &Int{Value: int64(len(arg.Value))}, nil
case *Bytes:
return &Int{Value: int64(len(arg.Value))}, nil
case *Map:
return &Int{Value: int64(len(arg.Value))}, nil
case *ImmutableMap:
return &Int{Value: int64(len(arg.Value))}, nil
default:
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "array/string/bytes/map",
Found: arg.TypeName(),
}
}
}

75
vendor/github.com/d5/tengo/objects/builtin_print.go generated vendored Normal file
View File

@ -0,0 +1,75 @@
package objects
import (
"fmt"
)
// print(args...)
func builtinPrint(args ...Object) (Object, error) {
for _, arg := range args {
if str, ok := arg.(*String); ok {
fmt.Println(str.Value)
} else {
fmt.Println(arg.String())
}
}
return nil, nil
}
// printf("format", args...)
func builtinPrintf(args ...Object) (Object, error) {
numArgs := len(args)
if numArgs == 0 {
return nil, ErrWrongNumArguments
}
format, ok := args[0].(*String)
if !ok {
return nil, ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
}
}
if numArgs == 1 {
fmt.Print(format)
return nil, nil
}
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] {
formatArgs[idx] = objectToInterface(arg)
}
fmt.Printf(format.Value, formatArgs...)
return nil, nil
}
// sprintf("format", args...)
func builtinSprintf(args ...Object) (Object, error) {
numArgs := len(args)
if numArgs == 0 {
return nil, ErrWrongNumArguments
}
format, ok := args[0].(*String)
if !ok {
return nil, ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
}
}
if numArgs == 1 {
return format, nil // okay to return 'format' directly as String is immutable
}
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] {
formatArgs[idx] = objectToInterface(arg)
}
return &String{Value: fmt.Sprintf(format.Value, formatArgs...)}, nil
}

9
vendor/github.com/d5/tengo/objects/builtin_type.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package objects
func builtinTypeName(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
return &String{Value: args[0].TypeName()}, nil
}

View File

@ -0,0 +1,183 @@
package objects
func builtinIsString(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*String); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsInt(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Int); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsFloat(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Float); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsBool(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Bool); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsChar(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Char); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsBytes(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Bytes); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsArray(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Array); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsImmutableArray(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*ImmutableArray); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsMap(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Map); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsImmutableMap(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*ImmutableMap); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsTime(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Time); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsError(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if _, ok := args[0].(*Error); ok {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsUndefined(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
if args[0] == UndefinedValue {
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsFunction(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
switch args[0].(type) {
case *CompiledFunction, *Closure:
return TrueValue, nil
}
return FalseValue, nil
}
func builtinIsCallable(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
}
switch args[0].(type) {
case *CompiledFunction, *Closure, Callable: // BuiltinFunction is Callable
return TrueValue, nil
}
return FalseValue, nil
}

135
vendor/github.com/d5/tengo/objects/builtins.go generated vendored Normal file
View File

@ -0,0 +1,135 @@
package objects
// NamedBuiltinFunc is a named builtin function.
type NamedBuiltinFunc struct {
Name string
Func CallableFunc
}
// Builtins contains all default builtin functions.
var Builtins = []NamedBuiltinFunc{
{
Name: "print",
Func: builtinPrint,
},
{
Name: "printf",
Func: builtinPrintf,
},
{
Name: "sprintf",
Func: builtinSprintf,
},
{
Name: "len",
Func: builtinLen,
},
{
Name: "copy",
Func: builtinCopy,
},
{
Name: "append",
Func: builtinAppend,
},
{
Name: "string",
Func: builtinString,
},
{
Name: "int",
Func: builtinInt,
},
{
Name: "bool",
Func: builtinBool,
},
{
Name: "float",
Func: builtinFloat,
},
{
Name: "char",
Func: builtinChar,
},
{
Name: "bytes",
Func: builtinBytes,
},
{
Name: "time",
Func: builtinTime,
},
{
Name: "is_int",
Func: builtinIsInt,
},
{
Name: "is_float",
Func: builtinIsFloat,
},
{
Name: "is_string",
Func: builtinIsString,
},
{
Name: "is_bool",
Func: builtinIsBool,
},
{
Name: "is_char",
Func: builtinIsChar,
},
{
Name: "is_bytes",
Func: builtinIsBytes,
},
{
Name: "is_array",
Func: builtinIsArray,
},
{
Name: "is_immutable_array",
Func: builtinIsImmutableArray,
},
{
Name: "is_map",
Func: builtinIsMap,
},
{
Name: "is_immutable_map",
Func: builtinIsImmutableMap,
},
{
Name: "is_time",
Func: builtinIsTime,
},
{
Name: "is_error",
Func: builtinIsError,
},
{
Name: "is_undefined",
Func: builtinIsUndefined,
},
{
Name: "is_function",
Func: builtinIsFunction,
},
{
Name: "is_callable",
Func: builtinIsCallable,
},
{
Name: "to_json",
Func: builtinToJSON,
},
{
Name: "from_json",
Func: builtinFromJSON,
},
{
Name: "type_name",
Func: builtinTypeName,
},
}

76
vendor/github.com/d5/tengo/objects/bytes.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
package objects
import (
"bytes"
"github.com/d5/tengo/compiler/token"
)
// Bytes represents a byte array.
type Bytes struct {
Value []byte
}
func (o *Bytes) String() string {
return string(o.Value)
}
// TypeName returns the name of the type.
func (o *Bytes) TypeName() string {
return "bytes"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) {
switch op {
case token.Add:
switch rhs := rhs.(type) {
case *Bytes:
return &Bytes{Value: append(o.Value, rhs.Value...)}, nil
}
}
return nil, ErrInvalidOperator
}
// Copy returns a copy of the type.
func (o *Bytes) Copy() Object {
return &Bytes{Value: append([]byte{}, o.Value...)}
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Bytes) IsFalsy() bool {
return len(o.Value) == 0
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Bytes) Equals(x Object) bool {
t, ok := x.(*Bytes)
if !ok {
return false
}
return bytes.Compare(o.Value, t.Value) == 0
}
// IndexGet returns an element (as Int) at a given index.
func (o *Bytes) IndexGet(index Object) (res Object, err error) {
intIdx, ok := index.(*Int)
if !ok {
err = ErrInvalidIndexType
return
}
idxVal := int(intIdx.Value)
if idxVal < 0 || idxVal >= len(o.Value) {
res = UndefinedValue
return
}
res = &Int{Value: int64(o.Value[idxVal])}
return
}

Some files were not shown because too many files have changed in this diff Show More