Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ParserDataBuilder.cs
Go to the documentation of this 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 
13 using System;
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.Linq;
17 using System.Text;
18 using System.Diagnostics;
19 
20 namespace Irony.Parsing.Construction {
21 
22  // Methods constructing LALR automaton.
23  // See _about_parser_construction.txt file in this folder for important comments
24 
25  internal partial class ParserDataBuilder {
26  LanguageData _language;
27  internal ParserData Data;
28  Grammar _grammar;
29  ParserStateHash _stateHash = new ParserStateHash();
30 
31  internal ParserDataBuilder(LanguageData language) {
32  _language = language;
33  _grammar = _language.Grammar;
34  }
35 
36  public void Build() {
37  _stateHash.Clear();
38  Data = _language.ParserData;
39  CreateParserStates();
40  var itemsNeedLookaheads = GetReduceItemsInInadequateState();
41  ComputeTransitions(itemsNeedLookaheads);
42  ComputeLookaheads(itemsNeedLookaheads);
43  ComputeAndResolveConflicts();
44  CreateRemainingReduceActions();
45  ComputeStatesExpectedTerminals();
46  }//method
47 
48  #region Creating parser states
49  private void CreateParserStates() {
50  var grammarData = _language.GrammarData;
51 
52  //1. Base automaton: create states for main augmented root for the grammar
53  Data.InitialState = CreateInitialState(grammarData.AugmentedRoot);
54  ExpandParserStateList(0);
55  CreateAcceptAction(Data.InitialState, grammarData.AugmentedRoot);
56 
57  //2. Expand automaton: add parser states from additional roots
58  foreach(var augmRoot in grammarData.AugmentedSnippetRoots) {
59  var initialState = CreateInitialState(augmRoot);
60  ExpandParserStateList(Data.States.Count - 1); //start with just added state - it is the last state in the list
61  CreateAcceptAction(initialState, augmRoot);
62  }
63  }
64 
65  private void CreateAcceptAction(ParserState initialState, NonTerminal augmentedRoot) {
66  var root = augmentedRoot.Productions[0].RValues[0];
67  var shiftOverRootState = initialState.Actions[root].NewState;
68  shiftOverRootState.Actions[_grammar.Eof] = new ParserAction(ParserActionType.Accept, null, null);
69  }
70 
71 
72  private ParserState CreateInitialState(NonTerminal augmentedRoot) {
73  //for an augmented root there is an initial production "Root' -> .Root"; so we need the LR0 item at 0 index
74  var iniItemSet = new LR0ItemSet();
75  iniItemSet.Add(augmentedRoot.Productions[0].LR0Items[0]);
76  var initialState = FindOrCreateState(iniItemSet);
77  var rootNt = augmentedRoot.Productions[0].RValues[0] as NonTerminal;
78  Data.InitialStates[rootNt] = initialState;
79  return initialState;
80  }
81 
82  private void ExpandParserStateList(int initialIndex) {
83  // Iterate through states (while new ones are created) and create shift transitions and new states
84  for (int index = initialIndex; index < Data.States.Count; index++) {
85  var state = Data.States[index];
86  //Get all possible shifts
87  foreach (var term in state.BuilderData.ShiftTerms) {
88  var shiftItems = state.BuilderData.ShiftItems.SelectByCurrent(term);
89  //Get set of shifted cores and find/create target state
90  var shiftedCoreItems = shiftItems.GetShiftedCores();
91  var newState = FindOrCreateState(shiftedCoreItems);
92  //Create shift action
93  var newAction = new ParserAction(ParserActionType.Shift, newState, null);
94  state.Actions[term] = newAction;
95  //Link items in old/new states
96  foreach (var shiftItem in shiftItems) {
97  shiftItem.ShiftedItem = newState.BuilderData.AllItems.FindByCore(shiftItem.Core.ShiftedItem);
98  }//foreach shiftItem
99  }//foreach term
100  } //for index
101  }//method
102 
103  private ParserState FindOrCreateState(LR0ItemSet coreItems) {
104  string key = ComputeLR0ItemSetKey(coreItems);
105  ParserState state;
106  if (_stateHash.TryGetValue(key, out state))
107  return state;
108  //create new state
109  state = new ParserState("S" + Data.States.Count);
110  state.BuilderData = new ParserStateData(state, coreItems);
111  Data.States.Add(state);
112  _stateHash[key] = state;
113  return state;
114  }
115 
116  #endregion
117 
118  #region Compute transitions, lookbacks, lookaheads
119  //We compute only transitions that are really needed to compute lookaheads in inadequate states.
120  // We start with reduce items in inadequate state and find their lookbacks - this is initial list of transitions.
121  // Then for each transition in the list we check if it has items with nullable tails; for those items we compute
122  // lookbacks - these are new or already existing transitons - and so on, we repeat the operation until no new transitions
123  // are created.
124  private void ComputeTransitions(LRItemSet forItems) {
125  var newItemsNeedLookbacks = forItems;
126  while(newItemsNeedLookbacks.Count > 0) {
127  var newTransitions = CreateLookbackTransitions(newItemsNeedLookbacks);
128  newItemsNeedLookbacks = SelectNewItemsThatNeedLookback(newTransitions);
129  }
130  }
131 
132  private LRItemSet SelectNewItemsThatNeedLookback(TransitionList transitions) {
133  //Select items with nullable tails that don't have lookbacks yet
134  var items = new LRItemSet();
135  foreach(var trans in transitions)
136  foreach(var item in trans.Items)
137  if (item.Core.TailIsNullable && item.Lookbacks.Count == 0) //only if it does not have lookbacks yet
138  items.Add(item);
139  return items;
140  }
141 
142  private LRItemSet GetReduceItemsInInadequateState() {
143  var result = new LRItemSet();
144  foreach(var state in Data.States) {
145  if (state.BuilderData.IsInadequate)
146  result.UnionWith(state.BuilderData.ReduceItems);
147  }
148  return result;
149  }
150 
151  private TransitionList CreateLookbackTransitions(LRItemSet sourceItems) {
152  var newTransitions = new TransitionList();
153  //Build set of initial cores - this is optimization for performance
154  //We need to find all initial items in all states that shift into one of sourceItems
155  // Each such initial item would have the core from the "initial" cores set that we build from source items.
156  var iniCores = new LR0ItemSet();
157  foreach(var sourceItem in sourceItems)
158  iniCores.Add(sourceItem.Core.Production.LR0Items[0]);
159  //find
160  foreach(var state in Data.States) {
161  foreach(var iniItem in state.BuilderData.InitialItems) {
162  if (!iniCores.Contains(iniItem.Core)) continue;
163  var iniItemNt = iniItem.Core.Production.LValue; // iniItem's non-terminal (left side of production)
164  Transition lookback = null; // local var for lookback - transition over iniItemNt
165  var currItem = iniItem; // iniItem is initial item for all currItem's in the shift chain.
166  while (currItem != null) {
167  if(sourceItems.Contains(currItem)) {
168  // We create transitions lazily, only when we actually need them. Check if we have iniItem's transition
169  // in local variable; if not, get it from state's transitions table; if not found, create it.
170  if(lookback == null && !state.BuilderData.Transitions.TryGetValue(iniItemNt, out lookback)) {
171  lookback = new Transition(state, iniItemNt);
172  newTransitions.Add(lookback);
173  }
174  //Now for currItem, either add trans to Lookbacks, or "include" it into currItem.Transition
175  // We need lookbacks ONLY for final items; for non-Final items we need proper Include lists on transitions
176  if (currItem.Core.IsFinal)
177  currItem.Lookbacks.Add(lookback);
178  else // if (currItem.Transition != null)
179  // Note: looks like checking for currItem.Transition is redundant - currItem is either:
180  // - Final - always the case for the first run of this method;
181  // - it has a transition after the first run, due to the way we select sourceItems list
182  // in SelectNewItemsThatNeedLookback (by transitions)
183  currItem.Transition.Include(lookback);
184  }//if
185  //move to next item
186  currItem = currItem.ShiftedItem;
187  }//while
188  }//foreach iniItem
189  }//foreach state
190  return newTransitions;
191  }
192 
193  private void ComputeLookaheads(LRItemSet forItems) {
194  foreach(var reduceItem in forItems) {
195  // Find all source states - those that contribute lookaheads
196  var sourceStates = new ParserStateSet();
197  foreach(var lookbackTrans in reduceItem.Lookbacks) {
198  sourceStates.Add(lookbackTrans.ToState);
199  sourceStates.UnionWith(lookbackTrans.ToState.BuilderData.ReadStateSet);
200  foreach(var includeTrans in lookbackTrans.Includes) {
201  sourceStates.Add(includeTrans.ToState);
202  sourceStates.UnionWith(includeTrans.ToState.BuilderData.ReadStateSet);
203  }//foreach includeTrans
204  }//foreach lookbackTrans
205  //Now merge all shift terminals from all source states
206  foreach(var state in sourceStates)
207  reduceItem.Lookaheads.UnionWith(state.BuilderData.ShiftTerminals);
208  //Remove SyntaxError - it is pseudo terminal
209  if (reduceItem.Lookaheads.Contains(_grammar.SyntaxError))
210  reduceItem.Lookaheads.Remove(_grammar.SyntaxError);
211  //Sanity check
212  if (reduceItem.Lookaheads.Count == 0)
213  _language.Errors.Add(GrammarErrorLevel.InternalError, reduceItem.State, "Reduce item '{0}' in state {1} has no lookaheads.", reduceItem.Core, reduceItem.State);
214  }//foreach reduceItem
215  }//method
216 
217  #endregion
218 
219  #region Analyzing and resolving conflicts
220  private void ComputeAndResolveConflicts() {
221  foreach(var state in Data.States) {
222  if(!state.BuilderData.IsInadequate)
223  continue;
224  //first detect conflicts
225  var stateData = state.BuilderData;
226  stateData.Conflicts.Clear();
227  var allLkhds = new BnfTermSet();
228  //reduce/reduce --------------------------------------------------------------------------------------
229  foreach(var item in stateData.ReduceItems) {
230  foreach(var lkh in item.Lookaheads) {
231  if(allLkhds.Contains(lkh)) {
232  state.BuilderData.Conflicts.Add(lkh);
233  }
234  allLkhds.Add(lkh);
235  }//foreach lkh
236  }//foreach item
237  //shift/reduce ---------------------------------------------------------------------------------------
238  foreach(var term in stateData.ShiftTerminals)
239  if(allLkhds.Contains(term)) {
240  stateData.Conflicts.Add(term);
241  }
242 
243  //Now resolve conflicts by hints and precedence -------------------------------------------------------
244  if(stateData.Conflicts.Count > 0) {
245  //Hints
246  foreach (var conflict in stateData.Conflicts)
247  ResolveConflictByHints(state, conflict);
248  stateData.Conflicts.ExceptWith(state.BuilderData.ResolvedConflicts);
249  //Precedence
250  foreach (var conflict in stateData.Conflicts)
251  ResolveConflictByPrecedence(state, conflict);
252  stateData.Conflicts.ExceptWith(state.BuilderData.ResolvedConflicts);
253  //if we still have conflicts, report and assign default action
254  if (stateData.Conflicts.Count > 0)
255  ReportAndCreateDefaultActionsForConflicts(state);
256  }//if Conflicts.Count > 0
257  }
258  }//method
259 
260  private void ResolveConflictByHints(ParserState state, Terminal conflict) {
261  var stateData = state.BuilderData;
262 
263  //reduce hints
264  var reduceItems = stateData.ReduceItems.SelectByLookahead(conflict);
265  foreach(var reduceItem in reduceItems)
266  if(reduceItem.Core.Hints.Find(h => h.HintType == HintType.ResolveToReduce) != null) {
267  state.Actions[conflict] = new ParserAction(ParserActionType.Reduce, null, reduceItem.Core.Production);
268  state.BuilderData.ResolvedConflicts.Add(conflict);
269  return;
270  }
271 
272  //shift hints
273  var shiftItems = stateData.ShiftItems.SelectByCurrent(conflict);
274  foreach (var shiftItem in shiftItems)
275  if(shiftItem.Core.Hints.Find(h => h.HintType == HintType.ResolveToShift) != null) {
276  //shift action is already there
277  state.BuilderData.ResolvedConflicts.Add(conflict);
278  return;
279  }
280 
281  //code hints
282  // first prepare data for conflict action: reduceProduction (for possible reduce) and newState (for possible shift)
283  var reduceProduction = reduceItems.First().Core.Production; //take first of reduce productions
284  ParserState newState = (state.Actions.ContainsKey(conflict) ? state.Actions[conflict].NewState : null);
285  // Get all items that might contain hints;
286  var allItems = new LRItemList();
287  allItems.AddRange(state.BuilderData.ShiftItems.SelectByCurrent(conflict));
288  allItems.AddRange(state.BuilderData.ReduceItems.SelectByLookahead(conflict));
289  // Scan all items and try to find hint with resolution type Code
290  foreach (var item in allItems) {
291  if (item.Core.Hints.Find(h => h.HintType == HintType.ResolveInCode) != null) {
292  state.Actions[conflict] = new ParserAction(ParserActionType.Code, newState, reduceProduction);
293  state.BuilderData.ResolvedConflicts.Add(conflict);
294  return;
295  }
296  }
297 
298  //custom hints
299  // find custom grammar hints and build custom conflict resolver
300  var customHints = new List<CustomGrammarHint>();
301  var hintItems = new Dictionary<CustomGrammarHint, LRItem>();
302  LRItem defaultItem = null; // the first item with no hints
303  foreach (var item in allItems) {
304  var hints = item.Core.Hints.OfType<CustomGrammarHint>();
305  foreach (var hint in hints) {
306  customHints.Add(hint);
307  hintItems[hint] = item;
308  }
309  if (defaultItem == null && !hints.Any())
310  defaultItem = item;
311  }
312  // if there are custom hints, build conflict resolver
313  if (customHints.Count > 0) {
314  state.Actions[conflict] = new ParserAction(newState, reduceProduction, args => {
315  // examine all custom hints and select the first production that matched
316  foreach (var customHint in customHints) {
317  if (customHint.Match(args)) {
318  var item = hintItems[customHint];
319  // If the ReduceProduction was clear, then use the default production following the hints
320  if (args.ReduceProduction == null)
321  args.ReduceProduction = item.Core.Production;
322  return;
323  }
324  }
325  // no hints matched, select default LRItem
326  if (defaultItem != null) {
327  args.ReduceProduction = defaultItem.Core.Production;
328  args.Result = args.NewShiftState != null ? ParserActionType.Shift : ParserActionType.Reduce;
329  return;
330  }
331  // prefer Reduce if Shift operation is not available
332  args.Result = args.NewShiftState != null ? ParserActionType.Shift : ParserActionType.Reduce;
333  // TODO: figure out what to do next
334  });
335  state.BuilderData.ResolvedConflicts.Add(conflict);
336  return;
337  }
338  }
339 
340  private void ResolveConflictByPrecedence(ParserState state, Terminal conflict) {
341  if (!conflict.FlagIsSet(TermFlags.IsOperator)) return;
342  var stateData = state.BuilderData;
343  if (!stateData.ShiftTerminals.Contains(conflict)) return; //it is not shift-reduce
344  var shiftAction = state.Actions[conflict];
345  //now get shift items for the conflict
346  var shiftItems = stateData.ShiftItems.SelectByCurrent(conflict);
347  //get reduce item
348  var reduceItems = stateData.ReduceItems.SelectByLookahead(conflict);
349  if (reduceItems.Count > 1) return; // if it is reduce-reduce conflict, we cannot fix it by precedence
350  var reduceItem = reduceItems.First();
351  shiftAction.ChangeToOperatorAction(reduceItem.Core.Production);
352  stateData.ResolvedConflicts.Add(conflict);
353  }//method
354 
355  //Resolve to default actions
356  private void ReportAndCreateDefaultActionsForConflicts(ParserState state) {
357  var shiftReduceConflicts = state.BuilderData.GetShiftReduceConflicts();
358  var reduceReduceConflicts = state.BuilderData.GetReduceReduceConflicts();
359  var stateData = state.BuilderData;
360  if (shiftReduceConflicts.Count > 0)
361  _language.Errors.Add(GrammarErrorLevel.Conflict, state, Resources.ErrSRConflict, state, shiftReduceConflicts.ToString());
362  if (reduceReduceConflicts.Count > 0)
363  _language.Errors.Add(GrammarErrorLevel.Conflict, state, Resources.ErrRRConflict, state, reduceReduceConflicts.ToString());
364  //Create default actions for these conflicts. For shift-reduce, default action is shift, and shift action already
365  // exist for all shifts from the state, so we don't need to do anything, only report it
366  //For reduce-reduce create reduce actions for the first reduce item (whatever comes first in the set).
367  foreach (var conflict in reduceReduceConflicts) {
368  var reduceItems = stateData.ReduceItems.SelectByLookahead(conflict);
369  var firstProd = reduceItems.First().Core.Production;
370  var action = new ParserAction(ParserActionType.Reduce, null, firstProd);
371  state.Actions[conflict] = action;
372  }
373  //Update ResolvedConflicts and Conflicts sets
374  stateData.ResolvedConflicts.UnionWith(shiftReduceConflicts);
375  stateData.ResolvedConflicts.UnionWith(reduceReduceConflicts);
376  stateData.Conflicts.ExceptWith(stateData.ResolvedConflicts);
377  }
378 
379  #endregion
380 
381  #region final actions: creating remaining reduce actions, computing expected terminals, cleaning up state data
382  private void CreateRemainingReduceActions() {
383  foreach (var state in Data.States) {
384  var stateData = state.BuilderData;
385  if (stateData.ShiftItems.Count == 0 && stateData.ReduceItems.Count == 1) {
386  state.DefaultAction = new ParserAction(ParserActionType.Reduce, null, stateData.ReduceItems.First().Core.Production);
387  continue;
388  }
389  //now create actions
390  foreach (var item in state.BuilderData.ReduceItems) {
391  var action = new ParserAction(ParserActionType.Reduce, null, item.Core.Production);
392  foreach (var lkh in item.Lookaheads) {
393  if (state.Actions.ContainsKey(lkh)) continue;
394  state.Actions[lkh] = action;
395  }
396  }//foreach item
397  }//foreach state
398  }
399 
400  //Note that for states with a single reduce item the result is empty
401  private void ComputeStatesExpectedTerminals() {
402  foreach (var state in Data.States) {
403  state.ExpectedTerminals.UnionWith(state.BuilderData.ShiftTerminals);
404  //Add lookaheads from reduce items
405  foreach (var reduceItem in state.BuilderData.ReduceItems)
406  state.ExpectedTerminals.UnionWith(reduceItem.Lookaheads);
407  RemoveTerminals(state.ExpectedTerminals, _grammar.SyntaxError, _grammar.Eof);
408  }//foreach state
409  }
410 
411  private void RemoveTerminals(TerminalSet terms, params Terminal[] termsToRemove) {
412  foreach(var termToRemove in termsToRemove)
413  if (terms.Contains(termToRemove)) terms.Remove(termToRemove);
414  }
415 
416  public void CleanupStateData() {
417  foreach (var state in Data.States)
418  state.ClearData();
419  }
420  #endregion
421 
422  #region Utilities: ComputeLR0ItemSetKey
423  //Parser states are distinguished by the subset of kernel LR0 items.
424  // So when we derive new LR0-item list by shift operation,
425  // we need to find out if we have already a state with the same LR0Item list.
426  // We do it by looking up in a state hash by a key - [LR0 item list key].
427  // Each list's key is a concatenation of items' IDs separated by ','.
428  // Before producing the key for a list, the list must be sorted;
429  // thus we garantee one-to-one correspondence between LR0Item sets and keys.
430  // And of course, we count only kernel items (with dot NOT in the first position).
431  public static string ComputeLR0ItemSetKey(LR0ItemSet items) {
432  if (items.Count == 0) return string.Empty;
433  //Copy non-initial items to separate list, and then sort it
434  LR0ItemList itemList = new LR0ItemList();
435  foreach (var item in items)
436  itemList.Add(item);
437  //quick shortcut
438  if (itemList.Count == 1)
439  return itemList[0].ID.ToString();
440  itemList.Sort(CompareLR0Items); //Sort by ID
441  //now build the key
442  StringBuilder sb = new StringBuilder(100);
443  foreach (LR0Item item in itemList) {
444  sb.Append(item.ID);
445  sb.Append(",");
446  }//foreach
447  return sb.ToString();
448  }
449 
450  private static int CompareLR0Items(LR0Item x, LR0Item y) {
451  if (x.ID < y.ID) return -1;
452  if (x.ID == y.ID) return 0;
453  return 1;
454  }
455  #endregion
456 
457  }//class
458 
459 
460 }//namespace
461 
462 
_In_ size_t _In_ DXGI_FORMAT _In_ size_t _In_ float size_t y
Definition: DirectXTexP.h:191
static string ErrRRConflict
Looks up a localized string similar to Reduce-reduce conflict. State {0}, lookaheads: {1}...
Normal nonterminal
static string ErrSRConflict
Looks up a localized string similar to Shift-reduce conflict. State {0}, lookaheads [{1}]...