mirror of
https://github.com/crskycode/GARbro.git
synced 2024-11-30 17:05:37 +08:00
876 lines
34 KiB
C#
876 lines
34 KiB
C#
|
// Gardens Point Parser Generator
|
||
|
// Copyright (c) Wayne Kelly, QUT 2005-2014
|
||
|
// (see accompanying GPPGcopyright.rtf)
|
||
|
|
||
|
using System;
|
||
|
using System.Text;
|
||
|
using System.Globalization;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Runtime.Serialization;
|
||
|
using System.Diagnostics.CodeAnalysis;
|
||
|
|
||
|
namespace QUT.Gppg {
|
||
|
/// <summary>
|
||
|
/// Abstract class for GPPG shift-reduce parsers.
|
||
|
/// Parsers generated by GPPG derive from this base
|
||
|
/// class, overriding the abstract Initialize() and
|
||
|
/// DoAction() methods.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="TValue">Semantic value type</typeparam>
|
||
|
/// <typeparam name="TSpan">Location type</typeparam>
|
||
|
#if EXPORT_GPPG
|
||
|
public abstract class ShiftReduceParser<TValue, TSpan>
|
||
|
#else
|
||
|
internal abstract class ShiftReduceParser<TValue, TSpan>
|
||
|
#endif
|
||
|
where TSpan : IMerge<TSpan>, new() {
|
||
|
private AbstractScanner<TValue, TSpan> scanner;
|
||
|
/// <summary>
|
||
|
/// The abstract scanner for this parser.
|
||
|
/// </summary>
|
||
|
protected AbstractScanner<TValue, TSpan> Scanner {
|
||
|
get { return scanner; }
|
||
|
set { scanner = value; }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor for base class
|
||
|
/// </summary>
|
||
|
/// <param name="scanner">Scanner instance for this parser</param>
|
||
|
protected ShiftReduceParser( AbstractScanner<TValue, TSpan> 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. In 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.
|
||
|
// ==============================================================
|
||
|
/// <summary>
|
||
|
/// The current value of the "$$" symbolic variable in the parser
|
||
|
/// </summary>
|
||
|
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
|
||
|
protected TValue CurrentSemanticValue;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current value of the "@$" symbolic variable in the parser
|
||
|
/// </summary>
|
||
|
[SuppressMessage( "Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields" )]
|
||
|
protected TSpan CurrentLocationSpan;
|
||
|
protected int NextToken;
|
||
|
|
||
|
private TSpan LastSpan;
|
||
|
private State FsaState;
|
||
|
private bool recovering;
|
||
|
private int tokensSinceLastError;
|
||
|
|
||
|
private PushdownPrefixState<State> StateStack = new PushdownPrefixState<State>();
|
||
|
private PushdownPrefixState<TValue> valueStack = new PushdownPrefixState<TValue>();
|
||
|
private PushdownPrefixState<TSpan> locationStack = new PushdownPrefixState<TSpan>();
|
||
|
|
||
|
/// <summary>
|
||
|
/// The stack of semantic value (YYSTYPE) values.
|
||
|
/// </summary>
|
||
|
protected PushdownPrefixState<TValue> ValueStack { get { return valueStack; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The stack of location value (YYLTYPE) varlues.
|
||
|
/// </summary>
|
||
|
protected PushdownPrefixState<TSpan> LocationStack { get { return locationStack; } }
|
||
|
|
||
|
private int errorToken;
|
||
|
private int endOfFileToken;
|
||
|
private string[] nonTerminals;
|
||
|
private State[] states;
|
||
|
private Rule[] rules;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialization method to allow derived classes
|
||
|
/// to insert the rule list into this base class.
|
||
|
/// </summary>
|
||
|
/// <param name="rules">The array of Rule objects</param>
|
||
|
protected void InitRules( Rule[] rules ) { this.rules = rules; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialization method to allow derived classes to
|
||
|
/// insert the states table into this base class.
|
||
|
/// </summary>
|
||
|
/// <param name="states">The pre-initialized states table</param>
|
||
|
protected void InitStates( State[] states ) { this.states = states; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// OBSOLETE FOR VERSION 1.4.0
|
||
|
/// </summary>
|
||
|
/// <param name="size"></param>
|
||
|
protected void InitStateTable( int size ) { states = new State[size]; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialization method to allow derived classes
|
||
|
/// to insert the special value for the error and EOF tokens.
|
||
|
/// </summary>
|
||
|
/// <param name="err">The error state ordinal</param>
|
||
|
/// <param name="end">The EOF stat ordinal</param>
|
||
|
protected void InitSpecialTokens( int err, int end ) {
|
||
|
errorToken = err;
|
||
|
endOfFileToken = end;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialization method to allow derived classes to
|
||
|
/// insert the non-terminal symbol names into this base class.
|
||
|
/// </summary>
|
||
|
/// <param name="names">Non-terminal symbol names</param>
|
||
|
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.
|
||
|
|
||
|
/// <summary>
|
||
|
/// Force parser to terminate, returning "true"
|
||
|
/// </summary>
|
||
|
protected static void YYAccept() { throw new AcceptException(); }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Force parser to terminate, returning "false"
|
||
|
/// </summary>
|
||
|
protected static void YYAbort() { throw new AbortException(); }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Force parser to terminate, returning
|
||
|
/// "false" if error recovery fails.
|
||
|
/// </summary>
|
||
|
protected static void YYError() { throw new ErrorException(); }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Check if parser in error recovery state.
|
||
|
/// </summary>
|
||
|
protected bool YYRecovering { get { return recovering; } }
|
||
|
#endregion
|
||
|
|
||
|
/// <summary>
|
||
|
/// Abstract base method. ShiftReduceParser calls this
|
||
|
/// to initialize the base class data structures. Concrete
|
||
|
/// parser classes must override this method.
|
||
|
/// </summary>
|
||
|
protected abstract void Initialize();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Main entry point of the Shift-Reduce Parser.
|
||
|
/// </summary>
|
||
|
/// <returns>True if parse succeeds, else false for
|
||
|
/// unrecoverable errors</returns>
|
||
|
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);
|
||
|
DisplayStack();
|
||
|
#endif
|
||
|
int action = FsaState.defaultAction;
|
||
|
|
||
|
if (FsaState.ParserTable != null) {
|
||
|
if (NextToken == 0) {
|
||
|
// 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( "Reading: Next token is {0}", TerminalToString( NextToken ) );
|
||
|
#endif
|
||
|
}
|
||
|
#if TRACE_ACTIONS
|
||
|
else
|
||
|
Console.Error.WriteLine( "Next token is still {0}", TerminalToString( NextToken ) );
|
||
|
#endif
|
||
|
if (FsaState.ParserTable.ContainsKey( NextToken ))
|
||
|
action = FsaState.ParserTable[NextToken];
|
||
|
}
|
||
|
|
||
|
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];
|
||
|
int rhLen = rule.RightHandSide.Length;
|
||
|
//
|
||
|
// Default actions for unit productions.
|
||
|
//
|
||
|
if (rhLen == 1) {
|
||
|
CurrentSemanticValue = valueStack.TopElement(); // Default action: $$ = $1;
|
||
|
CurrentLocationSpan = LocationStack.TopElement(); // Default action "@$ = @1;
|
||
|
}
|
||
|
else if (rhLen == 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[LocationStack.Depth - rhLen];
|
||
|
// Default action "@$ = @1.Merge(@N)" for location info.
|
||
|
TSpan at1 = LocationStack[LocationStack.Depth - rhLen];
|
||
|
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();
|
||
|
}
|
||
|
FsaState = StateStack.TopElement();
|
||
|
|
||
|
if (FsaState.Goto.ContainsKey( rule.LeftHandSide ))
|
||
|
FsaState = states[FsaState.Goto[rule.LeftHandSide]];
|
||
|
|
||
|
StateStack.Push( FsaState );
|
||
|
valueStack.Push( CurrentSemanticValue );
|
||
|
LocationStack.Push( CurrentLocationSpan );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Execute the selected action from array.
|
||
|
/// Must be overriden in derived classes.
|
||
|
/// </summary>
|
||
|
/// <param name="actionNumber">Index of the action to perform</param>
|
||
|
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 ReportError() {
|
||
|
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 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.WriteLine("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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Traditional YACC method. Discards the next input token.
|
||
|
/// </summary>
|
||
|
[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; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Tradional YACC method. Clear the "recovering" flag.
|
||
|
/// </summary>
|
||
|
[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; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// OBSOLETE FOR VERSION 1.4.0
|
||
|
/// Method used by derived types to insert new
|
||
|
/// state instances in the "states" array.
|
||
|
/// </summary>
|
||
|
/// <param name="stateNumber">index of the state</param>
|
||
|
/// <param name="state">data for the state</param>
|
||
|
protected void AddState( int stateNumber, State state ) {
|
||
|
states[stateNumber] = state;
|
||
|
state.number = stateNumber;
|
||
|
}
|
||
|
|
||
|
private void DisplayStack() {
|
||
|
Console.Error.Write( "State stack is now:" );
|
||
|
for (int i = 0; i < StateStack.Depth; i++)
|
||
|
Console.Error.Write( " {0}", StateStack[i].number );
|
||
|
Console.Error.WriteLine();
|
||
|
}
|
||
|
|
||
|
private 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 ) );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Abstract state class naming terminal symbols.
|
||
|
/// This is overridden by derived classes with the
|
||
|
/// name (or alias) to be used in error messages.
|
||
|
/// </summary>
|
||
|
/// <param name="terminal">The terminal ordinal</param>
|
||
|
/// <returns></returns>
|
||
|
protected abstract string TerminalToString( int terminal );
|
||
|
|
||
|
private string SymbolToString( int symbol ) {
|
||
|
if (symbol < 0)
|
||
|
return nonTerminals[-symbol - 1];
|
||
|
else
|
||
|
return TerminalToString( symbol );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Return text representation of argument character
|
||
|
/// </summary>
|
||
|
/// <param name="input">The character to convert</param>
|
||
|
/// <returns>String representation of the character</returns>
|
||
|
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 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="TSpan">The Location type</typeparam>
|
||
|
#if EXPORT_GPPG
|
||
|
public interface IMerge<TSpan>
|
||
|
#else
|
||
|
internal interface IMerge<TSpan>
|
||
|
#endif
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="last">The lexically last object to merge</param>
|
||
|
/// <returns>The merged location object</returns>
|
||
|
TSpan Merge( TSpan last );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
#if EXPORT_GPPG
|
||
|
public class LexLocation : IMerge<LexLocation>
|
||
|
#else
|
||
|
[SuppressMessage( "Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses" )]
|
||
|
internal class LexLocation : IMerge<LexLocation>
|
||
|
#endif
|
||
|
{
|
||
|
private int startLine; // start line
|
||
|
private int startColumn; // start column
|
||
|
private int endLine; // end line
|
||
|
private int endColumn; // end column
|
||
|
|
||
|
/// <summary>
|
||
|
/// The line at which the text span starts.
|
||
|
/// </summary>
|
||
|
public int StartLine { get { return startLine; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The column at which the text span starts.
|
||
|
/// </summary>
|
||
|
public int StartColumn { get { return startColumn; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The line on which the text span ends.
|
||
|
/// </summary>
|
||
|
public int EndLine { get { return endLine; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The column of the first character
|
||
|
/// beyond the end of the text span.
|
||
|
/// </summary>
|
||
|
public int EndColumn { get { return endColumn; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Default no-arg constructor.
|
||
|
/// </summary>
|
||
|
public LexLocation() { }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Constructor for text-span with given start and end.
|
||
|
/// </summary>
|
||
|
/// <param name="sl">start line</param>
|
||
|
/// <param name="sc">start column</param>
|
||
|
/// <param name="el">end line </param>
|
||
|
/// <param name="ec">end column</param>
|
||
|
public LexLocation( int sl, int sc, int el, int ec ) { startLine = sl; startColumn = sc; endLine = el; endColumn = ec; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Create a text location which spans from the
|
||
|
/// start of "this" to the end of the argument "last"
|
||
|
/// </summary>
|
||
|
/// <param name="last">The last location in the result span</param>
|
||
|
/// <returns>The merged span</returns>
|
||
|
public LexLocation Merge( LexLocation last ) { return new LexLocation( this.startLine, this.startColumn, last.endLine, last.endColumn ); }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Abstract scanner class that GPPG expects its scanners to
|
||
|
/// extend.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="TValue">Semantic value type YYSTYPE</typeparam>
|
||
|
/// <typeparam name="TSpan">Source location type YYLTYPE</typeparam>
|
||
|
#if EXPORT_GPPG
|
||
|
public abstract class AbstractScanner<TValue, TSpan>
|
||
|
#else
|
||
|
internal abstract class AbstractScanner<TValue, TSpan>
|
||
|
#endif
|
||
|
where TSpan : IMerge<TSpan> {
|
||
|
/// <summary>
|
||
|
/// Lexical value optionally set by the scanner. The value
|
||
|
/// is of the %YYSTYPE type declared in the parser spec.
|
||
|
/// </summary>
|
||
|
[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.
|
||
|
#pragma warning disable 649
|
||
|
public TValue yylval; // Lexical value: set by scanner
|
||
|
#pragma warning restore 649
|
||
|
|
||
|
/// <summary>
|
||
|
/// Current scanner location property. The value is of the
|
||
|
/// type declared by %YYLTYPE in the parser specification.
|
||
|
/// </summary>
|
||
|
[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.
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Main call point for LEX-like scanners. Returns an int
|
||
|
/// corresponding to the token recognized by the scanner.
|
||
|
/// </summary>
|
||
|
/// <returns>An int corresponding to the token</returns>
|
||
|
[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();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Traditional error reporting provided by LEX-like scanners
|
||
|
/// to their YACC-like clients.
|
||
|
/// </summary>
|
||
|
/// <param name="format">Message format string</param>
|
||
|
/// <param name="args">Optional array of args</param>
|
||
|
[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 ) { }
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Encapsulated state for the parser.
|
||
|
/// Opaque to users, visible to the tool-generated code.
|
||
|
/// </summary>
|
||
|
#if EXPORT_GPPG
|
||
|
public class State
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The number of states in the automaton.
|
||
|
/// </summary>
|
||
|
public int number;
|
||
|
#else
|
||
|
internal class State {
|
||
|
/// <summary>
|
||
|
/// The index of this state in the states array.
|
||
|
/// </summary>
|
||
|
internal int number;
|
||
|
#endif
|
||
|
internal Dictionary<int, int> ParserTable; // Terminal -> ParseAction
|
||
|
internal Dictionary<int, int> Goto; // NonTerminal -> State;
|
||
|
internal int defaultAction; // = 0; // ParseAction
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="actions">The action list</param>c
|
||
|
/// <param name="goToList">Next state data</param>
|
||
|
public State( int[] actions, int[] goToList )
|
||
|
: this( actions ) {
|
||
|
Goto = new Dictionary<int, int>();
|
||
|
for (int i = 0; i < goToList.Length; i += 2)
|
||
|
Goto.Add( goToList[i], goToList[i + 1] );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <param name="actions">The action array</param>
|
||
|
public State( int[] actions ) {
|
||
|
ParserTable = new Dictionary<int, int>();
|
||
|
for (int i = 0; i < actions.Length; i += 2)
|
||
|
ParserTable.Add( actions[i], actions[i + 1] );
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Set the default action for this state.
|
||
|
/// </summary>
|
||
|
/// <param name="defaultAction">Ordinal of the default action</param>
|
||
|
public State( int defaultAction ) {
|
||
|
this.defaultAction = defaultAction;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Set the default action and the state transition table.
|
||
|
/// </summary>
|
||
|
/// <param name="defaultAction">The default action</param>
|
||
|
/// <param name="goToList">Transitions from this state</param>
|
||
|
public State( int defaultAction, int[] goToList )
|
||
|
: this( defaultAction ) {
|
||
|
Goto = new Dictionary<int, int>();
|
||
|
for (int i = 0; i < goToList.Length; i += 2)
|
||
|
Goto.Add( goToList[i], goToList[i + 1] );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Rule representation at runtime.
|
||
|
/// </summary>
|
||
|
#if EXPORT_GPPG
|
||
|
public class Rule
|
||
|
#else
|
||
|
internal class Rule
|
||
|
#endif
|
||
|
{
|
||
|
internal int LeftHandSide; // symbol
|
||
|
internal int[] RightHandSide; // symbols
|
||
|
|
||
|
/// <summary>
|
||
|
/// Rule constructor. This holds the ordinal of
|
||
|
/// the left hand side symbol, and the list of
|
||
|
/// right hand side symbols, in lexical order.
|
||
|
/// </summary>
|
||
|
/// <param name="left">The LHS non-terminal</param>
|
||
|
/// <param name="right">The RHS symbols, in lexical order</param>
|
||
|
public Rule( int left, int[] right ) {
|
||
|
this.LeftHandSide = left;
|
||
|
this.RightHandSide = right;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T"></typeparam>
|
||
|
#if EXPORT_GPPG
|
||
|
public class PushdownPrefixState<T>
|
||
|
#else
|
||
|
internal class PushdownPrefixState<T>
|
||
|
#endif
|
||
|
{
|
||
|
// Note that we cannot use the BCL Stack<T> class
|
||
|
// here as derived types need to index into stacks.
|
||
|
//
|
||
|
private T[] array = new T[8];
|
||
|
private int tos = 0;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Indexer for values of the stack below the top.
|
||
|
/// </summary>
|
||
|
/// <param name="index">index of the element, starting from the bottom</param>
|
||
|
/// <returns>the selected element</returns>
|
||
|
public T this[int index] { get { return array[index]; } }
|
||
|
|
||
|
/// <summary>
|
||
|
/// The current depth of the stack.
|
||
|
/// </summary>
|
||
|
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; }
|
||
|
}
|
||
|
}
|