1
0
Fork 0
golang-github-ganigeorgiev-.../parser.go
Daniel Baumann 16e40566d2
Adding upstream version 0.5.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 20:57:13 +02:00

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
}