PageRenderTime 46ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Irony/Parsing/Parser/Parser.cs

#
C# | 254 lines | 181 code | 22 blank | 51 comment | 59 complexity | 7ec48d20c159eacc5d4e4a7866ea3201 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. using System;
  13. using System.Collections.Generic;
  14. using System.Diagnostics;
  15. using System.Linq;
  16. using System.Text;
  17. namespace Irony.Parsing {
  18. //Parser class represents combination of scanner and LALR parser (CoreParser)
  19. public class Parser {
  20. public readonly LanguageData Language;
  21. public readonly ParserData Data;
  22. private Grammar _grammar;
  23. //public readonly CoreParser CoreParser;
  24. public readonly Scanner Scanner;
  25. public ParsingContext Context { get; internal set; }
  26. public readonly NonTerminal Root;
  27. // Either language root or initial state for parsing snippets - like Ruby's expressions in strings : "result= #{x+y}"
  28. internal readonly ParserState InitialState;
  29. public Parser(Grammar grammar) : this (new LanguageData(grammar)) { }
  30. public Parser(LanguageData language) : this(language, null) {}
  31. public Parser(LanguageData language, NonTerminal root) {
  32. Language = language;
  33. Data = Language.ParserData;
  34. _grammar = Language.Grammar;
  35. Context = new ParsingContext(this);
  36. Scanner = new Scanner(this);
  37. Root = root;
  38. if(Root == null) {
  39. Root = Language.Grammar.Root;
  40. InitialState = Language.ParserData.InitialState;
  41. } else {
  42. if(Root != Language.Grammar.Root && !Language.Grammar.SnippetRoots.Contains(Root))
  43. throw new Exception(string.Format(Resources.ErrRootNotRegistered, root.Name));
  44. InitialState = Language.ParserData.InitialStates[Root];
  45. }
  46. }
  47. internal void Reset() {
  48. Context.Reset();
  49. Scanner.Reset();
  50. }
  51. public ParseTree Parse(string sourceText) {
  52. return Parse(sourceText, "Source");
  53. }
  54. public ParseTree Parse(string sourceText, string fileName) {
  55. SourceLocation loc = default(SourceLocation);
  56. Reset();
  57. /* if (Context.Status == ParserStatus.AcceptedPartial) {
  58. var oldLoc = Context.Source.Location;
  59. loc = new SourceLocation(oldLoc.Position, oldLoc.Line + 1, 0);
  60. } else {
  61. }*/
  62. Context.Source = new SourceStream(sourceText, this.Language.Grammar.CaseSensitive, Context.TabWidth, loc);
  63. Context.CurrentParseTree = new ParseTree(sourceText, fileName);
  64. Context.Status = ParserStatus.Parsing;
  65. var sw = new Stopwatch();
  66. sw.Start();
  67. ParseAll();
  68. //Set Parse status
  69. var parseTree = Context.CurrentParseTree;
  70. bool hasErrors = parseTree.HasErrors();
  71. if (hasErrors)
  72. parseTree.Status = ParseTreeStatus.Error;
  73. else if (Context.Status == ParserStatus.AcceptedPartial)
  74. parseTree.Status = ParseTreeStatus.Partial;
  75. else
  76. parseTree.Status = ParseTreeStatus.Parsed;
  77. //Build AST if no errors and AST flag is set
  78. bool createAst = _grammar.LanguageFlags.IsSet(LanguageFlags.CreateAst);
  79. if (createAst && !hasErrors)
  80. Language.Grammar.BuildAst(Language, parseTree);
  81. //Done; record the time
  82. sw.Stop();
  83. parseTree.ParseTimeMilliseconds = sw.ElapsedMilliseconds;
  84. if (parseTree.ParserMessages.Count > 0)
  85. parseTree.ParserMessages.Sort(LogMessageList.ByLocation);
  86. return parseTree;
  87. }
  88. private void ParseAll() {
  89. //main loop
  90. Context.Status = ParserStatus.Parsing;
  91. while (Context.Status == ParserStatus.Parsing) {
  92. ExecuteNextAction();
  93. }
  94. }//ParseAll method
  95. public ParseTree ScanOnly(string sourceText, string fileName) {
  96. Context.CurrentParseTree = new ParseTree(sourceText, fileName);
  97. Context.Source = new SourceStream(sourceText, Language.Grammar.CaseSensitive, Context.TabWidth);
  98. while (true) {
  99. var token = Scanner.GetToken();
  100. if (token == null || token.Terminal == Language.Grammar.Eof) break;
  101. }
  102. return Context.CurrentParseTree;
  103. }
  104. #region Parser Action execution
  105. private void ExecuteNextAction() {
  106. //Read input only if DefaultReduceAction is null - in this case the state does not contain ExpectedSet,
  107. // so parser cannot assist scanner when it needs to select terminal and therefore can fail
  108. if (Context.CurrentParserInput == null && Context.CurrentParserState.DefaultAction == null)
  109. ReadInput();
  110. //Check scanner error
  111. if (Context.CurrentParserInput != null && Context.CurrentParserInput.IsError) {
  112. RecoverFromError();
  113. return;
  114. }
  115. //Try getting action
  116. var action = GetNextAction();
  117. if (action == null) {
  118. if (CheckPartialInputCompleted()) return;
  119. RecoverFromError();
  120. return;
  121. }
  122. //We have action. Write trace and execute it
  123. if (Context.TracingEnabled)
  124. Context.AddTrace(action.ToString());
  125. action.Execute(Context);
  126. }
  127. internal ParserAction GetNextAction() {
  128. var currState = Context.CurrentParserState;
  129. var currInput = Context.CurrentParserInput;
  130. if (currState.DefaultAction != null)
  131. return currState.DefaultAction;
  132. ParserAction action;
  133. //First try as keyterm/key symbol; for example if token text = "while", then first try it as a keyword "while";
  134. // if this does not work, try as an identifier that happens to match a keyword but is in fact identifier
  135. Token inputToken = currInput.Token;
  136. if (inputToken != null && inputToken.KeyTerm != null) {
  137. var keyTerm = inputToken.KeyTerm;
  138. if (currState.Actions.TryGetValue(keyTerm, out action)) {
  139. #region comments
  140. // Ok, we found match as a key term (keyword or special symbol)
  141. // Backpatch the token's term. For example in most cases keywords would be recognized as Identifiers by Scanner.
  142. // Identifier would also check with SymbolTerms table and set AsSymbol field to SymbolTerminal if there exist
  143. // one for token content. So we first find action by Symbol if there is one; if we find action, then we
  144. // patch token's main terminal to AsSymbol value. This is important for recognizing keywords (for colorizing),
  145. // and for operator precedence algorithm to work when grammar uses operators like "AND", "OR", etc.
  146. //TODO: This might be not quite correct action, and we can run into trouble with some languages that have keywords that
  147. // are not reserved words. But proper implementation would require substantial addition to parser code:
  148. // when running into errors, we need to check the stack for places where we made this "interpret as Symbol"
  149. // decision, roll back the stack and try to reinterpret as identifier
  150. #endregion
  151. inputToken.SetTerminal(keyTerm);
  152. currInput.Term = keyTerm;
  153. currInput.Precedence = keyTerm.Precedence;
  154. currInput.Associativity = keyTerm.Associativity;
  155. return action;
  156. }
  157. }
  158. //Try to get by main Terminal, only if it is not the same as symbol
  159. if (currState.Actions.TryGetValue(currInput.Term, out action))
  160. return action;
  161. //If input is EOF and NewLineBeforeEof flag is set, try using NewLine to find action
  162. if (currInput.Term == _grammar.Eof && _grammar.LanguageFlags.IsSet(LanguageFlags.NewLineBeforeEOF) &&
  163. currState.Actions.TryGetValue(_grammar.NewLine, out action)) {
  164. //There's no action for EOF but there's action for NewLine. Let's add newLine token as input, just in case
  165. // action code wants to check input - it should see NewLine.
  166. var newLineToken = new Token(_grammar.NewLine, currInput.Token.Location, "\r\n", null);
  167. var newLineNode = new ParseTreeNode(newLineToken);
  168. Context.CurrentParserInput = newLineNode;
  169. return action;
  170. }//if
  171. return null;
  172. }
  173. #endregion
  174. #region reading input
  175. public void ReadInput() {
  176. Token token;
  177. Terminal term;
  178. //Get token from scanner while skipping all comment tokens (but accumulating them in comment block)
  179. do {
  180. token = Scanner.GetToken();
  181. term = token.Terminal;
  182. if (term.Category == TokenCategory.Comment)
  183. Context.CurrentCommentTokens.Add(token);
  184. } while (term.Flags.IsSet(TermFlags.IsNonGrammar) && term != _grammar.Eof);
  185. //Check brace token
  186. if (term.Flags.IsSet(TermFlags.IsBrace) && !CheckBraceToken(token))
  187. token = new Token(_grammar.SyntaxError, token.Location, token.Text,
  188. string.Format(Resources.ErrUnmatchedCloseBrace, token.Text));
  189. //Create parser input node
  190. Context.CurrentParserInput = new ParseTreeNode(token);
  191. //attach comments if any accumulated to content token
  192. if (token.Terminal.Category == TokenCategory.Content) {
  193. Context.CurrentParserInput.Comments = Context.CurrentCommentTokens;
  194. Context.CurrentCommentTokens = new TokenList();
  195. }
  196. //Fire event on Terminal
  197. token.Terminal.OnParserInputPreview(Context);
  198. }
  199. #endregion
  200. #region Error Recovery
  201. public void RecoverFromError() {
  202. this.Data.ErrorAction.Execute(Context);
  203. }
  204. #endregion
  205. #region Utilities
  206. private bool CheckPartialInputCompleted() {
  207. bool partialCompleted = (Context.Mode == ParseMode.CommandLine && Context.CurrentParserInput.Term == _grammar.Eof);
  208. if (!partialCompleted) return false;
  209. Context.Status = ParserStatus.AcceptedPartial;
  210. // clean up EOF in input so we can continue parsing next line
  211. Context.CurrentParserInput = null;
  212. return true;
  213. }
  214. // We assume here that the token is a brace (opening or closing)
  215. private bool CheckBraceToken(Token token) {
  216. if (token.Terminal.Flags.IsSet(TermFlags.IsOpenBrace)) {
  217. Context.OpenBraces.Push(token);
  218. return true;
  219. }
  220. //it is closing brace; check if we have opening brace in the stack
  221. var braces = Context.OpenBraces;
  222. var match = (braces.Count > 0 && braces.Peek().Terminal.IsPairFor == token.Terminal);
  223. if (!match) return false;
  224. //Link both tokens, pop the stack and return true
  225. var openingBrace = braces.Pop();
  226. openingBrace.OtherBrace = token;
  227. token.OtherBrace = openingBrace;
  228. return true;
  229. }
  230. #endregion
  231. }//class
  232. }//namespace