PageRenderTime 33ms CodeModel.GetById 2ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 1ms

/irony-47261/Irony.Samples/SourceSamples/c#/Irony_GrammarDataBuilder.cs

https://bitbucket.org/earwicker/spec
C# | 610 lines | 469 code | 43 blank | 98 comment | 125 complexity | b796739b07d2f4926ff324139f353585 MD5 | raw file
  1#region License
  2/* **********************************************************************************
  3 * Copyright (c) Roman Ivantsov
  4 * This source code is subject to terms and conditions of the MIT License
  5 * for Irony. A copy of the license can be found in the License.txt file
  6 * at the root of this distribution. 
  7 * By using this source code in any fashion, you are agreeing to be bound by the terms of the 
  8 * MIT License.
  9 * You must not remove this notice from this software.
 10 * **********************************************************************************/
 11#endregion
 12
 13using System;
 14using System.Collections.Generic;
 15using System.Text;
 16using System.Collections;
 17using System.Diagnostics;
 18
 19namespace Irony.Compiler {
 20  
 21  // This class contains all complex logic of extracting Parser/Scanner's DFA tables and other control information
 22  // from the language grammar. 
 23  // Warning: unlike other classes in this project, understanding what's going on here requires some knowledge of 
 24  // LR/LALR parsing algorithms. For this I refer you to the Dragon book or any other book on compiler/parser construction.
 25  public class GrammarDataBuilder {
 26    class ShiftTable : Dictionary<string, LR0ItemList> { }
 27    private ParserStateTable _stateHash;
 28    public readonly GrammarData Data;
 29    Grammar _grammar;
 30
 31
 32    public GrammarDataBuilder(Grammar grammar) {
 33      _grammar = grammar;
 34      Data = new GrammarData();
 35      Data.Grammar = _grammar;
 36    }
 37    public void Build() {
 38      try {
 39        Data.ScannerRecoverySymbols = _grammar.WhitespaceChars + _grammar.Delimiters;
 40        if (_grammar.Root == null) 
 41          Cancel("Root property of the grammar is not set.");
 42        //Create the augmented root for the grammar
 43        Data.AugmentedRoot = new NonTerminal(_grammar.Root.Name + "'", new BnfExpression(_grammar.Root));
 44        //Collect all terminals and non-terminals into corresponding collections
 45        CollectAllElements();
 46        //Adjust case for Symbols for case-insensitive grammar (change keys to lowercase)
 47        if (!_grammar.CaseSensitive)
 48          AdjustCaseForSymbols();
 49        //Create productions and LR0Items 
 50        CreateProductions();
 51        //Calculate nullability, Firsts and TailFirsts collections of all non-terminals
 52        CalculateNullability();
 53        CalculateFirsts();
 54        CalculateTailFirsts();
 55        //Create parser states list, including initial and final states 
 56        CreateParserStates();
 57        //Propagate Lookaheads
 58        PropagateLookaheads();
 59        //Debug.WriteLine("Time of PropagateLookaheads: " + time);
 60        //now run through all states and create Reduce actions
 61        CreateReduceActions();
 62        //finally check for conflicts and detect Operator-based actions
 63        CheckActionConflicts();
 64        //call Init on all elements in the grammar
 65        InitAll();
 66        //Build hash table of terminals for fast lookup by current input char; note that this must be run after Init
 67        BuildTerminalsLookupTable();
 68        //Validate
 69        ValidateAll();
 70      } catch (GrammarErrorException e) {
 71        Data.Errors.Add(e.Message);
 72        Data.AnalysisCanceled = true; 
 73      }
 74    }//method
 75
 76    private void Cancel(string msg) {
 77      if (msg == null) msg = "Grammar analysis canceled.";
 78      throw new GrammarErrorException(msg);
 79    }
 80
 81    #region Collecting non-terminals
 82    int _unnamedCount; //internal counter for generating names for unnamed non-terminals
 83    private void CollectAllElements() {
 84      Data.NonTerminals.Clear();
 85      Data.Terminals.Clear();
 86      Data.Terminals.AddRange(_grammar.ExtraTerminals);
 87      _unnamedCount = 0;
 88      CollectAllElementsRecursive(Data.AugmentedRoot);
 89      Data.Terminals.Sort(Terminal.ByName);
 90      if (Data.AnalysisCanceled)
 91        Cancel(null);
 92    }
 93
 94    private void CollectAllElementsRecursive(BnfTerm element) {
 95      //Terminal
 96      Terminal term = element as Terminal;
 97      // Do not add pseudo terminals defined as static singletons in Grammar class (Empty, Eof, etc)
 98      //  We will never see these terminals in the input stream.
 99      //   Filter them by type - their type is exactly "Terminal", not derived class. 
100      if (term != null && !Data.Terminals.Contains(term) && term.GetType() != typeof(Terminal)) {
101        Data.Terminals.Add(term);
102        return;
103      }
104      //NonTerminal
105      NonTerminal nt = element as NonTerminal;
106      if (nt == null || Data.NonTerminals.Contains(nt))
107        return;
108      if (nt.Name == null)
109        nt.Name = "NT" + (_unnamedCount++);
110      Data.NonTerminals.Add(nt);
111      if (nt.Rule == null) {
112        AddError("Non-terminal {0} has uninitialized Rule property.", nt.Name);
113        Data.AnalysisCanceled = true;
114        return;
115      }
116      //check all child elements
117      foreach(BnfTermList elemList in nt.Rule.Data)
118        for(int i = 0; i < elemList.Count; i++) {
119          BnfTerm child = elemList[i];
120          if (child == null) {
121            AddError("Rule for NonTerminal {0} contains null as an operand in position {1} in one of productions.", nt, i);
122            continue; //for i loop 
123          }
124          //Check for nested expression - convert to non-terminal
125          BnfExpression expr = child as BnfExpression;
126          if (expr != null) {
127            child = new NonTerminal(null, expr);
128            elemList[i] = child;
129          }
130          CollectAllElementsRecursive(child);
131        }
132    }//method
133
134    private void AdjustCaseForSymbols() {
135      if (_grammar.CaseSensitive) return;
136      foreach (Terminal term in Data.Terminals)
137        if (term is SymbolTerminal)
138          term.Key = term.Key.ToLower();
139    }
140
141    private void BuildTerminalsLookupTable() {
142      Data.TerminalsLookup.Clear();
143      Data.TerminalsWithoutPrefixes.Clear();
144      foreach (Terminal term in Data.Terminals) {
145        IList<string> prefixes = term.GetFirsts();
146        if (prefixes == null || prefixes.Count == 0) {
147          Data.TerminalsWithoutPrefixes.Add(term);
148          continue;
149        }
150        //Go through prefixes one-by-one
151        foreach (string prefix in prefixes) {
152          if (string.IsNullOrEmpty(prefix)) continue;
153          //Calculate hash key for the prefix
154          char hashKey = prefix[0];
155          if (!_grammar.CaseSensitive)
156            hashKey = char.ToLower(hashKey);
157          TerminalList currentList;
158          if (!Data.TerminalsLookup.TryGetValue(hashKey, out currentList)) {
159            //if list does not exist yet, create it
160            currentList = new TerminalList();
161            Data.TerminalsLookup[hashKey] = currentList;
162          }
163          //add terminal to the list
164          currentList.Add(term);
165        }
166      }//foreach term
167      //Now add _noPrefixTerminals to every list in table
168      if (Data.TerminalsWithoutPrefixes.Count > 0)
169        foreach (TerminalList list in Data.TerminalsLookup.Values)
170          list.AddRange(Data.TerminalsWithoutPrefixes);
171      //Sort all terminal lists by reverse priority, so that terminal with higher priority comes first in the list
172      foreach (TerminalList list in Data.TerminalsLookup.Values)
173        if (list.Count > 1)
174          list.Sort(Terminal.ByPriorityReverse);
175    }//method
176
177
178    #endregion
179
180    #region Creating Productions
181    private void CreateProductions() {
182      Data.Productions.Clear();
183      //each LR0Item gets its unique ID, last assigned (max) Id is kept in static field
184      LR0Item._maxID = 0; 
185      foreach(NonTerminal nt in Data.NonTerminals) {
186        nt.Productions.Clear();
187        //Get data (sequences) from both Rule and ErrorRule
188        BnfExpressionData allData = new BnfExpressionData();
189        allData.AddRange(nt.Rule.Data);
190        if (nt.ErrorRule != null) 
191          allData.AddRange(nt.ErrorRule.Data);
192        //actually create productions for each sequence
193        foreach (BnfTermList prodOperands in allData) {
194          bool isInitial = (nt == Data.AugmentedRoot);
195          Production prod = new Production(isInitial, nt, prodOperands);
196          nt.Productions.Add(prod);
197          Data.Productions.Add(prod);
198        }//foreach prodOperands
199      }
200    }
201    #endregion
202
203    #region Nullability calculation
204    private void CalculateNullability() {
205      NonTerminalList undecided = Data.NonTerminals;
206      while(undecided.Count > 0) {
207        NonTerminalList newUndecided = new NonTerminalList();
208        foreach(NonTerminal nt in undecided)
209          if (!CalculateNullability(nt, undecided))
210           newUndecided.Add(nt);
211        if (undecided.Count == newUndecided.Count) return;  //we didn't decide on any new, so we're done
212        undecided = newUndecided;
213      }//while
214    }
215
216    private bool CalculateNullability(NonTerminal nonTerminal, NonTerminalList undecided) {
217      foreach (Production prod in nonTerminal.Productions) {
218        //If production has terminals, it is not nullable and cannot contribute to nullability
219        if (prod.HasTerminals)   continue;
220        if (prod.IsEmpty()) {
221          nonTerminal.Nullable = true;
222          return true; //Nullable
223        }//if 
224        //Go thru all elements of production and check nullability
225        bool allNullable = true;
226        foreach (BnfTerm term in prod.RValues) {
227          NonTerminal nt = term as NonTerminal;
228          if (nt != null)
229            allNullable &= nt.Nullable;
230        }//foreach nt
231        if (allNullable) {
232          nonTerminal.Nullable = true;
233          return true;
234        }
235      }//foreach prod
236      return false; //cannot decide
237    }
238    #endregion
239
240    #region Calculating Firsts
241    private void CalculateFirsts() {
242      //1. Calculate PropagateTo lists and put initial terminals into Firsts lists
243      foreach (Production prod in Data.Productions) {
244        foreach (BnfTerm term in prod.RValues) {
245          if (term is Terminal) { //it is terminal, so add it to Firsts and that's all with this production
246            prod.LValue.Firsts.Add(term.Key); // Add terminal to Firsts (note: Add ignores repetitions)
247            break; //from foreach term
248          }//if
249          NonTerminal nt = term as NonTerminal;
250          if (!nt.PropagateFirstsTo.Contains(prod.LValue))
251            nt.PropagateFirstsTo.Add(prod.LValue); //ignores repetitions
252          if (!nt.Nullable) break; //if not nullable we're done
253        }//foreach oper
254      }//foreach prod
255      
256      //2. Propagate all firsts thru all dependencies
257      NonTerminalList workList = Data.NonTerminals;
258      while (workList.Count > 0) {
259        NonTerminalList newList = new NonTerminalList();
260        foreach (NonTerminal nt in workList) {
261          foreach (NonTerminal toNt in nt.PropagateFirstsTo)
262            foreach (string symbolKey in nt.Firsts) {
263              if (!toNt.Firsts.Contains(symbolKey)) {
264                toNt.Firsts.Add(symbolKey);
265                if (!newList.Contains(toNt))
266                  newList.Add(toNt);
267              }//if
268            }//foreach symbolKey
269        }//foreach nt in workList
270        workList = newList;
271      }//while
272    }//method
273
274    #endregion
275
276    #region Calculating Tail Firsts
277    private void CalculateTailFirsts() {
278      foreach (Production prod in Data.Productions) {
279        KeyList accumulatedFirsts = new KeyList();
280        bool allNullable = true;
281        //We are going backwards in LR0Items list
282        for(int i = prod.LR0Items.Count-1; i >= 0; i--) {
283          LR0Item item = prod.LR0Items[i];
284          if (i >= prod.LR0Items.Count-2) {
285            //Last and before last items have empty tails
286            item.TailIsNullable = true;
287            item.TailFirsts.Clear();
288            continue;
289          }
290          BnfTerm term = prod.RValues[item.Position + 1];  //Element after-after-dot
291          NonTerminal ntElem = term as NonTerminal;
292          if (ntElem == null || !ntElem.Nullable) { //term is a terminal or non-nullable NonTerminal
293            //term is not nullable, so we clear all old firsts and add this term
294            accumulatedFirsts.Clear();
295            allNullable = false;
296            item.TailIsNullable = false;   
297            if (ntElem == null) {
298              item.TailFirsts.Add(term.Key);//term is terminal so add its key
299              accumulatedFirsts.Add(term.Key);
300            } else {
301              item.TailFirsts.AddRange(ntElem.Firsts); //nonterminal
302              accumulatedFirsts.AddRange(ntElem.Firsts);
303            }
304            continue;
305          }
306          //if we are here, then ntElem is a nullable NonTerminal. We add 
307          accumulatedFirsts.AddRange(ntElem.Firsts);
308          item.TailFirsts.AddRange(accumulatedFirsts);
309          item.TailIsNullable = allNullable;
310        }//for i
311      }//foreach prod
312    }//method
313
314    #endregion
315
316    #region Creating parser states
317    private void CreateParserStates() {
318      Data.States.Clear();
319      _stateHash = new ParserStateTable();
320      //Create initial state
321      //there is always just one initial production Root' -> Root + LF, and we're interested in LR item at 0 index
322      LR0ItemList itemList = new LR0ItemList();
323      itemList.Add(Data.AugmentedRoot.Productions[0].LR0Items[0]);
324      Data.InitialState = FindOrCreateState(itemList); //it is actually create
325      Data.InitialState.Items[0].NewLookaheads.Add(Grammar.Eof.Key);
326      //create final state - we need to create it explicitly to assign to _data.FinalState property
327      // final state is based on the same initial production, but different LRItem - the one with dot AFTER the root nonterminal.
328      // it is item at index 1. 
329      itemList = new LR0ItemList();
330      itemList.Add(Data.AugmentedRoot.Productions[0].LR0Items[1]);
331      Data.FinalState = FindOrCreateState(itemList);
332
333      // Iterate through states (while new ones are created) and create shift transitions and new states 
334      for (int index = 0; index < Data.States.Count; index++) {
335        ParserState state = Data.States[index];
336        AddClosureItems(state);
337        //Get keys of all possible shifts
338        ShiftTable shiftTable = GetStateShifts(state);
339        //Each key in shifts dict is an input element 
340        // Value is LR0ItemList of shifted LR0Items for this input element.
341        foreach (string input in shiftTable.Keys) {
342          LR0ItemList shiftedCoreItems = shiftTable[input];
343          ParserState newState = FindOrCreateState(shiftedCoreItems);
344          state.Actions[input] = new ActionRecord(input,ParserActionType.Shift, newState, null);
345          //link original LRItems in original state to derived LRItems in newState
346          foreach (LR0Item coreItem in shiftedCoreItems) {
347            LRItem fromItem = FindItem(state, coreItem.Production, coreItem.Position - 1);
348            LRItem toItem = FindItem(newState, coreItem.Production, coreItem.Position);
349            if (!fromItem.PropagateTargets.Contains(toItem))
350              fromItem.PropagateTargets.Add(toItem);
351          }//foreach coreItem
352        }//foreach input
353      } //for index
354    }//method
355
356    private string AdjustCase(string key) {
357      return _grammar.CaseSensitive ? key : key.ToLower();
358    }
359    private LRItem TryFindItem(ParserState state, LR0Item core) {
360      foreach (LRItem item in state.Items)
361        if (item.Core == core)
362          return item;
363      return null;
364    }//method
365
366    private LRItem FindItem(ParserState state, Production production, int position) {
367      foreach(LRItem item in state.Items) 
368        if (item.Core.Production == production && item.Core.Position == position)
369          return item;
370      string msg = string.Format("Failed to find an LRItem in state {0} by production [{1}] and position {2}. ",
371        state, production.ToString(), position.ToString());
372      throw new IronyException(msg);
373    }//method
374
375    private ShiftTable GetStateShifts(ParserState state) {
376      ShiftTable shifts = new ShiftTable();
377      LR0ItemList list;
378      foreach (LRItem item in state.Items) {
379        BnfTerm term = item.Core.NextElement;
380        if (term == null)  continue;
381        LR0Item shiftedItem = item.Core.Production.LR0Items[item.Core.Position + 1];
382        if (!shifts.TryGetValue(term.Key, out list))
383          shifts[term.Key] = list = new LR0ItemList();
384        list.Add(shiftedItem);
385      }//foreach
386      return shifts;
387    }//method
388
389    private ParserState FindOrCreateState(LR0ItemList lr0Items) {
390      string key = CalcItemListKey(lr0Items);
391      ParserState result;
392      if (_stateHash.TryGetValue(key, out result))
393        return result; 
394      result = new ParserState("S" + Data.States.Count, lr0Items);
395      Data.States.Add(result);
396      _stateHash[key] = result;
397      return result;
398    }
399
400    //Creates closure items with "spontaneously generated" lookaheads
401    private bool AddClosureItems(ParserState state) {
402      bool result = false;
403      //note that we change collection while we iterate thru it, so we have to use "for i" loop
404      for(int i = 0; i < state.Items.Count; i++) {
405        LRItem item = state.Items[i];
406        NonTerminal nextNT = item.Core.NextElement as NonTerminal;
407        if (nextNT == null)  continue;
408        //1. Add normal closure items
409        foreach (Production prod in nextNT.Productions) {
410          LR0Item core = prod.LR0Items[0]; //item at zero index is the one that starts with dot
411          LRItem newItem = TryFindItem(state, core);
412          if (newItem == null) {
413            newItem = new LRItem(core);
414            state.Items.Add(newItem);
415            result = true;
416          }
417          #region Comments on lookaheads processing
418          // The general idea of generating ("spontaneously") the lookaheads is the following:
419          // Let's the original item be in the form 
420          //   [A -> alpha . B beta , lset]
421          //     where <B> is a non-terminal and <lset> is a set of lookaheads, 
422          //      <beta> is some string (B's tail in our terminology)
423          // Then the closure item on non-teminal B is an item
424          //   [B -> x, firsts(beta + lset)]
425          //      (the lookahead set is expression after the comma).
426          // To generate lookaheads on a closure item, we simply take "firsts" 
427          //   from the tail <beta> of the NonTerminal <B>. 
428          //   Normally if tail <beta> is nullable we would add ("propagate") 
429          //   the <lset> lookaheads from <A> to <B>.
430          //   We dont' do it right here - we simply add a propagation link.
431          //   We propagate all lookaheads later in a separate process.
432          #endregion
433          newItem.NewLookaheads.AddRange(item.Core.TailFirsts);
434          if (item.Core.TailIsNullable && !item.PropagateTargets.Contains(newItem))
435            item.PropagateTargets.Add(newItem);
436        }//foreach prod
437      }//for i (LRItem)
438      return result;
439    }
440
441
442    #region comments
443    //Parser states are distinguished by the subset of kernel LR0 items. 
444    // So when we derive new LR0-item list by shift operation, 
445    // we need to find out if we have already a state with the same LR0Item list.
446    // We do it by looking up in a state hash by a key - [LR0 item list key]. 
447    // Each list's key is a concatenation of items' IDs separated by ','.
448    // Before producing the key for a list, the list must be sorted; 
449    //   thus we garantee one-to-one correspondence between LR0Item sets and keys.
450    // And of course, we count only kernel items (with dot NOT in the first position).
451    #endregion
452    private string CalcItemListKey(LR0ItemList items) {
453      items.Sort(ById); //Sort by ID
454      if (items.Count == 0) return "";
455      //quick shortcut
456      if (items.Count == 1 && items[0].IsKernel) 
457        return items[0].ID.ToString();
458      StringBuilder sb = new StringBuilder(1024);
459      foreach (LR0Item item in items) {
460        if (item.IsKernel) {
461          sb.Append(item.ID);
462          sb.Append(",");
463        }
464      }//foreach
465      return sb.ToString();
466    }
467    private static int ById(LR0Item x, LR0Item y) {
468      if (x.ID < y.ID) return -1;
469      if (x.ID == y.ID) return 0;
470      return 1;
471    }
472
473    #endregion
474
475    #region Lookaheads propagation
476    private void PropagateLookaheads() {
477      LRItemList currentList = new LRItemList();
478      //first collect all items
479      foreach (ParserState state in Data.States)
480        currentList.AddRange(state.Items);
481      //Main loop - propagate until done
482      while (currentList.Count > 0) {
483        LRItemList newList = new LRItemList();
484        foreach (LRItem item in currentList) {
485          if (item.NewLookaheads.Count == 0) continue;
486          int oldCount = item.Lookaheads_.Count;
487          item.Lookaheads_.AddRange(item.NewLookaheads);
488          if (item.Lookaheads_.Count != oldCount) {
489            foreach (LRItem targetItem in item.PropagateTargets) {
490              targetItem.NewLookaheads.AddRange(item.NewLookaheads);
491              newList.Add(targetItem);
492            }//foreach targetItem
493          }//if
494          item.NewLookaheads.Clear();
495        }//foreach item
496        currentList = newList;
497      }//while         
498    }//method
499    #endregion
500
501    #region Final actions: createReduceActions
502    private void CreateReduceActions() {
503      foreach(ParserState state in Data.States) {
504        foreach (LRItem item in state.Items) {
505          //we are interested only in "dot  at the end" items
506          if (item.Core.NextElement != null)   continue;
507          foreach (string lookahead in item.Lookaheads_) {
508            ActionRecord action;
509            if (state.Actions.TryGetValue(lookahead, out action)) 
510              action.ReduceProductions.Add(item.Core.Production);
511            else
512              state.Actions[lookahead] = new ActionRecord(lookahead, ParserActionType.Reduce, null, item.Core.Production);
513          }//foreach lookahead
514        }//foreach item
515      }// foreach state
516    } //method
517    
518    #endregion
519
520    #region Check for shift-reduce conflicts
521    private void CheckActionConflicts() {
522      StringDictionary errorTable = new StringDictionary();
523      foreach (ParserState state in Data.States) {
524        foreach (ActionRecord action in state.Actions.Values) {
525          //1. Pure shift
526          if (action.NewState != null && action.ReduceProductions.Count == 0)
527            continue; //ActionType is shift by default
528          //2. Pure reduce
529          if (action.NewState == null && action.ReduceProductions.Count == 1) {
530            action.ActionType = ParserActionType.Reduce; 
531            continue;
532          }
533          //3. Shift-reduce conflict
534          if (action.NewState != null && action.ReduceProductions.Count > 0) {
535            //it might be an operation, with resolution by precedence/associativity
536            SymbolTerminal opTerm = SymbolTerminal.GetSymbol(action.Key);
537            if (opTerm != null && opTerm.IsSet(TermOptions.IsOperator)) {
538              action.ActionType = ParserActionType.Operator;
539            } else {
540                AddErrorForInput(errorTable, action.Key, "Shift-reduce conflict in state {0}, reduce production: {1}",
541                    state, action.ReduceProductions[0]); 
542              //NOTE: don't do "continue" here, we need to proceed to reduce-reduce conflict check
543            }//if...else
544          }//if action....
545          //4. Reduce-reduce conflicts
546          if (action.ReduceProductions.Count > 1) {
547            AddErrorForInput(errorTable, action.Key, "Reduce-reduce conflict in state {0} in productions: {1} ; {2}",
548                state, action.ReduceProductions[0], action.ReduceProductions[1]);
549          }
550
551        }//foreach action
552      }//foreach state
553      //copy errors to Errors collection; In errorTable keys are error messages, values are inputs for this message 
554      foreach (string msg in errorTable.Keys) {
555        Data.Errors.Add(msg + " on inputs: " + errorTable[msg]);
556      }
557    }//methods
558
559    //Aggregate error messages for different inputs (lookaheads) in errors dictionary
560    private void AddErrorForInput(StringDictionary errors, string input, string template, params object[] args) {
561      string msg = string.Format(template, args);
562      string tmpInputs;
563      errors.TryGetValue(msg, out tmpInputs);
564      errors[msg] = tmpInputs + input + " ";
565    }
566
567    private bool ContainsProduction(ProductionList productions, NonTerminal nonTerminal) {
568      foreach (Production prod in productions)
569        if (prod.LValue == nonTerminal) return true;
570      return false;
571    }
572    #endregion
573
574    #region Initialize elements
575    private void InitAll() {
576      foreach (Terminal term in Data.Terminals)
577        term.Init(_grammar);
578      foreach (NonTerminal nt in Data.NonTerminals)
579        nt.Init(_grammar);
580      foreach (TokenFilter filter in _grammar.TokenFilters)
581        filter.Init(_grammar);
582    }
583    #endregion
584
585    private void ValidateAll() {
586      //Check rule on all non-terminals
587      KeyList ntList = new KeyList();
588      foreach(NonTerminal nt in Data.NonTerminals) {
589        if (nt == Data.AugmentedRoot) continue; //augm root does not count
590        BnfExpressionData data = nt.Rule.Data;
591        if (data.Count == 1 && data[0].Count == 1 && data[0][0] is NonTerminal)
592          ntList.Add(nt.Name);
593      }//foreach
594      if (ntList.Count > 0) 
595        AddError("Warning: Possible non-terminal duplication. The following non-terminals have rules containing a single non-terminal: \r\n {0}. \r\n" +
596         "Consider merging two non-terminals; you may need to use 'nt1 = nt2;' instead of 'nt1.Rule=nt2'.", ntList.ToString(", "));
597    }
598
599    #region error handling: AddError
600    private void AddError(string message, params object[] args) {
601      if (args != null && args.Length > 0)
602        message = string.Format(message, args);
603      Data.Errors.Add(message);
604    }
605    #endregion
606
607  }//class
608
609
610}//namespace