aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/vartan/compile.go27
-rw-r--r--error/error.go16
-rw-r--r--grammar/grammar.go4
-rw-r--r--spec/parser.go109
-rw-r--r--spec/parser_test.go8
-rw-r--r--spec/syntax_error.go1
6 files changed, 126 insertions, 39 deletions
diff --git a/cmd/vartan/compile.go b/cmd/vartan/compile.go
index 3601f00..804bd4f 100644
--- a/cmd/vartan/compile.go
+++ b/cmd/vartan/compile.go
@@ -54,14 +54,16 @@ func runCompile(cmd *cobra.Command, args []string) (retErr error) {
}
if retErr != nil {
- specErr, ok := retErr.(*verr.SpecError)
+ specErrs, ok := retErr.(verr.SpecErrors)
if ok {
- if *compileFlags.grammar != "" {
- specErr.FilePath = grmPath
- specErr.SourceName = grmPath
- } else {
- specErr.FilePath = grmPath
- specErr.SourceName = "stdin"
+ for _, err := range specErrs {
+ if *compileFlags.grammar != "" {
+ err.FilePath = grmPath
+ err.SourceName = grmPath
+ } else {
+ err.FilePath = grmPath
+ err.SourceName = "stdin"
+ }
}
}
fmt.Fprintln(os.Stderr, retErr)
@@ -106,24 +108,17 @@ func runCompile(cmd *cobra.Command, args []string) (retErr error) {
}
func readGrammar(path string) (grm *grammar.Grammar, retErr error) {
- defer func() {
- err := recover()
- specErr, ok := err.(*verr.SpecError)
- if ok {
- specErr.FilePath = path
- retErr = specErr
- }
- }()
-
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("Cannot open the grammar file %s: %w", path, err)
}
defer f.Close()
+
ast, err := spec.Parse(f)
if err != nil {
return nil, err
}
+
return grammar.NewGrammar(ast)
}
diff --git a/error/error.go b/error/error.go
index cc4fed5..1e5df7a 100644
--- a/error/error.go
+++ b/error/error.go
@@ -7,6 +7,22 @@ import (
"strings"
)
+type SpecErrors []*SpecError
+
+func (e SpecErrors) Error() string {
+ if len(e) == 0 {
+ return ""
+ }
+
+ var b strings.Builder
+ fmt.Fprintf(&b, "%v", e[0])
+ for _, err := range e[1:] {
+ fmt.Fprintf(&b, "\n%v", err)
+ }
+
+ return b.String()
+}
+
type SpecError struct {
Cause error
FilePath string
diff --git a/grammar/grammar.go b/grammar/grammar.go
index b9f65dc..b4827df 100644
--- a/grammar/grammar.go
+++ b/grammar/grammar.go
@@ -144,6 +144,10 @@ func NewGrammar(root *spec.RootNode) (*Grammar, error) {
}
}
+ if len(root.Productions) == 0 {
+ return nil, fmt.Errorf("a grammar must have at least one production")
+ }
+
prods := newProductionSet()
var augStartSym symbol
astActs := map[productionID][]*astActionEntry{}
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")