// Gardens Point Parser Generator // Copyright (c) Wayne Kelly, QUT 2005-2009 // (see accompanying GPPGcopyright.rtf) #define EXPORT_GPPG using System; using System.Text; using System.Globalization; using System.Collections.Generic; using System.Runtime.Serialization; using System.Diagnostics.CodeAnalysis; using PascalABCCompiler.SyntaxTree; using System.IO; namespace QUT.Gppg { /// /// Abstract class for GPPG shift-reduce parsers. /// Parsers generated by GPPG derive from this base /// class, overriding the abstract Initialize() and /// DoAction() methods. /// /// Semantic value type /// Location type #if EXPORT_GPPG public abstract class ShiftReduceParser #else internal abstract class ShiftReduceParser #endif where TSpan : IMerge, new() { public AbstractScanner scanner; /// /// The abstract scanner for this parser. /// protected AbstractScanner Scanner { get { return scanner; } set { scanner = value; } } /// /// Constructor for base class /// /// Scanner instance for this parser protected ShiftReduceParser(AbstractScanner scanner) { this.scanner = scanner; } // ============================================================== // TECHNICAL EXPLANATION. // Why the next two fields are not exposed via properties. // ============================================================== // These fields are of the generic parameter types, and are // frequently instantiated as struct types in derived classes. // Semantic actions are defined in the derived classes and refer // to instance fields of these structs. Is such cases the code // "get_CurrentSemanticValue().myField = blah;" will fail since // the getter pushes the value of the field, not the reference. // So, in the presence of properties, gppg would need to encode // such field accesses as ... // "tmp = get_CurrentSemanticValue(); // Fetch value // tmp.myField = blah; // update // set_CurrentSemanticValue(tmp); " // Write update back. // There is no issue if TValue is restricted to be a ref type. // The same explanation applies to scanner.yylval. // ============================================================== /// /// The current value of the "$$" symbolic variable in the parser /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] protected TValue CurrentSemanticValue; /// /// The current value of the "@$" symbolic variable in the parser /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] protected TSpan CurrentLocationSpan; private TSpan LastSpan; private int NextToken; private State FsaState; private bool recovering; private int tokensSinceLastError; private PushdownPrefixState StateStack = new PushdownPrefixState(); private PushdownPrefixState valueStack = new PushdownPrefixState(); private PushdownPrefixState locationStack = new PushdownPrefixState(); /// /// The stack of semantic value (YYSTYPE) values. /// protected PushdownPrefixState ValueStack { get { return valueStack; } } /// /// The stack of location value (YYLTYPE) varlues. /// protected PushdownPrefixState LocationStack { get { return locationStack; } } private int errorToken; private int endOfFileToken; private string[] nonTerminals; private State[] states; private Rule[] rules; /// /// Initialization method to allow derived classes /// to insert the rule list into this base class. /// /// The array of Rule objects protected void InitRules(Rule[] rules) { this.rules = rules; } /// /// Initialization method to allow derived classes to /// insert the states table into this base class. /// /// The pre-initialized states table protected void InitStates(State[] states) { this.states = states; } /// /// OBSOLETE FOR VERSION 1.4.0 /// /// protected void InitStateTable(int size) { states = new State[size]; } /// /// Initialization method to allow derived classes /// to insert the special value for the error and EOF tokens. /// /// The error state ordinal /// The EOF stat ordinal protected void InitSpecialTokens(int err, int end) { errorToken = err; endOfFileToken = end; } /// /// Initialization method to allow derived classes to /// insert the non-terminal symbol names into this base class. /// /// Non-terminal symbol names protected void InitNonTerminals(string[] names) { nonTerminals = names; } #region YYAbort, YYAccept etcetera. [Serializable] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")] // Reason for FxCop message suppression - // This exception cannot escape from the local context private class AcceptException : Exception { internal AcceptException() { } protected AcceptException(SerializationInfo i, StreamingContext c) : base(i, c) { } } [Serializable] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")] // Reason for FxCop message suppression - // This exception cannot escape from the local context private class AbortException : Exception { internal AbortException() { } protected AbortException(SerializationInfo i, StreamingContext c) : base(i, c) { } } [Serializable] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic")] // Reason for FxCop message suppression - // This exception cannot escape from the local context private class ErrorException : Exception { internal ErrorException() { } protected ErrorException(SerializationInfo i, StreamingContext c) : base(i, c) { } } // The following methods are only called from within // a semantic action. The thrown exceptions can never // propagate outside the ShiftReduceParser class in // which they are nested. /// /// Force parser to terminate, returning "true" /// protected static void YYAccept() { throw new AcceptException(); } /// /// Force parser to terminate, returning "false" /// protected static void YYAbort() { throw new AbortException(); } /// /// Force parser to terminate, returning /// "false" if error recovery fails. /// protected static void YYError() { throw new ErrorException(); } /// /// Check if parser in error recovery state. /// protected bool YYRecovering { get { return recovering; } } #endregion /// /// Abstract base method. ShiftReduceParser calls this /// to initialize the base class data structures. Concrete /// parser classes must override this method. /// protected abstract void Initialize(); /// /// Main entry point of the Shift-Reduce Parser. /// /// True if parse succeeds, else false for /// unrecoverable errors public bool Parse() { Initialize(); // allow derived classes to instantiate rules, states and nonTerminals NextToken = 0; FsaState = states[0]; StateStack.Push(FsaState); valueStack.Push(CurrentSemanticValue); LocationStack.Push(CurrentLocationSpan); while (true) { #if TRACE_ACTIONS Console.Error.WriteLine("Entering state {0} ", FsaState.number); #endif int action = FsaState.defaultAction; if (FsaState.ParserTable != null) { if (NextToken == 0) { #if TRACE_ACTIONS Console.Error.Write("Reading a token: "); #endif // We save the last token span, so that the location span // of production right hand sides that begin or end with a // nullable production will be correct. LastSpan = scanner.yylloc; NextToken = scanner.yylex(); } #if TRACE_ACTIONS Console.Error.WriteLine("Next token is {0}", TerminalToString(NextToken)); #endif int tmpAction = 0; if (FsaState.ParserTable.TryGetValue(NextToken, out tmpAction)) action = tmpAction; } if (action > 0) // shift { Shift(action); } else if (action < 0) // reduce { try { Reduce(-action); if (action == -1) // accept return true; } catch (Exception x) { if (x is AbortException) return false; else if (x is AcceptException) return true; else if (x is ErrorException && !ErrorRecovery()) return false; else throw; // Rethrow x, preserving information. } } else if (action == 0) // error if (!ErrorRecovery()) return false; } } private void Shift(int stateIndex) { #if TRACE_ACTIONS Console.Error.Write("Shifting token {0}, ", TerminalToString(NextToken)); #endif FsaState = states[stateIndex]; valueStack.Push(scanner.yylval); StateStack.Push(FsaState); LocationStack.Push(scanner.yylloc); if (recovering) { if (NextToken != errorToken) tokensSinceLastError++; if (tokensSinceLastError > 5) recovering = false; } if (NextToken != endOfFileToken) NextToken = 0; } private void Reduce(int ruleNumber) { #if TRACE_ACTIONS DisplayRule(ruleNumber); #endif Rule rule = rules[ruleNumber]; // // Default actions for unit productions. // if (rule.RightHandSide.Length == 1) { CurrentSemanticValue = valueStack.TopElement(); // Default action: $$ = $1; CurrentLocationSpan = LocationStack.TopElement(); // Default action "@$ = @1; } else { if (rule.RightHandSide.Length == 0) { // Create a new blank value. // Explicit semantic action may mutate this value CurrentSemanticValue = default(TValue); // The location span for an empty production will start with the // beginning of the next lexeme, and end with the finish of the // previous lexeme. This gives the correct behaviour when this // nonsense value is used in later Merge operations. CurrentLocationSpan = (scanner.yylloc != null && LastSpan != null ? scanner.yylloc.Merge(LastSpan) : default(TSpan)); } else { // Default action: $$ = $1; CurrentSemanticValue = valueStack.TopElement(); // Default action "@$ = @1.Merge(@N)" for location info. TSpan at1 = LocationStack[LocationStack.Depth - rule.RightHandSide.Length]; TSpan atN = LocationStack[LocationStack.Depth - 1]; CurrentLocationSpan = ((at1 != null && atN != null) ? at1.Merge(atN) : default(TSpan)); } } DoAction(ruleNumber); for (int i = 0; i < rule.RightHandSide.Length; i++) { StateStack.Pop(); valueStack.Pop(); LocationStack.Pop(); } #if TRACE_ACTIONS DisplayStack(); #endif FsaState = StateStack.TopElement(); int fsaState; if (FsaState.Goto.TryGetValue(rule.LeftHandSide, out fsaState)) FsaState = states[fsaState]; StateStack.Push(FsaState); valueStack.Push(CurrentSemanticValue); LocationStack.Push(CurrentLocationSpan); } /// /// Execute the selected action from array. /// Must be overriden in derived classes. /// /// Index of the action to perform protected abstract void DoAction(int actionNumber); private bool ErrorRecovery() { bool discard; if (!recovering) // if not recovering from previous error ReportError(); if (!FindErrorRecoveryState()) return false; // // The interim fix for the "looping in error recovery" // artifact involved moving the setting of the recovering // bool until after invalid tokens have been discarded. // ShiftErrorToken(); discard = DiscardInvalidTokens(); recovering = true; tokensSinceLastError = 0; return discard; } private void ReportError1() { StringBuilder errorMsg = new StringBuilder(); errorMsg.AppendFormat("Syntax error, unexpected {0}", TerminalToString(NextToken)); if (FsaState.ParserTable.Count < 7) { bool first = true; foreach (int terminal in FsaState.ParserTable.Keys) { if (first) errorMsg.Append(", expecting "); else errorMsg.Append(", or "); errorMsg.Append(TerminalToString(terminal)); first = false; } } scanner.yyerror(errorMsg.ToString()); } private void ReportError() { object[] args = new object[FsaState.ParserTable.Keys.Count+1]; args[0] = TerminalToString(NextToken); int i=1; foreach (int terminal in FsaState.ParserTable.Keys) { args[i] = TerminalToString(terminal); i++; } scanner.yyerror("",args); } private void ShiftErrorToken() { int old_next = NextToken; NextToken = errorToken; Shift(FsaState.ParserTable[NextToken]); #if TRACE_ACTIONS Console.Error.WriteLine("Entering state {0} ", FsaState.number); #endif NextToken = old_next; } private bool FindErrorRecoveryState() { while (true) // pop states until one found that accepts error token { if (FsaState.ParserTable != null && FsaState.ParserTable.ContainsKey(errorToken) && FsaState.ParserTable[errorToken] > 0) // shift return true; #if TRACE_ACTIONS Console.Error.WriteLine("Error: popping state {0}", StateStack.TopElement().number); #endif StateStack.Pop(); valueStack.Pop(); LocationStack.Pop(); #if TRACE_ACTIONS DisplayStack(); #endif if (StateStack.IsEmpty()) { #if TRACE_ACTIONS Console.Error.Write("Aborting: didn't find a state that accepts error token"); #endif return false; } else FsaState = StateStack.TopElement(); } } private bool DiscardInvalidTokens() { int action = FsaState.defaultAction; if (FsaState.ParserTable != null) { // Discard tokens until find one that works ... while (true) { if (NextToken == 0) { #if TRACE_ACTIONS Console.Error.Write("Reading a token: "); #endif NextToken = scanner.yylex(); } #if TRACE_ACTIONS Console.Error.WriteLine("Next token is {0}", TerminalToString(NextToken)); #endif if (NextToken == endOfFileToken) return false; if (FsaState.ParserTable.ContainsKey(NextToken)) action = FsaState.ParserTable[NextToken]; if (action != 0) return true; else { #if TRACE_ACTIONS Console.Error.WriteLine("Error: Discarding {0}", TerminalToString(NextToken)); #endif NextToken = 0; } } } else if (recovering && tokensSinceLastError == 0) { // // Boolean recovering is not set until after the first // error token has been shifted. Thus if we get back // here with recovering set and no tokens read we are // looping on the same error recovery action. This // happens if current_state.ParserTable is null because // the state has an LR(0) reduction, but not all // lookahead tokens are valid. This only occurs for // error productions that *end* on "error". // // This action discards tokens one at a time until // the looping stops. Another attack would be to always // use the LALR(1) table if a production ends on "error" // #if TRACE_ACTIONS Console.Error.WriteLine("Error: panic discard of {0}", TerminalToString(NextToken)); #endif if (NextToken == endOfFileToken) return false; NextToken = 0; return true; } else return true; } /// /// Traditional YACC method. Discards the next input token. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyclearin")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyclearin")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality protected void yyclearin() { NextToken = 0; } /// /// Tradional YACC method. Clear the "recovering" flag. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerrok")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerrok")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality protected void yyerrok() { recovering = false; } /// /// OBSOLETE FOR VERSION 1.4.0 /// Method used by derived types to insert new /// state instances in the "states" array. /// /// index of the state /// data for the state protected void AddState(int stateNumber, State state) { states[stateNumber] = state; state.number = stateNumber; } private void DisplayStack() { Console.Error.Write("State now"); for (int i = 0; i < StateStack.Depth; i++) Console.Error.Write(" {0}", StateStack[i].number); Console.Error.WriteLine(); } protected void DisplayRule(int ruleNumber) { Console.Error.Write("Reducing stack by rule {0}, ", ruleNumber); DisplayProduction(rules[ruleNumber]); } private void DisplayProduction(Rule rule) { if (rule.RightHandSide.Length == 0) Console.Error.Write("/* empty */ "); else foreach (int symbol in rule.RightHandSide) Console.Error.Write("{0} ", SymbolToString(symbol)); Console.Error.WriteLine("-> {0}", SymbolToString(rule.LeftHandSide)); } protected void DisplayRuleMy(int ruleNumber) { Console.Error.Write("{0,3}: ", ruleNumber); DisplayProductionMy(rules[ruleNumber]); } private void DisplayProductionMy(Rule rule) { Console.Error.Write("{0} ::= ", SymbolToString(rule.LeftHandSide)); if (rule.RightHandSide.Length == 0) Console.Error.Write("/* empty */ "); else foreach (int symbol in rule.RightHandSide) Console.Error.Write("{0} ", SymbolToString(symbol)); Console.Error.WriteLine(); } /// /// Abstract state class naming terminal symbols. /// This is overridden by derived classes with the /// name (or alias) to be used in error messages. /// /// The terminal ordinal /// protected abstract string TerminalToString(int terminal); private string SymbolToString(int symbol) { if (symbol < 0) return nonTerminals[-symbol-1]; else return TerminalToString(symbol); } /// /// Return text representation of argument character /// /// The character to convert /// String representation of the character protected static string CharToString(char input) { switch (input) { case '\a': return @"'\a'"; case '\b': return @"'\b'"; case '\f': return @"'\f'"; case '\n': return @"'\n'"; case '\r': return @"'\r'"; case '\t': return @"'\t'"; case '\v': return @"'\v'"; case '\0': return @"'\0'"; default: return string.Format(CultureInfo.InvariantCulture, "'{0}'", input); } } } /// /// Classes implementing this interface must supply a /// method that merges two location objects to return /// a new object of the same type. /// GPPG-generated parsers have the default location /// action equivalent to "@$ = @1.Merge(@N);" where N /// is the right-hand-side length of the production. /// /// The Location type #if EXPORT_GPPG public interface IMerge #else internal interface IMerge #endif { /// /// Interface method that creates a location object from /// the current and last object. Typically used to create /// a location object extending from the start of the @1 /// object to the end of the @N object. /// /// The lexically last object to merge /// The merged location object TSpan Merge(TSpan last); } /// /// This is the default class that carries location /// information from the scanner to the parser. /// If you don't declare "%YYLTYPE Foo" the parser /// will expect to deal with this type. /// #if EXPORT_GPPG public class LexLocation : IMerge #else [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] internal class LexLocation : IMerge #endif { private int startLine; // start line private int startColumn; // start column private int endLine; // end line private int endColumn; // end column private string filename; /// /// The line at which the text span starts. /// public int StartLine { get { return startLine; } } /// /// The column at which the text span starts. /// public int StartColumn { get { return startColumn; } } /// /// The line on which the text span ends. /// public int EndLine { get { return endLine; } } /// /// The column of the first character /// beyond the end of the text span. /// public int EndColumn { get { return endColumn; } } /// /// Default no-arg constructor. /// public LexLocation() { } /// /// Constructor for text-span with given start and end. /// /// start line /// start column /// end line /// end column public LexLocation(int sl, int sc, int el, int ec, string fname = null) { startLine = sl; startColumn = sc; endLine = el; endColumn = ec; filename = fname; } /// /// Create a text location which spans from the /// start of "this" to the end of the argument "last" /// /// The last location in the result span /// The merged span public LexLocation Merge(LexLocation last) { string n_file_name = null; if ((last.filename == null)) n_file_name = this.filename; else if ((this.filename == null)) n_file_name = last.filename; else n_file_name = this.filename; return new LexLocation(this.startLine, this.startColumn, last.endLine, last.endColumn, n_file_name); } public static implicit operator SourceContext(LexLocation loc) { if (loc == null) return null; var sc = new SourceContext(loc.startLine, loc.startColumn + 1, loc.endLine, loc.endColumn, loc.filename); return sc; } public static LexLocation MergeAll(params LexLocation[] locs) { // первая - корректная var l = locs[0]; for (int i = 1; i < locs.Length; i++) { var endLine = locs[i].endLine; var endColumn = locs[i].endColumn; if (endLine > l.endLine || (endLine == l.endLine && endColumn > l.endColumn)) { l.endLine = endLine; l.endColumn = endColumn; } } return l; } public static LexLocation MergeAll1(params LexLocation[] locs) { // сливаем все до последней корректной var ind = -1; for (int i = locs.Length - 1; i >= 0; i--) { var l = locs[i]; if (l.endLine > l.startLine || (l.endLine == l.startLine && l.endColumn >= l.startColumn)) { ind = i; break; } } return new LexLocation(locs[0].startLine, locs[0].startColumn, locs[ind].endLine, locs[ind].endColumn); ; } } /// /// Abstract scanner class that GPPG expects its scanners to /// extend. /// /// Semantic value type YYSTYPE /// Source location type YYLTYPE #if EXPORT_GPPG public abstract class AbstractScanner #else internal abstract class AbstractScanner #endif where TSpan : IMerge { /// /// Lexical value optionally set by the scanner. The value /// is of the %YYSTYPE type declared in the parser spec. /// [SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylval")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylval")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality // A field must be declared for this value of parametric type, // since it may be instantiated by a value struct. If it were // implemented as a property, machine generated code in derived // types would not be able to select on the returned value. public TValue yylval; // Lexical value: set by scanner /// /// Current scanner location property. The value is of the /// type declared by %YYLTYPE in the parser specification. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylloc")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylloc")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality public virtual TSpan yylloc { get { return default(TSpan); } // Empty implementation allowing set { /* skip */ } // yylloc to be ignored entirely. } /// /// Main call point for LEX-like scanners. Returns an int /// corresponding to the token recognized by the scanner. /// /// An int corresponding to the token [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yylex")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yylex")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality public abstract int yylex(); /// /// Traditional error reporting provided by LEX-like scanners /// to their YACC-like clients. /// /// Message format string /// Optional array of args [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "yyerror")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "yyerror")] // Reason for FxCop message suppression - // This is a traditional name for YACC-like functionality public virtual void yyerror(string format, params object[] args) { } } /// /// Encapsulated state for the parser. /// Opaque to users, visible to the tool-generated code. /// #if EXPORT_GPPG public class State #else internal class State #endif { internal int number; internal Dictionary ParserTable; // Terminal -> ParseAction internal Dictionary Goto; // NonTerminal -> State; internal int defaultAction; // = 0; // ParseAction /// /// State transition data for this state. Pairs of elements of the /// goto array associate symbol ordinals with next state indices. /// The actions array is passed to another constructor. /// /// The action list /// Next state data public State(int[] actions, int[] goToList) : this(actions) { Goto = new Dictionary(); for (int i = 0; i < goToList.Length; i += 2) Goto.Add(goToList[i], goToList[i + 1]); } /// /// Action data for this state. Pairs of elements of the /// action array associate action ordinals with each of /// those symbols that have actions in the current state. /// /// The action array public State(int[] actions) { ParserTable = new Dictionary(); for (int i = 0; i < actions.Length; i += 2) ParserTable.Add(actions[i], actions[i + 1]); } /// /// Set the default action for this state. /// /// Ordinal of the default action public State(int defaultAction) { this.defaultAction = defaultAction; } /// /// Set the default action and the state transition table. /// /// The default action /// Transitions from this state public State(int defaultAction, int[] goToList) : this(defaultAction) { Goto = new Dictionary(); for (int i = 0; i < goToList.Length; i += 2) Goto.Add(goToList[i], goToList[i + 1]); } } /// /// Rule representation at runtime. /// #if EXPORT_GPPG public class Rule #else internal class Rule #endif { internal int LeftHandSide; // symbol internal int[] RightHandSide; // symbols /// /// Rule constructor. This holds the ordinal of /// the left hand side symbol, and the list of /// right hand side symbols, in lexical order. /// /// The LHS non-terminal /// The RHS symbols, in lexical order public Rule(int left, int[] right) { this.LeftHandSide = left; this.RightHandSide = right; } } /// /// Stack utility for the shift-reduce parser. /// GPPG parsers have three instances: /// (1) The parser state stack, T = QUT.Gppg.State, /// (2) The semantic value stack, T = TValue, /// (3) The location stack, T = TSpan. /// /// #if EXPORT_GPPG public class PushdownPrefixState #else internal class PushdownPrefixState #endif { // Note that we cannot use the BCL Stack class // here as derived types need to index into stacks. // private T[] array = new T[256]; private int tos = 0; /// /// Indexer for values of the stack below the top. /// /// index of the element, starting from the bottom /// the selected element public T this[int index] { get { return array[index]; } } /// /// The current depth of the stack. /// public int Depth { get { return tos; } } internal void Push(T value) { if (tos >= array.Length) { T[] newarray = new T[array.Length * 2]; System.Array.Copy(array, newarray, tos); array = newarray; } array[tos++] = value; } internal T Pop() { T rslt = array[--tos]; array[tos] = default(T); return rslt; } internal T TopElement() { return array[tos - 1]; } internal bool IsEmpty() { return tos == 0; } } }