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 }