diff --git a/cli/queryflags/flags.go b/cli/queryflags/flags.go index 779926ddd1..618a4b0fc4 100644 --- a/cli/queryflags/flags.go +++ b/cli/queryflags/flags.go @@ -14,6 +14,7 @@ import ( "github.com/brimdata/zed/compiler" "github.com/brimdata/zed/compiler/ast" "github.com/brimdata/zed/compiler/data" + "github.com/brimdata/zed/compiler/parser" "github.com/brimdata/zed/compiler/semantic" "github.com/brimdata/zed/pkg/storage" "github.com/brimdata/zed/zbuf" @@ -40,7 +41,8 @@ func (f *Flags) ParseSourcesAndInputs(paths []string) ([]string, ast.Seq, bool, // Consider a lone argument to be a query if it compiles // and appears to start with a from or yield operator. // Otherwise, consider it a file. - if query, err := compiler.Parse(src, f.Includes...); err == nil { + query, err := compiler.Parse(src, f.Includes...) + if err == nil { if s, err := semantic.Analyze(context.Background(), query, data.NewSource(storage.NewLocalEngine(), nil), nil); err == nil { if semantic.HasSource(s) { return nil, query, false, nil @@ -50,7 +52,7 @@ func (f *Flags) ParseSourcesAndInputs(paths []string) ([]string, ast.Seq, bool, } } } - return nil, nil, false, singleArgError(src) + return nil, nil, false, singleArgError(src, err) } } query, err := compiler.Parse(src, f.Includes...) @@ -78,13 +80,25 @@ func (f *Flags) PrintStats(stats zbuf.Progress) { } } -func singleArgError(src string) error { +func singleArgError(src string, err error) error { var b strings.Builder b.WriteString("could not invoke zq with a single argument because:") - b.WriteString("\n - the argument did not parse as a valid Zed query") if len(src) > 20 { src = src[:20] + "..." } fmt.Fprintf(&b, "\n - a file could not be found with the name %q", src) + var perr *parser.Error + if errors.As(err, &perr) { + b.WriteString("\n - the argument could not be compiled as a valid Zed query due to parse error (") + if perr.LineNum > 0 { + fmt.Fprintf(&b, "line %d, ", perr.LineNum) + } + fmt.Fprintf(&b, "column %d):", perr.Column) + for _, l := range strings.Split(perr.ParseErrorContext(), "\n") { + fmt.Fprintf(&b, "\n %s", l) + } + } else { + b.WriteString("\n - the argument did not parse as a valid Zed query") + } return errors.New(b.String()) } diff --git a/cmd/zq/ztests/from-pool-error.yaml b/cmd/zq/ztests/from-pool-error.yaml index bcbc40e285..3df958f054 100644 --- a/cmd/zq/ztests/from-pool-error.yaml +++ b/cmd/zq/ztests/from-pool-error.yaml @@ -5,5 +5,5 @@ outputs: - name: stderr data: | zq: could not invoke zq with a single argument because: - - the argument did not parse as a valid Zed query - a file could not be found with the name "from ( pool a )" + - the argument did not parse as a valid Zed query diff --git a/cmd/zq/ztests/no-files.yaml b/cmd/zq/ztests/no-files.yaml index 470b884ffe..bf2732049c 100644 --- a/cmd/zq/ztests/no-files.yaml +++ b/cmd/zq/ztests/no-files.yaml @@ -7,5 +7,5 @@ outputs: - name: stderr data: | zq: could not invoke zq with a single argument because: - - the argument did not parse as a valid Zed query - a file could not be found with the name "doesnotexist" + - the argument did not parse as a valid Zed query diff --git a/cmd/zq/ztests/single-arg-error.yaml b/cmd/zq/ztests/single-arg-error.yaml index cd4b240981..e59212acc9 100644 --- a/cmd/zq/ztests/single-arg-error.yaml +++ b/cmd/zq/ztests/single-arg-error.yaml @@ -5,5 +5,7 @@ outputs: - name: stderr data: | zq: could not invoke zq with a single argument because: - - the argument did not parse as a valid Zed query - a file could not be found with the name "file sample.zson | c..." + - the argument could not be compiled as a valid Zed query due to parse error (column 25): + file sample.zson | count( + === ^ === diff --git a/compiler/parser/api.go b/compiler/parser/api.go index 5a880180eb..a4b0935bdf 100644 --- a/compiler/parser/api.go +++ b/compiler/parser/api.go @@ -72,10 +72,10 @@ type Error struct { Offset int // offset into original source code filename string // omitted from formatting if "" - lineNum int // zero-based; omitted from formatting if negative + LineNum int // zero-based; omitted from formatting if negative line string // contains no newlines - column int // zero-based + Column int // zero-based } // NewError returns an Error. src is the source code containing the error. If @@ -105,10 +105,10 @@ func NewError(src string, sis []SourceInfo, offset int) error { } return &Error{ Offset: offset, + LineNum: lineNum, + Column: column, filename: filename, - lineNum: lineNum, line: src, - column: column, } } @@ -119,12 +119,19 @@ func (e *Error) Error() string { fmt.Fprintf(&b, "in %s ", e.filename) } b.WriteString("at ") - if e.lineNum >= 0 { - fmt.Fprintf(&b, "line %d, ", e.lineNum+1) + if e.LineNum >= 0 { + fmt.Fprintf(&b, "line %d, ", e.LineNum+1) } - fmt.Fprintf(&b, "column %d:\n%s\n", e.column+1, e.line) - for k := 0; k < e.column; k++ { - if k >= e.column-4 && k != e.column-1 { + fmt.Fprintf(&b, "column %d:\n", e.Column+1) + b.WriteString(e.ParseErrorContext()) + return b.String() +} + +func (e *Error) ParseErrorContext() string { + var b strings.Builder + b.WriteString(e.line + "\n") + for k := 0; k < e.Column; k++ { + if k >= e.Column-4 && k != e.Column-1 { b.WriteByte('=') } else { b.WriteByte(' ')