Skip to content

Commit

Permalink
syntax: add ParseError.Incomplete
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvdan committed Feb 24, 2019
1 parent df5f8fe commit 170fa2e
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 4 deletions.
25 changes: 21 additions & 4 deletions syntax/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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(),
})
}

Expand Down Expand Up @@ -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
}
}
Expand Down
36 changes: 36 additions & 0 deletions syntax/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
})
}
}

0 comments on commit 170fa2e

Please sign in to comment.