Merge pull request #659 from prometheus/fabxc/pql/parse-err

Remove `name` arg from `Parse*` functions, enhance parsing errors.
This commit is contained in:
Fabian Reinartz 2015-04-29 16:50:15 +02:00
commit 43e291e978
8 changed files with 51 additions and 34 deletions

View file

@ -294,7 +294,7 @@ func (ng *Engine) Stop() {
// NewQuery returns a new query of the given query string. // NewQuery returns a new query of the given query string.
func (ng *Engine) NewQuery(qs string) (Query, error) { func (ng *Engine) NewQuery(qs string) (Query, error) {
stmts, err := ParseStmts("query", qs) stmts, err := ParseStmts(qs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -325,7 +325,7 @@ func (ng *Engine) NewInstantQuery(es string, ts clientmodel.Timestamp) (Query, e
// NewRangeQuery returns an evaluation query for the given time range and with // NewRangeQuery returns an evaluation query for the given time range and with
// the resolution set by the interval. // the resolution set by the interval.
func (ng *Engine) NewRangeQuery(qs string, start, end clientmodel.Timestamp, interval time.Duration) (Query, error) { func (ng *Engine) NewRangeQuery(qs string, start, end clientmodel.Timestamp, interval time.Duration) (Query, error) {
expr, err := ParseExpr("query", qs) expr, err := ParseExpr(qs)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -222,7 +222,6 @@ type Pos int
// lexer holds the state of the scanner. // lexer holds the state of the scanner.
type lexer struct { type lexer struct {
name string // The name of the input; used only for error reports.
input string // The string being scanned. input string // The string being scanned.
state stateFn // The next lexing function to enter. state stateFn // The next lexing function to enter.
pos Pos // Current position in the input. pos Pos // Current position in the input.
@ -298,12 +297,12 @@ func (l *lexer) lineNumber() int {
// linePosition reports at which character in the current line // linePosition reports at which character in the current line
// we are on. // we are on.
func (l *lexer) linePosition() Pos { func (l *lexer) linePosition() int {
lb := Pos(strings.LastIndex(l.input[:l.lastPos], "\n")) lb := strings.LastIndex(l.input[:l.lastPos], "\n")
if lb == -1 { if lb == -1 {
return 1 + l.lastPos return 1 + int(l.lastPos)
} }
return 1 + l.lastPos - lb return 1 + int(l.lastPos) - lb
} }
// errorf returns an error token and terminates the scan by passing // errorf returns an error token and terminates the scan by passing
@ -321,9 +320,8 @@ func (l *lexer) nextItem() item {
} }
// lex creates a new scanner for the input string. // lex creates a new scanner for the input string.
func lex(name, input string) *lexer { func lex(input string) *lexer {
l := &lexer{ l := &lexer{
name: name,
input: input, input: input,
items: make(chan item), items: make(chan item),
} }

View file

@ -14,7 +14,6 @@
package promql package promql
import ( import (
"fmt"
"reflect" "reflect"
"testing" "testing"
) )
@ -328,8 +327,7 @@ var tests = []struct {
// for the parser to avoid duplicated effort. // for the parser to avoid duplicated effort.
func TestLexer(t *testing.T) { func TestLexer(t *testing.T) {
for i, test := range tests { for i, test := range tests {
tn := fmt.Sprintf("test.%d \"%s\"", i, test.input) l := lex(test.input)
l := lex(tn, test.input)
out := []item{} out := []item{}
for it := range l.items { for it := range l.items {
@ -339,20 +337,20 @@ func TestLexer(t *testing.T) {
lastItem := out[len(out)-1] lastItem := out[len(out)-1]
if test.fail { if test.fail {
if lastItem.typ != itemError { if lastItem.typ != itemError {
t.Fatalf("%s: expected lexing error but did not fail", tn) t.Fatalf("%d: expected lexing error but did not fail", i)
} }
continue continue
} }
if lastItem.typ == itemError { if lastItem.typ == itemError {
t.Fatalf("%s: unexpected lexing error: %s", tn, lastItem) t.Fatalf("%d: unexpected lexing error: %s", i, lastItem)
} }
if !reflect.DeepEqual(lastItem, item{itemEOF, Pos(len(test.input)), ""}) { if !reflect.DeepEqual(lastItem, item{itemEOF, Pos(len(test.input)), ""}) {
t.Fatalf("%s: lexing error: expected output to end with EOF item", tn) t.Fatalf("%d: lexing error: expected output to end with EOF item", i)
} }
out = out[:len(out)-1] out = out[:len(out)-1]
if !reflect.DeepEqual(out, test.expected) { if !reflect.DeepEqual(out, test.expected) {
t.Errorf("%s: lexing mismatch:\nexpected: %#v\n-----\ngot: %#v", tn, test.expected, out) t.Errorf("%d: lexing mismatch:\nexpected: %#v\n-----\ngot: %#v", i, test.expected, out)
} }
} }
} }

View file

@ -17,6 +17,7 @@ import (
"fmt" "fmt"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
clientmodel "github.com/prometheus/client_golang/model" clientmodel "github.com/prometheus/client_golang/model"
@ -25,15 +26,29 @@ import (
) )
type parser struct { type parser struct {
name string
lex *lexer lex *lexer
token [3]item token [3]item
peekCount int peekCount int
} }
// ParseErr wraps a parsing error with line and position context.
// If the parsing input was a single line, line will be 0 and omitted
// from the error string.
type ParseErr struct {
Line, Pos int
Err error
}
func (e *ParseErr) Error() string {
if e.Line == 0 {
return fmt.Sprintf("Parse error at char %d: %s", e.Pos, e.Err)
}
return fmt.Sprintf("Parse error at line %d, char %d: %s", e.Line, e.Pos, e.Err)
}
// ParseStmts parses the input and returns the resulting statements or any ocurring error. // ParseStmts parses the input and returns the resulting statements or any ocurring error.
func ParseStmts(name, input string) (Statements, error) { func ParseStmts(input string) (Statements, error) {
p := newParser(name, input) p := newParser(input)
stmts, err := p.parseStmts() stmts, err := p.parseStmts()
if err != nil { if err != nil {
@ -44,8 +59,8 @@ func ParseStmts(name, input string) (Statements, error) {
} }
// ParseExpr returns the expression parsed from the input. // ParseExpr returns the expression parsed from the input.
func ParseExpr(name, input string) (Expr, error) { func ParseExpr(input string) (Expr, error) {
p := newParser(name, input) p := newParser(input)
expr, err := p.parseExpr() expr, err := p.parseExpr()
if err != nil { if err != nil {
@ -56,10 +71,9 @@ func ParseExpr(name, input string) (Expr, error) {
} }
// newParser returns a new parser. // newParser returns a new parser.
func newParser(name, input string) *parser { func newParser(input string) *parser {
p := &parser{ p := &parser{
name: name, lex: lex(input),
lex: lex(name, input),
} }
return p return p
} }
@ -144,13 +158,20 @@ func (p *parser) backup() {
// errorf formats the error and terminates processing. // errorf formats the error and terminates processing.
func (p *parser) errorf(format string, args ...interface{}) { func (p *parser) errorf(format string, args ...interface{}) {
format = fmt.Sprintf("%s:%d,%d %s", p.name, p.lex.lineNumber(), p.lex.linePosition(), format) p.error(fmt.Errorf(format, args...))
panic(fmt.Errorf(format, args...))
} }
// error terminates processing. // error terminates processing.
func (p *parser) error(err error) { func (p *parser) error(err error) {
p.errorf("%s", err) perr := &ParseErr{
Line: p.lex.lineNumber(),
Pos: p.lex.linePosition(),
Err: err,
}
if strings.Count(strings.TrimSpace(p.lex.input), "\n") == 0 {
perr.Line = 0
}
panic(perr)
} }
// expect consumes the next token and guarantees it has the required type. // expect consumes the next token and guarantees it has the required type.

View file

@ -785,7 +785,7 @@ var testExpr = []struct {
func TestParseExpressions(t *testing.T) { func TestParseExpressions(t *testing.T) {
for _, test := range testExpr { for _, test := range testExpr {
parser := newParser("test", test.input) parser := newParser(test.input)
expr, err := parser.parseExpr() expr, err := parser.parseExpr()
if !test.fail && err != nil { if !test.fail && err != nil {
@ -819,7 +819,7 @@ func TestParseExpressions(t *testing.T) {
// NaN has no equality. Thus, we need a separate test for it. // NaN has no equality. Thus, we need a separate test for it.
func TestNaNExpression(t *testing.T) { func TestNaNExpression(t *testing.T) {
parser := newParser("test", "NaN") parser := newParser("NaN")
expr, err := parser.parseExpr() expr, err := parser.parseExpr()
if err != nil { if err != nil {
@ -1028,7 +1028,7 @@ var testStatement = []struct {
func TestParseStatements(t *testing.T) { func TestParseStatements(t *testing.T) {
for _, test := range testStatement { for _, test := range testStatement {
parser := newParser("test", test.input) parser := newParser(test.input)
stmts, err := parser.parseStmts() stmts, err := parser.parseStmts()
if !test.fail && err != nil { if !test.fail && err != nil {

View file

@ -173,7 +173,7 @@ func TestAlertingRule(t *testing.T) {
engine := promql.NewEngine(storage) engine := promql.NewEngine(storage)
defer engine.Stop() defer engine.Stop()
expr, err := promql.ParseExpr("test", `http_requests{group="canary", job="app-server"} < 100`) expr, err := promql.ParseExpr(`http_requests{group="canary", job="app-server"} < 100`)
if err != nil { if err != nil {
t.Fatalf("Unable to parse alert expression: %s", err) t.Fatalf("Unable to parse alert expression: %s", err)
} }

View file

@ -39,7 +39,7 @@ func checkRules(filename string, in io.Reader, out io.Writer) error {
return err return err
} }
rules, err := promql.ParseStmts(filename, string(content)) rules, err := promql.ParseStmts(string(content))
if err != nil { if err != nil {
return err return err
} }

View file

@ -52,7 +52,7 @@ func TestQuery(t *testing.T) {
{ {
queryStr: "", queryStr: "",
status: http.StatusOK, status: http.StatusOK,
bodyRe: `{"type":"error","value":"query:1,1 no expression found in input","version":1}`, bodyRe: `{"type":"error","value":"Parse error at char 1: no expression found in input","version":1}`,
}, },
{ {
queryStr: "expr=testmetric", queryStr: "expr=testmetric",
@ -77,7 +77,7 @@ func TestQuery(t *testing.T) {
{ {
queryStr: "expr=(badexpression", queryStr: "expr=(badexpression",
status: http.StatusOK, status: http.StatusOK,
bodyRe: `{"type":"error","value":"query:1,15 unexpected unclosed left parenthesis in paren expression","version":1}`, bodyRe: `{"type":"error","value":"Parse error at char 15: unexpected unclosed left parenthesis in paren expression","version":1}`,
}, },
} }