private static NonTerminal GetAliasedSymbol(NonTerminal symbol, IReadOnlyDictionary <NonTerminal, ImmutableList <Rule> > rules) { var referencingRules = rules.SelectMany(kvp => kvp.Value) .Where(r => r.Symbols.Contains(symbol)) .ToArray(); return(referencingRules.Length == 1 && referencingRules.Single().Symbols.Count == 1 ? referencingRules.Single().Produced : null); }
private LeftRecursionRewriter RewriteOne() { if (this.aliases.Count != 0) { throw new InvalidOperationException("sanity check"); } // Example with all right associative (left-associative just replaces (+ E)? with (+ EX)*) // // START // E = -E | E * E | E + E | ID // // ONE REWRITE // E = E0 (* E)? | E + E // E0 = -E0 | ID // // TWO REWRITES // E = E1 (+ E)? // E1 = E0 (* E)? // E0 = -E0 | ID // pick the highest precedence left-recursive rule we can find var leftRecursiveRule = this.rulesByProduced.SelectMany(kvp => kvp.Value) .FirstOrDefault(IsSimpleLeftRecursive); if (leftRecursiveRule == null) { return(this); } var isBinary = IsSimpleRightRecursive(leftRecursiveRule); var replacements = new List <RuleReplacement>(); var newSymbol = new NonTerminal($"{leftRecursiveRule.Produced}_{this.rulesByProduced.Count(kvp => kvp.Key.Name.StartsWith(leftRecursiveRule.Produced.Name + "_"))}"); // determine which rules get pushed to the new symbol. These will be any rules that are neither left or simple right recursive // as well as any simple right-recursive rules that are higher precedence than the current rule. Note: we don't use ToDictionary // here because we want to preserve order var leftRecursiveRuleIndex = this.rulesByProduced[leftRecursiveRule.Produced].IndexOf(leftRecursiveRule); replacements.AddRange( this.rulesByProduced[leftRecursiveRule.Produced] .Select( // this check is simpler than the requirement stated above, because we leverage the fact that we know the current // rule is the highest-precedence left-recursive rule for the symbol (r, index) => index < leftRecursiveRuleIndex || (!IsSimpleLeftRecursive(r) && !IsSimpleRightRecursive(r)) // note that right-recursive rules have their last symbol replaced by the new symbol as well. This is to indicate the fact // that E => -E can't parse as -(1 + 1) because unary minus binds tighter ? new RuleReplacement(r, new Rule(newSymbol, r.Symbols.Select((s, i) => i == r.Symbols.Count - 1 && s == leftRecursiveRule.Produced ? newSymbol : s))) : null ) .Where(t => t != null) ); // associativity is only a binary concept var isLeftAssociative = isBinary && !this.rightAssociativeRules.Contains(leftRecursiveRule); if (!isLeftAssociative) { // for E ? E : E, we have E = T | T ? E : E // the former is a no-op replacements.AddRange( new[] { new RuleReplacement(null, new Rule(leftRecursiveRule.Produced, newSymbol)), new RuleReplacement(leftRecursiveRule, new Rule(leftRecursiveRule.Produced, leftRecursiveRule.Symbols.Select((s, i) => i == 0 ? newSymbol : s))), } ); } else { // for left associativity, we do: // E = E + E // E = T List<+T> // every parse of "+ T" maps to E + E rule var suffixSymbol = new NonTerminal($"'{string.Join(" ", leftRecursiveRule.Symbols.Skip(1).Take(leftRecursiveRule.Symbols.Count - 2).Append(newSymbol))}'"); var suffixListSymbol = new NonTerminal($"List<{suffixSymbol}>"); replacements.AddRange( new[] { // E = T List<+T> new RuleReplacement(null, new Rule(leftRecursiveRule.Produced, newSymbol, suffixListSymbol)), // List<+T> = +T List<+T> | empty new RuleReplacement(null, new Rule(suffixListSymbol, suffixSymbol, suffixListSymbol)), new RuleReplacement(null, new Rule(suffixListSymbol)), // +T = + T new RuleReplacement(leftRecursiveRule, new Rule(suffixSymbol, leftRecursiveRule.Symbols.Select((s, i) => i == leftRecursiveRule.Symbols.Count - 1 ? newSymbol : s).Skip(1))) } ); } return(this.Replace(replacements)); }
private bool IsAliasOf(Symbol a, NonTerminal b) => a != b && Traverse.Along(a as NonTerminal, s => this.aliases.GetValueOrDefault(s)).Contains(b);