/**
 * 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 <parsing-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 Index>, <RHS.Length>, <handler> }
         *
         * 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<int, string>()
         *     {
         *         {0, "1"},
         *         {3, "s8"},
         *         {4, "s2"},
         *     },
         *     // 1
         *     new Dictionary<int, string>()
         *     {
         *         {1, "s3"},
         *         {2, "s4"},
         *         {6, "acc"},
         *     },
         *     ...
         * }
         */
        private static Dictionary<int, string>[] mTable = {{{TABLE}}};

        /**
         * Parsing stack. Stores instances of StackEntry, and state numbers.
         */
        Stack<object> 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<string> onParseBegin {get; set; } = null;

        /**
         * On parse end callback.
         *
         * Example: parser.onParseEnd = (object parsed) => { ... };
         */
        public static Action<object> 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<object>();
            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<object>();
                    List<object> locationArgs = null;

                    if (mShouldCaptureLocations) {
                        locationArgs = new List<object>();
                    }

                    // 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}}}