diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2022-05-10 01:34:23 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2022-05-10 23:14:52 +0900 |
commit | 3e1620a781fe0eb097a9624cffb408bfb32bd5c8 (patch) | |
tree | d9dbd22dfa3efbe1d709edc98d4c0ca37de5f71e | |
parent | Make the identifier format strict (diff) | |
download | urubu-3e1620a781fe0eb097a9624cffb408bfb32bd5c8.tar.gz urubu-3e1620a781fe0eb097a9624cffb408bfb32bd5c8.tar.xz |
Add spelling inconsistencies check
-rw-r--r-- | grammar/grammar.go | 70 | ||||
-rw-r--r-- | grammar/grammar_test.go | 144 | ||||
-rw-r--r-- | grammar/semantic_error.go | 47 |
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") ) |