aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2022-04-13 23:09:49 +0900
committerRyo Nihei <nihei.dev@gmail.com>2022-04-14 00:40:39 +0900
commit18a3317ac9c79651e5c74a2afc6b14fd9a3f9d4a (patch)
tree2e935d489805de4ba310a4dfbdfde702a1d5b0e7
parentUpdate CHANGELOG (diff)
downloadurubu-18a3317ac9c79651e5c74a2afc6b14fd9a3f9d4a.tar.gz
urubu-18a3317ac9c79651e5c74a2afc6b14fd9a3f9d4a.tar.xz
Move compiler tests from driver package to grammar package
-rw-r--r--driver/parser_test.go547
-rw-r--r--grammar/grammar.go23
-rw-r--r--grammar/grammar_test.go641
3 files changed, 658 insertions, 553 deletions
diff --git a/driver/parser_test.go b/driver/parser_test.go
index 04c42c5..d6fccc5 100644
--- a/driver/parser_test.go
+++ b/driver/parser_test.go
@@ -30,7 +30,6 @@ func TestParser_Parse(t *testing.T) {
src string
cst *Node
ast *Node
- specErr bool
}{
{
specSrc: `
@@ -153,118 +152,6 @@ bar_text: "bar";
),
),
},
- // `name` is missing.
- {
- specSrc: `
-a
- : foo
- ;
-foo: "foo";
-`,
- src: `foo`,
- specErr: true,
- },
- // `name` needs a parameter.
- {
- specSrc: `
-%name
-
-a
- : foo
- ;
-foo: "foo";
-`,
- src: `foo`,
- specErr: true,
- },
- // `name` takes just one parameter.
- {
- specSrc: `
-%name test foo
-
-a
- : foo
- ;
-foo: "foo";
-`,
- src: `foo`,
- specErr: true,
- },
- // Production `b` is unused.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-b
- : foo;
-foo: "foo";
-`,
- src: `foo`,
- specErr: true,
- },
- // Terminal `bar` is unused.
- {
- specSrc: `
-%name test
-
-s
- : foo
- ;
-foo: "foo";
-bar: "bar";
-`,
- src: `foo`,
- specErr: true,
- },
- // Production `b` and terminal `bar` is unused.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-b
- : bar
- ;
-foo: "foo";
-bar: "bar";
-`,
- src: `foo`,
- specErr: true,
- },
- // A terminal used in productions cannot have the skip directive.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-
-foo #skip
- : "foo";
-`,
- src: `foo`,
- specErr: true,
- },
- // A production cannot have production directives.
- {
- specSrc: `
-%name test
-
-s #prec foo
- : foo
- ;
-
-foo: 'foo' #skip;
-`,
- src: `foo`,
- specErr: true,
- },
// A production can have multiple alternative productions.
{
specSrc: `
@@ -348,52 +235,6 @@ pop #mode a b #pop
termNode("pop", "<-"),
),
},
- // A lexical production cannot have alternative directives.
- {
- specSrc: `
-%name test
-
-s
- : foo
- ;
-
-foo: 'foo' #skip;
-`,
- src: `foo`,
- specErr: true,
- },
- // A production directive must not be duplicated.
- {
- specSrc: `
-%name test
-
-s
- : foo
- ;
-
-foo #skip #skip
- : 'foo';
-`,
- src: `foo`,
- specErr: true,
- },
- // An alternative directive must not be duplicated.
- {
- specSrc: `
-%name test
-
-s
- : foo bar #ast foo bar #ast foo bar
- ;
-
-foo
- : 'foo';
-bar
- : 'bar';
-`,
- src: `foobar`,
- specErr: true,
- },
{
specSrc: `
%name test
@@ -541,221 +382,6 @@ num: "0|[1-9][0-9]*";
),
),
},
- // The expansion cannot be applied to a terminal symbol.
- {
- specSrc: `
-%name test
-
-s
- : foo #ast foo...
- ;
-foo: "foo";
-`,
- specErr: true,
- },
- // The expansion cannot be applied to a pattern.
- {
- specSrc: `
-%name test
-
-s
- : foo "bar"@b #ast foo b...
- ;
-foo: "foo";
-`,
- specErr: true,
- },
- // The expansion cannot be applied to a string.
- {
- specSrc: `
-%name test
-
-s
- : foo 'bar'@b #ast foo b...
- ;
-foo: "foo";
-`,
- specErr: true,
- },
- // A parameter of #ast directive must be either a symbol or a label in an alternative.
- {
- specSrc: `
-%name test
-
-s
- : foo bar #ast foo x
- ;
-foo: "foo";
-bar: "bar";
-`,
- specErr: true,
- },
- // A symbol in a different alternative cannot be a parameter of #ast directive.
- {
- specSrc: `
-%name test
-
-s
- : foo #ast bar
- | bar
- ;
-foo: "foo";
-bar: "bar";
-`,
- specErr: true,
- },
- // A label in a different alternative cannot be a parameter of #ast directive.
- {
- specSrc: `
-%name test
-
-s
- : foo #ast b
- | bar@b
- ;
-foo: "foo";
-bar: "bar";
-`,
- specErr: true,
- },
- // A production must not have a duplicate alternative.
- {
- specSrc: `
-%name test
-
-s
- : foo
- | foo
- ;
-foo: "foo";
-`,
- specErr: true,
- },
- // A production must not have a duplicate alternative.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-b
- :
- |
- ;
-foo: "foo";
-`,
- specErr: true,
- },
- // A production must not have a duplicate alternative.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-b
- : bar
- ;
-a
- : foo
- ;
-foo: "foo";
-bar: "bar";
-`,
- specErr: true,
- },
- // A terminal and a non-terminal (start symbol) are duplicates.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-foo: "foo";
-a: "a";
-`,
- specErr: true,
- },
- // A terminal and a non-terminal (not start symbol) are duplicates.
- {
- specSrc: `
-%name test
-
-a
- : foo
- ;
-b
- : bar
- ;
-foo: "foo";
-bar: "bar";
-b: "a";
-`,
- specErr: true,
- },
- // Invalid associativity type
- {
- specSrc: `
-%name test
-
-%foo
-
-s
- : a
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
- // Associativity needs at least one symbol.
- {
- specSrc: `
-%name test
-
-%left
-
-s
- : a
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
- // Associativity cannot take an undefined symbol.
- {
- specSrc: `
-%name test
-
-%left b
-
-s
- : a
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
- // Associativity cannot take a non-terminal symbol.
- {
- specSrc: `
-%name test
-
-%left s
-
-s
- : a
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
// The 'prec' directive can set precedence and associativity of a production.
{
specSrc: `
@@ -815,53 +441,6 @@ div
),
),
},
- // The 'prec' directive needs an ID parameter.
- {
- specSrc: `
-%name test
-
-s
- : a #prec
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
- // The 'prec' directive cannot take an unknown symbol.
- {
- specSrc: `
-%name test
-
-s
- : a #prec foo
- ;
-
-a: 'a';
-`,
- specErr: true,
- },
- // The 'prec' directive cannot take a non-terminal symbol.
- {
- specSrc: `
-%name test
-
-s
- : foo #prec bar
- | bar
- ;
-foo
- : a
- ;
-bar
- : b
- ;
-
-a: 'a';
-b: 'b';
-`,
- specErr: true,
- },
// The grammar can contain the 'error' symbol.
{
specSrc: `
@@ -900,90 +479,6 @@ id
`,
src: `a b c ; d e f ;`,
},
- // The 'recover' directive cannot take a parameter.
- {
- specSrc: `
-%name test
-
-seq
- : seq elem
- | elem
- ;
-elem
- : id id id ';'
- | error ';' #recover foo
- ;
-
-ws #skip
- : "[\u{0009}\u{0020}]+";
-id
- : "[A-Za-z_]+";
-`,
- src: `a b c ; d e f ;`,
- specErr: true,
- },
- // You cannot use the error symbol as a non-terminal symbol.
- {
- specSrc: `
-%name test
-
-s
- : foo
- ;
-error
- : bar
- ;
-
-foo: 'foo';
-bar: 'bar';
-`,
- specErr: true,
- },
- // You cannot use the error symbol as a terminal symbol.
- {
- specSrc: `
-%name test
-
-s
- : foo
- | error
- ;
-
-foo: 'foo';
-error: 'error';
-`,
- specErr: true,
- },
- // You cannot use the error symbol as a terminal symbol, even if given the skip directive.
- {
- specSrc: `
-%name test
-
-s
- : foo
- ;
-
-foo
- : 'foo';
-error #skip
- : 'error';
-`,
- specErr: true,
- },
- // A label must be unique in an alternative.
- {
- specSrc: `
-%name test
-
-s
- : foo@x bar@x
- ;
-
-foo: 'foo';
-bar: 'bar';
-`,
- specErr: true,
- },
// The same label can be used between different alternatives.
{
specSrc: `
@@ -999,37 +494,6 @@ bar: 'bar';
`,
src: `foo`,
},
- // A label cannot be the same name as terminal symbols.
- {
- specSrc: `
-%name test
-
-s
- : foo bar@foo
- ;
-
-foo: 'foo';
-bar: 'bar';
-`,
- specErr: true,
- },
- // A label cannot be the same name as non-terminal symbols.
- {
- specSrc: `
-%name test
-
-s
- : foo@a
- ;
-a
- : bar
- ;
-
-foo: 'foo';
-bar: 'bar';
-`,
- specErr: true,
- },
}
classes := []grammar.Class{
@@ -1049,15 +513,8 @@ bar: 'bar';
AST: ast,
}
g, err := b.Build()
- if tt.specErr {
- if err == nil {
- t.Fatal("an expected error didn't occur")
- }
- return
- } else {
- if err != nil {
- t.Fatal(err)
- }
+ if err != nil {
+ t.Fatal(err)
}
cg, err := grammar.Compile(g, grammar.SpecifyClass(class))
diff --git a/grammar/grammar.go b/grammar/grammar.go
index bc8cc6b..fcc836f 100644
--- a/grammar/grammar.go
+++ b/grammar/grammar.go
@@ -154,6 +154,9 @@ func (b *GrammarBuilder) Build() (*Grammar, error) {
if err != nil {
return nil, err
}
+ if pa == nil && len(b.errs) > 0 {
+ return nil, b.errs
+ }
syms, err := findUsedAndUnusedSymbols(b.AST)
if err != nil {
@@ -957,39 +960,43 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS
// Since `name` is used for a purpose other than priority, we will ignore it here.
continue
default:
- return nil, &verr.SpecError{
+ b.errs = append(b.errs, &verr.SpecError{
Cause: semErrMDInvalidName,
Row: md.Pos.Row,
Col: md.Pos.Col,
- }
+ })
+ return nil, nil
}
if len(md.Parameters) == 0 {
- return nil, &verr.SpecError{
+ b.errs = append(b.errs, &verr.SpecError{
Cause: semErrMDInvalidParam,
Detail: "associativity needs at least one symbol",
Row: md.Pos.Row,
Col: md.Pos.Col,
- }
+ })
+ return nil, nil
}
for _, p := range md.Parameters {
sym, ok := symTab.toSymbol(p.ID)
if !ok {
- return nil, &verr.SpecError{
+ b.errs = append(b.errs, &verr.SpecError{
Cause: semErrMDInvalidParam,
Detail: fmt.Sprintf("'%v' is undefined", p.ID),
Row: p.Pos.Row,
Col: p.Pos.Col,
- }
+ })
+ return nil, nil
}
if !sym.isTerminal() {
- return nil, &verr.SpecError{
+ b.errs = append(b.errs, &verr.SpecError{
Cause: semErrMDInvalidParam,
Detail: fmt.Sprintf("associativity can take only terminal symbol ('%v' is a non-terminal)", p.ID),
Row: p.Pos.Row,
Col: p.Pos.Col,
- }
+ })
+ return nil, nil
}
termPrec[sym.num()] = precN
diff --git a/grammar/grammar_test.go b/grammar/grammar_test.go
new file mode 100644
index 0000000..f59acef
--- /dev/null
+++ b/grammar/grammar_test.go
@@ -0,0 +1,641 @@
+package grammar
+
+import (
+ "strings"
+ "testing"
+
+ verr "github.com/nihei9/vartan/error"
+ "github.com/nihei9/vartan/spec"
+)
+
+func TestGrammarBuilderSpecError(t *testing.T) {
+ type specErrTest struct {
+ caption string
+ specSrc string
+ errs []*SemanticError
+ }
+
+ prodTests := []*specErrTest{
+ {
+ caption: "a production `b` is unused",
+ specSrc: `
+%name test
+
+a
+ : foo
+ ;
+b
+ : foo;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrUnusedProduction},
+ },
+ {
+ caption: "a terminal symbol `bar` is unused",
+ specSrc: `
+%name test
+
+s
+ : foo
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{semErrUnusedTerminal},
+ },
+ {
+ caption: "a production `b` and terminal symbol `bar` is unused",
+ specSrc: `
+%name test
+
+a
+ : foo
+ ;
+b
+ : bar
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{
+ semErrUnusedProduction,
+ semErrUnusedTerminal,
+ },
+ },
+ {
+ caption: "a production cannot have production directives",
+ specSrc: `
+%name test
+
+s #prec foo
+ : foo
+ ;
+
+foo: 'foo';
+`,
+ errs: []*SemanticError{semErrInvalidProdDir},
+ },
+ {
+ caption: "a lexical production cannot have alternative directives",
+ specSrc: `
+%name test
+
+s
+ : foo
+ ;
+
+foo: 'foo' #skip;
+`,
+ errs: []*SemanticError{semErrInvalidAltDir},
+ },
+ {
+ caption: "a production directive must not be duplicated",
+ specSrc: `
+%name test
+
+s
+ : foo
+ ;
+
+foo #skip #skip
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrDuplicateDir},
+ },
+ {
+ caption: "an alternative directive must not be duplicated",
+ specSrc: `
+%name test
+
+s
+ : foo bar #ast foo bar #ast foo bar
+ ;
+
+foo
+ : 'foo';
+bar
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrDuplicateDir},
+ },
+ {
+ caption: "a production must not have a duplicate alternative (non-empty alternatives)",
+ specSrc: `
+%name test
+
+s
+ : foo
+ | foo
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDuplicateProduction},
+ },
+ {
+ caption: "a production must not have a duplicate alternative (non-empty and split alternatives)",
+ specSrc: `
+%name test
+
+a
+ : foo
+ | b
+ ;
+b
+ : bar
+ ;
+a
+ : foo
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{semErrDuplicateProduction},
+ },
+ {
+ caption: "a production must not have a duplicate alternative (empty alternatives)",
+ specSrc: `
+%name test
+
+a
+ : foo
+ | b
+ ;
+b
+ :
+ |
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDuplicateProduction},
+ },
+ {
+ caption: "a production must not have a duplicate alternative (empty and split alternatives)",
+ specSrc: `
+%name test
+
+a
+ : foo
+ | b
+ ;
+b
+ :
+ | foo
+ ;
+b
+ :
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDuplicateProduction},
+ },
+ {
+ caption: "a terminal symbol and a non-terminal symbol (start symbol) are duplicates",
+ specSrc: `
+%name test
+
+a
+ : foo
+ ;
+foo: "foo";
+a: "a";
+`,
+ errs: []*SemanticError{semErrDuplicateName},
+ },
+ {
+ caption: "a terminal symbol and a non-terminal symbol (not start symbol) are duplicates",
+ specSrc: `
+%name test
+
+a
+ : foo
+ | b
+ ;
+b
+ : bar
+ ;
+foo: "foo";
+bar: "bar";
+b: "a";
+`,
+ errs: []*SemanticError{semErrDuplicateName},
+ },
+ {
+ caption: "an invalid associativity type",
+ specSrc: `
+%name test
+
+%foo
+
+s
+ : a
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrMDInvalidName},
+ },
+ {
+ caption: "a label must be unique in an alternative",
+ specSrc: `
+%name test
+
+s
+ : foo@x bar@x
+ ;
+
+foo: 'foo';
+bar: 'bar';
+`,
+ errs: []*SemanticError{semErrDuplicateLabel},
+ },
+ {
+ caption: "a label cannot be the same name as terminal symbols",
+ specSrc: `
+%name test
+
+s
+ : foo bar@foo
+ ;
+
+foo: 'foo';
+bar: 'bar';
+`,
+ errs: []*SemanticError{semErrDuplicateLabel},
+ },
+ {
+ caption: "a label cannot be the same name as non-terminal symbols",
+ specSrc: `
+%name test
+
+s
+ : foo@a
+ | a
+ ;
+a
+ : bar
+ ;
+
+foo: 'foo';
+bar: 'bar';
+`,
+ errs: []*SemanticError{
+ semErrInvalidLabel,
+ },
+ },
+ }
+
+ nameTests := []*specErrTest{
+ {
+ caption: "the `%name` is missing",
+ specSrc: `
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrMDMissingName},
+ },
+ {
+ caption: "the `%name` needs a parameter",
+ specSrc: `
+%name
+
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%name` takes just one parameter",
+ specSrc: `
+%name test foo
+
+a
+ : foo
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ }
+
+ assocTests := []*specErrTest{
+ {
+ caption: "associativity needs at least one symbol",
+ specSrc: `
+%name test
+
+%left
+
+s
+ : a
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "associativity cannot take an undefined symbol",
+ specSrc: `
+%name test
+
+%left b
+
+s
+ : a
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "associativity cannot take a non-terminal symbol",
+ specSrc: `
+%name test
+
+%left s
+
+s
+ : a
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ }
+
+ errorSymTests := []*specErrTest{
+ {
+ caption: "cannot use the error symbol as a non-terminal symbol",
+ specSrc: `
+%name test
+
+s
+ : foo
+ ;
+error
+ : bar
+ ;
+
+foo: 'foo';
+bar: 'bar';
+`,
+ errs: []*SemanticError{
+ semErrErrSymIsReserved,
+ semErrDuplicateName,
+ // The compiler determines the symbol `bar` is unreachable because the production rule `error → bar` contains
+ // a build error and the compiler doesn't recognize the production rule as a valid one.
+ // This error is essentially irrelevant to this test case.
+ semErrUnusedTerminal, // This error means `bar` is unreachable.
+ },
+ },
+ {
+ caption: "cannot use the error symbol as a terminal symbol",
+ specSrc: `
+%name test
+
+s
+ : foo
+ | error
+ ;
+
+foo: 'foo';
+error: 'error';
+`,
+ errs: []*SemanticError{semErrErrSymIsReserved},
+ },
+ {
+ caption: "cannot use the error symbol as a terminal symbol, even if given the skip directive",
+ specSrc: `
+%name test
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+error #skip
+ : 'error';
+`,
+ errs: []*SemanticError{semErrErrSymIsReserved},
+ },
+ }
+
+ astDirTests := []*specErrTest{
+ {
+ caption: "a parameter of the `#ast` directive must be either a symbol or a label in an alternative",
+ specSrc: `
+%name test
+
+s
+ : foo bar #ast foo x
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "a symbol in a different alternative cannot be a parameter of the `#ast` directive",
+ specSrc: `
+%name test
+
+s
+ : foo #ast bar
+ | bar
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "a label in a different alternative cannot be a parameter of the `#ast` directive",
+ specSrc: `
+%name test
+
+s
+ : foo #ast b
+ | bar@b
+ ;
+foo: "foo";
+bar: "bar";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the expansion operator cannot be applied to a terminal symbol",
+ specSrc: `
+%name test
+
+s
+ : foo #ast foo...
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the expansion operator cannot be applied to a pattern",
+ specSrc: `
+%name test
+
+s
+ : foo "bar"@b #ast foo b...
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the expansion operator cannot be applied to a string",
+ specSrc: `
+%name test
+
+s
+ : foo 'bar'@b #ast foo b...
+ ;
+foo: "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ }
+
+ precDirTests := []*specErrTest{
+ {
+ caption: "the `#prec` directive needs an ID parameter",
+ specSrc: `
+%name test
+
+s
+ : a #prec
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the `#prec` directive cannot take an unknown symbol",
+ specSrc: `
+%name test
+
+s
+ : a #prec foo
+ ;
+
+a: 'a';
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the `#prec` directive cannot take a non-terminal symbol",
+ specSrc: `
+%name test
+
+s
+ : foo #prec bar
+ | bar
+ ;
+foo
+ : a
+ ;
+bar
+ : b
+ ;
+
+a: 'a';
+b: 'b';
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ }
+
+ recoverDirTests := []*specErrTest{
+ {
+ caption: "the `#recover` directive cannot take a parameter",
+ specSrc: `
+%name test
+
+seq
+ : seq elem
+ | elem
+ ;
+elem
+ : id id id ';'
+ | error ';' #recover foo
+ ;
+
+ws #skip
+ : "[\u{0009}\u{0020}]+";
+id
+ : "[A-Za-z_]+";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ }
+
+ skipDirTests := []*specErrTest{
+ {
+ caption: "a terminal symbol used in productions cannot have the skip directive",
+ specSrc: `
+%name test
+
+a
+ : foo
+ ;
+
+foo #skip
+ : "foo";
+`,
+ errs: []*SemanticError{semErrTermCannotBeSkipped},
+ },
+ }
+
+ var tests []*specErrTest
+ tests = append(tests, prodTests...)
+ tests = append(tests, nameTests...)
+ tests = append(tests, assocTests...)
+ tests = append(tests, errorSymTests...)
+ tests = append(tests, astDirTests...)
+ tests = append(tests, precDirTests...)
+ tests = append(tests, recoverDirTests...)
+ tests = append(tests, skipDirTests...)
+ for _, test := range tests {
+ t.Run(test.caption, func(t *testing.T) {
+ ast, err := spec.Parse(strings.NewReader(test.specSrc))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ b := GrammarBuilder{
+ AST: ast,
+ }
+ _, err = b.Build()
+ if err == nil {
+ t.Fatal("an expected error didn't occur")
+ }
+ specErrs, ok := err.(verr.SpecErrors)
+ if !ok {
+ t.Fatalf("unexpected error type: want: %T, got: %T: %v", verr.SpecErrors{}, err, err)
+ }
+ if len(specErrs) != len(test.errs) {
+ t.Fatalf("unexpected spec error count: want: %+v, got: %+v", test.errs, specErrs)
+ }
+ for _, expected := range test.errs {
+ for _, actual := range specErrs {
+ if actual.Cause == expected {
+ return
+ }
+ }
+ }
+ t.Fatalf("an expected spec error didn't occur: want: %v, got: %+v", test.errs, specErrs)
+ })
+ }
+}