Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
CoreParser.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.Generic;
15 using System.Text;
16 using System.Collections;
17 using System.Diagnostics;
18 
19 
20 namespace Irony.Parsing {
21  // CoreParser class implements NLALR parser automaton. Its behavior is controlled by the state transition graph
22  // with root in Data.InitialState. Each state contains a dictionary of parser actions indexed by input
23  // element (terminal or non-terminal).
24  public partial class CoreParser {
25 
26  #region Constructors
27  public CoreParser(Parser parser) {
28  Parser = parser;
29  Data = parser.Language.ParserData;
30  _grammar = Data.Language.Grammar;
31  }
32  #endregion
33 
34  #region Properties and fields: Parser, Data, _grammar
35  public readonly Parser Parser;
36  public readonly ParserData Data;
37  Grammar _grammar;
38  bool _traceEnabled;
39 
40  private ParsingContext Context {
41  get { return Parser.Context; }
42  }
43  #endregion
44 
45  internal void Reset() {
46  }
47 
48  #region Parse method
49  public void Parse() {
50  //main loop
51  Context.Status = ParserStatus.Parsing;
52  while(Context.Status == ParserStatus.Parsing) {
53  ExecuteAction();
54  }
55  }//Parse
56 
57  #endregion
58 
59  #region reading input
60  private void ReadInput() {
61  if (Context.ParserInputStack.Count > 0)
62  Context.ParserInputStack.Pop();
63  if (Context.ParserInputStack.Count > 0) {
64  Context.CurrentParserInput = Context.ParserInputStack.Top;
65  return;
66  }
67  FetchToken();
68  }
69 
70  private void FetchToken() {
71  Token token;
72  //First skip all non-grammar tokens
73  do {
74  token = Parser.Scanner.GetToken();
75  } while (token.Terminal.FlagIsSet(TermFlags.IsNonGrammar) && token.Terminal != _grammar.Eof);
76  if (token.Terminal.FlagIsSet(TermFlags.IsBrace))
77  token = CheckBraceToken(token);
78  Context.CurrentParserInput = new ParseTreeNode(token);
79  Context.ParserInputStack.Push(Context.CurrentParserInput);
80  }
81  #endregion
82 
83  #region execute actions
84  private void ExecuteAction() {
85  _traceEnabled = Context.OptionIsSet(ParseOptions.TraceParser);
86  //Read input only if DefaultReduceAction is null - in this case the state does not contain ExpectedSet,
87  // so parser cannot assist scanner when it needs to select terminal and therefore can fail
88  if (Context.CurrentParserInput == null && Context.CurrentParserState.DefaultAction == null)
89  ReadInput();
90  //Check scanner error
91  if (Context.CurrentParserInput != null && Context.CurrentParserInput.IsError) {
92  ProcessParserError();
93  return;
94  }
95  //Try getting action
96  var action = FindActionForStateAndInput();
97  if (action == null) {
98  if (CheckPartialInputCompleted())
99  return;
100  ProcessParserError();
101  return;
102  }
103  //We have action. First, write trace
104  if (_traceEnabled) Context.AddTrace("{0}", action);
105  //Execute it
106  switch (action.ActionType) {
107  case ParserActionType.Shift: ExecuteShift(action); break;
108  case ParserActionType.Operator: ExecuteOperatorAction(action); break;
109  case ParserActionType.Reduce: ExecuteReduce(action); break;
110  case ParserActionType.Code: ExecuteConflictAction(action); break;
111  case ParserActionType.Accept: ExecuteAccept(action); break;
112  }
113  }
114 
115  private bool CheckPartialInputCompleted() {
116  bool partialCompleted = (Context.Mode == ParseMode.CommandLine && Context.CurrentParserInput.Term == _grammar.Eof);
117  if (!partialCompleted) return false;
118  Context.Status = ParserStatus.AcceptedPartial;
119  // clean up EOF in input so we can continue parsing next line
120  Context.CurrentParserInput = null;
121  Context.ParserInputStack.Pop();
122  return true;
123  }
124 
125 
126  private ParserAction FindActionForStateAndInput() {
127  if (Context.CurrentParserState.DefaultAction != null)
128  return Context.CurrentParserState.DefaultAction;
129  ParserAction action;
130  //First try as keyterm/key symbol; for example if token text = "while", then first try it as a keyword "while";
131  // if this does not work, try as an identifier that happens to match a keyword but is in fact identifier
132  Token inputToken = Context.CurrentParserInput.Token;
133  if (inputToken != null && inputToken.KeyTerm != null) {
134  var keyTerm = inputToken.KeyTerm;
135  if (Context.CurrentParserState.Actions.TryGetValue(keyTerm, out action)) {
136  #region comments
137  // Ok, we found match as a key term (keyword or special symbol)
138  // Backpatch the token's term. For example in most cases keywords would be recognized as Identifiers by Scanner.
139  // Identifier would also check with SymbolTerms table and set AsSymbol field to SymbolTerminal if there exist
140  // one for token content. So we first find action by Symbol if there is one; if we find action, then we
141  // patch token's main terminal to AsSymbol value. This is important for recognizing keywords (for colorizing),
142  // and for operator precedence algorithm to work when grammar uses operators like "AND", "OR", etc.
143  //TODO: This might be not quite correct action, and we can run into trouble with some languages that have keywords that
144  // are not reserved words. But proper implementation would require substantial addition to parser code:
145  // when running into errors, we need to check the stack for places where we made this "interpret as Symbol"
146  // decision, roll back the stack and try to reinterpret as identifier
147  #endregion
148  inputToken.SetTerminal(keyTerm);
149  Context.CurrentParserInput.Term = keyTerm;
150  Context.CurrentParserInput.Precedence = keyTerm.Precedence;
151  Context.CurrentParserInput.Associativity = keyTerm.Associativity;
152  return action;
153  }
154  }
155  //Try to get by main Terminal, only if it is not the same as symbol
156  if (Context.CurrentParserState.Actions.TryGetValue(Context.CurrentParserInput.Term, out action))
157  return action;
158  //If input is EOF and NewLineBeforeEof flag is set, try injecting NewLine into input
159  if(Context.CurrentParserInput.Term == _grammar.Eof && _grammar.FlagIsSet(LanguageFlags.NewLineBeforeEOF) &&
160  Context.CurrentParserState.Actions.TryGetValue(_grammar.NewLine, out action)) {
161  InjectNewLineToken();
162  return action;
163  }//if
164  return null;
165  }
166 
167  private void InjectNewLineToken() {
168  var newLineToken = new Token(_grammar.NewLine, Context.CurrentParserInput.Token.Location, "\r\n", null);
169  var newLineNode = new ParseTreeNode(newLineToken);
170  Context.ParserInputStack.Push(newLineNode);
171  Context.CurrentParserInput = newLineNode;
172  }
173 
174 
175  private void ExecuteShift(ParserAction action) {
176  Context.ParserStack.Push(Context.CurrentParserInput, action.NewState);
177  Context.CurrentParserState = action.NewState;
178  Context.CurrentParserInput = null;
179  if (action.NewState.DefaultAction == null) //read only if new state is NOT single-reduce state
180  ReadInput();
181  }
182 
183  #region ExecuteReduce
184  private void ExecuteReduce(ParserAction action) {
185  var reduceProduction = action.ReduceProduction;
186  ParseTreeNode newNode;
187  if(reduceProduction.IsSet(ProductionFlags.IsListBuilder))
188  newNode = ReduceExistingList(action);
189  else if(reduceProduction.LValue.FlagIsSet(TermFlags.IsListContainer))
190  newNode = ReduceListContainer(action);
191  else if (reduceProduction.LValue.FlagIsSet(TermFlags.IsTransient))
192  newNode = ReduceTransientNonTerminal(action);
193  else
194  newNode = ReduceRegularNode(action);
195  //final reduce actions ----------------------------------------------------------
196  Context.ParserStack.Pop(reduceProduction.RValues.Count);
197  //Push new node into stack and move to new state
198  //First read the state from top of the stack
199  Context.CurrentParserState = Context.ParserStack.Top.State;
200  if (_traceEnabled) Context.AddTrace(Resources.MsgTracePoppedState, reduceProduction.LValue.Name);
201  // Shift to new state (LALR) - execute shift over non-terminal
202  var shift = Context.CurrentParserState.Actions[reduceProduction.LValue];
203  Context.ParserStack.Push(newNode, shift.NewState);
204  Context.CurrentParserState = shift.NewState;
205  }
206 
207  private ParseTreeNode ReduceExistingList(ParserAction action) {
208  int childCount = action.ReduceProduction.RValues.Count;
209  int firstChildIndex = Context.ParserStack.Count - childCount;
210  var listNode = Context.ParserStack[firstChildIndex]; //get the list already created - it is the first child node
211  listNode.Span = ComputeNewNodeSpan(childCount);
212  var listMember = Context.ParserStack.Top; //next list member is the last child - at the top of the stack
213  if (ShouldSkipChildNode(listMember))
214  return listNode;
215  CheckCreateAstNode(listMember);
216  listNode.ChildNodes.Add(listMember);
217  return listNode;
218  }
219 
220  private bool ShouldSkipChildNode(ParseTreeNode childNode) {
221  if (childNode.Term.FlagIsSet(TermFlags.IsPunctuation))
222  return true;
223  if (childNode.Term.FlagIsSet(TermFlags.IsTransient) && childNode.ChildNodes.Count == 0)
224  return true;
225  return false;
226  }
227 
228  //List container is created by MakePlusRule, MakeStarRule with allowTrailingDelimiter = true
229  // it is a special case for parser. The "real" list in grammar is the "container", but list members had been accumulated under
230  // the transient "plus-list" which is a child of this container. So we need to copy all "grandchildren" from child to parent.
231  private ParseTreeNode ReduceListContainer(ParserAction action) {
232  int childCount = action.ReduceProduction.RValues.Count;
233  int firstChildIndex = Context.ParserStack.Count - childCount;
234  var span = ComputeNewNodeSpan(childCount);
235  var newNode = new ParseTreeNode(action.ReduceProduction, span);
236  if(childCount > 0) { //if it is not empty production - might happen for MakeStarRule
237  var listNode = Context.ParserStack[firstChildIndex]; //get the transient list with all members - it is the first child node
238  newNode.ChildNodes.AddRange(listNode.ChildNodes); //copy all list members
239  }
240  return newNode;
241  }
242 
243  private ParseTreeNode ReduceTransientNonTerminal(ParserAction action) {
244  var topIndex = Context.ParserStack.Count - 1;
245  var childCount = action.ReduceProduction.RValues.Count;
246  for(int i = 0; i < childCount; i++) {
247  var child = Context.ParserStack[topIndex - i];
248  if (ShouldSkipChildNode(child)) continue;
249  CheckCreateAstNode(child);
250  return child;
251  }
252  //Otherwise return an empty transient node; if it is part of the list, the list will skip it
253  var span = ComputeNewNodeSpan(childCount);
254  return new ParseTreeNode(action.ReduceProduction, span);
255  }
256 
257  private ParseTreeNode ReduceRegularNode(ParserAction action) {
258  var childCount = action.ReduceProduction.RValues.Count;
259  int firstChildIndex = Context.ParserStack.Count - childCount;
260  var span = ComputeNewNodeSpan(childCount);
261  var newNode = new ParseTreeNode(action.ReduceProduction, span);
262  for(int i = 0; i < childCount; i++) {
263  var childNode = Context.ParserStack[firstChildIndex + i];
264  if(ShouldSkipChildNode(childNode))
265  continue; //skip punctuation or empty transient nodes
266  CheckCreateAstNode(childNode); //AST nodes for lists and for terminals are created here
267  //For single-child reduces inherit precedence and associativity, to cover a standard case: BinOp->+|-|*|/;
268  // BinOp node should inherit precedence from underlying operator symbol
269  if(childCount == 1 && childNode.Precedence != BnfTerm.NoPrecedence) {
270  newNode.Precedence = childNode.Precedence;
271  newNode.Associativity = childNode.Associativity;
272  }
273  newNode.ChildNodes.Add(childNode);
274  }//for i
275  return newNode;
276  }
277 
278  private SourceSpan ComputeNewNodeSpan(int childCount) {
279  if(childCount == 0)
280  return new SourceSpan(Context.CurrentParserInput.Span.Location, 0);
281  var first = Context.ParserStack[Context.ParserStack.Count - childCount];
282  var last = Context.ParserStack.Top;
283  return new SourceSpan(first.Span.Location, last.Span.EndPosition - first.Span.Location.Position);
284  }
285 
286  private void CheckCreateAstNode(ParseTreeNode parseNode) {
287  try {
288  //Check preconditions
289  if ( !_grammar.FlagIsSet(LanguageFlags.CreateAst)) return;
290  if (parseNode.AstNode != null || parseNode.Term.FlagIsSet(TermFlags.IsTransient | TermFlags.NoAstNode)) return;
291  //if (Context.Status != ParserStatus.Parsing || Context.HasErrors) return;
292  if (Context.Status != ParserStatus.Parsing) return;
293  //Actually create node
294  _grammar.CreateAstNode(Context, parseNode);
295  if (parseNode.AstNode != null)
296  parseNode.Term.OnAstNodeCreated(parseNode);
297  } catch (Exception ex) {
298  Context.AddParserMessage(ParserErrorLevel.Error, parseNode.Span.Location, Resources.ErrFailedCreateNode, parseNode.Term.Name, ex.Message);
299  }
300  }
301  #endregion
302 
303  private void ExecuteConflictAction(ParserAction action) {
304  var args = action.ResolveConflict(_grammar, Context);
305  switch(args.Result) {
306  case ParserActionType.Reduce:
307  ExecuteReduce(new ParserAction(ParserActionType.Reduce, null, args.ReduceProduction));
308  break;
309  case ParserActionType.Operator:
310  ExecuteOperatorAction(new ParserAction(ParserActionType.Operator, action.NewState, args.ReduceProduction));
311  break;
312  case ParserActionType.Shift:
313  default:
314  ExecuteShift(action);
315  break;
316  }
317  if (_traceEnabled) Context.AddTrace(Resources.MsgTraceConflictResolved);
318  }
319 
320  private void ExecuteAccept(ParserAction action) {
321  var root = Context.ParserStack.Pop(); //Pop root
322  CheckCreateAstNode(root);
323  Context.CurrentParseTree.Root = root;
324  Context.Status = ParserStatus.Accepted;
325  }
326 
327  private void ExecuteOperatorAction(ParserAction action) {
328  var realActionType = GetActionTypeForOperation(action);
329  if (_traceEnabled) Context.AddTrace(Resources.MsgTraceOpResolved, realActionType);
330  switch (realActionType) {
331  case ParserActionType.Shift: ExecuteShift(action); break;
332  case ParserActionType.Reduce: ExecuteReduce(action); break;
333  }//switch
334  }
335 
336  private ParserActionType GetActionTypeForOperation(ParserAction action) {
337  for (int i = Context.ParserStack.Count - 1; i >= 0; i--) {
338  var prevNode = Context.ParserStack[i];
339  if (prevNode == null) continue;
340  if (prevNode.Precedence == BnfTerm.NoPrecedence) continue;
341  ParserActionType result;
342  //if previous operator has the same precedence then use associativity
343  var input = Context.CurrentParserInput;
344  if (prevNode.Precedence == input.Precedence)
345  result = input.Associativity == Associativity.Left ? ParserActionType.Reduce : ParserActionType.Shift;
346  else
347  result = prevNode.Precedence > input.Precedence ? ParserActionType.Reduce : ParserActionType.Shift;
348  return result;
349  }
350  //If no operators found on the stack, do simple shift
351  return ParserActionType.Shift;
352  }
353  #endregion
354 
355  #region Braces handling
356  private Token CheckBraceToken(Token token) {
357  if (token.Terminal.FlagIsSet(TermFlags.IsOpenBrace)) {
358  Context.OpenBraces.Push(token);
359  return token;
360  }
361  //it is closing brace; check if we have opening brace
362  if (Context.OpenBraces.Count == 0)
363  return CreateBraceMismatchErrorToken(token);
364  //check that braces match
365  var lastBrace = Context.OpenBraces.Peek();
366  if (lastBrace.Terminal.IsPairFor != token.Terminal)
367  return CreateBraceMismatchErrorToken(token);
368  //Link both tokens, pop the stack and return true
369  Token.LinkMatchingBraces(lastBrace, token);
370  Context.OpenBraces.Pop();
371  return token;
372  }
373 
374  private Token CreateBraceMismatchErrorToken(Token closingBrace) {
375  return new Token(_grammar.SyntaxError, closingBrace.Location, closingBrace.Text,
376  string.Format(Resources.ErrUnmatchedCloseBrace, closingBrace.Text));
377  }
378  #endregion
379 
380  }//class
381 
382 
383 
384 }//namespace
static string MsgTracePoppedState
Looks up a localized string similar to Popped state from stack, pushing {0}.
CoreParser(Parser parser)
Definition: CoreParser.cs:27
static string ErrFailedCreateNode
Looks up a localized string similar to Failed to create AST node for non-terminal [{0}]...
static string MsgTraceConflictResolved
Looks up a localized string similar to Parsing conflict resolved in code..
SiliconStudio.Shaders.Ast.SourceSpan SourceSpan
Definition: Node.cs:8
readonly Parser Parser
Definition: CoreParser.cs:35
static string MsgTraceOpResolved
Looks up a localized string similar to Operator - resolved to {0}.
readonly ParserData Data
Definition: CoreParser.cs:36