130 lines
2.9 KiB
Go
130 lines
2.9 KiB
Go
package fexpr
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
)
|
|
|
|
var ErrEmpty = errors.New("empty filter expression")
|
|
var ErrIncomplete = errors.New("invalid or incomplete filter expression")
|
|
var ErrInvalidComment = errors.New("invalid comment")
|
|
|
|
// Expr represents an individual tokenized expression consisting
|
|
// of left operand, operator and a right operand.
|
|
type Expr struct {
|
|
Left Token
|
|
Op SignOp
|
|
Right Token
|
|
}
|
|
|
|
// IsZero checks if the current Expr has zero-valued props.
|
|
func (e Expr) IsZero() bool {
|
|
return e.Op == "" && e.Left.Literal == "" && e.Left.Type == "" && e.Right.Literal == "" && e.Right.Type == ""
|
|
}
|
|
|
|
// ExprGroup represents a wrapped expression and its join type.
|
|
//
|
|
// The group's Item could be either an `Expr` instance or `[]ExprGroup` slice (for nested expressions).
|
|
type ExprGroup struct {
|
|
Item interface{}
|
|
Join JoinOp
|
|
}
|
|
|
|
// parser's state machine steps
|
|
const (
|
|
stepBeforeSign = iota
|
|
stepSign
|
|
stepAfterSign
|
|
StepJoin
|
|
)
|
|
|
|
// Parse parses the provided text and returns its processed AST
|
|
// in the form of `ExprGroup` slice(s).
|
|
//
|
|
// Comments and whitespaces are ignored.
|
|
func Parse(text string) ([]ExprGroup, error) {
|
|
result := []ExprGroup{}
|
|
scanner := NewScanner([]byte(text))
|
|
step := stepBeforeSign
|
|
join := JoinAnd
|
|
|
|
var expr Expr
|
|
|
|
for {
|
|
t, err := scanner.Scan()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if t.Type == TokenEOF {
|
|
break
|
|
}
|
|
|
|
if t.Type == TokenWS || t.Type == TokenComment {
|
|
continue
|
|
}
|
|
|
|
if t.Type == TokenGroup {
|
|
groupResult, err := Parse(t.Literal)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// append only if non-empty group
|
|
if len(groupResult) > 0 {
|
|
result = append(result, ExprGroup{Join: join, Item: groupResult})
|
|
}
|
|
|
|
step = StepJoin
|
|
continue
|
|
}
|
|
|
|
switch step {
|
|
case stepBeforeSign:
|
|
if t.Type != TokenIdentifier && t.Type != TokenText && t.Type != TokenNumber && t.Type != TokenFunction {
|
|
return nil, fmt.Errorf("expected left operand (identifier, function, text or number), got %q (%s)", t.Literal, t.Type)
|
|
}
|
|
|
|
expr = Expr{Left: t}
|
|
|
|
step = stepSign
|
|
case stepSign:
|
|
if t.Type != TokenSign {
|
|
return nil, fmt.Errorf("expected a sign operator, got %q (%s)", t.Literal, t.Type)
|
|
}
|
|
|
|
expr.Op = SignOp(t.Literal)
|
|
step = stepAfterSign
|
|
case stepAfterSign:
|
|
if t.Type != TokenIdentifier && t.Type != TokenText && t.Type != TokenNumber && t.Type != TokenFunction {
|
|
return nil, fmt.Errorf("expected right operand (identifier, function text or number), got %q (%s)", t.Literal, t.Type)
|
|
}
|
|
|
|
expr.Right = t
|
|
result = append(result, ExprGroup{Join: join, Item: expr})
|
|
|
|
step = StepJoin
|
|
case StepJoin:
|
|
if t.Type != TokenJoin {
|
|
return nil, fmt.Errorf("expected && or ||, got %q (%s)", t.Literal, t.Type)
|
|
}
|
|
|
|
join = JoinAnd
|
|
if t.Literal == "||" {
|
|
join = JoinOr
|
|
}
|
|
|
|
step = stepBeforeSign
|
|
}
|
|
}
|
|
|
|
if step != StepJoin {
|
|
if len(result) == 0 && expr.IsZero() {
|
|
return nil, ErrEmpty
|
|
}
|
|
|
|
return nil, ErrIncomplete
|
|
}
|
|
|
|
return result, nil
|
|
}
|