diff options
-rw-r--r-- | grammar/grammar.go | 27 | ||||
-rw-r--r-- | grammar/grammar_test.go | 804 | ||||
-rw-r--r-- | grammar/semantic_error.go | 1 | ||||
-rw-r--r-- | spec/parser.go | 16 | ||||
-rw-r--r-- | spec/parser_test.go | 5 | ||||
-rw-r--r-- | spec/syntax_error.go | 1 |
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") |