/** * LR parser for C# generated by the Syntax tool. * * https://www.npmjs.com/package/syntax-cli * * npm install -g syntax-cli * * syntax-cli --help * * To regenerate run: * * syntax-cli \ * --grammar ~/path-to-grammar-file \ * --mode \ * --output ~/ParserClassName.cs */ using System; using System.Collections.Generic; using System.Reflection; using System.Text.RegularExpressions; {{{MODULE_INCLUDE}}} namespace SyntaxParser { // -------------------------------------------- // Location object. public class YyLoc { public YyLoc() {} public int StartOffset; public int EndOffset; public int StartLine; public int EndLine; public int StartColumn; public int EndColumn; public static YyLoc yyloc(dynamic start, dynamic end) { // Epsilon doesn't produce location. if (start == null || end == null) { return start == null ? end : start; } return new YyLoc() { StartOffset = start.StartOffset, EndOffset = end.EndOffset, StartLine = start.StartLine, EndLine = end.EndLine, StartColumn = start.StartColumn, EndColumn = end.EndColumn, }; } } // -------------------------------------------- // Parser. /** * Entires on the parsing stack are: * * - an actual entry {symbol, semanticValue}, implemented by `StackEntry`. * - a state number */ public class StackEntry { public int Symbol; public object SemanticValue; public YyLoc Loc; public StackEntry(int symbol, object semanticValue, YyLoc loc) { Symbol = symbol; SemanticValue = semanticValue; Loc = loc; } } /** * SyntaxException. */ public class SyntaxException : Exception { public SyntaxException(string message) : base(message) { } } /** * Base class for the parser. Implements LR parsing algorithm. * * Should implement at least the following API: * * - parse(string stringToParse): object * - setTokenizer(Tokenizer tokenizer): void, or equivalent Tokenizer * accessor property with a setter. */ public class yyparse { public yyparse() { // A tokenizer instance, which is reused for all // `parse` method calls. The actual string is set // in the tokenizer.initString("..."). tokenizer = new Tokenizer(); // Run init hook to setup callbacks, etc. Init.run(); } /** * Encoded grammar productions table. * Format of a record: * { , , } * * Non-terminal indices are 0-Last Non-terminal. LR-algorithm uses * length of RHS to pop symbols from the stack; this length is stored * as the second element of a record. The last element is an optional * name of the semantic action handler. The first record is always * a special marker {-1, -1} entry representing an augmented production. * * Example: * * { * new object[] {-1, 1}, * new object[] {0, 3, "_handler1"}, * new object[] {0, 2, "_handler2"}, * } * */ private static object[][] mProductions = {{{PRODUCTIONS}}}; /** * Actual parsing table. An array of records, where * index is a state number, and a value is a dictionary * from an encoded symbol (number) to parsing action. * The parsing action can be "Shift/s", "Reduce/r", a state * transition number, or "Accept/acc". * * Example: * { * // 0 * new Dictionary() * { * {0, "1"}, * {3, "s8"}, * {4, "s2"}, * }, * // 1 * new Dictionary() * { * {1, "s3"}, * {2, "s4"}, * {6, "acc"}, * }, * ... * } */ private static Dictionary[] mTable = {{{TABLE}}}; /** * Parsing stack. Stores instances of StackEntry, and state numbers. */ Stack mStack = null; /** * __ holds a result value from a production * handler. In the grammar usually used as $$. */ private dynamic __ = null; /** * __loc holds a result location info. In the grammar * usually used as @$. */ private dynamic __loc = null; /** * Whether locations should be captured and propagated. */ private bool mShouldCaptureLocations = {{{CAPTURE_LOCATIONS}}}; /** * On parse begin callback. * * Example: parser.onParseBegin = (string code) => { ... }; */ public static Action onParseBegin {get; set; } = null; /** * On parse end callback. * * Example: parser.onParseEnd = (object parsed) => { ... }; */ public static Action onParseEnd {get; set; } = null; /** * Tokenizer instance. */ public Tokenizer tokenizer { get; set; } = null; /** * Global constants used across the tokenizer, and parser. * The `yytext` stores a matched token value, `yyleng` is its length. */ public static string EOF = "$"; public static string yytext = null; public static int yyleng = 0; /** * Production handles. The handlers receive arguments as _1, _2, etc. * The result is always stored in __. * * Example: * * public void _handler1(dynamic _1, dynamic _2, dynamic _3) * { * __ = _1 + _3; * } */ {{{PRODUCTION_HANDLERS}}} /** * Main parsing method which applies LR-algorithm. */ public object parse(string str) { // On parse begin hook. if (onParseBegin != null) { onParseBegin(str); } // Tokenizer should be set prior calling the parse. if (tokenizer == null) { throw new Exception("Tokenizer instance isn't specified."); } tokenizer.initString(str); // Initialize the parsing stack to the initial state 0. mStack = new Stack(); mStack.Push(0); Token token = tokenizer.getNextToken(); Token shiftedToken = null; do { if (token == null) { unexpectedEndOfInput(); } var state = Convert.ToInt32(mStack.Peek()); var column = token.Type; if (!mTable[state].ContainsKey(column)) { unexpectedToken(token); break; } var entry = mTable[state][column]; // --------------------------------------------------- // "Shift". Shift-entries always have 's' as their // first char, after which goes *next state number*, e.g. "s5". // On shift we push the token, and the next state on the stack. if (entry[0] == 's') { YyLoc loc = null; if (mShouldCaptureLocations) { loc = new YyLoc { StartOffset = token.StartOffset, EndOffset = token.EndOffset, StartLine = token.StartLine, EndLine = token.EndLine, StartColumn = token.StartColumn, EndColumn = token.EndColumn, }; } // Push token. mStack.Push(new StackEntry(token.Type, token.Value, loc)); // Push next state number: "s5" -> 5 mStack.Push(Convert.ToInt32(entry.Substring(1))); shiftedToken = token; token = tokenizer.getNextToken(); } // --------------------------------------------------- // "Reduce". Reduce-entries always have 'r' as their // first char, after which goes *production number* to // reduce by, e.g. "r3" - reduce by production 3 in the grammar. // On reduce, we pop of the stack number of symbols on the RHS // of the production, and their pushed state numbers, i.e. // total RHS * 2 symbols. else if (entry[0] == 'r') { // "r3" -> 3 var productionNumber = Convert.ToInt32(entry.Substring(1)); var production = mProductions[productionNumber]; // Handler can be optional: {0, 3} - no handler, // {0, 3, "_handler1"} - has handler. var hasSemanticAction = production.Length > 2; var semanticValueArgs = new List(); List locationArgs = null; if (mShouldCaptureLocations) { locationArgs = new List(); } // The length of RHS is stored in the production[1]. var rhsLength = (int)production[1]; if (rhsLength != 0) { while (rhsLength-- > 0) { // Pop the state number. mStack.Pop(); // Pop the stack entry. var stackEntry = (StackEntry)mStack.Pop(); // Collect all semantic values from the stack // to the arguments list, which is passed to the // semantic action handler. if (hasSemanticAction) { semanticValueArgs.Insert(0, stackEntry.SemanticValue); if (mShouldCaptureLocations) { locationArgs.Insert(0, stackEntry.Loc); } } } } var previousState = Convert.ToInt32(mStack.Peek()); var symbolToReduceWith = (int)production[0]; var reduceStackEntry = new StackEntry(symbolToReduceWith, null, null); // Execute the semantic action handler. if (hasSemanticAction) { yyparse.yytext = shiftedToken != null ? shiftedToken.Value : null; yyparse.yyleng = shiftedToken != null ? shiftedToken.Value.Length : 0; var semanticAction = (string)production[2]; MethodInfo semanticActionHandler = GetType().GetMethod(semanticAction); var semanticActionArgs = semanticValueArgs; if (mShouldCaptureLocations) { semanticActionArgs.AddRange(locationArgs); } // Call the action, the result is in __. semanticActionHandler.Invoke(this, semanticActionArgs.ToArray()); reduceStackEntry.SemanticValue = __; if (mShouldCaptureLocations) { reduceStackEntry.Loc = __loc; } } // Then push LHS onto the stack. mStack.Push(reduceStackEntry); // And the next state number. var nextState = mTable[previousState][symbolToReduceWith]; mStack.Push(nextState); } // --------------------------------------------------- // Accept. Pop starting production and its state number. else if (entry == "acc") { // Pop state number. mStack.Pop(); // Pop the parsed value. var parsed = (StackEntry)mStack.Pop(); if (mStack.Count != 1 || (int)mStack.Peek() != 0 || tokenizer.hasMoreTokens()) { unexpectedToken(token); } var parsedValue = parsed.SemanticValue; if (onParseEnd != null) { onParseEnd(parsedValue); } return parsedValue; } } while (tokenizer.hasMoreTokens() || mStack.Count > 1); return null; } private void unexpectedToken(Token token) { if (token.Type == Tokenizer.EOF_TOKEN.Type) { unexpectedEndOfInput(); } tokenizer.throwUnexpectedToken( token.Value, token.StartLine, token.StartColumn ); } private void unexpectedEndOfInput() { parseError("Unexpected end of input."); } private void parseError(string message) { throw new SyntaxException("Parse error: " + message); } } /** * An actual parser class. */ public class {{{PARSER_CLASS_NAME}}} : yyparse { } } /** * Tokenizer class. */ {{{TOKENIZER}}}