woodpecker/vendor/github.com/drone/expr/parse/parse.go
Matt Leung 4c2ff78d20 Add expression parser evaulator for build filter
Vendor github.com/drone/expr
Vendor github.com/drone/expr/parse
2017-09-08 19:39:24 -07:00

223 lines
4.6 KiB
Go

package parse
import (
"bytes"
"fmt"
)
// Tree is the representation of a single parsed SQL statement.
type Tree struct {
Root BoolExpr
// Parsing only; cleared after parse.
lex *lexer
}
// Parse parses the SQL statement and returns a Tree.
func Parse(buf []byte) (*Tree, error) {
t := new(Tree)
t.lex = new(lexer)
return t.Parse(buf)
}
// Parse parses the SQL statement buffer to construct an ast
// representation for execution.
func (t *Tree) Parse(buf []byte) (tree *Tree, err error) {
defer t.recover(&err)
t.lex.init(buf)
t.Root = t.parseExpr()
return t, nil
}
// recover is the handler that turns panics into returns.
func (t *Tree) recover(err *error) {
if e := recover(); e != nil {
*err = e.(error)
}
}
// errorf formats the error and terminates processing.
func (t *Tree) errorf(format string, args ...interface{}) {
t.Root = nil
format = fmt.Sprintf("selector: parse error:%d: %s", t.lex.start, format)
panic(fmt.Errorf(format, args...))
}
func (t *Tree) parseExpr() BoolExpr {
if t.lex.peek() == tokenNot {
t.lex.scan()
return t.parseNot()
}
left := t.parseVal()
node := t.parseComparison(left)
switch t.lex.scan() {
case tokenOr:
return t.parseOr(node)
case tokenAnd:
return t.parseAnd(node)
default:
return node
}
}
func (t *Tree) parseAnd(left BoolExpr) BoolExpr {
node := new(AndExpr)
node.Left = left
node.Right = t.parseExpr()
return node
}
func (t *Tree) parseOr(left BoolExpr) BoolExpr {
node := new(OrExpr)
node.Left = left
node.Right = t.parseExpr()
return node
}
func (t *Tree) parseNot() BoolExpr {
node := new(NotExpr)
node.Expr = t.parseExpr()
return node
}
func (t *Tree) parseComparison(left ValExpr) BoolExpr {
var negate bool
if t.lex.peek() == tokenNot {
t.lex.scan()
negate = true
}
node := new(ComparisonExpr)
node.Operator = t.parseOperator()
node.Left = left
if negate {
switch node.Operator {
case OperatorIn:
node.Operator = OperatorNotIn
case OperatorGlob:
node.Operator = OperatorNotGlob
case OperatorRe:
node.Operator = OperatorNotRe
}
}
switch node.Operator {
case OperatorIn, OperatorNotIn:
node.Right = t.parseList()
case OperatorRe, OperatorNotRe:
// TODO placeholder for custom Regexp Node
node.Right = t.parseVal()
default:
node.Right = t.parseVal()
}
return node
}
func (t *Tree) parseOperator() (op Operator) {
switch t.lex.scan() {
case tokenEq:
return OperatorEq
case tokenGt:
return OperatorGt
case tokenGte:
return OperatorGte
case tokenLt:
return OperatorLt
case tokenLte:
return OperatorLte
case tokenNeq:
return OperatorNeq
case tokenIn:
return OperatorIn
case tokenRegexp:
return OperatorRe
case tokenGlob:
return OperatorGlob
default:
t.errorf("illegal operator")
return
}
}
func (t *Tree) parseVal() ValExpr {
switch t.lex.scan() {
case tokenIdent:
node := new(Field)
node.Name = t.lex.bytes()
return node
case tokenText:
return t.parseText()
case tokenReal, tokenInteger, tokenTrue, tokenFalse:
node := new(BasicLit)
node.Value = t.lex.bytes()
return node
default:
t.errorf("illegal value expression")
return nil
}
}
func (t *Tree) parseList() ValExpr {
if t.lex.scan() != tokenLparen {
t.errorf("unexpected token, expecting (")
return nil
}
node := new(ArrayLit)
for {
next := t.lex.peek()
switch next {
case tokenEOF:
t.errorf("unexpected eof, expecting )")
case tokenComma:
t.lex.scan()
case tokenRparen:
t.lex.scan()
return node
default:
child := t.parseVal()
node.Values = append(node.Values, child)
}
}
}
func (t *Tree) parseText() ValExpr {
node := new(BasicLit)
node.Value = t.lex.bytes()
// this is where we strip the starting and ending quote
// and unescape the string. On the surface this might look
// like it is subject to index out of bounds errors but
// it is safe because it is already verified by the lexer.
node.Value = node.Value[1 : len(node.Value)-1]
node.Value = bytes.Replace(node.Value, quoteEscaped, quoteUnescaped, -1)
return node
}
// errString indicates the string literal does no have the right syntax.
// var errString = errors.New("invalid string literal")
var (
quoteEscaped = []byte("\\'")
quoteUnescaped = []byte("'")
)
// unquote interprets buf as a single-quoted literal, returning the
// value that buf quotes.
// func unquote(buf []byte) ([]byte, error) {
// n := len(buf)
// if n < 2 {
// return nil, errString
// }
// quote := buf[0]
// if quote != quoteUnescaped[0] {
// return nil, errString
// }
// if quote != buf[n-1] {
// return nil, errString
// }
// buf = buf[1 : n-1]
// return bytes.Replace(buf, quoteEscaped, quoteUnescaped, -1), nil
// }