private void CoagulateVariableDeclarationAssignmentsRecursively(List <EvaluatedStatement> evaluatedStatements, HashSet <string> parentScopeDeclaredVariables)
        {
            if (!evaluatedStatements.Any())
            {
                return;
            }

            int firstIndex = evaluatedStatements.First().InstructionIndex;
            int lastIndex  = evaluatedStatements.Last().InstructionIndex;
            //LogInfo( $"Coagulating variable declarations and assignments: { firstIndex } - { lastIndex }" );

            // Declared variables in the current scope
            var declaredVariables = new HashSet <string>();

            var allIfStatements = mOriginalEvaluatedStatements
                                  .Where(x => x.InstructionIndex >= firstIndex)
                                  .Where(x => x.InstructionIndex <= lastIndex)
                                  .Where(x => x.Statement is IfStatement)
                                  .ToList();

            // All referenced variable identifiers in statements, and if statements
            var referencedLocalVariableIdentifiers =
                mEvaluatedProcedure.ReferencedVariables
                .Where(x => x.InstructionIndex >= firstIndex && x.InstructionIndex <= lastIndex)
                .GroupBy(x => x.Identifier.Text);

            foreach (var referencedLocalVariableIdentifier in referencedLocalVariableIdentifiers)
            {
                var identifierText = referencedLocalVariableIdentifier.Key;
                int firstReferenceInstructionIndex = referencedLocalVariableIdentifier.Min(x => x.InstructionIndex);

                // Check if the variable was declared in either the scope of the parent or the current scope
                if (parentScopeDeclaredVariables.Contains(identifierText) ||
                    declaredVariables.Contains(identifierText) ||
                    !mEvaluatedProcedure.Scope.Variables.TryGetValue(identifierText, out var declaration))
                {
                    continue;
                }

                // Variable hasn't already been declared
                // Find the index of the statement
                int        evaluatedStatementIndex         = evaluatedStatements.FindIndex(x => x.InstructionIndex == firstReferenceInstructionIndex);
                Expression initializer                     = null;
                bool       shouldDeclareBeforeIfStatements = false;
                bool       accessedLaterInBody             = referencedLocalVariableIdentifier.Any(x => evaluatedStatements.Any(y => y.InstructionIndex == x.InstructionIndex));

                // Hack: Edge case - variable was assigned a non existent value
                // This causes the assignment to not exist in the AST
                // So just insert the assignment before the first reference
                if (evaluatedStatementIndex == -1 &&
                    evaluatedStatements.Any(x => x.InstructionIndex == firstReferenceInstructionIndex - 1) && evaluatedStatements.Any(x => x.InstructionIndex == firstReferenceInstructionIndex + 1))
                {
                    evaluatedStatementIndex = evaluatedStatements.FindIndex(x => x.InstructionIndex == firstReferenceInstructionIndex - 1);
                }

                if (evaluatedStatementIndex == -1 && !mConvertIfStatementsToGotos)
                {
                    // Referenced first in one of the if statements

                    // But maybe it's accessed later in the body?
                    bool accessedInIfStatementOnce = false;
                    var  curIfStatements           = allIfStatements;

                    foreach (var ifStatement in allIfStatements)
                    {
                        // Check condition
                        var conditionIdentifiers = SyntaxNodeCollector <Identifier> .Collect((( IfStatement )ifStatement.Statement).Condition);

                        if (conditionIdentifiers.Any(x => x.Text == referencedLocalVariableIdentifier.Key))
                        {
                            // Really Good Code
                            shouldDeclareBeforeIfStatements = true;
                            break;
                        }

                        // Check if any of instructions in the if body map to any of the instruction indices of the references
                        var body            = mIfStatementBodyMap[ifStatement.InstructionIndex];
                        var bodyIdentifiers = body.SelectMany(x => SyntaxNodeCollector <Identifier> .Collect(x.Statement));
                        if (bodyIdentifiers.Any(x => x.Text == referencedLocalVariableIdentifier.Key))
                        {
                            if (!accessedInIfStatementOnce)
                            {
                                accessedInIfStatementOnce = true;
                                if (accessedLaterInBody)
                                {
                                    shouldDeclareBeforeIfStatements = true;
                                }
                            }
                            else
                            {
                                shouldDeclareBeforeIfStatements = true;
                                break;
                            }
                        }

                        // Same for else body
                        if (mIfStatementElseBodyMap.TryGetValue(ifStatement.InstructionIndex, out var elseBody))
                        {
                            // Check if any of instructions in the if else body map to any of the instruction indices of the references
                            var elseBodyIdentifiers = body.SelectMany(x => SyntaxNodeCollector <Identifier> .Collect(x.Statement));
                            if (elseBodyIdentifiers.Any(x => x.Text == referencedLocalVariableIdentifier.Key))
                            {
                                if (!accessedInIfStatementOnce)
                                {
                                    accessedInIfStatementOnce = true;
                                    if (accessedLaterInBody)
                                    {
                                        shouldDeclareBeforeIfStatements = true;
                                    }
                                }
                                else
                                {
                                    shouldDeclareBeforeIfStatements = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                else
                {
                    var evaluatedStatement = evaluatedStatements[evaluatedStatementIndex];

                    // Check if the statement is an assignment expression
                    // Which would mean we have an initializer
                    if (evaluatedStatement.Statement is AssignmentOperator assignment)
                    {
                        // Only match initializers if the target of the operator
                        // Is actually the same identifier
                        if (((Identifier)assignment.Left).Text == identifierText)
                        {
                            initializer = assignment.Right;
                        }
                    }
                }

                if ((evaluatedStatementIndex != -1 || shouldDeclareBeforeIfStatements) && !mConvertIfStatementsToGotos)
                {
                    // Find the best insertion index
                    int insertionIndex;
                    if (evaluatedStatementIndex != -1)
                    {
                        insertionIndex = evaluatedStatementIndex;
                    }
                    else
                    {
                        insertionIndex = evaluatedStatements.IndexOf(allIfStatements.First());
                    }

                    int instructionIndex = firstReferenceInstructionIndex;

                    // Check if the variable has been referenced before in an if statement
                    foreach (var evaluatedIfStatement in allIfStatements.Where(x => x.InstructionIndex <= firstReferenceInstructionIndex))
                    {
                        var ifStatementBody = mIfStatementBodyMap[evaluatedIfStatement.InstructionIndex];
                        var referencedLocalVariableIdentifiersInIfStatementBody =
                            mEvaluatedProcedure.ReferencedVariables.Where(
                                x => ifStatementBody.Any(y => x.InstructionIndex == y.InstructionIndex));

                        if (referencedLocalVariableIdentifiersInIfStatementBody.Any(
                                x => x.Identifier.Text == identifierText))
                        {
                            // The variable was referenced in a previous if statement, so we should insert it before the start of the if statement
                            insertionIndex   = evaluatedStatements.IndexOf(evaluatedIfStatement);
                            instructionIndex = evaluatedIfStatement.InstructionIndex - 1;

                            // Edge case
                            if (instructionIndex < 0)
                            {
                                instructionIndex = 0;
                            }

                            break;
                        }

                        if (mIfStatementElseBodyMap.TryGetValue(evaluatedIfStatement.InstructionIndex,
                                                                out var ifStatementElseBody))
                        {
                            var referencedLocalVariableIdentifiersInIfStatementElseBody =
                                mEvaluatedProcedure.ReferencedVariables.Where(
                                    x => ifStatementElseBody.Any(y => x.InstructionIndex == y.InstructionIndex));

                            if (referencedLocalVariableIdentifiersInIfStatementElseBody.Any(
                                    x => x.Identifier.Text == identifierText))
                            {
                                // The variable was referenced in a previous if statement, so we should insert it before the start of the if statement
                                insertionIndex   = evaluatedStatements.IndexOf(evaluatedIfStatement);
                                instructionIndex = evaluatedIfStatement.InstructionIndex - 1;

                                // Edge case
                                if (instructionIndex < 0)
                                {
                                    instructionIndex = 0;
                                }

                                break;
                            }
                        }
                    }

                    if (insertionIndex == -1)
                    {
                        // Variable was referenced in both the body and in a nested if statement
                        if (evaluatedStatements.Any(x => x.Statement is IfStatement))
                        {
                            insertionIndex = evaluatedStatements.IndexOf(evaluatedStatements.First(x => x.Statement is IfStatement));
                        }
                        else
                        {
                            insertionIndex = 0;
                        }
                    }

                    if (insertionIndex != evaluatedStatementIndex)
                    {
                        // If the insertion index isn't equal to the evaluated statement index
                        // Then that means it was previously referenced in the body of an if statement
                        // So we insert declaration before if statement in which it was used

                        // Just to be safe
                        declaration.Initializer = new IntLiteral(0);

                        evaluatedStatements.Insert(insertionIndex,
                                                   new EvaluatedStatement(declaration, instructionIndex, null));
                    }
                    else
                    {
                        // If the insertion index is still the same, then that means we probably have a declaration with an assignment
                        // Or maybe we have a reference to an undeclared variable!

                        if (initializer == null)
                        {
                            // Reference to undeclared variable
                            LogInfo($"Reference to uninitialized variable! Adding 0 initializer: {declaration}");
                            initializer = new IntLiteral(0);
                        }

                        // Coagulate assignment with declaration
                        declaration.Initializer = initializer;
                        evaluatedStatements[evaluatedStatementIndex] = new EvaluatedStatement(
                            declaration, instructionIndex, null);
                    }

                    declaredVariables.Add(identifierText);
                }
            }

            // Merge parent scope with local scope
            foreach (string declaredVariable in parentScopeDeclaredVariables)
            {
                declaredVariables.Add(declaredVariable);
            }

            if (!mConvertIfStatementsToGotos)
            {
                var ifStatementsInScope = evaluatedStatements
                                          .Where(x => x.Statement is IfStatement);

                foreach (var ifStatement in ifStatementsInScope)
                {
                    // Do the same for each if statement
                    var body = mIfStatementBodyMap[ifStatement.InstructionIndex];
                    CoagulateVariableDeclarationAssignmentsRecursively(body, declaredVariables);

                    if (mIfStatementElseBodyMap.TryGetValue(ifStatement.InstructionIndex, out var elseBody))
                    {
                        CoagulateVariableDeclarationAssignmentsRecursively(elseBody, declaredVariables);
                    }
                }
            }
        }
        private void BuildIfStatementMaps()
        {
            // Build if statement bodies
            var evaluatedIfStatements = mEvaluatedStatements.Where(x => x.Statement is IfStatement).ToList();

            foreach (var evaluatedIfStatement in evaluatedIfStatements)
            {
                var falseLabel = evaluatedIfStatement.ReferencedLabel;

                if (mConvertIfStatementsToGotos)
                {
                    var index       = mEvaluatedStatements.IndexOf(evaluatedIfStatement);
                    var ifStatement = ( IfStatement )evaluatedIfStatement.Statement;

                    mEvaluatedStatements.Insert(index, new EvaluatedStatement(ifStatement.Condition,
                                                                              evaluatedIfStatement.InstructionIndex - 1, null));
                    mEvaluatedStatements[index + 1] = new EvaluatedStatement(
                        new GotoStatement(new Identifier(falseLabel.Name)), evaluatedIfStatement.InstructionIndex, falseLabel);
                }
                else
                {
                    // Extract statements contained in the if statement's body
                    var bodyEvaluatedStatements = mEvaluatedStatements
                                                  .Where(x => x != evaluatedIfStatement && x.InstructionIndex >= evaluatedIfStatement.InstructionIndex && x.InstructionIndex < falseLabel.InstructionIndex)
                                                  .ToList();

                    // We keep the if statements in a map to retain evaluation info until we finally build the if statements
                    mIfStatementBodyMap[evaluatedIfStatement.InstructionIndex] = bodyEvaluatedStatements;

                    // Detect else if
                    var evaluatedGotoStatement = bodyEvaluatedStatements.LastOrDefault();
                    if (evaluatedGotoStatement != null && evaluatedGotoStatement.Statement is GotoStatement)
                    {
                        if (evaluatedGotoStatement.ReferencedLabel.InstructionIndex !=
                            evaluatedGotoStatement.InstructionIndex + 1)
                        {
                            // Try to detect if-else pattern
                            var elseBodyEvaluatedStatements = mEvaluatedStatements
                                                              .Where(x => x != evaluatedGotoStatement && x.InstructionIndex >= evaluatedGotoStatement.InstructionIndex && x.InstructionIndex < evaluatedGotoStatement.ReferencedLabel.InstructionIndex)
                                                              .ToList();

                            if (elseBodyEvaluatedStatements.Any())
                            {
                                mIfStatementElseBodyMap[evaluatedIfStatement.InstructionIndex] = elseBodyEvaluatedStatements;
                            }
                        }
                    }
                }
            }

            if (mConvertIfStatementsToGotos)
            {
                return;
            }

            // Remove statements in if statement bodies from list of statements
            foreach (var evaluatedIfStatement in evaluatedIfStatements)
            {
                var body = mIfStatementBodyMap[evaluatedIfStatement.InstructionIndex];
                mIfStatementElseBodyMap.TryGetValue(evaluatedIfStatement.InstructionIndex, out var elseBody);

                body.ForEach(x => mEvaluatedStatements.Remove(x));
                if (elseBody != null)
                {
                    elseBody.ForEach(x => mEvaluatedStatements.Remove(x));
                }

                foreach (var ifStatementBodyMap in mIfStatementBodyMap)
                {
                    if (ifStatementBodyMap.Value.Contains(evaluatedIfStatement))
                    {
                        body.ForEach(x => ifStatementBodyMap.Value.Remove(x));
                        if (elseBody != null)
                        {
                            elseBody.ForEach(x => ifStatementBodyMap.Value.Remove(x));
                        }
                    }
                }

                foreach (var ifStatementBodyMap in mIfStatementElseBodyMap)
                {
                    if (ifStatementBodyMap.Value.Contains(evaluatedIfStatement))
                    {
                        body.ForEach(x => ifStatementBodyMap.Value.Remove(x));
                        if (elseBody != null)
                        {
                            elseBody.ForEach(x => ifStatementBodyMap.Value.Remove(x));
                        }
                    }
                }
            }

            // Clean up if statement bodies
            if (mKeepLabelsAndGotos)
            {
                return;
            }

            foreach (var evaluatedIfStatement in evaluatedIfStatements)
            {
                var bodyEvaluatedStatements = mIfStatementBodyMap[evaluatedIfStatement.InstructionIndex];

                // Remove goto to after if statement inside body if it's right after the if statement body
                var evaluatedGotoStatement = bodyEvaluatedStatements.LastOrDefault();
                if (evaluatedGotoStatement != null && evaluatedGotoStatement.Statement is GotoStatement)
                {
                    // Likely a single if statement
                    if (evaluatedGotoStatement.ReferencedLabel.InstructionIndex == evaluatedGotoStatement.InstructionIndex + 1)
                    {
                        bodyEvaluatedStatements.Remove(evaluatedGotoStatement);
                    }

                    if (mIfStatementElseBodyMap.TryGetValue(evaluatedIfStatement.InstructionIndex,
                                                            out var elseBodyEvaluatedStatements))
                    {
                        if (elseBodyEvaluatedStatements.Any())
                        {
                            bodyEvaluatedStatements.Remove(evaluatedGotoStatement);

                            if (elseBodyEvaluatedStatements.First().Statement is LabelDeclaration)
                            {
                                elseBodyEvaluatedStatements.Remove(elseBodyEvaluatedStatements.First());
                            }

                            if (elseBodyEvaluatedStatements.Any() && elseBodyEvaluatedStatements.Last().Statement is GotoStatement)
                            {
                                var elseBodyGotoStatement = elseBodyEvaluatedStatements.Last();
                                if (elseBodyGotoStatement.ReferencedLabel.InstructionIndex ==
                                    elseBodyGotoStatement.InstructionIndex + 1)
                                {
                                    elseBodyEvaluatedStatements.Remove(elseBodyGotoStatement);
                                }
                            }
                        }
                    }
                }
            }
        }