diff options
author | Ryo Nihei <nihei.dev@gmail.com> | 2021-07-31 01:48:16 +0900 |
---|---|---|
committer | Ryo Nihei <nihei.dev@gmail.com> | 2021-07-31 01:48:16 +0900 |
commit | e9361ebd47bba3254c3d44f25f9942665b88db2f (patch) | |
tree | 7f037bde5ba11dd4a31f43344953b84072717d4b | |
parent | Detect unused-symbol error (diff) | |
download | urubu-e9361ebd47bba3254c3d44f25f9942665b88db2f.tar.gz urubu-e9361ebd47bba3254c3d44f25f9942665b88db2f.tar.xz |
Prevent terminals used in productions from being skipped
A terminal symbol used in productions cannot have the skip directive.
-rw-r--r-- | driver/parser_test.go | 11 | ||||
-rw-r--r-- | grammar/grammar.go | 79 | ||||
-rw-r--r-- | grammar/semantic_error.go | 1 |
3 files changed, 68 insertions, 23 deletions
diff --git a/driver/parser_test.go b/driver/parser_test.go index e32e34c..8145d7b 100644 --- a/driver/parser_test.go +++ b/driver/parser_test.go @@ -188,6 +188,17 @@ bar: "bar"; src: `foo`, specErr: true, }, + // A terminal used in productions cannot have the skip directive. + { + specSrc: ` +a + : foo + ; +foo: "foo" #skip; +`, + src: `foo`, + specErr: true, + }, { specSrc: ` mode_tran_seq diff --git a/grammar/grammar.go b/grammar/grammar.go index 20bddfe..60f1705 100644 --- a/grammar/grammar.go +++ b/grammar/grammar.go @@ -43,16 +43,29 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { return nil, err } - unusedProds, unusedTerms, err := findUnusedSymbols(b.AST) + syms, err := findUsedAndUnusedSymbols(b.AST) if err != nil { return nil, err } + // When a terminal symbol that cannot be reached from the start symbol has the skip directive, + // the compiler treats its terminal as a used symbol, not unused. for _, sym := range symTabAndLexSpec.skipSyms { - delete(unusedTerms, sym) + if _, ok := syms.unusedTerminals[sym]; !ok { + prod := syms.usedTerminals[sym] + + b.errs = append(b.errs, &verr.SpecError{ + Cause: semErrTermCannotBeSkipped, + Detail: sym, + Row: prod.Pos.Row, + }) + continue + } + + delete(syms.unusedTerminals, sym) } - for sym, prod := range unusedProds { + for sym, prod := range syms.unusedProductions { b.errs = append(b.errs, &verr.SpecError{ Cause: semErrUnusedProduction, Detail: sym, @@ -60,7 +73,7 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { }) } - for sym, prod := range unusedTerms { + for sym, prod := range syms.unusedTerminals { b.errs = append(b.errs, &verr.SpecError{ Cause: semErrUnusedTerminal, Detail: sym, @@ -83,62 +96,82 @@ func (b *GrammarBuilder) Build() (*Grammar, error) { }, nil } -func findUnusedSymbols(root *spec.RootNode) (map[string]*spec.ProductionNode, map[string]*spec.ProductionNode, error) { +type usedAndUnusedSymbols struct { + unusedProductions map[string]*spec.ProductionNode + unusedTerminals map[string]*spec.ProductionNode + usedTerminals map[string]*spec.ProductionNode +} + +func findUsedAndUnusedSymbols(root *spec.RootNode) (*usedAndUnusedSymbols, error) { prods := map[string]*spec.ProductionNode{} lexProds := map[string]*spec.ProductionNode{} - check := map[string]bool{} + mark := map[string]bool{} { for _, p := range root.Productions { prods[p.LHS] = p - check[p.LHS] = false + mark[p.LHS] = false for _, alt := range p.RHS { for _, e := range alt.Elements { if e.ID == "" { continue } - check[e.ID] = false + mark[e.ID] = false } } } + for _, p := range root.LexProductions { lexProds[p.LHS] = p - check[p.LHS] = false + mark[p.LHS] = false } start := root.Productions[0] - check[start.LHS] = true - markUsedSymbols(check, prods, start) + mark[start.LHS] = true + markUsedSymbols(mark, map[string]bool{}, prods, start) } + // usedProds := make(map[string]*spec.ProductionNode, len(prods)) + usedTerms := make(map[string]*spec.ProductionNode, len(lexProds)) unusedProds := map[string]*spec.ProductionNode{} unusedTerms := map[string]*spec.ProductionNode{} - for sym, used := range check { - if used { - continue - } - + for sym, used := range mark { if p, ok := prods[sym]; ok { + if used { + continue + } unusedProds[sym] = p continue } if p, ok := lexProds[sym]; ok { - unusedTerms[sym] = p + if used { + usedTerms[sym] = p + } else { + unusedTerms[sym] = p + } continue } - return nil, nil, fmt.Errorf("a definition of unused production was not found: %v", sym) + return nil, fmt.Errorf("a definition of unused production was not found: %v", sym) } - return unusedProds, unusedTerms, nil + return &usedAndUnusedSymbols{ + usedTerminals: usedTerms, + unusedProductions: unusedProds, + unusedTerminals: unusedTerms, + }, nil } -func markUsedSymbols(check map[string]bool, prods map[string]*spec.ProductionNode, prod *spec.ProductionNode) { +func markUsedSymbols(mark map[string]bool, marked map[string]bool, prods map[string]*spec.ProductionNode, prod *spec.ProductionNode) { + if marked[prod.LHS] { + return + } + for _, alt := range prod.RHS { for _, e := range alt.Elements { if e.ID == "" { continue } - check[e.ID] = true + mark[e.ID] = true p, ok := prods[e.ID] if !ok { @@ -146,9 +179,9 @@ func markUsedSymbols(check map[string]bool, prods map[string]*spec.ProductionNod } // Remove a production to avoid inifinite recursion. - delete(prods, prod.LHS) + marked[prod.LHS] = true - markUsedSymbols(check, prods, p) + markUsedSymbols(mark, marked, prods, p) } } } diff --git a/grammar/semantic_error.go b/grammar/semantic_error.go index 9006124..4dcd313 100644 --- a/grammar/semantic_error.go +++ b/grammar/semantic_error.go @@ -17,6 +17,7 @@ func (e *SemanticError) Error() string { var ( 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") |