aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyo Nihei <nihei.dev@gmail.com>2022-05-10 01:34:23 +0900
committerRyo Nihei <nihei.dev@gmail.com>2022-05-10 23:14:52 +0900
commit3e1620a781fe0eb097a9624cffb408bfb32bd5c8 (patch)
treed9dbd22dfa3efbe1d709edc98d4c0ca37de5f71e
parentMake the identifier format strict (diff)
downloadurubu-3e1620a781fe0eb097a9624cffb408bfb32bd5c8.tar.gz
urubu-3e1620a781fe0eb097a9624cffb408bfb32bd5c8.tar.xz
Add spelling inconsistencies check
-rw-r--r--grammar/grammar.go70
-rw-r--r--grammar/grammar_test.go144
-rw-r--r--grammar/semantic_error.go47
3 files changed, 238 insertions, 23 deletions
diff --git a/grammar/grammar.go b/grammar/grammar.go
index 0cde454..82eff5d 100644
--- a/grammar/grammar.go
+++ b/grammar/grammar.go
@@ -137,6 +137,11 @@ func (b *GrammarBuilder) Build() (*Grammar, error) {
}
}
+ b.checkSpellingInconsistenciesOfUserDefinedIDs(b.AST)
+ if len(b.errs) > 0 {
+ return nil, b.errs
+ }
+
symTabAndLexSpec, err := b.genSymbolTableAndLexSpec(b.AST)
if err != nil {
return nil, err
@@ -316,6 +321,71 @@ func markUsedSymbols(mark map[string]bool, marked map[string]bool, prods map[str
}
}
+func (b *GrammarBuilder) checkSpellingInconsistenciesOfUserDefinedIDs(root *spec.RootNode) {
+ var ids []string
+ {
+ for _, prod := range root.Productions {
+ ids = append(ids, prod.LHS)
+ for _, alt := range prod.RHS {
+ for _, elem := range alt.Elements {
+ if elem.Label != nil {
+ ids = append(ids, elem.Label.Name)
+ }
+ }
+ }
+ }
+ for _, prod := range root.LexProductions {
+ ids = append(ids, prod.LHS)
+ }
+ for _, dir := range root.Directives {
+ dirIDs := collectUserDefinedIDsFromDirective(dir)
+ if len(dirIDs) > 0 {
+ ids = append(ids, dirIDs...)
+ }
+ }
+ }
+
+ duplicated := mlspec.FindSpellingInconsistencies(ids)
+ if len(duplicated) == 0 {
+ return
+ }
+
+ for _, dup := range duplicated {
+ var s string
+ {
+ var b strings.Builder
+ fmt.Fprintf(&b, "%+v", dup[0])
+ for _, id := range dup[1:] {
+ fmt.Fprintf(&b, ", %+v", id)
+ }
+ s = b.String()
+ }
+
+ b.errs = append(b.errs, &verr.SpecError{
+ Cause: semErrSpellingInconsistency,
+ Detail: s,
+ })
+ }
+}
+
+func collectUserDefinedIDsFromDirective(dir *spec.DirectiveNode) []string {
+ var ids []string
+ for _, param := range dir.Parameters {
+ if param.Group != nil {
+ for _, d := range param.Group {
+ dIDs := collectUserDefinedIDsFromDirective(d)
+ if len(dIDs) > 0 {
+ ids = append(ids, dIDs...)
+ }
+ }
+ }
+ if param.OrderedSymbol != "" {
+ ids = append(ids, param.OrderedSymbol)
+ }
+ }
+ return ids
+}
+
type symbolTableAndLexSpec struct {
symTab *symbolTable
anonPat2Sym map[string]symbol
diff --git a/grammar/grammar_test.go b/grammar/grammar_test.go
index e61e422..5a8bb4a 100644
--- a/grammar/grammar_test.go
+++ b/grammar/grammar_test.go
@@ -1078,6 +1078,149 @@ func TestGrammarBuilderSpecError(t *testing.T) {
errs []*SemanticError
}
+ spellingInconsistenciesTests := []*specErrTest{
+ {
+ caption: "a spelling inconsistency appears among non-terminal symbols",
+ specSrc: `
+#name test;
+
+a1
+ : a_1
+ ;
+a_1
+ : foo
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among terminal symbols",
+ specSrc: `
+#name test;
+
+s
+ : foo1 foo_1
+ ;
+
+foo1
+ : 'foo1';
+foo_1
+ : 'foo_1';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among non-terminal and terminal symbols",
+ specSrc: `
+#name test;
+
+a1
+ : a_1
+ ;
+
+a_1
+ : 'a_1';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among ordered symbols whose precedence is the same",
+ specSrc: `
+#name test;
+
+#prec (
+ #assign $p1 $p_1
+);
+
+s
+ : foo #prec $p1
+ | bar #prec $p_1
+ ;
+
+foo
+ : 'foo';
+bar
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among ordered symbols whose precedence is not the same",
+ specSrc: `
+#name test;
+
+#prec (
+ #assign $p1
+ #assign $p_1
+);
+
+s
+ : foo #prec $p1
+ | bar #prec $p_1
+ ;
+
+foo
+ : 'foo';
+bar
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among labels the same alternative contains",
+ specSrc: `
+#name test;
+
+s
+ : foo@l1 foo@l_1
+ ;
+
+foo
+ : 'foo';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among labels the same production contains",
+ specSrc: `
+#name test;
+
+s
+ : foo@l1
+ | bar@l_1
+ ;
+
+foo
+ : 'foo';
+bar
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ {
+ caption: "a spelling inconsistency appears among labels different productions contain",
+ specSrc: `
+#name test;
+
+s
+ : foo@l1
+ ;
+a
+ : bar@l_1
+ ;
+
+foo
+ : 'foo';
+bar
+ : 'bar';
+`,
+ errs: []*SemanticError{semErrSpellingInconsistency},
+ },
+ }
+
prodTests := []*specErrTest{
{
caption: "a production `b` is unused",
@@ -3212,6 +3355,7 @@ bar
}
var tests []*specErrTest
+ tests = append(tests, spellingInconsistenciesTests...)
tests = append(tests, prodTests...)
tests = append(tests, nameDirTests...)
tests = append(tests, precDirTests...)
diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go
index 794d8da..589e324 100644
--- a/grammar/semantic_error.go
+++ b/grammar/semantic_error.go
@@ -15,27 +15,28 @@ func (e *SemanticError) Error() string {
}
var (
- semErrNoGrammarName = newSemanticError("name is missing")
- semErrDuplicateAssoc = newSemanticError("associativity and precedence cannot be specified multiple times for a symbol")
- semErrUndefinedPrec = newSemanticError("symbol must has precedence")
- semErrUndefinedOrdSym = newSemanticError("undefined ordered symbol")
- semErrUnusedProduction = newSemanticError("unused production")
- semErrUnusedTerminal = newSemanticError("unused terminal")
- semErrTermCannotBeSkipped = newSemanticError("a terminal used in productions cannot be skipped")
- semErrNoProduction = newSemanticError("a grammar needs at least one production")
- 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")
- semErrInvalidLabel = newSemanticError("a label must differ from terminal symbols or non-terminal symbols")
- semErrDirInvalidName = newSemanticError("invalid directive name")
- semErrDirInvalidParam = newSemanticError("invalid parameter")
- semErrDuplicateDir = newSemanticError("a directive must not be duplicated")
- semErrDuplicateElem = newSemanticError("duplicate element")
- semErrAmbiguousElem = newSemanticError("ambiguous element")
- semErrInvalidProdDir = newSemanticError("invalid production directive")
- semErrInvalidAltDir = newSemanticError("invalid alternative directive")
+ semErrNoGrammarName = newSemanticError("name is missing")
+ semErrSpellingInconsistency = newSemanticError("the identifiers are treated as the same. please use the same spelling")
+ semErrDuplicateAssoc = newSemanticError("associativity and precedence cannot be specified multiple times for a symbol")
+ semErrUndefinedPrec = newSemanticError("symbol must has precedence")
+ semErrUndefinedOrdSym = newSemanticError("undefined ordered symbol")
+ semErrUnusedProduction = newSemanticError("unused production")
+ semErrUnusedTerminal = newSemanticError("unused terminal")
+ semErrTermCannotBeSkipped = newSemanticError("a terminal used in productions cannot be skipped")
+ semErrNoProduction = newSemanticError("a grammar needs at least one production")
+ 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")
+ semErrInvalidLabel = newSemanticError("a label must differ from terminal symbols or non-terminal symbols")
+ semErrDirInvalidName = newSemanticError("invalid directive name")
+ semErrDirInvalidParam = newSemanticError("invalid parameter")
+ semErrDuplicateDir = newSemanticError("a directive must not be duplicated")
+ semErrDuplicateElem = newSemanticError("duplicate element")
+ semErrAmbiguousElem = newSemanticError("ambiguous element")
+ semErrInvalidProdDir = newSemanticError("invalid production directive")
+ semErrInvalidAltDir = newSemanticError("invalid alternative directive")
)