aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2021-07-28 01:54:39 +0900
committerRyo Nihei <nihei.dev@gmail.com>2021-07-28 01:54:39 +0900
commitadb12c2c1b2873d8775f55c02f54e6690687f1a2 (patch)
tree4715e03c051bfca3262b74046b79252d49c069b4
parentAdd literal pattern syntax and change tree structure syntax (diff)
downloadurubu-adb12c2c1b2873d8775f55c02f54e6690687f1a2.tar.gz
urubu-adb12c2c1b2873d8775f55c02f54e6690687f1a2.tar.xz
Detect duplicate production errors
-rw-r--r--driver/parser_test.go42
-rw-r--r--grammar/grammar.go37
-rw-r--r--grammar/production.go6
-rw-r--r--grammar/semantic_error.go11
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")
)