diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-07-28 01:54:39 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-07-28 01:54:39 +0900 |
commit | adb12c2c1b2873d8775f55c02f54e6690687f1a2 (patch) | |
tree | 4715e03c051bfca3262b74046b79252d49c069b4 | |
parent | Add literal pattern syntax and change tree structure syntax (diff) | |
download | urubu-adb12c2c1b2873d8775f55c02f54e6690687f1a2.tar.gz urubu-adb12c2c1b2873d8775f55c02f54e6690687f1a2.tar.xz |
Detect duplicate production errors
-rw-r--r-- | driver/parser_test.go | 42 | ||||
-rw-r--r-- | grammar/grammar.go | 37 | ||||
-rw-r--r-- | grammar/production.go | 6 | ||||
-rw-r--r-- | grammar/semantic_error.go | 11 |
4 files changed, 87 insertions, 9 deletions
diff --git a/driver/parser_test.go b/driver/parser_test.go index 272a14d..73d8fac 100644 --- a/driver/parser_test.go +++ b/driver/parser_test.go @@ -273,6 +273,48 @@ foo: "foo"; `, specErr: true, }, + // A production must not have a duplicate alternative. + { + specSrc: ` +s + : foo + | foo + ; +foo: "foo"; +`, + specErr: true, + }, + // A production must not have a duplicate alternative. + { + specSrc: ` +a + : foo + ; +b + : + | + ; +foo: "foo"; +`, + specErr: true, + }, + // A production must not have a duplicate alternative. + { + specSrc: ` +a + : foo + ; +b + : bar + ; +a + : foo + ; +foo: "foo"; +bar: "bar"; +`, + specErr: true, + }, } for i, tt := range tests { t.Run(fmt.Sprintf("#%v", i), func(t *testing.T) { diff --git a/grammar/grammar.go b/grammar/grammar.go index 7cd2409..6130e55 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -3,6 +3,7 @@ package grammar import ( "fmt" "os" + "strings" mlcompiler "github.com/nihei9/maleeni/compiler" mlspec "github.com/nihei9/maleeni/spec" @@ -343,6 +344,42 @@ func (b *GrammarBuilder) genProductionsAndActions(root *spec.RootNode, symTabAnd if err != nil { return nil, err } + if _, exist := prods.findByID(p.id); exist { + // Report the line number of a duplicate alternative. + // When the alternative is empty, we report the position of its LHS. + var row int + if len(alt.Elements) > 0 { + row = alt.Elements[0].Pos.Row + } else { + row = prod.Pos.Row + } + + var detail string + { + var b strings.Builder + fmt.Fprintf(&b, "%v →", prod.LHS) + for _, elem := range alt.Elements { + switch { + case elem.ID != "": + fmt.Fprintf(&b, " %v", elem.ID) + case elem.Pattern != "": + fmt.Fprintf(&b, ` "%v"`, elem.Pattern) + } + } + if len(alt.Elements) == 0 { + fmt.Fprintf(&b, " ε") + } + + detail = b.String() + } + + b.errs = append(b.errs, &verr.SpecError{ + Cause: semErrDuplicateProduction, + Detail: detail, + Row: row, + }) + continue LOOP_RHS + } prods.append(p) if alt.Directive != nil { diff --git a/grammar/production.go b/grammar/production.go index a935037..d4ea7a1 100644 --- a/grammar/production.go +++ b/grammar/production.go @@ -80,9 +80,9 @@ func newProductionSet() *productionSet { } } -func (ps *productionSet) append(prod *production) bool { +func (ps *productionSet) append(prod *production) { if _, ok := ps.id2Prod[prod.id]; ok { - return false + return } if prod.lhs.isStart() { @@ -98,8 +98,6 @@ func (ps *productionSet) append(prod *production) bool { ps.lhs2Prods[prod.lhs] = []*production{prod} } ps.id2Prod[prod.id] = prod - - return true } func (ps *productionSet) findByID(id productionID) (*production, bool) { diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go index 63f9325..ca84cf0 100644 --- a/grammar/semantic_error.go +++ b/grammar/semantic_error.go @@ -15,9 +15,10 @@ func (e *SemanticError) Error() string { } var ( - semErrNoProduction = newSemanticError("a grammar needs at least one production") - semErrUndefinedSym = newSemanticError("undefined symbol") - semErrDuplicateSym = newSemanticError("duplicate symbol") - semErrDirInvalidName = newSemanticError("invalid directive name") - semErrDirInvalidParam = newSemanticError("invalid parameter") + semErrNoProduction = newSemanticError("a grammar needs at least one production") + semErrUndefinedSym = newSemanticError("undefined symbol") + semErrDuplicateProduction = newSemanticError("duplicate production") + semErrDuplicateSym = newSemanticError("duplicate symbol") + semErrDirInvalidName = newSemanticError("invalid directive name") + semErrDirInvalidParam = newSemanticError("invalid parameter") ) |