/// <summary>
        /// Converts given user code into a IL program which can then be compiled.
        /// </summary>
        /// <param name="settings">The settings.</param>
        /// <returns></returns>
        public static (ErrorCodes errorCode, List <Instruction> Il) CompileAst(CompilerSettings settings)
        {
            AbstractSyntaxTree tree = Lexer.LexAst(settings.InputCode);

            Optimizer.Optimize(tree, settings);
            List <Instruction> Il = tree.ToIl();

            Optimizer.Optimize(Il, settings);
            return(ErrorCodes.Successful, Il);
        }
        private static (ErrorCodes errorCode, List <Instruction> Il) CompileCommon(CompilerSettings settings)
        {
            if (settings == null)
            {
                throw new ArgumentNullException(nameof(settings));
            }

            if (settings.InputCode == null)
            {
                throw new ArgumentException(nameof(settings.InputCode));
            }

            return(CompileAst(settings));
        }
        /// <summary>
        /// Writes the compiled exe along with the input code (brainf*ck), CSharp source and IL to files.
        /// </summary>
        /// <param name="IlString">The IL as a string. (gets written to IL.txt)</param>
        /// <param name="outputSrc">The CSharp source code of the output. (gets written to output-src.cs)</param>
        /// <param name="settings">The settings.</param>
        private static void WriteToFiles(string IlString, string outputSrc, CompilerSettings settings)
        {
            if (!string.IsNullOrEmpty(settings.FileNameUserCode))
            {
                File.WriteAllText(Path.Combine(appdir, settings.FileNameUserCode + ".txt"), settings.InputCode);
            }

            if (!string.IsNullOrEmpty(settings.FileNameIL))
            {
                File.WriteAllText(Path.Combine(appdir, settings.FileNameIL + ".txt"), IlString);
            }

            if (!string.IsNullOrEmpty(settings.FileNameCSharpSrc))
            {
                File.WriteAllText(Path.Combine(appdir, settings.FileNameCSharpSrc + ".cs"), outputSrc);
            }
        }
        /// <summary>
        /// Attempts to compile the code specified in <paramref name="settings"/> outputs code in output.exe
        /// </summary>
        /// <param name="settings"></param>
        /// <returns>The success of the compilation.</returns>
        public static ErrorCodes Compile(CompilerSettings settings)
        {
            (ErrorCodes errorCode, List <Instruction> Il)preCompResults = CompileCommon(settings);
            if (preCompResults.errorCode != ErrorCodes.Successful)
            {
                return(preCompResults.errorCode);
            }

            // Why is this using a constant string? because a better way to do this hasn't been
            // found. The reason why this isn't a $ string is because of the brackets.
            string compiled =
                "using System;public class Program{public static void Main(){byte[] ram=new byte[256];" +
                GetInjectString(preCompResults.Il) +
                "Console.ReadKey();}}";

            using (CSharpCodeProvider provider = new CSharpCodeProvider())
            {
                CompilerParameters paramaters = new CompilerParameters
                {
                    CompilerOptions    = "/optimize", // This currently does nothing?
                    GenerateExecutable = true,
                    OutputAssembly     = Path.Combine(
                        appdir,
                        ((!string.IsNullOrEmpty(settings.FileNameOutputExe))
                        ? settings.FileNameOutputExe : "output") + ".exe")
                };

                if (provider.CompileAssemblyFromSource(paramaters, compiled).Errors.Count > 0)
                {
                    // This *shouldn't* ever happen because all errors *should* be caught by the
                    // validator. The only known instance of this occuring is when the application
                    // can't write to the file.
                    return(ErrorCodes.UnknownCompilationFailure);
                }
            }

            // create a string which contains all the IL on new lines & pass the other args.
            WriteToFiles(string.Join(Environment.NewLine, preCompResults.Il), compiled, settings);
            return(ErrorCodes.Successful); // Made it.
        }
        /// <summary>
        /// Uses <paramref name="settings"/> to optimize <paramref name="IL"/>.
        /// </summary>
        /// <param name="IL">The IL that is to be optimized.</param>
        /// <param name="settings">
        /// The compiler settings that tell which optimizations should be used.
        /// </param>
        internal static void Optimize(List <Instruction> IL, CompilerSettings settings)
        {
            int CodeLength = 0;

            IL.RemoveNops(); // Just in case.

            // It feels evil to use a do while loop but this seems to be the best way to not loop
            // more than required.
            do
            {
                CodeLength = IL.Count;
                if (settings.CombineMatchingInstructions)
                {
                    CombineMatchingInstructions(IL);
                }

                if (settings.EliminateEmptyLoops)
                {
                    EliminateEmptyLoops(IL);
                }
            } while (IL.Count < CodeLength);

            // This one gets it's own loop because it doesn't help the other ones, less time spent here.
            if (settings.EliminateDeadStores)
            {
                do
                {
                    CodeLength = IL.Count;
                    EliminateDeadStores(IL);
                } while (IL.Count < CodeLength);
            }

            // Doesn't need a loop.
            if (settings.MergeAssignThenModifyInstructions)
            {
                MergeAssignThenModifyInstructions(IL);
            }
        }
        internal static void Optimize(AbstractSyntaxTree ast, CompilerSettings settings)
        {
            bool Continue = true;

            while (Continue)
            {
                Continue = false;
                if (settings.SimplifyAssignZeroLoops)
                {
                    Continue |= SimplifyAssignZeroLoops(ast);
                    ast.ColapseNops();
                }

                if (settings.EliminateDeadStores)
                {
                    Continue |= EliminateDeadStores(ast, true);
                    ast.ColapseNops();
                }

                if (settings.EliminateUnreachableLoops)
                {
                    Continue |= EliminateUnreachableLoops(ast, true);
                    ast.ColapseNops();
                }

                if (settings.EliminateConflictingInstructions)
                {
                    Continue |= EliminateConflictingInstructions(ast);
                    ast.ColapseNops();
                }
                if (true)
                {
                    Continue |= SimplifyScans(ast);
                    ast.ColapseNops();
                }
            }
        }
        public static ErrorCodes CompileDmg(CompilerSettings settings)
        {
            (ErrorCodes errorCode, List <Instruction> Il)preCompResults = CompileCommon(settings);
            if (preCompResults.errorCode != ErrorCodes.Successful)
            {
                return(preCompResults.errorCode);
            }

            Stack <int> jumpOffsets = new Stack <int>();
            List <byte> rom         = new byte[0x100].Concat(DmgHeader).ToList();

            rom.Add(0xAF); // xor A
            rom.Add(0x47); // ld B,A (set B to 0)
            rom.Add(0x6F); // ld L,A (set L to 0)

            // ld H,C0
            rom.Add(0x26);
            rom.Add(0xC0);

            foreach (Instruction instr in preCompResults.Il)
            {
                int instrOffset = rom.Count;
                switch (instr.OpCode)
                {
                case OpCode.Nop:
                    rom.Add(0x00);     // Maybe emit a compiler warning here?
                    break;

                case OpCode.AddVal:
                case OpCode.SubVal:
                    rom.Add(instr.OpCode == OpCode.AddVal ? (byte)0xC6 : (byte)0xD6);
                    rom.Add(instr.Value);
                    break;

                case OpCode.AddPtr:
                case OpCode.SubPtr:
                    rom.Add(0x77);     // Store A into the pointer.

                    // Load a with the address
                    rom.Add(0x3E);
                    rom.Add(instr.Value);
                    rom.Add(instr.OpCode == OpCode.AddPtr ? (byte)0x85 : (byte)0x95); // Modify the address.
                    rom.Add(0x6F);                                                    // Store the result into L
                    rom.Add(0x7E);                                                    // Read at the new pointer.
                    rom.Add(0xBB);
                    break;

                case OpCode.StartLoop:
                    rom.Add(0xCA);
                    rom.Add(0);
                    rom.Add(0);
                    jumpOffsets.Push(instrOffset);
                    break;

                case OpCode.EndLoop:
                    int baseAddr = jumpOffsets.Pop();

                    // Setup jump to the instruction after this.
                    rom[baseAddr + 1] = (byte)(instrOffset + 3);
                    rom[baseAddr + 2] = (byte)((instrOffset + 3) >> 8);

                    // Setup this jump.
                    rom.Add(0xC2);
                    rom.Add((byte)(baseAddr + 3));
                    rom.Add((byte)((baseAddr + 3) >> 8));
                    break;

                case OpCode.AssignVal:
                    rom.Add(0x3E);
                    rom.Add(instr.Value);
                    break;

                case OpCode.AssignZero:
                    rom.Add(0xAF);
                    break;

                case OpCode.ScanLeft:
                case OpCode.ScanRight:
                    rom.Add(0x28);
                    rom.Add(0x05);                                                       // this is a fixed address based on the start address of this.
                    rom.Add(0xAF);
                    rom.Add(instr.OpCode == OpCode.ScanRight ? (byte)0x2C : (byte)0x2D); // INC/DEC L
                    rom.Add(0xBE);
                    rom.Add(0x20);                                                       // JR NZ
                    rom.Add(0xFC);                                                       // addr: INC/DEC L.
                    break;

                case OpCode.GetInput:     // TODO: load from serial.
                case OpCode.SetOutput:    // TODO: write to serial.
                default:
                    return(ErrorCodes.UnexpectedIlInstruction);
                }
            }

            rom.Add(0x10);
            rom.Add(0x00);

            File.WriteAllBytes(((!string.IsNullOrEmpty(settings.FileNameOutputExe))
                        ? settings.FileNameOutputExe : "output") + ".gb", rom.ToArray());
            if (!string.IsNullOrEmpty(settings.FileNameIL))
            {
                File.WriteAllText(Path.Combine(appdir, settings.FileNameIL + ".txt"),
                                  string.Join(Environment.NewLine, preCompResults.Il));
            }

            return(ErrorCodes.Successful);
        }