From 170fa2e7fab623d4550075e3db76c77d03f87895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Mon, 25 Feb 2019 00:04:41 +0100 Subject: [PATCH] syntax: add ParseError.Incomplete During read calls, it was already possible to check if more bytes were required to finish parsing without errors, via the Parser.Incomplete method. However, once an error was returned, that information was not easily available. Add a field to the error type, and add an IsIncomplete function to quickly check errors for that property. --- syntax/parser.go | 25 +++++++++++++++++++++---- syntax/parser_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/syntax/parser.go b/syntax/parser.go index df2b254d9..298d93139 100644 --- a/syntax/parser.go +++ b/syntax/parser.go @@ -314,6 +314,11 @@ type Parser struct { litBs []byte } +// Incomplete reports whether the parser is waiting to read more bytes because +// it needs to finish properly parsing a statement. +// +// It is only safe to call while the parser is blocked on a read. For an example +// use case, see the documentation for Parser.Interactive. func (p *Parser) Incomplete() bool { // If we're in a quote state other than noState, we're parsing a node // such as a double-quoted string. @@ -642,12 +647,22 @@ func (p *Parser) errPass(err error) { } } +// IsIncomplete reports whether a Parser error could have been avoided with +// extra input bytes. For example, if an io.EOF was encountered while there was +// an unclosed quote or parenthesis. +func IsIncomplete(err error) bool { + perr, ok := err.(ParseError) + return ok && perr.Incomplete +} + // ParseError represents an error found when parsing a source file, from which // the parser cannot recover. type ParseError struct { Filename string Pos Text string + + Incomplete bool } func (e ParseError) Error() string { @@ -691,9 +706,10 @@ func (e LangError) Error() string { func (p *Parser) posErr(pos Pos, format string, a ...interface{}) { p.errPass(ParseError{ - Filename: p.f.Name, - Pos: pos, - Text: fmt.Sprintf(format, a...), + Filename: p.f.Name, + Pos: pos, + Text: fmt.Sprintf(format, a...), + Incomplete: p.tok == _EOF && p.Incomplete(), }) } @@ -978,7 +994,8 @@ func (p *Parser) wordPart() WordPart { p.next() return sq case utf8.RuneSelf: - p.posErr(sq.Pos(), "reached EOF without closing quote %s", sglQuote) + p.tok = _EOF + p.quoteErr(sq.Pos(), sglQuote) return nil } } diff --git a/syntax/parser_test.go b/syntax/parser_test.go index 76ab1f7a7..0086a98f7 100644 --- a/syntax/parser_test.go +++ b/syntax/parser_test.go @@ -2143,3 +2143,39 @@ func TestValidName(t *testing.T) { }) } } + +func TestIsIncomplete(t *testing.T) { + t.Parallel() + + tests := []struct { + in string + want bool + }{ + {"foo\n", false}, + {"foo;", false}, + {"\n", false}, + {"'incomp", true}, + {"foo; 'incomp", true}, + {" (incomp", true}, + {"badsyntax)", false}, + } + p := NewParser() + for i, tc := range tests { + t.Run(fmt.Sprintf("Parse%02d", i), func(t *testing.T) { + r := strings.NewReader(tc.in) + _, err := p.Parse(r, "") + if got := IsIncomplete(err); got != tc.want { + t.Fatalf("%q got %t, wanted %t", tc.in, got, tc.want) + } + }) + t.Run(fmt.Sprintf("Interactive%02d", i), func(t *testing.T) { + r := strings.NewReader(tc.in) + err := p.Interactive(r, func([]*Stmt) bool { + return false + }) + if got := IsIncomplete(err); got != tc.want { + t.Fatalf("%q got %t, wanted %t", tc.in, got, tc.want) + } + }) + } +}