PageRenderTime 45ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

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