From 93ec0cd14995f551f5fbdc4e365c302ceaeb88bc Mon Sep 17 00:00:00 2001 From: Matthew Nibecker Date: Thu, 12 Feb 2026 18:00:58 -0800 Subject: [PATCH] Add line numbers to sup parsing errors Closes #6627 --- runtime/ztests/expr/function/parse-sup.yaml | 2 +- service/ztests/curl-load-error.yaml | 2 +- service/ztests/load-garbage.yaml | 2 +- sup/lexer.go | 6 ++++ sup/parser-values.go | 3 +- sup/parser.go | 4 +-- sup/ztests/parser-errors.yaml | 36 +++++++++++++++++++++ 7 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 sup/ztests/parser-errors.yaml diff --git a/runtime/ztests/expr/function/parse-sup.yaml b/runtime/ztests/expr/function/parse-sup.yaml index 1f38c353a0..1c94059ce4 100644 --- a/runtime/ztests/expr/function/parse-sup.yaml +++ b/runtime/ztests/expr/function/parse-sup.yaml @@ -12,4 +12,4 @@ output: | {a:1} null error({message:"parse_sup: string arg required",on:{}}) - error({message:"parse_sup: SUP syntax error",on:"!"}) + error({message:"parse_sup: line 1: parse error: syntax error",on:"!"}) diff --git a/service/ztests/curl-load-error.yaml b/service/ztests/curl-load-error.yaml index f95549fc3b..688a894f42 100644 --- a/service/ztests/curl-load-error.yaml +++ b/service/ztests/curl-load-error.yaml @@ -16,7 +16,7 @@ inputs: outputs: - name: stdout data: | - {"type":"Error","kind":"invalid operation","error":"format detection error\n\tarrows: schema message length exceeds 1 MiB\n\tbsup: malformed BSUP value\n\tcsup: auto-detection requires seekable input\n\tcsv: line 1: EOF\n\tjson: invalid character 'T' looking for beginning of value\n\tline: auto-detection not supported\n\tparquet: auto-detection requires seekable input\n\tsup: SUP syntax error\n\ttsv: line 1: EOF\n\tzeek: line 1: bad types/fields definition in zeek header\n\tjsup: line 1: malformed JSUP: bad type object: \"This is not a detectable format.\": unpacker error parsing JSON: invalid character 'T' looking for beginning of value"} + {"type":"Error","kind":"invalid operation","error":"format detection error\n\tarrows: schema message length exceeds 1 MiB\n\tbsup: malformed BSUP value\n\tcsup: auto-detection requires seekable input\n\tcsv: line 1: EOF\n\tjson: invalid character 'T' looking for beginning of value\n\tline: auto-detection not supported\n\tparquet: auto-detection requires seekable input\n\tsup: line 1: parse error: syntax error\n\ttsv: line 1: EOF\n\tzeek: line 1: bad types/fields definition in zeek header\n\tjsup: line 1: malformed JSUP: bad type object: \"This is not a detectable format.\": unpacker error parsing JSON: invalid character 'T' looking for beginning of value"} code 400 {"type":"Error","kind":"invalid operation","error":"unsupported MIME type: unsupported"} code 400 diff --git a/service/ztests/load-garbage.yaml b/service/ztests/load-garbage.yaml index 13e0460a50..67f3d2dbd0 100644 --- a/service/ztests/load-garbage.yaml +++ b/service/ztests/load-garbage.yaml @@ -20,7 +20,7 @@ outputs: json: invalid character 'T' looking for beginning of value line: auto-detection not supported parquet: auto-detection requires seekable input - sup: SUP syntax error + sup: line 1: parse error: syntax error tsv: line 1: delimiter '\t' not found zeek: line 1: bad types/fields definition in zeek header jsup: line 1: malformed JSUP: bad type object: "This file contains no records.": unpacker error parsing JSON: invalid character 'T' looking for beginning of value diff --git a/sup/lexer.go b/sup/lexer.go index e3d629d72f..7fcba64e04 100644 --- a/sup/lexer.go +++ b/sup/lexer.go @@ -25,6 +25,7 @@ type Lexer struct { primitive *regexp.Regexp indentation *regexp.Regexp ip6follow *regexp.Regexp + line int } const ( @@ -43,6 +44,7 @@ func NewLexer(r io.Reader) *Lexer { primitive: primitive, indentation: indentation, ip6follow: regexp.MustCompile(ip6followRE), + line: 1, } } @@ -90,6 +92,7 @@ func (l *Lexer) skip(n int) error { if err := l.check(n); err != nil { return err } + l.line += bytes.Count(l.cursor[:n], []byte{'\n'}) l.cursor = l.cursor[n:] return nil } @@ -170,6 +173,9 @@ func (l *Lexer) readByte() (byte, error) { } b := l.cursor[0] l.cursor = l.cursor[1:] + if b == '\n' { + l.line++ + } return b, nil } diff --git a/sup/parser-values.go b/sup/parser-values.go index cff635cfcb..303a3d8129 100644 --- a/sup/parser-values.go +++ b/sup/parser-values.go @@ -2,7 +2,6 @@ package sup import ( "encoding/hex" - "errors" "fmt" "io" "net/netip" @@ -22,7 +21,7 @@ func (p *Parser) ParseValue() (ast.Value, error) { } if v == nil && err == nil { if err := p.lexer.check(1); (err != nil && err != io.EOF) || len(p.lexer.cursor) > 0 { - return nil, errors.New("SUP syntax error") + return nil, p.error("syntax error") } } return v, err diff --git a/sup/parser.go b/sup/parser.go index 080dedc8f3..3fe070b1fe 100644 --- a/sup/parser.go +++ b/sup/parser.go @@ -18,7 +18,5 @@ func (p *Parser) errorf(msg string, args ...any) error { } func (p *Parser) error(msg string) error { - // format a message based on the contents in the scanner buffer - // (could also track column and line number) - return fmt.Errorf("parse error: %s", msg) + return fmt.Errorf("line %d: parse error: %s", p.lexer.line, msg) } diff --git a/sup/ztests/parser-errors.yaml b/sup/ztests/parser-errors.yaml new file mode 100644 index 0000000000..f32e08850e --- /dev/null +++ b/sup/ztests/parser-errors.yaml @@ -0,0 +1,36 @@ +# Test parse errors with unescaped quotes. +spq: pass + +input: | + {id:1,text:"valid record before the bad one"} + {id:2,text:"this has "unescaped quotes" inside it"} + {id:3,text:"valid record after the bad one"} + +error: | + line 2: parse error: mismatched braces while parsing record type + +--- + +# Unclosed object. +spq: pass + +input: | + {x:1} + {x:2} + {x:3 + {x:4} + +error: | + line 4: parse error: mismatched braces while parsing record type + +--- + +spq: pass + +input-flags: -i sup + +input: | + {x} + +error: | + line 1: parse error: no type name found for field "x"