diff options
author | Wim <wim@42.be> | 2019-02-23 16:39:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-23 16:39:44 +0100 |
commit | 1bb39eba8717f62336cc98c5bb7cfbef194f3626 (patch) | |
tree | 0437ae89473b8e25ad1c9597e1049a23a7933f6a /vendor/github.com/d5/tengo/compiler/parser | |
parent | 3190703dc8618896c932a23d8ca155fbbf6fab13 (diff) | |
download | matterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.tar.gz matterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.tar.bz2 matterbridge-msglm-1bb39eba8717f62336cc98c5bb7cfbef194f3626.zip |
Add scripting (tengo) support for every incoming message (#731)
TengoModifyMessage allows you to specify the location of a tengo (https://github.com/d5/tengo/) script.
This script will receive every incoming message and can be used to modify the Username and the Text of that message.
The script will have the following global variables:
to modify: msgUsername and msgText
to read: msgChannel and msgAccount
The script is reloaded on every message, so you can modify the script on the fly.
Example script can be found in https://github.com/42wim/matterbridge/tree/master/gateway/bench.tengo
and https://github.com/42wim/matterbridge/tree/master/contrib/example.tengo
The example below will check if the text contains blah and if so, it'll replace the text and the username of that message.
text := import("text")
if text.re_match("blah",msgText) {
msgText="replaced by this"
msgUsername="fakeuser"
}
More information about tengo on: https://github.com/d5/tengo/blob/master/docs/tutorial.md and
https://github.com/d5/tengo/blob/master/docs/stdlib.md
Diffstat (limited to 'vendor/github.com/d5/tengo/compiler/parser')
6 files changed, 1326 insertions, 0 deletions
diff --git a/vendor/github.com/d5/tengo/compiler/parser/error.go b/vendor/github.com/d5/tengo/compiler/parser/error.go new file mode 100644 index 00000000..80544fbd --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/error.go @@ -0,0 +1,21 @@ +package parser + +import ( + "fmt" + + "github.com/d5/tengo/compiler/source" +) + +// Error represents a parser error. +type Error struct { + Pos source.FilePos + Msg string +} + +func (e Error) Error() string { + if e.Pos.Filename != "" || e.Pos.IsValid() { + return fmt.Sprintf("Parse Error: %s\n\tat %s", e.Msg, e.Pos) + } + + return fmt.Sprintf("Parse Error: %s", e.Msg) +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/error_list.go b/vendor/github.com/d5/tengo/compiler/parser/error_list.go new file mode 100644 index 00000000..599eaf7d --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/error_list.go @@ -0,0 +1,68 @@ +package parser + +import ( + "fmt" + "sort" + + "github.com/d5/tengo/compiler/source" +) + +// ErrorList is a collection of parser errors. +type ErrorList []*Error + +// Add adds a new parser error to the collection. +func (p *ErrorList) Add(pos source.FilePos, msg string) { + *p = append(*p, &Error{pos, msg}) +} + +// Len returns the number of elements in the collection. +func (p ErrorList) Len() int { + return len(p) +} + +func (p ErrorList) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +func (p ErrorList) Less(i, j int) bool { + e := &p[i].Pos + f := &p[j].Pos + + if e.Filename != f.Filename { + return e.Filename < f.Filename + } + + if e.Line != f.Line { + return e.Line < f.Line + } + + if e.Column != f.Column { + return e.Column < f.Column + } + + return p[i].Msg < p[j].Msg +} + +// Sort sorts the collection. +func (p ErrorList) Sort() { + sort.Sort(p) +} + +func (p ErrorList) Error() string { + switch len(p) { + case 0: + return "no errors" + case 1: + return p[0].Error() + } + return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) +} + +// Err returns an error. +func (p ErrorList) Err() error { + if len(p) == 0 { + return nil + } + + return p +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parse_file.go b/vendor/github.com/d5/tengo/compiler/parser/parse_file.go new file mode 100644 index 00000000..0482c775 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parse_file.go @@ -0,0 +1,28 @@ +package parser + +import ( + "io" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// ParseFile parses a file with a given src. +func ParseFile(file *source.File, src []byte, trace io.Writer) (res *ast.File, err error) { + p := NewParser(file, src, trace) + + defer func() { + if e := recover(); e != nil { + if _, ok := e.(bailout); !ok { + panic(e) + } + } + + p.errors.Sort() + err = p.errors.Err() + }() + + res, err = p.ParseFile() + + return +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parse_source.go b/vendor/github.com/d5/tengo/compiler/parser/parse_source.go new file mode 100644 index 00000000..5d242db4 --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parse_source.go @@ -0,0 +1,16 @@ +package parser + +import ( + "io" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// ParseSource parses source code 'src' and builds an AST. +func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) { + fileSet := source.NewFileSet() + file := fileSet.AddFile(filename, -1, len(src)) + + return ParseFile(file, src, trace) +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/parser.go b/vendor/github.com/d5/tengo/compiler/parser/parser.go new file mode 100644 index 00000000..93f04f7c --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/parser.go @@ -0,0 +1,1181 @@ +/* + Parser parses the Tengo source files. + + Parser is a modified version of Go's parser implementation. + + Copyright 2009 The Go Authors. All rights reserved. + Use of this source code is governed by a BSD-style + license that can be found in the LICENSE file. +*/ + +package parser + +import ( + "fmt" + "io" + "strconv" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/scanner" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/compiler/token" +) + +type bailout struct{} + +// Parser parses the Tengo source files. +type Parser struct { + file *source.File + errors ErrorList + scanner *scanner.Scanner + pos source.Pos + token token.Token + tokenLit string + exprLevel int // < 0: in control clause, >= 0: in expression + syncPos source.Pos // last sync position + syncCount int // number of advance calls without progress + trace bool + indent int + traceOut io.Writer +} + +// NewParser creates a Parser. +func NewParser(file *source.File, src []byte, trace io.Writer) *Parser { + p := &Parser{ + file: file, + trace: trace != nil, + traceOut: trace, + } + + p.scanner = scanner.NewScanner(p.file, src, func(pos source.FilePos, msg string) { + p.errors.Add(pos, msg) + }, 0) + + p.next() + + return p +} + +// ParseFile parses the source and returns an AST file unit. +func (p *Parser) ParseFile() (*ast.File, error) { + if p.trace { + defer un(trace(p, "File")) + } + + if p.errors.Len() > 0 { + return nil, p.errors.Err() + } + + stmts := p.parseStmtList() + if p.errors.Len() > 0 { + return nil, p.errors.Err() + } + + return &ast.File{ + InputFile: p.file, + Stmts: stmts, + }, nil +} + +func (p *Parser) parseExpr() ast.Expr { + if p.trace { + defer un(trace(p, "Expression")) + } + + expr := p.parseBinaryExpr(token.LowestPrec + 1) + + // ternary conditional expression + if p.token == token.Question { + return p.parseCondExpr(expr) + } + + return expr +} + +func (p *Parser) parseBinaryExpr(prec1 int) ast.Expr { + if p.trace { + defer un(trace(p, "BinaryExpression")) + } + + x := p.parseUnaryExpr() + + for { + op, prec := p.token, p.token.Precedence() + if prec < prec1 { + return x + } + + pos := p.expect(op) + + y := p.parseBinaryExpr(prec + 1) + + x = &ast.BinaryExpr{ + LHS: x, + RHS: y, + Token: op, + TokenPos: pos, + } + } +} + +func (p *Parser) parseCondExpr(cond ast.Expr) ast.Expr { + questionPos := p.expect(token.Question) + + trueExpr := p.parseExpr() + + colonPos := p.expect(token.Colon) + + falseExpr := p.parseExpr() + + return &ast.CondExpr{ + Cond: cond, + True: trueExpr, + False: falseExpr, + QuestionPos: questionPos, + ColonPos: colonPos, + } +} + +func (p *Parser) parseUnaryExpr() ast.Expr { + if p.trace { + defer un(trace(p, "UnaryExpression")) + } + + switch p.token { + case token.Add, token.Sub, token.Not, token.Xor: + pos, op := p.pos, p.token + p.next() + x := p.parseUnaryExpr() + return &ast.UnaryExpr{ + Token: op, + TokenPos: pos, + Expr: x, + } + } + + return p.parsePrimaryExpr() +} + +func (p *Parser) parsePrimaryExpr() ast.Expr { + if p.trace { + defer un(trace(p, "PrimaryExpression")) + } + + x := p.parseOperand() + +L: + for { + switch p.token { + case token.Period: + p.next() + + switch p.token { + case token.Ident: + x = p.parseSelector(x) + default: + pos := p.pos + p.errorExpected(pos, "selector") + p.advance(stmtStart) + return &ast.BadExpr{From: pos, To: p.pos} + } + case token.LBrack: + x = p.parseIndexOrSlice(x) + case token.LParen: + x = p.parseCall(x) + default: + break L + } + } + + return x +} + +func (p *Parser) parseCall(x ast.Expr) *ast.CallExpr { + if p.trace { + defer un(trace(p, "Call")) + } + + lparen := p.expect(token.LParen) + p.exprLevel++ + + var list []ast.Expr + for p.token != token.RParen && p.token != token.EOF { + list = append(list, p.parseExpr()) + + if !p.expectComma(token.RParen, "call argument") { + break + } + } + + p.exprLevel-- + rparen := p.expect(token.RParen) + + return &ast.CallExpr{ + Func: x, + LParen: lparen, + RParen: rparen, + Args: list, + } +} + +func (p *Parser) expectComma(closing token.Token, want string) bool { + if p.token == token.Comma { + p.next() + + if p.token == closing { + p.errorExpected(p.pos, want) + return false + } + + return true + } + + if p.token == token.Semicolon && p.tokenLit == "\n" { + p.next() + } + + return false +} + +func (p *Parser) parseIndexOrSlice(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "IndexOrSlice")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var index [2]ast.Expr + if p.token != token.Colon { + index[0] = p.parseExpr() + } + numColons := 0 + if p.token == token.Colon { + numColons++ + p.next() + + if p.token != token.RBrack && p.token != token.EOF { + index[1] = p.parseExpr() + } + } + + p.exprLevel-- + rbrack := p.expect(token.RBrack) + + if numColons > 0 { + // slice expression + return &ast.SliceExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Low: index[0], + High: index[1], + } + } + + return &ast.IndexExpr{ + Expr: x, + LBrack: lbrack, + RBrack: rbrack, + Index: index[0], + } +} + +func (p *Parser) parseSelector(x ast.Expr) ast.Expr { + if p.trace { + defer un(trace(p, "Selector")) + } + + sel := p.parseIdent() + + return &ast.SelectorExpr{Expr: x, Sel: &ast.StringLit{ + Value: sel.Name, + ValuePos: sel.NamePos, + Literal: sel.Name, + }} +} + +func (p *Parser) parseOperand() ast.Expr { + if p.trace { + defer un(trace(p, "Operand")) + } + + switch p.token { + case token.Ident: + return p.parseIdent() + + case token.Int: + v, _ := strconv.ParseInt(p.tokenLit, 10, 64) + x := &ast.IntLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.Float: + v, _ := strconv.ParseFloat(p.tokenLit, 64) + x := &ast.FloatLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.Char: + return p.parseCharLit() + + case token.String: + v, _ := strconv.Unquote(p.tokenLit) + x := &ast.StringLit{ + Value: v, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.True: + x := &ast.BoolLit{ + Value: true, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.False: + x := &ast.BoolLit{ + Value: false, + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + + case token.Undefined: + x := &ast.UndefinedLit{TokenPos: p.pos} + p.next() + return x + + case token.Import: + return p.parseImportExpr() + + case token.LParen: + lparen := p.pos + p.next() + p.exprLevel++ + x := p.parseExpr() + p.exprLevel-- + rparen := p.expect(token.RParen) + return &ast.ParenExpr{ + LParen: lparen, + Expr: x, + RParen: rparen, + } + + case token.LBrack: // array literal + return p.parseArrayLit() + + case token.LBrace: // map literal + return p.parseMapLit() + + case token.Func: // function literal + return p.parseFuncLit() + + case token.Error: // error expression + return p.parseErrorExpr() + + case token.Immutable: // immutable expression + return p.parseImmutableExpr() + } + + pos := p.pos + p.errorExpected(pos, "operand") + p.advance(stmtStart) + return &ast.BadExpr{From: pos, To: p.pos} +} + +func (p *Parser) parseImportExpr() ast.Expr { + pos := p.pos + + p.next() + + p.expect(token.LParen) + + if p.token != token.String { + p.errorExpected(p.pos, "module name") + p.advance(stmtStart) + return &ast.BadExpr{From: pos, To: p.pos} + } + + // module name + moduleName, _ := strconv.Unquote(p.tokenLit) + + expr := &ast.ImportExpr{ + ModuleName: moduleName, + Token: token.Import, + TokenPos: pos, + } + + p.next() + + p.expect(token.RParen) + + return expr +} + +func (p *Parser) parseCharLit() ast.Expr { + if n := len(p.tokenLit); n >= 3 { + if code, _, _, err := strconv.UnquoteChar(p.tokenLit[1:n-1], '\''); err == nil { + x := &ast.CharLit{ + Value: rune(code), + ValuePos: p.pos, + Literal: p.tokenLit, + } + p.next() + return x + } + } + + pos := p.pos + p.error(pos, "illegal char literal") + p.next() + return &ast.BadExpr{ + From: pos, + To: p.pos, + } +} + +func (p *Parser) parseFuncLit() ast.Expr { + if p.trace { + defer un(trace(p, "FuncLit")) + } + + typ := p.parseFuncType() + + p.exprLevel++ + body := p.parseBody() + p.exprLevel-- + + return &ast.FuncLit{ + Type: typ, + Body: body, + } +} + +func (p *Parser) parseArrayLit() ast.Expr { + if p.trace { + defer un(trace(p, "ArrayLit")) + } + + lbrack := p.expect(token.LBrack) + p.exprLevel++ + + var elements []ast.Expr + for p.token != token.RBrack && p.token != token.EOF { + elements = append(elements, p.parseExpr()) + + if !p.expectComma(token.RBrack, "array element") { + break + } + } + + p.exprLevel-- + rbrack := p.expect(token.RBrack) + + return &ast.ArrayLit{ + Elements: elements, + LBrack: lbrack, + RBrack: rbrack, + } +} + +func (p *Parser) parseErrorExpr() ast.Expr { + pos := p.pos + + p.next() + + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + + expr := &ast.ErrorExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } + + return expr +} + +func (p *Parser) parseImmutableExpr() ast.Expr { + pos := p.pos + + p.next() + + lparen := p.expect(token.LParen) + value := p.parseExpr() + rparen := p.expect(token.RParen) + + expr := &ast.ImmutableExpr{ + ErrorPos: pos, + Expr: value, + LParen: lparen, + RParen: rparen, + } + + return expr +} + +func (p *Parser) parseFuncType() *ast.FuncType { + if p.trace { + defer un(trace(p, "FuncType")) + } + + pos := p.expect(token.Func) + params := p.parseIdentList() + + return &ast.FuncType{ + FuncPos: pos, + Params: params, + } +} + +func (p *Parser) parseBody() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "Body")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + + return &ast.BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseStmtList() (list []ast.Stmt) { + if p.trace { + defer un(trace(p, "StatementList")) + } + + for p.token != token.RBrace && p.token != token.EOF { + list = append(list, p.parseStmt()) + } + + return +} + +func (p *Parser) parseIdent() *ast.Ident { + pos := p.pos + name := "_" + + if p.token == token.Ident { + name = p.tokenLit + p.next() + } else { + p.expect(token.Ident) + } + + return &ast.Ident{ + NamePos: pos, + Name: name, + } +} + +func (p *Parser) parseIdentList() *ast.IdentList { + if p.trace { + defer un(trace(p, "IdentList")) + } + + var params []*ast.Ident + lparen := p.expect(token.LParen) + if p.token != token.RParen { + params = append(params, p.parseIdent()) + for p.token == token.Comma { + p.next() + params = append(params, p.parseIdent()) + } + } + rparen := p.expect(token.RParen) + + return &ast.IdentList{ + LParen: lparen, + RParen: rparen, + List: params, + } +} + +func (p *Parser) parseStmt() (stmt ast.Stmt) { + if p.trace { + defer un(trace(p, "Statement")) + } + + switch p.token { + case // simple statements + token.Func, token.Error, token.Immutable, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, + token.Undefined, token.Import, token.LParen, token.LBrace, token.LBrack, + token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: + s := p.parseSimpleStmt(false) + p.expectSemi() + return s + case token.Return: + return p.parseReturnStmt() + case token.Export: + return p.parseExportStmt() + case token.If: + return p.parseIfStmt() + case token.For: + return p.parseForStmt() + case token.Break, token.Continue: + return p.parseBranchStmt(p.token) + case token.Semicolon: + s := &ast.EmptyStmt{Semicolon: p.pos, Implicit: p.tokenLit == "\n"} + p.next() + return s + case token.RBrace: + // semicolon may be omitted before a closing "}" + return &ast.EmptyStmt{Semicolon: p.pos, Implicit: true} + default: + pos := p.pos + p.errorExpected(pos, "statement") + p.advance(stmtStart) + return &ast.BadStmt{From: pos, To: p.pos} + } +} + +func (p *Parser) parseForStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ForStmt")) + } + + pos := p.expect(token.For) + + // for {} + if p.token == token.LBrace { + body := p.parseBlockStmt() + p.expectSemi() + + return &ast.ForStmt{ + ForPos: pos, + Body: body, + } + } + + prevLevel := p.exprLevel + p.exprLevel = -1 + + var s1 ast.Stmt + if p.token != token.Semicolon { // skipping init + s1 = p.parseSimpleStmt(true) + } + + // for _ in seq {} or + // for value in seq {} or + // for key, value in seq {} + if forInStmt, isForIn := s1.(*ast.ForInStmt); isForIn { + forInStmt.ForPos = pos + p.exprLevel = prevLevel + forInStmt.Body = p.parseBlockStmt() + p.expectSemi() + return forInStmt + } + + // for init; cond; post {} + var s2, s3 ast.Stmt + if p.token == token.Semicolon { + p.next() + if p.token != token.Semicolon { + s2 = p.parseSimpleStmt(false) // cond + } + p.expect(token.Semicolon) + if p.token != token.LBrace { + s3 = p.parseSimpleStmt(false) // post + } + } else { + // for cond {} + s2 = s1 + s1 = nil + } + + // body + p.exprLevel = prevLevel + body := p.parseBlockStmt() + p.expectSemi() + + cond := p.makeExpr(s2, "condition expression") + + return &ast.ForStmt{ + ForPos: pos, + Init: s1, + Cond: cond, + Post: s3, + Body: body, + } + +} + +func (p *Parser) parseBranchStmt(tok token.Token) ast.Stmt { + if p.trace { + defer un(trace(p, "BranchStmt")) + } + + pos := p.expect(tok) + + var label *ast.Ident + if p.token == token.Ident { + label = p.parseIdent() + } + p.expectSemi() + + return &ast.BranchStmt{ + Token: tok, + TokenPos: pos, + Label: label, + } +} + +func (p *Parser) parseIfStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "IfStmt")) + } + + pos := p.expect(token.If) + + init, cond := p.parseIfHeader() + body := p.parseBlockStmt() + + var elseStmt ast.Stmt + if p.token == token.Else { + p.next() + + switch p.token { + case token.If: + elseStmt = p.parseIfStmt() + case token.LBrace: + elseStmt = p.parseBlockStmt() + p.expectSemi() + default: + p.errorExpected(p.pos, "if or {") + elseStmt = &ast.BadStmt{From: p.pos, To: p.pos} + } + } else { + p.expectSemi() + } + + return &ast.IfStmt{ + IfPos: pos, + Init: init, + Cond: cond, + Body: body, + Else: elseStmt, + } +} + +func (p *Parser) parseBlockStmt() *ast.BlockStmt { + if p.trace { + defer un(trace(p, "BlockStmt")) + } + + lbrace := p.expect(token.LBrace) + list := p.parseStmtList() + rbrace := p.expect(token.RBrace) + + return &ast.BlockStmt{ + LBrace: lbrace, + RBrace: rbrace, + Stmts: list, + } +} + +func (p *Parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) { + if p.token == token.LBrace { + p.error(p.pos, "missing condition in if statement") + cond = &ast.BadExpr{From: p.pos, To: p.pos} + return + } + + outer := p.exprLevel + p.exprLevel = -1 + + if p.token == token.Semicolon { + p.error(p.pos, "missing init in if statement") + return + } + + init = p.parseSimpleStmt(false) + + var condStmt ast.Stmt + if p.token == token.LBrace { + condStmt = init + init = nil + } else if p.token == token.Semicolon { + p.next() + + condStmt = p.parseSimpleStmt(false) + } else { + p.error(p.pos, "missing condition in if statement") + } + + if condStmt != nil { + cond = p.makeExpr(condStmt, "boolean expression") + } + + if cond == nil { + cond = &ast.BadExpr{From: p.pos, To: p.pos} + } + + p.exprLevel = outer + + return +} + +func (p *Parser) makeExpr(s ast.Stmt, want string) ast.Expr { + if s == nil { + return nil + } + + if es, isExpr := s.(*ast.ExprStmt); isExpr { + return es.Expr + } + + found := "simple statement" + if _, isAss := s.(*ast.AssignStmt); isAss { + found = "assignment" + } + + p.error(s.Pos(), fmt.Sprintf("expected %s, found %s", want, found)) + + return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} +} + +func (p *Parser) parseReturnStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ReturnStmt")) + } + + pos := p.pos + p.expect(token.Return) + + var x ast.Expr + if p.token != token.Semicolon && p.token != token.RBrace { + x = p.parseExpr() + } + p.expectSemi() + + return &ast.ReturnStmt{ + ReturnPos: pos, + Result: x, + } +} + +func (p *Parser) parseExportStmt() ast.Stmt { + if p.trace { + defer un(trace(p, "ExportStmt")) + } + + pos := p.pos + p.expect(token.Export) + + x := p.parseExpr() + p.expectSemi() + + return &ast.ExportStmt{ + ExportPos: pos, + Result: x, + } +} + +func (p *Parser) parseSimpleStmt(forIn bool) ast.Stmt { + if p.trace { + defer un(trace(p, "SimpleStmt")) + } + + x := p.parseExprList() + + switch p.token { + case token.Assign, token.Define: // assignment statement + pos, tok := p.pos, p.token + p.next() + + y := p.parseExprList() + + return &ast.AssignStmt{ + LHS: x, + RHS: y, + Token: tok, + TokenPos: pos, + } + case token.In: + if forIn { + p.next() + + y := p.parseExpr() + + var key, value *ast.Ident + var ok bool + + switch len(x) { + case 1: + key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + + value, ok = x[0].(*ast.Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + value = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + } + case 2: + key, ok = x[0].(*ast.Ident) + if !ok { + p.errorExpected(x[0].Pos(), "identifier") + key = &ast.Ident{Name: "_", NamePos: x[0].Pos()} + } + value, ok = x[1].(*ast.Ident) + if !ok { + p.errorExpected(x[1].Pos(), "identifier") + value = &ast.Ident{Name: "_", NamePos: x[1].Pos()} + } + } + + return &ast.ForInStmt{ + Key: key, + Value: value, + Iterable: y, + } + } + } + + if len(x) > 1 { + p.errorExpected(x[0].Pos(), "1 expression") + // continue with first expression + } + + switch p.token { + case token.Define, + token.AddAssign, token.SubAssign, token.MulAssign, token.QuoAssign, token.RemAssign, + token.AndAssign, token.OrAssign, token.XorAssign, token.ShlAssign, token.ShrAssign, token.AndNotAssign: + pos, tok := p.pos, p.token + p.next() + + y := p.parseExpr() + + return &ast.AssignStmt{ + LHS: []ast.Expr{x[0]}, + RHS: []ast.Expr{y}, + Token: tok, + TokenPos: pos, + } + case token.Inc, token.Dec: + // increment or decrement statement + s := &ast.IncDecStmt{Expr: x[0], Token: p.token, TokenPos: p.pos} + p.next() + return s + } + + // expression statement + return &ast.ExprStmt{Expr: x[0]} +} + +func (p *Parser) parseExprList() (list []ast.Expr) { + if p.trace { + defer un(trace(p, "ExpressionList")) + } + + list = append(list, p.parseExpr()) + for p.token == token.Comma { + p.next() + list = append(list, p.parseExpr()) + } + + return +} + +func (p *Parser) parseMapElementLit() *ast.MapElementLit { + if p.trace { + defer un(trace(p, "MapElementLit")) + } + + // key: read identifier token but it's not actually an identifier + ident := p.parseIdent() + + colonPos := p.expect(token.Colon) + + valueExpr := p.parseExpr() + + return &ast.MapElementLit{ + Key: ident.Name, + KeyPos: ident.NamePos, + ColonPos: colonPos, + Value: valueExpr, + } +} + +func (p *Parser) parseMapLit() *ast.MapLit { + if p.trace { + defer un(trace(p, "MapLit")) + } + + lbrace := p.expect(token.LBrace) + p.exprLevel++ + + var elements []*ast.MapElementLit + for p.token != token.RBrace && p.token != token.EOF { + elements = append(elements, p.parseMapElementLit()) + + if !p.expectComma(token.RBrace, "map element") { + break + } + } + + p.exprLevel-- + rbrace := p.expect(token.RBrace) + + return &ast.MapLit{ + LBrace: lbrace, + RBrace: rbrace, + Elements: elements, + } +} + +func (p *Parser) expect(token token.Token) source.Pos { + pos := p.pos + + if p.token != token { + p.errorExpected(pos, "'"+token.String()+"'") + } + p.next() + + return pos +} + +func (p *Parser) expectSemi() { + switch p.token { + case token.RParen, token.RBrace: + // semicolon is optional before a closing ')' or '}' + case token.Comma: + // permit a ',' instead of a ';' but complain + p.errorExpected(p.pos, "';'") + fallthrough + case token.Semicolon: + p.next() + default: + p.errorExpected(p.pos, "';'") + p.advance(stmtStart) + } + +} + +func (p *Parser) advance(to map[token.Token]bool) { + for ; p.token != token.EOF; p.next() { + if to[p.token] { + if p.pos == p.syncPos && p.syncCount < 10 { + p.syncCount++ + return + } + + if p.pos > p.syncPos { + p.syncPos = p.pos + p.syncCount = 0 + return + } + } + } +} + +func (p *Parser) error(pos source.Pos, msg string) { + filePos := p.file.Position(pos) + + n := len(p.errors) + if n > 0 && p.errors[n-1].Pos.Line == filePos.Line { + // discard errors reported on the same line + return + } + + if n > 10 { + // too many errors; terminate early + panic(bailout{}) + } + + p.errors.Add(filePos, msg) +} + +func (p *Parser) errorExpected(pos source.Pos, msg string) { + msg = "expected " + msg + if pos == p.pos { + // error happened at the current position: provide more specific + switch { + case p.token == token.Semicolon && p.tokenLit == "\n": + msg += ", found newline" + case p.token.IsLiteral(): + msg += ", found " + p.tokenLit + default: + msg += ", found '" + p.token.String() + "'" + } + } + + p.error(pos, msg) +} + +func (p *Parser) next() { + if p.trace && p.pos.IsValid() { + s := p.token.String() + switch { + case p.token.IsLiteral(): + p.printTrace(s, p.tokenLit) + case p.token.IsOperator(), p.token.IsKeyword(): + p.printTrace(`"` + s + `"`) + default: + p.printTrace(s) + } + } + + p.token, p.tokenLit, p.pos = p.scanner.Scan() +} + +func (p *Parser) printTrace(a ...interface{}) { + const ( + dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " + n = len(dots) + ) + + filePos := p.file.Position(p.pos) + _, _ = fmt.Fprintf(p.traceOut, "%5d: %5d:%3d: ", p.pos, filePos.Line, filePos.Column) + + i := 2 * p.indent + for i > n { + _, _ = fmt.Fprint(p.traceOut, dots) + i -= n + } + _, _ = fmt.Fprint(p.traceOut, dots[0:i]) + _, _ = fmt.Fprintln(p.traceOut, a...) +} + +func (p *Parser) safePos(pos source.Pos) source.Pos { + fileBase := p.file.Base + fileSize := p.file.Size + + if int(pos) < fileBase || int(pos) > fileBase+fileSize { + return source.Pos(fileBase + fileSize) + } + + return pos +} + +func trace(p *Parser, msg string) *Parser { + p.printTrace(msg, "(") + p.indent++ + + return p +} + +func un(p *Parser) { + p.indent-- + p.printTrace(")") +} diff --git a/vendor/github.com/d5/tengo/compiler/parser/sync.go b/vendor/github.com/d5/tengo/compiler/parser/sync.go new file mode 100644 index 00000000..e68d623a --- /dev/null +++ b/vendor/github.com/d5/tengo/compiler/parser/sync.go @@ -0,0 +1,12 @@ +package parser + +import "github.com/d5/tengo/compiler/token" + +var stmtStart = map[token.Token]bool{ + token.Break: true, + token.Continue: true, + token.For: true, + token.If: true, + token.Return: true, + token.Export: true, +} |