diff --git a/pkg/logql/expr.go b/pkg/logql/expr.go index 70c2cd7a8d97a188695b88fb9546a72e2d5ad2e9..16e485923e8b9ee7cd75c54fe823cf846fd3d009 100644 --- a/pkg/logql/expr.go +++ b/pkg/logql/expr.go @@ -15,6 +15,8 @@ import ( type exprSymType struct { yys int Expr Expr + Filter labels.MatchType + Selector []*labels.Matcher Matchers []*labels.Matcher Matcher *labels.Matcher str string @@ -61,56 +63,59 @@ const exprEofCode = 1 const exprErrCode = 2 const exprInitialStackSize = 16 -//line pkg/logql/expr.y:51 +//line pkg/logql/expr.y:65 //line yacctab:1 var exprExca = [...]int{ -1, 1, 1, -1, -2, 0, + -1, 2, + 1, 1, + -2, 0, } const exprPrivate = 57344 -const exprLast = 26 +const exprLast = 30 var exprAct = [...]int{ - 8, 10, 16, 17, 7, 3, 6, 18, 19, 20, - 21, 4, 5, 26, 25, 24, 23, 15, 14, 22, - 13, 12, 11, 9, 2, 1, + 6, 13, 20, 4, 29, 18, 28, 10, 14, 9, + 21, 22, 23, 24, 7, 8, 17, 19, 27, 16, + 26, 25, 15, 12, 11, 14, 3, 5, 2, 1, } var exprPact = [...]int{ - -7, -1000, -5, 18, 16, 15, 13, 12, -1000, -11, - -1000, -1, -1000, -1000, -1000, -1000, -1000, 18, 11, 10, - 9, 8, -1000, -1000, -1000, -1000, -1000, + -9, -1000, -2, -1000, 21, 17, -1000, -1000, -1000, -1000, + -1000, 3, -11, -1000, 2, -1000, -1000, -1000, -1000, 4, + -1000, 15, 13, 1, -1, -1000, -1000, -1000, -1000, -1000, } var exprPgo = [...]int{ - 0, 25, 24, 23, 1, + 0, 29, 28, 27, 26, 24, 1, } var exprR1 = [...]int{ - 0, 1, 2, 2, 2, 2, 2, 2, 2, 3, - 3, 4, 4, 4, 4, + 0, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 5, 5, 6, 6, 6, 6, } var exprR2 = [...]int{ - 0, 1, 3, 3, 3, 3, 3, 2, 2, 1, - 3, 3, 3, 3, 3, + 0, 1, 1, 3, 3, 2, 1, 1, 1, 1, + 3, 3, 3, 1, 3, 3, 3, 3, 3, } var exprChk = [...]int{ - -1000, -1, -2, 12, 16, 17, 11, 9, 5, -3, - -4, 4, 5, 5, 5, 5, 13, 14, 8, 9, - 10, 11, -4, 5, 5, 5, 5, + -1000, -1, -2, -4, 12, -3, 2, 16, 17, 11, + 9, -5, 2, -6, 4, 5, 2, 13, 2, 14, + 13, 8, 9, 10, 11, -6, 5, 5, 5, 5, } var exprDef = [...]int{ - 0, -2, 1, 0, 7, 0, 0, 0, 8, 0, - 9, 0, 3, 4, 5, 6, 2, 0, 0, 0, - 0, 0, 10, 11, 12, 13, 14, + 0, -2, -2, 2, 0, 0, 5, 6, 7, 8, + 9, 0, 0, 13, 0, 3, 4, 10, 11, 0, + 12, 0, 0, 0, 0, 14, 15, 16, 17, 18, } var exprTok1 = [...]int{ @@ -464,85 +469,96 @@ exprdefault: case 1: exprDollar = exprS[exprpt-1 : exprpt+1] -//line pkg/logql/expr.y:28 +//line pkg/logql/expr.y:32 { exprlex.(*lexer).expr = exprDollar[1].Expr } case 2: - exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:31 + exprDollar = exprS[exprpt-1 : exprpt+1] +//line pkg/logql/expr.y:35 { - exprVAL.Expr = &matchersExpr{matchers: exprDollar[2].Matchers} + exprVAL.Expr = &matchersExpr{matchers: exprDollar[1].Selector} } case 3: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:32 +//line pkg/logql/expr.y:36 { - exprVAL.Expr = NewFilterExpr(exprDollar[1].Expr, labels.MatchRegexp, exprDollar[3].str) + exprVAL.Expr = NewFilterExpr(exprDollar[1].Expr, exprDollar[2].Filter, exprDollar[3].str) } - case 4: - exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:33 + case 6: + exprDollar = exprS[exprpt-1 : exprpt+1] +//line pkg/logql/expr.y:42 { - exprVAL.Expr = NewFilterExpr(exprDollar[1].Expr, labels.MatchEqual, exprDollar[3].str) + exprVAL.Filter = labels.MatchRegexp } - case 5: - exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:34 + case 7: + exprDollar = exprS[exprpt-1 : exprpt+1] +//line pkg/logql/expr.y:43 { - exprVAL.Expr = NewFilterExpr(exprDollar[1].Expr, labels.MatchNotRegexp, exprDollar[3].str) + exprVAL.Filter = labels.MatchEqual } - case 6: + case 8: + exprDollar = exprS[exprpt-1 : exprpt+1] +//line pkg/logql/expr.y:44 + { + exprVAL.Filter = labels.MatchNotRegexp + } + case 9: + exprDollar = exprS[exprpt-1 : exprpt+1] +//line pkg/logql/expr.y:45 + { + exprVAL.Filter = labels.MatchNotEqual + } + case 10: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:35 +//line pkg/logql/expr.y:49 { - exprVAL.Expr = NewFilterExpr(exprDollar[1].Expr, labels.MatchNotEqual, exprDollar[3].str) + exprVAL.Selector = exprDollar[2].Matchers } - case 7: - exprDollar = exprS[exprpt-2 : exprpt+1] -//line pkg/logql/expr.y:36 + case 11: + exprDollar = exprS[exprpt-3 : exprpt+1] +//line pkg/logql/expr.y:50 { - exprlex.(*lexer).Error("unexpected end of query, expected string") + exprVAL.Selector = exprDollar[2].Matchers } - case 8: - exprDollar = exprS[exprpt-2 : exprpt+1] -//line pkg/logql/expr.y:37 + case 12: + exprDollar = exprS[exprpt-3 : exprpt+1] +//line pkg/logql/expr.y:51 { - exprlex.(*lexer).Error("unexpected string, expected pipe") } - case 9: + case 13: exprDollar = exprS[exprpt-1 : exprpt+1] -//line pkg/logql/expr.y:41 +//line pkg/logql/expr.y:55 { exprVAL.Matchers = []*labels.Matcher{exprDollar[1].Matcher} } - case 10: + case 14: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:42 +//line pkg/logql/expr.y:56 { exprVAL.Matchers = append(exprDollar[1].Matchers, exprDollar[3].Matcher) } - case 11: + case 15: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:46 +//line pkg/logql/expr.y:60 { exprVAL.Matcher = mustNewMatcher(labels.MatchEqual, exprDollar[1].str, exprDollar[3].str) } - case 12: + case 16: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:47 +//line pkg/logql/expr.y:61 { exprVAL.Matcher = mustNewMatcher(labels.MatchNotEqual, exprDollar[1].str, exprDollar[3].str) } - case 13: + case 17: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:48 +//line pkg/logql/expr.y:62 { exprVAL.Matcher = mustNewMatcher(labels.MatchRegexp, exprDollar[1].str, exprDollar[3].str) } - case 14: + case 18: exprDollar = exprS[exprpt-3 : exprpt+1] -//line pkg/logql/expr.y:49 +//line pkg/logql/expr.y:63 { exprVAL.Matcher = mustNewMatcher(labels.MatchNotRegexp, exprDollar[1].str, exprDollar[3].str) } diff --git a/pkg/logql/expr.y b/pkg/logql/expr.y index 0a9588163fad58a80198aa9137bf021a953a9946..cbe8ded5cb72f7dcb042ea99d6cd1797c4a10d6c 100644 --- a/pkg/logql/expr.y +++ b/pkg/logql/expr.y @@ -8,6 +8,8 @@ import ( %union{ Expr Expr + Filter labels.MatchType + Selector []*labels.Matcher Matchers []*labels.Matcher Matcher *labels.Matcher str string @@ -16,9 +18,11 @@ import ( %start root -%type <Expr> expr -%type <Matchers> matchers -%type <Matcher> matcher +%type <Expr> expr +%type <Filter> filter +%type <Selector> selector +%type <Matchers> matchers +%type <Matcher> matcher %token <str> IDENTIFIER STRING %token <val> MATCHERS LABELS EQ NEQ RE NRE OPEN_BRACE CLOSE_BRACE COMMA DOT PIPE_MATCH PIPE_EXACT @@ -28,13 +32,23 @@ import ( root: expr { exprlex.(*lexer).expr = $1 }; expr: - OPEN_BRACE matchers CLOSE_BRACE { $$ = &matchersExpr{ matchers: $2 } } - | expr PIPE_MATCH STRING { $$ = NewFilterExpr( $1, labels.MatchRegexp, $3 ) } - | expr PIPE_EXACT STRING { $$ = NewFilterExpr( $1, labels.MatchEqual, $3 ) } - | expr NRE STRING { $$ = NewFilterExpr( $1, labels.MatchNotRegexp, $3 ) } - | expr NEQ STRING { $$ = NewFilterExpr( $1, labels.MatchNotEqual, $3 ) } - | expr PIPE_MATCH { exprlex.(*lexer).Error("unexpected end of query, expected string") } - | expr STRING { exprlex.(*lexer).Error("unexpected string, expected pipe") } + selector { $$ = &matchersExpr{ matchers: $1 } } + | expr filter STRING { $$ = NewFilterExpr( $1, $2, $3 ) } + | expr filter error + | expr error + ; + +filter: + PIPE_MATCH { $$ = labels.MatchRegexp } + | PIPE_EXACT { $$ = labels.MatchEqual } + | NRE { $$ = labels.MatchNotRegexp } + | NEQ { $$ = labels.MatchNotEqual } + ; + +selector: + OPEN_BRACE matchers CLOSE_BRACE { $$ = $2 } + | OPEN_BRACE matchers error { $$ = $2 } + | OPEN_BRACE error CLOSE_BRACE { } ; matchers: diff --git a/pkg/logql/parser.go b/pkg/logql/parser.go index de0c4499d6eeaf440ace006ff880deb0dce8db7c..7d34c8820b23e6c70f605f2fcd2f3008fe4cad77 100644 --- a/pkg/logql/parser.go +++ b/pkg/logql/parser.go @@ -7,18 +7,28 @@ import ( "text/scanner" ) +func init() { + // Improve the error messages coming out of yacc. + exprErrorVerbose = true + for str, tok := range tokens { + exprToknames[tok-exprPrivate+1] = str + } +} + // ParseExpr parses a string and returns an Expr. func ParseExpr(input string) (Expr, error) { - var l lexer + var l = lexer{ + parser: exprNewParser().(*exprParserImpl), + } l.Init(strings.NewReader(input)) //l.Scanner.Mode = scanner.SkipComments | scanner.ScanStrings | scanner.ScanInts l.Scanner.Error = func(_ *scanner.Scanner, msg string) { l.Error(msg) } - e := exprParse(&l) - if e != 0 || l.err != nil { - return nil, l.err + e := l.parser.Parse(&l) + if e != 0 || len(l.errs) > 0 { + return nil, l.errs[0] } return l.expr, nil } @@ -38,8 +48,9 @@ var tokens = map[string]int{ type lexer struct { scanner.Scanner - err error - expr Expr + errs []ParseError + expr Expr + parser *exprParserImpl } func (l *lexer) Lex(lval *exprSymType) int { @@ -73,16 +84,11 @@ func (l *lexer) Lex(lval *exprSymType) int { } func (l *lexer) Error(msg string) { - // We want to return the first error (from the lexer), and ignore subsequent ones. - if l.err != nil { - return - } - - l.err = ParseError{ + l.errs = append(l.errs, ParseError{ msg: msg, line: l.Line, col: l.Column, - } + }) } // ParseError is what is returned when we failed to parse. diff --git a/pkg/logql/parser_test.go b/pkg/logql/parser_test.go index cf53433d454af050430359019a8e2ffb4bc24b9e..1b889500ee591d1e97f38d217c2b3302f430e114 100644 --- a/pkg/logql/parser_test.go +++ b/pkg/logql/parser_test.go @@ -119,18 +119,36 @@ func TestParse(t *testing.T) { col: 6, }, }, + { + in: `{foo="bar"`, + err: ParseError{ + msg: "syntax error: unexpected $end, expecting } or ,", + line: 1, + col: 11, + }, + }, + { in: `{foo="bar"} |~`, err: ParseError{ - msg: "unexpected end of query, expected string", + msg: "syntax error: unexpected $end, expecting STRING", line: 1, col: 15, }, }, + { in: `{foo="bar"} "foo"`, err: ParseError{ - msg: "unexpected string, expected pipe", + msg: "syntax error: unexpected STRING, expecting != or !~ or |~ or |=", + line: 1, + col: 13, + }, + }, + { + in: `{foo="bar"} foo`, + err: ParseError{ + msg: "syntax error: unexpected IDENTIFIER, expecting != or !~ or |~ or |=", line: 1, col: 13, },