Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: add wrap expr support #5

Merged
merged 2 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/laojianzi/kql-go/parser"
)

query := `service_name: "redis" OR service_name: "mysql" AND level: "error" and start_time > 1723286863 anD latency >= 1.5`
query := `(service_name: "redis" OR service_name: "mysql") AND level: ("error" OR "warn") and start_time > 1723286863 anD latency >= 1.5`
// Parse query into AST
stmt, err := parser.New(query).Stmt()
if err != nil {
Expand All @@ -29,7 +29,7 @@ if err != nil {
// output AST to KQL query
fmt.Println(stmt.String())
// output:
// service_name: "redis" OR service_name: "mysql" AND level: "error" AND start_time > 1723286863 AND latency >= 1.5
// (service_name: "redis" OR service_name: "mysql") AND level: ("error" OR "warn") AND start_time > 1723286863 AND latency >= 1.5
```

## Contact us
Expand Down
33 changes: 33 additions & 0 deletions parser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@ type Expr interface {
String() string
}

type WrapExpr struct {
pos int
Field string
Layers int
Expr Expr
}

func (e *WrapExpr) Pos() int {
return e.pos
}

func (e *WrapExpr) End() int {
return e.Expr.End() + e.Layers
}

func (e *WrapExpr) String() string {
var buf strings.Builder
if e.Field != "" {
buf.WriteString(e.Field)
buf.WriteString(": ")
}

buf.WriteString(strings.Repeat("(", e.Layers))
buf.WriteString(e.Expr.String())
buf.WriteString(strings.Repeat(")", e.Layers))

return buf.String()
}

type CombineExpr struct {
LeftExpr Expr
Keyword Kind
Expand Down Expand Up @@ -51,6 +80,10 @@ func (e *MatchExpr) Pos() int {
}

func (e *MatchExpr) End() int {
if e.Value.WithDoubleQuote {
return e.Value.End() + 1
}

return e.Value.End()
}

Expand Down
25 changes: 24 additions & 1 deletion parser/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func (l *defaultLexer) peekToken() (*Token, error) {
switch l.peekN(0) {
case ':', '<', '>': // operator
return l.peekOperator()
case '(', ')':
return l.peekWrapper()
case '+', '-': // with #t or float
fallthrough // jump to number case
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': // int or float
Expand Down Expand Up @@ -105,7 +107,7 @@ func (l *defaultLexer) peekNumber() (*Token, error) {

for l.peekOk(i) {
b := l.peekN(i)
if unicode.IsSpace(rune(b)) {
if unicode.IsSpace(rune(b)) || b == ')' {
break
}

Expand Down Expand Up @@ -150,6 +152,27 @@ func (l *defaultLexer) peekOperator() (*Token, error) {
return tok, nil
}

func (l *defaultLexer) peekWrapper() (*Token, error) {
tok := &Token{
Pos: l.current,
End: l.current + 1,
Value: l.slice(0, 1),
}

switch l.peekN(0) {
case '(':
tok.Kind = TokenKindLparen
case ')':
tok.Kind = TokenKindRparen
default:
return nil, fmt.Errorf("expected token \"(\" or \")\", but got %q", string(l.input[l.current]))
}

l.skipN(1)

return tok, nil
}

func (l *defaultLexer) peekWhitespace() error {
oldCurrent := l.current
l.skipWhitespace()
Expand Down
104 changes: 95 additions & 9 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,45 @@ func (p *defaultParser) Stmt() (Expr, error) {
}

func (p *defaultParser) parseExpr() (Expr, error) {
return p.parseCombineExpr(nil)
return p.parseWrapExpr(0)
}

func (p *defaultParser) parseWrapExpr(layers int) (Expr, error) {
oldCurrent := p.lexer.current

token, err := p.lexer.peekToken()
if err != nil {
return nil, err
}

if token.Kind == TokenKindLparen {
return p.parseWrapExpr(layers + 1)
}

p.lexer.current = oldCurrent // rollback current index

expr, err := p.parseCombineExpr(nil)
if err != nil {
return nil, err
}

if layers == 0 {
return expr, nil
}

// close wrap
for i := 0; i < layers; i++ {
token, err = p.lexer.peekToken()
if err != nil {
return nil, err
}

if token.Kind != TokenKindRparen {
return nil, fmt.Errorf("expected token <Rparen>, but got %q", token.Kind.String())
}
}

return p.parseCombineExpr(&WrapExpr{pos: expr.Pos() - layers, Layers: layers, Expr: expr})
}

func (p *defaultParser) parseCombineExpr(left Expr) (Expr, error) {
Expand All @@ -35,13 +73,27 @@ func (p *defaultParser) parseCombineExpr(left Expr) (Expr, error) {
}

return p.parseCombineExpr(matchExpr)
case *MatchExpr:
case *MatchExpr, *WrapExpr:
return p.parseCombineExpr(&CombineExpr{LeftExpr: expr})
case *CombineExpr:
if p.isEof() {
if expr.Keyword.IsKeyword() {
return expr, nil
}

return expr.LeftExpr, nil
}

// try peek wrap close
if token, err := p.lexer.peekWrapper(); err == nil {
// rollback pos
p.lexer.current = token.Pos

if token.Kind == TokenKindRparen {
return expr.LeftExpr, nil
}
}

if err := p.lexer.peekWhitespace(); err != nil {
return nil, err
}
Expand All @@ -61,7 +113,7 @@ func (p *defaultParser) parseCombineExpr(left Expr) (Expr, error) {
return nil, err
}

expr.RightExpr, err = p.parseCombineExpr(nil)
expr.RightExpr, err = p.parseExpr()
if err != nil {
return nil, err
}
Expand All @@ -72,9 +124,11 @@ func (p *defaultParser) parseCombineExpr(left Expr) (Expr, error) {
return nil, fmt.Errorf("unexpected Expr(%T)", left)
}

// need fix lint (funlen)
// //nolint: funlen
func (p *defaultParser) parseMatchExpr() (Expr, error) {
if p.isEof() {
return nil, errors.New("expected value or match expr, but got Eof")
return nil, errors.New("expected field or value, but got Eof")
}

pos := p.lexer.current
Expand Down Expand Up @@ -102,10 +156,10 @@ func (p *defaultParser) parseMatchExpr() (Expr, error) {
return nil, fmt.Errorf("expected field or value, but got %q", token.Kind.String())
}

// maby is field or value
mabyValue := &Literal{token.Pos, token.End, token.Kind, token.Value, token.Kind == TokenKindString}
// maybe is field or value
maybeValue := &Literal{token.Pos, token.End, token.Kind, token.Value, token.Kind == TokenKindString}
// default operator = ":" if only value
expr := &MatchExpr{pos: pos, HasNot: hasNot, Operator: TokenKindOperatorEql, Value: mabyValue}
expr := &MatchExpr{pos: pos, HasNot: hasNot, Operator: TokenKindOperatorEql, Value: maybeValue}

if p.isEof() {
return expr, nil
Expand All @@ -117,18 +171,23 @@ func (p *defaultParser) parseMatchExpr() (Expr, error) {
}

if !token.Kind.IsOperator() {
p.lexer.current = mabyValue.end
p.lexer.current = expr.End()

return expr, nil
}

expr.Field, expr.Operator = mabyValue.Value, token.Kind
expr.Field, expr.Operator = maybeValue.Value, token.Kind

token, err = p.lexer.peekToken()
if err != nil {
return nil, err
}

// e.g. field: (...)
if token.Kind == TokenKindLparen {
return p.parseWrapExprInMatchExpr(token, expr)
}

if !token.Kind.IsValue() {
return nil, fmt.Errorf("expected value, but got %q", token.Kind.String())
}
Expand All @@ -138,6 +197,33 @@ func (p *defaultParser) parseMatchExpr() (Expr, error) {
return expr, nil
}

func (p *defaultParser) parseWrapExprInMatchExpr(token *Token, matchExpr *MatchExpr) (Expr, error) {
p.lexer.current = token.Pos

wrapExpr, err := p.parseExpr()
if err != nil {
return nil, err
}

switch e := wrapExpr.(type) {
case *CombineExpr:
if left, ok := e.LeftExpr.(*WrapExpr); ok {
left.pos = matchExpr.pos
left.Field = matchExpr.Field
}

if right, ok := e.RightExpr.(*WrapExpr); ok {
right.pos = matchExpr.pos
right.Field = matchExpr.Field
}
case *WrapExpr:
e.pos = matchExpr.pos
e.Field = matchExpr.Field
}

return wrapExpr, nil
}

func (p *defaultParser) isEof() bool {
return p.lexer.isEof()
}
4 changes: 2 additions & 2 deletions parser/parser_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
)

func Example() {
query := `service_name: "redis" OR service_name: "mysql" AND level: "error" and start_time > 1723286863 anD latency >= 1.5`
query := `(service_name: "redis" OR service_name: "mysql") AND level: ("error" OR "warn") and start_time > 1723286863 anD latency >= 1.5`
// Parse query into AST
stmt, err := parser.New(query).Stmt()
if err != nil {
Expand All @@ -17,5 +17,5 @@ func Example() {
// output AST to KQL query
fmt.Println(stmt.String())
// output:
// service_name: "redis" OR service_name: "mysql" AND level: "error" AND start_time > 1723286863 AND latency >= 1.5
// (service_name: "redis" OR service_name: "mysql") AND level: ("error" OR "warn") AND start_time > 1723286863 AND latency >= 1.5
}
Loading