diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/parser.go | 109 | ||||
-rw-r--r-- | spec/parser_test.go | 8 | ||||
-rw-r--r-- | spec/syntax_error.go | 1 |
3 files changed, 95 insertions, 23 deletions
diff --git a/spec/parser.go b/spec/parser.go index 1bd2fb4..8b825bf 100644 --- a/spec/parser.go +++ b/spec/parser.go @@ -1,6 +1,7 @@ package spec import ( + "fmt" "io" verr "github.com/nihei9/vartan/error" @@ -64,17 +65,15 @@ func Parse(src io.Reader) (*RootNode, error) { if err != nil { return nil, err } - root, err := p.parse() - if err != nil { - return nil, err - } - return root, nil + + return p.parse() } type parser struct { lex *lexer peekedTok *token lastTok *token + errs verr.SpecErrors // A token position that the parser read at last. // It is used as additional information in error messages. @@ -92,22 +91,29 @@ func newParser(src io.Reader) (*parser, error) { } func (p *parser) parse() (root *RootNode, retErr error) { + root = p.parseRoot() + if len(p.errs) > 0 { + return nil, p.errs + } + + return root, nil +} + +func (p *parser) parseRoot() *RootNode { defer func() { err := recover() if err != nil { - retErr = err.(error) - return + specErr, ok := err.(*verr.SpecError) + if !ok { + panic(fmt.Errorf("an unexpected error occurred: %v", err)) + } + p.errs = append(p.errs, specErr) } }() - return p.parseRoot(), nil -} -func (p *parser) parseRoot() *RootNode { var prods []*ProductionNode var fragments []*FragmentNode for { - p.consume(tokenKindNewline) - fragment := p.parseFragment() if fragment != nil { fragments = append(fragments, fragment) @@ -120,10 +126,9 @@ func (p *parser) parseRoot() *RootNode { continue } - break - } - if len(prods) == 0 { - raiseSyntaxError(0, synErrNoProduction) + if p.consume(tokenKindEOF) { + break + } } return &RootNode{ @@ -133,6 +138,25 @@ func (p *parser) parseRoot() *RootNode { } func (p *parser) parseFragment() *FragmentNode { + defer func() { + err := recover() + if err == nil { + return + } + + specErr, ok := err.(*verr.SpecError) + if !ok { + panic(err) + } + + p.errs = append(p.errs, specErr) + p.skipOverTo(tokenKindSemicolon) + + return + }() + + p.consume(tokenKindNewline) + if !p.consume(tokenKindKWFragment) { return nil } @@ -174,6 +198,25 @@ func (p *parser) parseFragment() *FragmentNode { } func (p *parser) parseProduction() *ProductionNode { + defer func() { + err := recover() + if err == nil { + return + } + + specErr, ok := err.(*verr.SpecError) + if !ok { + panic(err) + } + + p.errs = append(p.errs, specErr) + p.skipOverTo(tokenKindSemicolon) + + return + }() + + p.consume(tokenKindNewline) + if p.consume(tokenKindEOF) { return nil } @@ -351,3 +394,37 @@ func (p *parser) consume(expected tokenKind) bool { return false } + +func (p *parser) skip() { + var tok *token + var err error + for { + if p.peekedTok != nil { + tok = p.peekedTok + p.peekedTok = nil + } else { + tok, err = p.lex.next() + if err != nil { + p.errs = append(p.errs, &verr.SpecError{ + Cause: err, + Row: p.pos.row, + }) + continue + } + } + + break + } + + p.lastTok = tok + p.pos = tok.pos +} + +func (p *parser) skipOverTo(kind tokenKind) { + for { + if p.consume(kind) || p.consume(tokenKindEOF) { + return + } + p.skip() + } +} diff --git a/spec/parser_test.go b/spec/parser_test.go index 8500a02..89cc4d1 100644 --- a/spec/parser_test.go +++ b/spec/parser_test.go @@ -144,11 +144,6 @@ c: ; synErr: synErrInvalidToken, }, { - caption: "a grammar must have at least one production", - src: ``, - synErr: synErrNoProduction, - }, - { caption: "a production must have its name as the first element", src: `: "a";`, synErr: synErrNoProductionName, @@ -350,10 +345,11 @@ foo: "foo"; t.Run(tt.caption, func(t *testing.T) { ast, err := Parse(strings.NewReader(tt.src)) if tt.synErr != nil { - synErr, ok := err.(*verr.SpecError) + synErrs, ok := err.(verr.SpecErrors) if !ok { t.Fatalf("unexpected error; want: %v, got: %v", tt.synErr, err) } + synErr := synErrs[0] if tt.synErr != synErr.Cause { t.Fatalf("unexpected error; want: %v, got: %v", tt.synErr, synErr.Cause) } diff --git a/spec/syntax_error.go b/spec/syntax_error.go index 041486d..9d67ccc 100644 --- a/spec/syntax_error.go +++ b/spec/syntax_error.go @@ -24,7 +24,6 @@ var ( // syntax errors synErrInvalidToken = newSyntaxError("invalid token") - synErrNoProduction = newSyntaxError("a grammar must have at least one production") synErrNoProductionName = newSyntaxError("a production name is missing") synErrNoColon = newSyntaxError("the colon must precede alternatives") synErrNoSemicolon = newSyntaxError("the semicolon is missing at the last of an alternative") |