From 97d36965cbb30108340727a982539e67dafea92d Mon Sep 17 00:00:00 2001 From: Ryo Nihei Date: Thu, 14 Apr 2022 02:11:06 +0900 Subject: [PATCH] Add tests for compiler --- grammar/grammar.go | 27 +- grammar/grammar_test.go | 910 +++++++++++++++++++++++++++++++------- grammar/semantic_error.go | 1 + spec/parser.go | 16 +- spec/parser_test.go | 5 + spec/syntax_error.go | 1 + 6 files changed, 788 insertions(+), 172 deletions(-) diff --git a/grammar/grammar.go b/grammar/grammar.go index fcc836f..babfb10 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -158,9 +158,9 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { return nil, b.errs } - syms, err := findUsedAndUnusedSymbols(b.AST) - if err != nil { - return nil, err + syms := findUsedAndUnusedSymbols(b.AST) + if syms == nil && len(b.errs) > 0 { + return nil, b.errs } // When a terminal symbol that cannot be reached from the start symbol has the skip directive, @@ -227,7 +227,7 @@ type usedAndUnusedSymbols struct { usedTerminals map[string]*spec.ProductionNode } -func findUsedAndUnusedSymbols(root *spec.RootNode) (*usedAndUnusedSymbols, error) { +func findUsedAndUnusedSymbols(root *spec.RootNode) *usedAndUnusedSymbols { prods := map[string]*spec.ProductionNode{} lexProds := map[string]*spec.ProductionNode{} mark := map[string]bool{} @@ -277,14 +277,17 @@ func findUsedAndUnusedSymbols(root *spec.RootNode) (*usedAndUnusedSymbols, error } continue } - return nil, fmt.Errorf("unknown symbol: a symbol must be a terminal symbol or a non-terminal symbol: %v", sym) + + // May be reached here when a fragment name appears on the right-hand side of a production rule. However, an error + // to the effect that a production rule cannot contain a fragment will be detected in a subsequent process. So we can + // ignore it here. } return &usedAndUnusedSymbols{ usedTerminals: usedTerms, unusedProductions: unusedProds, unusedTerminals: unusedTerms, - }, nil + } } func markUsedSymbols(mark map[string]bool, marked map[string]bool, prods map[string]*spec.ProductionNode, prod *spec.ProductionNode) { @@ -445,7 +448,7 @@ func (b *GrammarBuilder) genSymbolTableAndLexSpec(root *spec.RootNode) (*symbolT for _, fragment := range root.Fragments { if _, exist := checkedFragments[fragment.LHS]; exist { b.errs = append(b.errs, &verr.SpecError{ - Cause: semErrDuplicateTerminal, + Cause: semErrDuplicateFragment, Detail: fragment.LHS, Row: fragment.Pos.Row, Col: fragment.Pos.Col, @@ -979,6 +982,16 @@ func (b *GrammarBuilder) genPrecAndAssoc(symTab *symbolTable, prods *productionS } for _, p := range md.Parameters { + if p.ID == "" { + b.errs = append(b.errs, &verr.SpecError{ + Cause: semErrMDInvalidParam, + Detail: "a parameter must be an ID", + Row: p.Pos.Row, + Col: p.Pos.Col, + }) + return nil, nil + } + sym, ok := symTab.toSymbol(p.ID) if !ok { b.errs = append(b.errs, &verr.SpecError{ diff --git a/grammar/grammar_test.go b/grammar/grammar_test.go index f59acef..40c07aa 100644 --- a/grammar/grammar_test.go +++ b/grammar/grammar_test.go @@ -25,8 +25,11 @@ a : foo ; b - : foo; -foo: "foo"; + : foo + ; + +foo + : "foo"; `, errs: []*SemanticError{semErrUnusedProduction}, }, @@ -38,8 +41,11 @@ foo: "foo"; s : foo ; -foo: "foo"; -bar: "bar"; + +foo + : "foo"; +bar + : "bar"; `, errs: []*SemanticError{semErrUnusedTerminal}, }, @@ -54,8 +60,11 @@ a b : bar ; -foo: "foo"; -bar: "bar"; + +foo + : "foo"; +bar + : "bar"; `, errs: []*SemanticError{ semErrUnusedProduction, @@ -71,7 +80,8 @@ s #prec foo : foo ; -foo: 'foo'; +foo + : 'foo'; `, errs: []*SemanticError{semErrInvalidProdDir}, }, @@ -84,7 +94,8 @@ s : foo ; -foo: 'foo' #skip; +foo + : 'foo' #skip; `, errs: []*SemanticError{semErrInvalidAltDir}, }, @@ -127,7 +138,9 @@ s : foo | foo ; -foo: "foo"; + +foo + : "foo"; `, errs: []*SemanticError{semErrDuplicateProduction}, }, @@ -136,18 +149,21 @@ foo: "foo"; specSrc: ` %name test -a +s : foo - | b + | a ; -b +a : bar ; -a +s : foo ; -foo: "foo"; -bar: "bar"; + +foo + : "foo"; +bar + : "bar"; `, errs: []*SemanticError{semErrDuplicateProduction}, }, @@ -156,15 +172,17 @@ bar: "bar"; specSrc: ` %name test -a +s : foo - | b + | a ; -b +a : | ; -foo: "foo"; + +foo + : "foo"; `, errs: []*SemanticError{semErrDuplicateProduction}, }, @@ -173,18 +191,20 @@ foo: "foo"; specSrc: ` %name test -a +s : foo - | b + | a ; -b +a : | foo ; -b +a : ; -foo: "foo"; + +foo + : "foo"; `, errs: []*SemanticError{semErrDuplicateProduction}, }, @@ -193,11 +213,14 @@ foo: "foo"; specSrc: ` %name test -a +s : foo ; -foo: "foo"; -a: "a"; + +foo + : "foo"; +s + : "a"; `, errs: []*SemanticError{semErrDuplicateName}, }, @@ -206,16 +229,20 @@ a: "a"; specSrc: ` %name test -a +s : foo - | b + | a ; -b +a : bar ; -foo: "foo"; -bar: "bar"; -b: "a"; + +foo + : "foo"; +bar + : "bar"; +a + : "a"; `, errs: []*SemanticError{semErrDuplicateName}, }, @@ -230,7 +257,8 @@ s : a ; -a: 'a'; +a + : 'a'; `, errs: []*SemanticError{semErrMDInvalidName}, }, @@ -243,8 +271,10 @@ s : foo@x bar@x ; -foo: 'foo'; -bar: 'bar'; +foo + : 'foo'; +bar + : 'bar'; `, errs: []*SemanticError{semErrDuplicateLabel}, }, @@ -257,8 +287,10 @@ s : foo bar@foo ; -foo: 'foo'; -bar: 'bar'; +foo + : 'foo'; +bar + : 'bar'; `, errs: []*SemanticError{semErrDuplicateLabel}, }, @@ -275,8 +307,10 @@ a : bar ; -foo: 'foo'; -bar: 'bar'; +foo + : 'foo'; +bar + : 'bar'; `, errs: []*SemanticError{ semErrInvalidLabel, @@ -286,313 +320,865 @@ bar: 'bar'; nameTests := []*specErrTest{ { - caption: "the `%name` is missing", + caption: "the `%name` is required", specSrc: ` -a +s : foo ; -foo: "foo"; + +foo + : 'foo'; `, errs: []*SemanticError{semErrMDMissingName}, }, { - caption: "the `%name` needs a parameter", + caption: "the `%name` needs an ID parameter", specSrc: ` %name -a +s : foo ; -foo: "foo"; + +foo + : 'foo'; `, errs: []*SemanticError{semErrMDInvalidParam}, }, { caption: "the `%name` takes just one parameter", specSrc: ` -%name test foo +%name test1 test2 -a +s : foo ; -foo: "foo"; + +foo + : 'foo'; `, errs: []*SemanticError{semErrMDInvalidParam}, }, } - assocTests := []*specErrTest{ + leftTests := []*specErrTest{ { - caption: "associativity needs at least one symbol", + caption: "the `%left` needs ID parameters", specSrc: ` %name test %left s - : a + : foo ; -a: 'a'; +foo + : 'foo'; `, errs: []*SemanticError{semErrMDInvalidParam}, }, { - caption: "associativity cannot take an undefined symbol", + caption: "the `%left` cannot take an undefined symbol", specSrc: ` %name test -%left b +%left x s - : a + : foo ; -a: 'a'; +foo + : 'foo'; `, errs: []*SemanticError{semErrMDInvalidParam}, }, { - caption: "associativity cannot take a non-terminal symbol", + caption: "the `%left` cannot take a non-terminal symbol", specSrc: ` %name test %left s s - : a + : foo ; -a: 'a'; +foo + : 'foo'; `, errs: []*SemanticError{semErrMDInvalidParam}, }, - } - - errorSymTests := []*specErrTest{ { - caption: "cannot use the error symbol as a non-terminal symbol", + caption: "the `%left` cannot take a pattern parameter", specSrc: ` %name test +%left "foo" + s : foo ; -error - : bar - ; -foo: 'foo'; -bar: 'bar'; +foo + : 'foo'; `, - 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. - }, + errs: []*SemanticError{semErrMDInvalidParam}, }, { - caption: "cannot use the error symbol as a terminal symbol", + caption: "the `%left` cannot take a string parameter", specSrc: ` %name test +%left 'foo' + s : foo - | error ; -foo: 'foo'; -error: 'error'; +foo + : 'foo'; `, - errs: []*SemanticError{semErrErrSymIsReserved}, + errs: []*SemanticError{semErrMDInvalidParam}, }, + } + + rightTests := []*specErrTest{ { - caption: "cannot use the error symbol as a terminal symbol, even if given the skip directive", + caption: "the `%right` needs ID parameters", specSrc: ` %name test +%right + s : foo ; foo : 'foo'; -error #skip - : 'error'; `, - errs: []*SemanticError{semErrErrSymIsReserved}, + errs: []*SemanticError{semErrMDInvalidParam}, }, - } + { + caption: "the `%right` cannot take an undefined symbol", + specSrc: ` +%name test - astDirTests := []*specErrTest{ +%right x + +s + : foo + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrMDInvalidParam}, + }, { - caption: "a parameter of the `#ast` directive must be either a symbol or a label in an alternative", + caption: "the `%right` cannot take a non-terminal symbol", specSrc: ` %name test +%right s + s - : foo bar #ast foo x + : foo ; -foo: "foo"; -bar: "bar"; + +foo + : 'foo'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{semErrMDInvalidParam}, }, { - caption: "a symbol in a different alternative cannot be a parameter of the `#ast` directive", + caption: "the `%right` cannot take a pattern parameter", specSrc: ` %name test +%right "foo" + s - : foo #ast bar - | bar + : foo ; -foo: "foo"; -bar: "bar"; + +foo + : 'foo'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{semErrMDInvalidParam}, }, { - caption: "a label in a different alternative cannot be a parameter of the `#ast` directive", + caption: "the `%right` cannot take a string parameter", specSrc: ` %name test +%right 'foo' + s - : foo #ast b - | bar@b + : foo ; -foo: "foo"; -bar: "bar"; + +foo + : 'foo'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{semErrMDInvalidParam}, }, + } + + errorSymTests := []*specErrTest{ { - caption: "the expansion operator cannot be applied to a terminal symbol", + caption: "cannot use the error symbol as a non-terminal symbol", specSrc: ` %name test s - : foo #ast foo... + : error + ; +error + : foo ; -foo: "foo"; + +foo: 'foo'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{ + semErrErrSymIsReserved, + semErrDuplicateName, + }, }, { - caption: "the expansion operator cannot be applied to a pattern", + caption: "cannot use the error symbol as a terminal symbol", specSrc: ` %name test s - : foo "bar"@b #ast foo b... + : error ; -foo: "foo"; + +error: 'error'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{semErrErrSymIsReserved}, }, { - caption: "the expansion operator cannot be applied to a string", + caption: "cannot use the error symbol as a terminal symbol, even if given the skip directive", specSrc: ` %name test s - : foo 'bar'@b #ast foo b... + : foo ; -foo: "foo"; + +foo + : 'foo'; +error #skip + : 'error'; `, - errs: []*SemanticError{semErrDirInvalidParam}, + errs: []*SemanticError{semErrErrSymIsReserved}, }, } - precDirTests := []*specErrTest{ + astDirTests := []*specErrTest{ { - caption: "the `#prec` directive needs an ID parameter", + caption: "the `#ast` directive needs ID or label prameters", specSrc: ` %name test s - : a #prec + : foo #ast ; -a: 'a'; +foo + : "foo"; `, errs: []*SemanticError{semErrDirInvalidParam}, }, { - caption: "the `#prec` directive cannot take an unknown symbol", + caption: "the `#ast` directive cannot take a pattern parameter", specSrc: ` %name test s - : a #prec foo + : foo #ast "foo" ; -a: 'a'; +foo + : "foo"; `, errs: []*SemanticError{semErrDirInvalidParam}, }, { - caption: "the `#prec` directive cannot take a non-terminal symbol", + caption: "the `#ast` directive cannot take a string parameter", specSrc: ` %name test s - : foo #prec bar - | bar - ; -foo - : a - ; -bar - : b + : foo #ast 'foo' ; -a: 'a'; -b: 'b'; +foo + : "foo"; `, errs: []*SemanticError{semErrDirInvalidParam}, }, - } - - recoverDirTests := []*specErrTest{ { - caption: "the `#recover` directive cannot take a parameter", + caption: "a parameter of the `#ast` directive must be either a symbol or a label in an alternative", specSrc: ` %name test -seq - : seq elem - | elem - ; -elem - : id id id ';' - | error ';' #recover foo +s + : foo bar #ast foo x ; -ws #skip - : "[\u{0009}\u{0020}]+"; -id - : "[A-Za-z_]+"; +foo + : "foo"; +bar + : "bar"; `, errs: []*SemanticError{semErrDirInvalidParam}, }, - } - - skipDirTests := []*specErrTest{ { - caption: "a terminal symbol used in productions cannot have the skip directive", + caption: "a symbol in a different alternative cannot be a parameter of the `#ast` directive", specSrc: ` %name test -a - : foo - ; +s + : foo #ast bar + | bar + ; -foo #skip +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 + : foo #prec + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#prec` directive cannot take an undefined symbol", + specSrc: ` +%name test + +s + : foo #prec x + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#prec` directive cannot take a non-terminal symbol", + specSrc: ` +%name test + +s + : a #prec b + | b + ; +a + : foo + ; +b + : bar + ; + +foo + : 'foo'; +bar + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#prec` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo #prec "foo" + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#prec` directive cannot take a string parameter", + specSrc: ` +%name test + +s + : foo #prec 'foo' + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + recoverDirTests := []*specErrTest{ + { + caption: "the `#recover` directive cannot take an ID parameter", + specSrc: ` +%name test + +%name test + +s + : foo #recover foo + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#recover` directive cannot take a pattern parameter", + specSrc: ` +%name test + +%name test + +s + : foo #recover "foo" + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#recover` directive cannot take a string parameter", + specSrc: ` +%name test + +%name test + +s + : foo #recover 'foo' + ; + +foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + fragmentTests := []*specErrTest{ + { + caption: "a production cannot contain a fragment", + specSrc: ` +%name test + +s + : f + ; + +fragment f + : 'fragment'; +`, + errs: []*SemanticError{semErrUndefinedSym}, + }, + { + caption: "fragments cannot be duplicated", + specSrc: ` +%name test + +s + : foo + ; + +foo + : "\f{f}"; +fragment f + : 'fragment 1'; +fragment f + : 'fragment 2'; +`, + errs: []*SemanticError{semErrDuplicateFragment}, + }, + } + + aliasDirTests := []*specErrTest{ + { + caption: "the `#alias` directive needs a string parameter", + specSrc: ` +%name test + +s + : foo + ; + +foo #alias + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#alias` directive takes just one string parameter", + specSrc: ` +%name test + +s + : foo + ; + +foo #alias 'Foo' 'FOO' + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#alias` directive cannot take an ID parameter", + specSrc: ` +%name test + +s + : foo + ; + +foo #alias Foo + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#alias` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo + ; + +foo #alias "Foo" + : 'foo'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + modeTests := []*specErrTest{ + { + caption: "the `#mode` directive needs an ID parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push mode_1 + : 'foo'; +bar #mode + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#mode` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push mode_1 + : 'foo'; +bar #mode "mode_1" + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#mode` directive cannot take a string parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push mode_1 + : 'foo'; +bar #mode 'mode_1' + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + pushTests := []*specErrTest{ + { + caption: "the `#push` directive needs an ID parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push + : 'foo'; +bar #mode mode_1 + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#push` directive takes just one ID parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push mode_1 mode_2 + : 'foo'; +bar #mode mode_1 + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#push` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push "mode_1" + : 'foo'; +bar #mode mode_1 + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#push` directive cannot take a string parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #push 'mode_1' + : 'foo'; +bar #mode mode_1 + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + popTests := []*specErrTest{ + { + caption: "the `#pop` directive cannot take an ID parameter", + specSrc: ` +%name test + +s + : foo bar baz + ; + +foo #push mode_1 + : 'foo'; +bar #mode mode_1 + : 'bar'; +baz #pop mode_1 + : 'baz'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#pop` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo bar baz + ; + +foo #push mode_1 + : 'foo'; +bar #mode mode_1 + : 'bar'; +baz #pop "mode_1" + : 'baz'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#pop` directive cannot take a string parameter", + specSrc: ` +%name test + +s + : foo bar baz + ; + +foo #push mode_1 + : 'foo'; +bar #mode mode_1 + : 'bar'; +baz #pop 'mode_1' + : 'baz'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + } + + skipDirTests := []*specErrTest{ + { + caption: "the `#skip` directive cannot take an ID parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #skip bar + : 'foo'; +bar + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#skip` directive cannot take a pattern parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #skip "bar" + : 'foo'; +bar + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "the `#skip` directive cannot take a string parameter", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #skip 'bar' + : 'foo'; +bar + : 'bar'; +`, + errs: []*SemanticError{semErrDirInvalidParam}, + }, + { + caption: "a terminal symbol used in productions cannot have the skip directive", + specSrc: ` +%name test + +s + : foo bar + ; + +foo #skip + : 'foo'; +bar + : 'bar'; `, errs: []*SemanticError{semErrTermCannotBeSkipped}, }, @@ -601,11 +1187,17 @@ foo #skip var tests []*specErrTest tests = append(tests, prodTests...) tests = append(tests, nameTests...) - tests = append(tests, assocTests...) + tests = append(tests, leftTests...) + tests = append(tests, rightTests...) tests = append(tests, errorSymTests...) tests = append(tests, astDirTests...) tests = append(tests, precDirTests...) tests = append(tests, recoverDirTests...) + tests = append(tests, fragmentTests...) + tests = append(tests, aliasDirTests...) + tests = append(tests, modeTests...) + tests = append(tests, pushTests...) + tests = append(tests, popTests...) tests = append(tests, skipDirTests...) for _, test := range tests { t.Run(test.caption, func(t *testing.T) { diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go index 04cc020..8e5b558 100644 --- a/grammar/semantic_error.go +++ b/grammar/semantic_error.go @@ -25,6 +25,7 @@ var ( semErrUndefinedSym = newSemanticError("undefined symbol") semErrDuplicateProduction = newSemanticError("duplicate production") semErrDuplicateTerminal = newSemanticError("duplicate terminal") + semErrDuplicateFragment = newSemanticError("duplicate fragment") semErrDuplicateName = newSemanticError("duplicate names are not allowed between terminals and non-terminals") semErrErrSymIsReserved = newSemanticError("symbol 'error' is reserved as a terminal symbol") semErrDuplicateLabel = newSemanticError("a label must be unique in an alternative") diff --git a/spec/parser.go b/spec/parser.go index 2d16614..a1d23f0 100644 --- a/spec/parser.go +++ b/spec/parser.go @@ -56,6 +56,7 @@ type DirectiveNode struct { type ParameterNode struct { ID string + Pattern string String string Expansion bool Pos Position @@ -197,19 +198,17 @@ func (p *parser) parseMetaData() *DirectiveNode { mdPos := p.lastTok.pos if !p.consume(tokenKindID) { - raiseSyntaxError(p.pos.Row, synErrNoProductionName) + raiseSyntaxError(p.pos.Row, synErrNoMDName) } name := p.lastTok.text var params []*ParameterNode for { - if !p.consume(tokenKindID) { + param := p.parseParameter() + if param == nil { break } - params = append(params, &ParameterNode{ - ID: p.lastTok.text, - Pos: p.lastTok.pos, - }) + params = append(params, param) } return &DirectiveNode{ @@ -463,6 +462,11 @@ func (p *parser) parseParameter() *ParameterNode { ID: p.lastTok.text, Pos: p.lastTok.pos, } + case p.consume(tokenKindTerminalPattern): + param = &ParameterNode{ + Pattern: p.lastTok.text, + Pos: p.lastTok.pos, + } case p.consume(tokenKindStringLiteral): param = &ParameterNode{ String: p.lastTok.text, diff --git a/spec/parser_test.go b/spec/parser_test.go index 0772dae..2a44acd 100644 --- a/spec/parser_test.go +++ b/spec/parser_test.go @@ -183,6 +183,11 @@ c: ; }, }, }, + { + caption: "`fragment` is a reserved word", + src: `fragment: 'fragment';`, + synErr: synErrNoProductionName, + }, { caption: "when a source contains an unknown token, the parser raises a syntax error", src: `a: !;`, diff --git a/spec/syntax_error.go b/spec/syntax_error.go index c4e6594..fdf9c40 100644 --- a/spec/syntax_error.go +++ b/spec/syntax_error.go @@ -25,6 +25,7 @@ var ( // syntax errors synErrInvalidToken = newSyntaxError("invalid token") + synErrNoMDName = newSyntaxError("a metadata name is missing") 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")