PageRenderTime 56ms CodeModel.GetById 2ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 1ms

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

https://bitbucket.org/earwicker/spec
C# | 635 lines | 490 code | 44 blank | 101 comment | 125 complexity | f72cb4115fabe3a06ee8d9ed71e6ad1c 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.CompilerServices.Lalr {
 20  
 21  // This class contains all complex logic of constructing LALR parser 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 ParserControlDataBuilder {
 26    class ShiftTable : Dictionary<string, LR0ItemList> { }
 27    private ParserStateTable _stateHash;
 28    public readonly ParserControlData Data;
 29    Grammar _grammar;
 30
 31
 32    public ParserControlDataBuilder(Grammar grammar) {
 33      _grammar = grammar;
 34      Data = new ParserControlData(grammar);
 35      Data.Grammar = _grammar;
 36    }
 37    public ParserControlData Build() {
 38      try {
 39        if (_grammar.Root == null) 
 40          Cancel("Root property of the grammar is not set.");
 41        if (!_grammar.Initialized)
 42          _grammar.Init();
 43
 44        GetNonTerminalsFromGrammar();
 45        InitNonTerminalData();
 46        //Create the augmented root for the grammar
 47        CreateAugmentedRootForGrammar();
 48        //Create productions and LR0Items 
 49        CreateProductions();
 50        //Calculate nullability, Firsts and TailFirsts collections of all non-terminals
 51        CalculateNullability();
 52        CalculateFirsts();
 53        CalculateTailFirsts();
 54        //Create parser states list, including initial and final states 
 55        CreateParserStates();
 56        //Propagate Lookaheads
 57        PropagateLookaheads();
 58        //Debug.WriteLine("Time of PropagateLookaheads: " + time);
 59        //now run through all states and create Reduce actions
 60        CreateReduceActions();
 61        //finally check for conflicts and detect Operator-based actions
 62        CheckActionConflicts();
 63        //Validate
 64        ValidateAll();
 65      } catch (GrammarErrorException e) {
 66        _grammar.Errors.Add(e.Message);
 67        Data.AnalysisCanceled = true; 
 68      }
 69      return Data;
 70    }//method
 71
 72    private void Cancel(string msg) {
 73      if (msg == null) msg = "Grammar analysis canceled.";
 74      throw new GrammarErrorException(msg);
 75    }
 76
 77    private void GetNonTerminalsFromGrammar() {
 78      foreach (BnfTerm t in _grammar.AllTerms) {
 79        NonTerminal nt = t as NonTerminal;
 80        if (nt != null)
 81          Data.NonTerminals.Add(nt);
 82      }
 83    }
 84    private void InitNonTerminalData() {
 85      foreach (NonTerminal nt in Data.NonTerminals)
 86        NtData.GetOrCreate(nt);
 87    }
 88
 89    private void CreateAugmentedRootForGrammar() {
 90      Data.AugmentedRoot = new NonTerminal(_grammar.Root.Name + "'", new BnfExpression(_grammar.Root));
 91      Data.NonTerminals.Add(Data.AugmentedRoot);
 92    }
 93
 94    #region Creating Productions
 95    private void CreateProductions() {
 96      Data.Productions.Clear();
 97      //each LR0Item gets its unique ID, last assigned (max) Id is kept in static field
 98      LR0Item._maxID = 0; 
 99      foreach(NonTerminal nt in Data.NonTerminals) {
100        NtData ntInfo = NtData.GetOrCreate(nt);
101        ntInfo.Productions.Clear();
102        //Get data (sequences) from both Rule and ErrorRule
103        BnfExpressionData allData = new BnfExpressionData();
104        allData.AddRange(nt.Rule.Data);
105        if (nt.ErrorRule != null) 
106          allData.AddRange(nt.ErrorRule.Data);
107        //actually create productions for each sequence
108        foreach (BnfTermList prodOperands in allData) {
109          Production prod = CreateProduction(nt, prodOperands);
110          //Add the production to non-terminal's list and to global list 
111          ntInfo.Productions.Add(prod);
112          Data.Productions.Add(prod);
113        }//foreach prodOperands
114      }
115    }
116    private Production CreateProduction(NonTerminal lvalue, BnfTermList operands) {
117      Production prod = new Production(lvalue);
118      //create RValues list skipping Empty terminal and collecting grammar hints
119      foreach (BnfTerm operand in operands) {
120        if (operand == Grammar.Empty) 
121          continue;
122        //Collect hints as we go - they will be added to the next non-hint element
123        GrammarHint hint = operand as GrammarHint;
124        if (hint != null) {
125          hint.Position = prod.RValues.Count;
126          prod.Hints.Add(hint);
127          continue;
128        }
129        //Check if it is a Terminal or Error element
130        Terminal t = operand as Terminal;
131        if (t != null) {
132          prod.Flags |= ProductionFlags.HasTerminals;
133          if (t.Category == TokenCategory.Error) prod.Flags |= ProductionFlags.IsError; 
134        }
135        //Add the operand info and LR0 Item
136        LR0Item item = new LR0Item(prod, prod.RValues.Count);
137        prod.LR0Items.Add(item);
138        prod.RValues.Add(operand);
139      }//foreach operand
140      //set the flags
141      if (lvalue == Data.AugmentedRoot)
142        prod.Flags |= ProductionFlags.IsInitial;
143      if (prod.RValues.Count == 0)
144        prod.Flags |= ProductionFlags.IsEmpty;
145      //Add final LRItem
146      prod.LR0Items.Add(new LR0Item(prod, prod.RValues.Count));
147      return prod; 
148    }
149    #endregion
150
151    #region Nullability calculation
152    private void CalculateNullability() {
153      NonTerminalList undecided = Data.NonTerminals;
154      while(undecided.Count > 0) {
155        NonTerminalList newUndecided = new NonTerminalList();
156        foreach(NonTerminal nt in undecided)
157          if (!CalculateNullability(nt, undecided))
158           newUndecided.Add(nt);
159        if (undecided.Count == newUndecided.Count) return;  //we didn't decide on any new, so we're done
160        undecided = newUndecided;
161      }//while
162    }
163
164    private bool CalculateNullability(NonTerminal nonTerminal, NonTerminalList undecided) {
165      NtData nonTerminalInfo = (NtData) nonTerminal.ParserData;
166      foreach (Production prod in nonTerminalInfo.Productions) {
167        //If production has terminals, it is not nullable and cannot contribute to nullability
168        if (prod.IsSet(ProductionFlags.HasTerminals))   continue;
169        if (prod.IsSet(ProductionFlags.IsEmpty)) {
170          nonTerminalInfo.Nullable = true;
171          return true; //Nullable
172        }//if 
173        //Go thru all elements of production and check nullability
174        bool allNullable = true;
175        foreach (BnfTerm  term in prod.RValues) {
176          NtData ntd = term.ParserData as NtData;
177          if (ntd != null)
178            allNullable &= ntd.Nullable;
179        }//foreach nt
180        if (allNullable) {
181          nonTerminalInfo.Nullable = true;
182          return true;
183        }
184      }//foreach prod
185      return false; //cannot decide
186    }
187    #endregion
188
189    #region Calculating Firsts
190    private void CalculateFirsts() {
191      //1. Calculate PropagateTo lists and put initial terminals into Firsts lists
192      foreach (Production prod in Data.Productions) {
193        NtData lvData = prod.LValue.ParserData as NtData;
194        foreach (BnfTerm term in prod.RValues) {
195          if (term is Terminal) { //it is terminal, so add it to Firsts and that's all with this production
196            lvData.Firsts.Add(term.Key); // Add terminal to Firsts (note: Add ignores repetitions)
197            break; //from foreach term
198          }//if
199          NtData ntInfo = term.ParserData as NtData;
200          if (!ntInfo.PropagateFirstsTo.Contains(prod.LValue))
201            ntInfo.PropagateFirstsTo.Add(prod.LValue); //ignores repetitions
202          if (!ntInfo.Nullable) break; //if not nullable we're done
203        }//foreach oper
204      }//foreach prod
205      
206      //2. Propagate all firsts thru all dependencies
207      NonTerminalList workList = Data.NonTerminals;
208      while (workList.Count > 0) {
209        NonTerminalList newList = new NonTerminalList();
210        foreach (NonTerminal nt in workList) {
211          NtData ntInfo = (NtData)nt.ParserData;
212          foreach (NonTerminal toNt in ntInfo.PropagateFirstsTo) {
213            NtData toInfo = (NtData)toNt.ParserData;
214            foreach (string symbolKey in ntInfo.Firsts) {
215              if (!toInfo.Firsts.Contains(symbolKey)) {
216                toInfo.Firsts.Add(symbolKey);
217                if (!newList.Contains(toNt))
218                  newList.Add(toNt);
219              }//if
220            }//foreach symbolKey
221          }//foreach toNt
222        }//foreach nt in workList
223        workList = newList;
224      }//while
225    }//method
226
227    #endregion
228
229    #region Calculating Tail Firsts
230    private void CalculateTailFirsts() {
231      foreach (Production prod in Data.Productions) {
232        StringSet accumulatedFirsts = new StringSet();
233        bool allNullable = true;
234        //We are going backwards in LR0Items list
235        for(int i = prod.LR0Items.Count-1; i >= 0; i--) {
236          LR0Item item = prod.LR0Items[i];
237          if (i >= prod.LR0Items.Count-2) {
238            //Last and before last items have empty tails
239            item.TailIsNullable = true;
240            item.TailFirsts.Clear();
241            continue;
242          }
243          BnfTerm nextTerm = prod.RValues[i + 1];  //Element after-after-dot; remember we're going in reverse direction
244          NtData nextData = (NtData)nextTerm.ParserData;
245          //if (ntElem == null) continue; //it is not NonTerminal
246          bool notNullable = nextTerm is Terminal || nextData != null && !nextData.Nullable; 
247          if (notNullable) { //next term is not nullable  (a terminal or non-nullable NonTerminal)
248            //term is not nullable, so we clear all old firsts and add this term
249            accumulatedFirsts.Clear();
250            allNullable = false;
251            item.TailIsNullable = false;
252            if (nextTerm is Terminal) {
253              item.TailFirsts.Add(nextTerm.Key);//term is terminal so add its key
254              accumulatedFirsts.Add(nextTerm.Key);
255            } else if (nextData != null) { //it is NonTerminal
256              item.TailFirsts.AddRange(nextData.Firsts); //nonterminal
257              accumulatedFirsts.AddRange(nextData.Firsts);
258            }
259            continue;
260          }
261          //if we are here, then ntElem is a nullable NonTerminal. We add 
262          accumulatedFirsts.AddRange(nextData.Firsts);
263          item.TailFirsts.AddRange(accumulatedFirsts);
264          item.TailIsNullable = allNullable;
265        }//for i
266      }//foreach prod
267    }//method
268
269    #endregion
270
271    #region Creating parser states
272    private void CreateInitialAndFinalStates() {
273      //there is always just one initial production "Root' -> .Root", and we're interested in LR item at 0 index
274      LR0ItemList itemList = new LR0ItemList();
275      NtData rootData = (NtData)Data.AugmentedRoot.ParserData;
276      itemList.Add(rootData.Productions[0].LR0Items[0]);
277      Data.InitialState = FindOrCreateState(itemList); //it is actually create
278      Data.InitialState.Items[0].NewLookaheads.Add(Grammar.Eof.Key);
279      #region comment about FinalState
280      //Create final state - because of the way states construction works, it doesn't create the final state automatically. 
281      // We need to create it explicitly and assign it to _data.FinalState property
282      // The final executed reduction is "Root' -> Root.". This jump is executed as follows: 
283      //   1. parser creates Root' node 
284      //   2. Parser pops the state from stack - that would be initial state
285      //   3. Finally, parser tries to find the transition in state.Actions table by the key of [Root'] element. 
286      // We must create the final state, and create the entry in transition table
287      // The final state is based on the same initial production, but different LRItem - the one with dot AFTER the root nonterminal.
288      // it is item at index 1.
289      #endregion
290      itemList.Clear();
291      itemList.Add(rootData.Productions[0].LR0Items[1]);
292      Data.FinalState = FindOrCreateState(itemList); //it is actually create
293      //Create shift transition from initial to final state
294      string rootKey = Data.AugmentedRoot.Key;
295      Data.InitialState.Actions[rootKey] = new ActionRecord(rootKey, ParserActionType.Shift, Data.FinalState, null);
296    }
297
298    private void CreateParserStates() {
299      Data.States.Clear();
300      _stateHash = new ParserStateTable();
301      CreateInitialAndFinalStates();
302
303      string augmRootKey = Data.AugmentedRoot.Key;
304      // Iterate through states (while new ones are created) and create shift transitions and new states 
305      for (int index = 0; index < Data.States.Count; index++) {
306        ParserState state = Data.States[index];
307        AddClosureItems(state);
308        //Get keys of all possible shifts
309        ShiftTable shiftTable = GetStateShifts(state);
310        //Each key in shifts dict is an input element 
311        // Value is LR0ItemList of shifted LR0Items for this input element.
312        foreach (string input in shiftTable.Keys) {
313          LR0ItemList shiftedCoreItems = shiftTable[input];
314          ParserState newState = FindOrCreateState(shiftedCoreItems);
315          ActionRecord newAction = new ActionRecord(input, ParserActionType.Shift, newState, null);
316          state.Actions[input] = newAction;
317          //link original LRItems in original state to derived LRItems in newState
318          foreach (LR0Item coreItem in shiftedCoreItems) {
319            LRItem fromItem = FindItem(state, coreItem.Production, coreItem.Position - 1);
320            LRItem toItem = FindItem(newState, coreItem.Production, coreItem.Position);
321            if (!fromItem.PropagateTargets.Contains(toItem))
322              fromItem.PropagateTargets.Add(toItem);
323            //copy hints from core items into the newAction
324            newAction.ShiftItems.Add(fromItem); 
325          }//foreach coreItem
326        }//foreach input
327      } //for index
328      Data.FinalState = Data.InitialState.Actions[augmRootKey].NewState;
329    }//method
330
331    private string AdjustCase(string key) {
332      return _grammar.CaseSensitive ? key : key.ToLower();
333    }
334    private LRItem TryFindItem(ParserState state, LR0Item core) {
335      foreach (LRItem item in state.Items)
336        if (item.Core == core)
337          return item;
338      return null;
339    }//method
340
341    private LRItem FindItem(ParserState state, Production production, int position) {
342      foreach(LRItem item in state.Items) 
343        if (item.Core.Production == production && item.Core.Position == position)
344          return item;
345      string msg = string.Format("Failed to find an LRItem in state {0} by production [{1}] and position {2}. ",
346        state, production.ToString(), position.ToString());
347      throw new CompilerException(msg);
348    }//method
349
350    private ShiftTable GetStateShifts(ParserState state) {
351      ShiftTable shifts = new ShiftTable();
352      LR0ItemList list;
353      foreach (LRItem item in state.Items) {
354        BnfTerm term = item.Core.Current;
355        if (term == null)  continue;
356        LR0Item shiftedItem = item.Core.Production.LR0Items[item.Core.Position + 1];
357        if (!shifts.TryGetValue(term.Key, out list))
358          shifts[term.Key] = list = new LR0ItemList();
359        list.Add(shiftedItem);
360      }//foreach
361      return shifts;
362    }//method
363
364    private ParserState FindOrCreateState(LR0ItemList lr0Items) {
365      string key = CalcItemListKey(lr0Items);
366      ParserState result;
367      if (_stateHash.TryGetValue(key, out result))
368        return result; 
369      result = new ParserState("S" + Data.States.Count, lr0Items);
370      Data.States.Add(result);
371      _stateHash[key] = result;
372      return result;
373    }
374
375    //Creates closure items with "spontaneously generated" lookaheads
376    private bool AddClosureItems(ParserState state) {
377      bool result = false;
378      //note that we change collection while we iterate thru it, so we have to use "for i" loop
379      for(int i = 0; i < state.Items.Count; i++) {
380        LRItem item = state.Items[i];
381        BnfTerm currTerm = item.Core.Current;
382        if (currTerm == null || !(currTerm is NonTerminal))  
383          continue;
384        //1. Add normal closure items
385        NtData currInfo = (NtData)currTerm.ParserData;
386        foreach (Production prod in currInfo.Productions) {
387          LR0Item core = prod.LR0Items[0]; //item at zero index is the one that starts with dot
388          LRItem newItem = TryFindItem(state, core);
389          if (newItem == null) {
390            newItem = new LRItem(state, core);
391            state.Items.Add(newItem);
392            result = true;
393          }
394          #region Comments on lookaheads processing
395          // The general idea of generating ("spontaneously") the lookaheads is the following:
396          // Let's the original item be in the form 
397          //   [A -> alpha . B beta , lset]
398          //     where <B> is a non-terminal and <lset> is a set of lookaheads, 
399          //      <beta> is some string (B's tail in our terminology)
400          // Then the closure item on non-teminal B is an item
401          //   [B -> x, firsts(beta + lset)]
402          //      (the lookahead set is expression after the comma).
403          // To generate lookaheads on a closure item, we simply take "firsts" 
404          //   from the tail <beta> of the NonTerminal <B>. 
405          //   Normally if tail <beta> is nullable we would add ("propagate") 
406          //   the <lset> lookaheads from <A> to <B>.
407          //   We dont' do it right here - we simply add a propagation link.
408          //   We propagate all lookaheads later in a separate process.
409          #endregion
410          newItem.NewLookaheads.AddRange(item.Core.TailFirsts);
411          if (item.Core.TailIsNullable && !item.PropagateTargets.Contains(newItem))
412            item.PropagateTargets.Add(newItem);
413        }//foreach prod
414      }//for i (LRItem)
415      return result;
416    }
417
418
419    #region comments
420    //Parser states are distinguished by the subset of kernel LR0 items. 
421    // So when we derive new LR0-item list by shift operation, 
422    // we need to find out if we have already a state with the same LR0Item list.
423    // We do it by looking up in a state hash by a key - [LR0 item list key]. 
424    // Each list's key is a concatenation of items' IDs separated by ','.
425    // Before producing the key for a list, the list must be sorted; 
426    //   thus we garantee one-to-one correspondence between LR0Item sets and keys.
427    // And of course, we count only kernel items (with dot NOT in the first position).
428    #endregion
429    private string CalcItemListKey(LR0ItemList items) {
430      items.Sort(ById); //Sort by ID
431      if (items.Count == 0) return "";
432      //quick shortcut
433      if (items.Count == 1 && items[0].IsKernel) 
434        return items[0].ID.ToString();
435      StringBuilder sb = new StringBuilder(1024);
436      foreach (LR0Item item in items) {
437        if (item.IsKernel) {
438          sb.Append(item.ID);
439          sb.Append(",");
440        }
441      }//foreach
442      return sb.ToString();
443    }
444    private static int ById(LR0Item x, LR0Item y) {
445      if (x.ID < y.ID) return -1;
446      if (x.ID == y.ID) return 0;
447      return 1;
448    }
449
450    #endregion
451
452    #region Lookaheads propagation
453    private void PropagateLookaheads() {
454      LRItemList currentList = new LRItemList();
455      //first collect all items
456      foreach (ParserState state in Data.States)
457        currentList.AddRange(state.Items);
458      //Main loop - propagate until done
459      while (currentList.Count > 0) {
460        LRItemList newList = new LRItemList();
461        foreach (LRItem item in currentList) {
462          if (item.NewLookaheads.Count == 0) continue;
463          int oldCount = item.Lookaheads.Count;
464          item.Lookaheads.AddRange(item.NewLookaheads);
465          if (item.Lookaheads.Count != oldCount) {
466            foreach (LRItem targetItem in item.PropagateTargets) {
467              targetItem.NewLookaheads.AddRange(item.NewLookaheads);
468              newList.Add(targetItem);
469            }//foreach targetItem
470          }//if
471          item.NewLookaheads.Clear();
472        }//foreach item
473        currentList = newList;
474      }//while         
475    }//method
476    #endregion
477
478    #region Final actions: createReduceActions
479    private void CreateReduceActions() {
480      foreach(ParserState state in Data.States) {
481        foreach (LRItem item in state.Items) {
482          //we are interested only in "dot  at the end" items
483          if (item.Core.Current != null)   continue;
484          foreach (string lookahead in item.Lookaheads) {
485            ActionRecord action;
486            if (state.Actions.TryGetValue(lookahead, out action)) 
487              action.ReduceProductions.Add(item.Core.Production);
488            else
489              state.Actions[lookahead] = new ActionRecord(lookahead, ParserActionType.Reduce, null, item.Core.Production);
490          }//foreach lookahead
491        }//foreach item
492      }// foreach state
493    } //method
494    
495    #endregion
496
497    #region Check for shift-reduce conflicts
498    private void CheckActionConflicts() {
499      StringDictionary errorTable = new StringDictionary();
500      foreach (ParserState state in Data.States) {
501        foreach (ActionRecord action in state.Actions.Values) {
502          //1. Pure shift
503          if (action.ShiftItems.Count > 0 && action.ReduceProductions.Count == 0) {
504            action.ActionType = ParserActionType.Shift; 
505            continue; 
506          }
507          //2. Pure reduce
508          if (action.ShiftItems.Count == 0 && action.ReduceProductions.Count == 1) {
509            action.ActionType = ParserActionType.Reduce; 
510            continue;
511          } 
512          //3. Shift-reduce and reduce-reduce conflicts
513          if (action.ShiftItems.Count > 0 && action.ReduceProductions.Count > 0) {
514            if (CheckConflictResolutionByPrecedence(action))   continue; 
515            if (CheckShiftHint(action))     continue;
516            if (CheckReduceHint(action))    continue; 
517            //if we are here, we couldn't resolve the conflict, so post a grammar error(actually warning)
518            AddErrorForInput(errorTable, action.Key, "Shift-reduce conflict in state {0}, reduce production: {1}",
519                state, action.ReduceProductions[0]);
520          }//Shift-reduce conflict
521
522          //4. Reduce-reduce conflicts
523          if (action.ReduceProductions.Count > 1) {
524            if (CheckReduceHint(action)) continue;
525            //if we are here, we reduce-reduce conflict, so post a grammar error
526            AddErrorForInput(errorTable, action.Key, "Reduce-reduce conflict in state {0} in productions: {1} ; {2}",
527                state, action.ReduceProductions[0], action.ReduceProductions[1]);
528          }
529        }//foreach action
530      }//foreach state
531      //copy errors to Errors collection; In errorTable keys are error messages, values are inputs for this message 
532      foreach (string msg in errorTable.Keys) {
533        _grammar.Errors.Add(msg + " on inputs: " + errorTable[msg]);
534      }
535    }//methods
536
537    private bool CheckConflictResolutionByPrecedence(ActionRecord action) {
538      SymbolTerminal opTerm = SymbolTerminal.GetSymbol(action.Key);
539      if (opTerm != null && opTerm.IsSet(TermOptions.IsOperator)) {
540        action.ActionType = ParserActionType.Operator;
541        action.ConflictResolved = true;
542        return true;
543      }
544      return false;
545    }
546    //Checks  shift items for PreferShift grammar hint. Hints are associated with a particular position
547    // inside production, which is in fact an LR0 item. The LR0 item is available thru shiftItem.Core property. 
548    // If PreferShift hint found, moves the hint-owning shiftItem to the beginning of the list and returns true.
549    private bool CheckShiftHint(ActionRecord action) {
550      foreach(LRItem shiftItem in action.ShiftItems) {
551        GrammarHint shiftHint = GetHint(shiftItem.Core.Production, shiftItem.Core.Position, HintType.PreferShift);
552        if (shiftHint != null) {
553            action.ActionType = ParserActionType.Shift;
554            action.ShiftItems.Remove(shiftItem);
555            action.ShiftItems.Insert(0, shiftItem);
556            action.ConflictResolved = true; 
557            return true;
558        }//if
559      }//foreach shiftItem
560      return false; 
561    }//method
562
563    //Checks Reduce productions of an action for a ReduceThis hint. If found, the production is moved to the beginning of the list.
564    private bool CheckReduceHint(ActionRecord action) {
565      foreach (Production prod in action.ReduceProductions) {
566        GrammarHint reduceHint = GetHint(prod, prod.RValues.Count, HintType.ReduceThis);
567        if (reduceHint != null) {
568          action.ReduceProductions.Remove(prod);
569          action.ReduceProductions.Insert(0, prod);
570          action.ActionType = ParserActionType.Reduce;
571          action.ConflictResolved = true;
572          return true; 
573        }//if
574      }//foreach prod
575      return false; 
576    }//method
577
578    private GrammarHint GetHint(Production production, int position, HintType hintType) {
579      foreach (GrammarHint hint in production.Hints)
580        if (hint.Position == position && hint.HintType == hintType)
581          return hint;
582      return null;
583    }
584    //Aggregate error messages for different inputs (lookaheads) in errors dictionary
585    private void AddErrorForInput(StringDictionary errors, string input, string template, params object[] args) {
586      string msg = string.Format(template, args);
587      string tmpInputs;
588      errors.TryGetValue(msg, out tmpInputs);
589      errors[msg] = tmpInputs + input + " ";
590    }
591
592    #endregion
593
594    private void ValidateAll() {
595      //Check rule on all non-terminals
596      StringSet ntList = new StringSet();
597      foreach(NonTerminal nt in Data.NonTerminals) {
598        if (nt == Data.AugmentedRoot) continue; //augm root does not count
599        BnfExpressionData data = nt.Rule.Data;
600        if (data.Count == 1 && data[0].Count == 1 && data[0][0] is NonTerminal)
601          ntList.Add(nt.Name);
602      }//foreach
603      if (ntList.Count > 0) {
604        string slist =  TextUtils.Cleanup(ntList.ToString(", "));
605        AddError("Warning: Possible non-terminal duplication. The following non-terminals have rules containing a single non-terminal: \r\n {0}. \r\n" +
606         "Consider merging two non-terminals; you may need to use 'nt1 = nt2;' instead of 'nt1.Rule=nt2'.", slist);
607      }
608      //Check constructors of all nodes referenced in Non-terminals that don't use NodeCreator delegate
609      var ctorArgTypes = new Type[] {typeof(NodeArgs)};
610      foreach (NonTerminal nt in Data.NonTerminals) {
611        if (nt.NodeCreator == null && nt.NodeType != null) {
612          object ci = nt.NodeType.GetConstructor(ctorArgTypes);
613          if (ci == null)
614            AddError(
615@"AST Node class {0} referenced by non-terminal {1} does not have a constructor for automatic node creation. 
616Provide a constructor with a single NodeArgs parameter, or use NodeCreator delegate property in NonTerminal.", 
617  nt.NodeType, nt.Name);
618
619        }//if
620      }//foreach ntInfo
621
622    }//method
623
624    #region error handling: AddError
625    private void AddError(string message, params object[] args) {
626      if (args != null && args.Length > 0)
627        message = string.Format(message, args);
628      _grammar.Errors.Add(message);
629    }
630    #endregion
631
632  }//class
633
634
635}//namespace