aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--grammar/grammar.go27
-rw-r--r--grammar/grammar_test.go804
-rw-r--r--grammar/semantic_error.go1
-rw-r--r--spec/parser.go16
-rw-r--r--spec/parser_test.go5
-rw-r--r--spec/syntax_error.go1
6 files changed, 735 insertions, 119 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,84 +320,208 @@ 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
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%left` cannot take a pattern parameter",
+ specSrc: `
+%name test
+
+%left "foo"
+
+s
+ : foo
;
-a: 'a';
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%left` cannot take a string parameter",
+ specSrc: `
+%name test
+
+%left 'foo'
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ }
+
+ rightTests := []*specErrTest{
+ {
+ caption: "the `%right` needs ID parameters",
+ specSrc: `
+%name test
+
+%right
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%right` cannot take an undefined symbol",
+ specSrc: `
+%name test
+
+%right x
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%right` cannot take a non-terminal symbol",
+ specSrc: `
+%name test
+
+%right s
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%right` cannot take a pattern parameter",
+ specSrc: `
+%name test
+
+%right "foo"
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrMDInvalidParam},
+ },
+ {
+ caption: "the `%right` cannot take a string parameter",
+ specSrc: `
+%name test
+
+%right 'foo'
+
+s
+ : foo
+ ;
+
+foo
+ : 'foo';
`,
errs: []*SemanticError{semErrMDInvalidParam},
},
@@ -376,22 +534,17 @@ a: 'a';
%name test
s
- : foo
+ : error
;
error
- : bar
+ : foo
;
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.
},
},
{
@@ -400,11 +553,9 @@ bar: 'bar';
%name test
s
- : foo
- | error
+ : error
;
-foo: 'foo';
error: 'error';
`,
errs: []*SemanticError{semErrErrSymIsReserved},
@@ -429,6 +580,48 @@ error #skip
astDirTests := []*specErrTest{
{
+ caption: "the `#ast` directive needs ID or label prameters",
+ specSrc: `
+%name test
+
+s
+ : foo #ast
+ ;
+
+foo
+ : "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the `#ast` directive cannot take a pattern parameter",
+ specSrc: `
+%name test
+
+s
+ : foo #ast "foo"
+ ;
+
+foo
+ : "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the `#ast` directive cannot take a string parameter",
+ specSrc: `
+%name test
+
+s
+ : foo #ast 'foo'
+ ;
+
+foo
+ : "foo";
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
caption: "a parameter of the `#ast` directive must be either a symbol or a label in an alternative",
specSrc: `
%name test
@@ -436,8 +629,11 @@ error #skip
s
: foo bar #ast foo x
;
-foo: "foo";
-bar: "bar";
+
+foo
+ : "foo";
+bar
+ : "bar";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -450,8 +646,11 @@ s
: foo #ast bar
| bar
;
-foo: "foo";
-bar: "bar";
+
+foo
+ : "foo";
+bar
+ : "bar";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -464,8 +663,11 @@ s
: foo #ast b
| bar@b
;
-foo: "foo";
-bar: "bar";
+
+foo
+ : "foo";
+bar
+ : "bar";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -477,7 +679,9 @@ bar: "bar";
s
: foo #ast foo...
;
-foo: "foo";
+
+foo
+ : "foo";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -489,7 +693,9 @@ foo: "foo";
s
: foo "bar"@b #ast foo b...
;
-foo: "foo";
+
+foo
+ : "foo";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -501,7 +707,9 @@ foo: "foo";
s
: foo 'bar'@b #ast foo b...
;
-foo: "foo";
+
+foo
+ : "foo";
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -514,23 +722,25 @@ foo: "foo";
%name test
s
- : a #prec
+ : foo #prec
;
-a: 'a';
+foo
+ : 'foo';
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
{
- caption: "the `#prec` directive cannot take an unknown symbol",
+ caption: "the `#prec` directive cannot take an undefined symbol",
specSrc: `
%name test
s
- : a #prec foo
+ : foo #prec x
;
-a: 'a';
+foo
+ : 'foo';
`,
errs: []*SemanticError{semErrDirInvalidParam},
},
@@ -540,18 +750,48 @@ a: 'a';
%name test
s
- : foo #prec bar
- | bar
+ : a #prec b
+ | b
;
-foo
- : a
+a
+ : foo
+ ;
+b
+ : bar
;
+
+foo
+ : 'foo';
bar
- : b
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrDirInvalidParam},
+ },
+ {
+ caption: "the `#prec` directive cannot take a pattern parameter",
+ specSrc: `
+%name test
+
+s
+ : foo #prec "foo"
;
-a: 'a';
-b: 'b';
+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},
},
@@ -559,23 +799,319 @@ b: 'b';
recoverDirTests := []*specErrTest{
{
- caption: "the `#recover` directive cannot take a parameter",
+ 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
-seq
- : seq elem
- | elem
+s
+ : foo bar
;
-elem
- : id id id ';'
- | error ';' #recover foo
+
+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
;
-ws #skip
- : "[\u{0009}\u{0020}]+";
-id
- : "[A-Za-z_]+";
+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},
},
@@ -583,16 +1119,66 @@ id
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
-a
- : foo
+s
+ : foo bar
;
foo #skip
- : "foo";
+ : '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
@@ -184,6 +184,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: !;`,
synErr: synErrInvalidToken,
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")