mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Rename search
package to filter
package
This commit is contained in:
250
pkg/filter/parser.go
Normal file
250
pkg/filter/parser.go
Normal file
@ -0,0 +1,250 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type precedence int
|
||||
|
||||
const (
|
||||
_ precedence = iota
|
||||
precLowest
|
||||
precAnd
|
||||
precOr
|
||||
precNot
|
||||
precEq
|
||||
precLessGreater
|
||||
precPrefix
|
||||
precGroup
|
||||
)
|
||||
|
||||
type (
|
||||
prefixParser func(*Parser) (Expression, error)
|
||||
infixParser func(*Parser, Expression) (Expression, error)
|
||||
)
|
||||
|
||||
var (
|
||||
prefixParsers = map[TokenType]prefixParser{}
|
||||
infixParsers = map[TokenType]infixParser{}
|
||||
)
|
||||
|
||||
var tokenPrecedences = map[TokenType]precedence{
|
||||
TokParenOpen: precGroup,
|
||||
TokOpNot: precNot,
|
||||
TokOpAnd: precAnd,
|
||||
TokOpOr: precOr,
|
||||
TokOpEq: precEq,
|
||||
TokOpNotEq: precEq,
|
||||
TokOpGt: precLessGreater,
|
||||
TokOpLt: precLessGreater,
|
||||
TokOpGtEq: precLessGreater,
|
||||
TokOpLtEq: precLessGreater,
|
||||
TokOpRe: precEq,
|
||||
TokOpNotRe: precEq,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Populate maps in `init`, because package global variables would cause an
|
||||
// initialization cycle.
|
||||
infixOperators := []TokenType{
|
||||
TokOpAnd,
|
||||
TokOpOr,
|
||||
TokOpEq,
|
||||
TokOpNotEq,
|
||||
TokOpGt,
|
||||
TokOpLt,
|
||||
TokOpGtEq,
|
||||
TokOpLtEq,
|
||||
TokOpRe,
|
||||
TokOpNotRe,
|
||||
}
|
||||
for _, op := range infixOperators {
|
||||
infixParsers[op] = parseInfixExpression
|
||||
}
|
||||
|
||||
prefixParsers[TokOpNot] = parsePrefixExpression
|
||||
prefixParsers[TokString] = parseStringLiteral
|
||||
prefixParsers[TokParenOpen] = parseGroupedExpression
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
l *Lexer
|
||||
cur Token
|
||||
peek Token
|
||||
}
|
||||
|
||||
func NewParser(l *Lexer) *Parser {
|
||||
p := &Parser{l: l}
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func ParseQuery(input string) (expr Expression, err error) {
|
||||
p := &Parser{l: NewLexer(input)}
|
||||
p.nextToken()
|
||||
p.nextToken()
|
||||
|
||||
if p.curTokenIs(TokEOF) {
|
||||
return nil, fmt.Errorf("filter: unexpected EOF")
|
||||
}
|
||||
|
||||
for !p.curTokenIs(TokEOF) {
|
||||
right, err := p.parseExpression(precLowest)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
return nil, fmt.Errorf("filter: could not parse expression: %w", err)
|
||||
case expr == nil:
|
||||
expr = right
|
||||
default:
|
||||
expr = InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: expr,
|
||||
Right: right,
|
||||
}
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Parser) nextToken() {
|
||||
p.cur = p.peek
|
||||
p.peek = p.l.Next()
|
||||
}
|
||||
|
||||
func (p *Parser) curTokenIs(t TokenType) bool {
|
||||
return p.cur.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) peekTokenIs(t TokenType) bool {
|
||||
return p.peek.Type == t
|
||||
}
|
||||
|
||||
func (p *Parser) curPrecedence() precedence {
|
||||
if p, ok := tokenPrecedences[p.cur.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return precLowest
|
||||
}
|
||||
|
||||
func (p *Parser) peekPrecedence() precedence {
|
||||
if p, ok := tokenPrecedences[p.peek.Type]; ok {
|
||||
return p
|
||||
}
|
||||
|
||||
return precLowest
|
||||
}
|
||||
|
||||
func (p *Parser) parseExpression(prec precedence) (Expression, error) {
|
||||
prefixParser, ok := prefixParsers[p.cur.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no prefix parse function for %v found", p.cur.Type)
|
||||
}
|
||||
|
||||
expr, err := prefixParser(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse expression prefix: %w", err)
|
||||
}
|
||||
|
||||
for !p.peekTokenIs(eof) && prec < p.peekPrecedence() {
|
||||
infixParser, ok := infixParsers[p.peek.Type]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
expr, err = infixParser(p, expr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse infix expression: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parsePrefixExpression(p *Parser) (Expression, error) {
|
||||
expr := PrefixExpression{
|
||||
Operator: p.cur.Type,
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
|
||||
right, err := p.parseExpression(precPrefix)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse expression for right operand: %w", err)
|
||||
}
|
||||
|
||||
expr.Right = right
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parseInfixExpression(p *Parser, left Expression) (Expression, error) {
|
||||
expr := InfixExpression{
|
||||
Operator: p.cur.Type,
|
||||
Left: left,
|
||||
}
|
||||
|
||||
prec := p.curPrecedence()
|
||||
p.nextToken()
|
||||
|
||||
right, err := p.parseExpression(prec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse expression for right operand: %w", err)
|
||||
}
|
||||
|
||||
if expr.Operator == TokOpRe || expr.Operator == TokOpNotRe {
|
||||
if rightStr, ok := right.(StringLiteral); ok {
|
||||
re, err := regexp.Compile(rightStr.Value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not compile regular expression %q: %w", rightStr.Value, err)
|
||||
}
|
||||
|
||||
right = RegexpLiteral{re}
|
||||
}
|
||||
}
|
||||
|
||||
expr.Right = right
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parseStringLiteral(p *Parser) (Expression, error) {
|
||||
return StringLiteral{Value: p.cur.Literal}, nil
|
||||
}
|
||||
|
||||
func parseGroupedExpression(p *Parser) (Expression, error) {
|
||||
p.nextToken()
|
||||
|
||||
expr, err := p.parseExpression(precLowest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse grouped expression: %w", err)
|
||||
}
|
||||
|
||||
for p.nextToken(); !p.curTokenIs(TokParenClose); p.nextToken() {
|
||||
if p.curTokenIs(TokEOF) {
|
||||
return nil, fmt.Errorf("unexpected EOF: unmatched parentheses")
|
||||
}
|
||||
|
||||
right, err := p.parseExpression(precLowest)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse expression: %w", err)
|
||||
}
|
||||
|
||||
expr = InfixExpression{
|
||||
Operator: TokOpAnd,
|
||||
Left: expr,
|
||||
Right: right,
|
||||
}
|
||||
}
|
||||
|
||||
return expr, nil
|
||||
}
|
Reference in New Issue
Block a user