14
\$\begingroup\$

This is a C# programme to convert Brainfuck to C#, then convert that C# to a .exe. So basically, it's a Brainfuck to Windows exe file converter/compiler.

This requires any of:

  • .NET 2.0 in C:\Windows\Microsoft.NET\Framework\v2.0.50727
  • .NET 3.5 in C:\Windows\Microsoft.NET\Framework\v3.5
  • .NET 4.0 in C:\Windows\Microsoft.NET\Framework\v4.0.30319

Just specify 2.0, 3.5, or 4.0 at the appropriate prompt.

Do note that this uses classes from my Framework library. If you wish to run it you'll need to download the Evbpc.Framework project there and compile it. (Or remove all of the ConsolePrompt, ConsoleLogger, and IPrompt stuff.)

It's also on GitHub.

class Program
{
    static string AddExtension(string data, string extension)
    {
        if (data.IndexOf('.') == -1)
        {
            data += extension;
        }

        return data;
    }

    static void AcquireInput(ILogger logger, out string sourceFile, out string programName, out string destinationFile, out string executableFile, out int bufferSize, out string dotNetVersion)
    {
        ConsolePrompt prompt = new ConsolePrompt(new EmptyLogger());
        logger.LogVerbose("Prompting for input...");
        sourceFile = AddExtension(prompt.Prompt<string>("Enter the source filename (.bf)", PromptOptions.Required), ".bf");
        programName = sourceFile.Substring(0, sourceFile.LastIndexOf('.'));
        destinationFile = AddExtension(prompt.Prompt("Enter the destination filename (.cs)", PromptOptions.Optional, programName + ".cs"), ".cs");
        executableFile = AddExtension(prompt.Prompt("Enter the destination executable (.exe)", PromptOptions.Optional, programName + ".exe"), ".exe");
        bufferSize = prompt.Prompt("Enter buffer size", PromptOptions.Optional, 2048);
        dotNetVersion = prompt.Prompt("Enter .NET version to compile with", PromptOptions.Optional, "4.0", "Version can be any of: 2.0, 3.5, 4.0", x => _dotNetFolders.ContainsKey(x));
        logger.LogVerbose("Input acquired.");
    }

    static string LoadBrainfuckCode(ILogger logger, string filename)
    {
        logger.LogInformation("Acquiring Brainfuck code...");
        string brainfuck = string.Empty;
        using (StreamReader sr = new StreamReader(filename))
        {
            brainfuck = sr.ReadToEnd();
        }
        logger.LogInformation("Brainfuck code acquired.");

        return brainfuck;
    }

    static string AcquireTemplate(ILogger logger, string filename)
    {
        logger.LogInformation("Acquiring compilation template...");
        using (StreamReader sr = new StreamReader(filename))
        {
            filename = sr.ReadToEnd();
        }
        logger.LogInformation("Template acquired.");

        return filename;
    }

    static string InterpretBrainfuck(ILogger logger, string code)
    {
        logger.LogInformation("Interpreting Brainfuck to C#...");
        StringBuilder brainfuckCode = new StringBuilder();
        int indents = 3;
        foreach (char c in code)
        {
            WriteBrainfuckLine(c, brainfuckCode, ref indents);
        }
        logger.LogInformation("Brainfuck interpreted.");

        return brainfuckCode.ToString();
    }

    static void WriteToCsFile(ILogger logger, string filename, string data)
    {
        logger.LogInformation("Writing C# file...");
        using (StreamWriter sw = new StreamWriter(filename))
        {
            sw.WriteLine(data);
        }
        logger.LogInformation("C# file written.");
    }

    static void CompileCsFile(ILogger logger, string dotNetPath, string sourceFile, string executableFile)
    {
        logger.LogInformation("Compiling C# file...");
        logger.LogImportant($"C:\\Windows\\Microsoft.NET\\Framework\\{dotNetPath}\\csc.exe /t:exe /out:\"{Directory.GetCurrentDirectory()}\\{executableFile}\" \"{Directory.GetCurrentDirectory()}\\{sourceFile}\"");
        ProcessStartInfo info = new ProcessStartInfo($@"C:\Windows\Microsoft.NET\Framework\{dotNetPath}\csc.exe", $"/t:exe /out:\"{Directory.GetCurrentDirectory()}\\{executableFile}\" \"{Directory.GetCurrentDirectory()}\\{sourceFile}\"");
        var process = Process.Start(info);
        process.WaitForExit();
        logger.LogInformation("C# code compiled.");
    }

    static Dictionary<string, string> _dotNetFolders = new Dictionary<string, string> { { "2.0", "v2.0.50727" }, { "3.5", "v3.5" }, { "4.0", "v4.0.30319" } };

    static void Main(string[] args)
    {
        ConsoleLogger logger = new ConsoleLogger(LoggingType.All);

        string sourceFile = string.Empty;
        string programName = string.Empty;
        string destinationFile = string.Empty;
        string executableFile = string.Empty;
        string dotNetVersion = string.Empty;
        int bufferSize = 0;

        AcquireInput(logger, out sourceFile, out programName, out destinationFile, out executableFile, out bufferSize, out dotNetVersion);

        string brainfuck = LoadBrainfuckCode(logger, sourceFile);
        string result = AcquireTemplate(logger, "CompiledTemplate.cs");
        string brainfuckCode = InterpretBrainfuck(logger, brainfuck);

        logger.LogVerbose("Replacing placeholders in result...");
        result = result.Replace("BufferSize", bufferSize.ToString());
        result = result.Replace("BrainfuckCode;", brainfuckCode.ToString().TrimStart());
        logger.LogVerbose("Placeholders replaced.");

        WriteToCsFile(logger, destinationFile, result);
        CompileCsFile(logger, _dotNetFolders[dotNetVersion], destinationFile, executableFile);

        logger.LogInformation("Launching programme...");
        Process.Start(executableFile);
        logger.LogInformation("Programme launched.");

        Console.WriteLine("Press enter key to exit...");
        Console.ReadLine();
    }

    static void WriteBrainfuckLine(char c, StringBuilder sb, ref int indents)
    {
        switch (c)
        {
            case '>':
                WriteIndents(sb, indents);
                sb.AppendLine("index++;");
                break;
            case '<':
                WriteIndents(sb, indents);
                sb.AppendLine("index--;");
                break;
            case '[':
                sb.AppendLine("");
                WriteIndents(sb, indents);
                sb.AppendLine("while (buffer[index] != 0)");
                WriteIndents(sb, indents);
                sb.AppendLine("{");
                indents++;
                break;
            case ']':
                indents--;
                WriteIndents(sb, indents);
                sb.AppendLine("}");
                sb.AppendLine("");
                break;
            case '+':
                WriteIndents(sb, indents);
                sb.AppendLine("buffer[index]++;");
                break;
            case '-':
                WriteIndents(sb, indents);
                sb.AppendLine("buffer[index]--;");
                break;
            case ',':
                WriteIndents(sb, indents);
                sb.AppendLine("buffer[index] = ReadChar();");
                break;
            case '.':
                WriteIndents(sb, indents);
                sb.AppendLine("Console.Write((char)buffer[index]);");
                break;
        }
    }

    static void WriteIndents(StringBuilder sb, int indents)
    {
        sb.Append($"{new string(' ', indents * 4)}");
    }
}

The nice thing about it is that it writes well-structured Brainfuck.

The template is:

using System;

namespace Brainfuck_Interpreter
{
    class CompiledTemplate
    {
        static byte ReadChar()
        {
            return (byte)Console.ReadKey().KeyChar;
        }

        static void Main(string[] args)
        {
            byte[] buffer = new byte[BufferSize];
            int index = 0;

            BrainfuckCode;

            Console.WriteLine("Program terminated successfully...");
            Console.ReadLine();
        }
    }
}

HelloWorld is converted from:

[ This program prints "Hello World!" and a newline to the screen, its
  length is 106 active command characters. [It is not the shortest.]

 This loop is a "comment loop", a simple way of adding a comment
  to a BF program such that you don't have to worry about any command
  characters. Any ".", ",", "+", "-", "<" and ">" characters are simply
  ignored, the "[" and "]" characters just have to be balanced. This
  loop and the commands it contains are ignored because the current cell
  defaults to a value of 0; the 0 value causes this loop to be skipped.
]
+++++ +++               Set Cell #0 to 8
[
    >++++               Add 4 to Cell #1; this will always set Cell #1 to 4
    [                   as the cell will be cleared by the loop
        >++             Add 2 to Cell #2
        >+++            Add 3 to Cell #3
        >+++            Add 3 to Cell #4
        >+              Add 1 to Cell #5
        <<<<-           Decrement the loop counter in Cell #1
    ]                   Loop till Cell #1 is zero; number of iterations is 4
    >+                  Add 1 to Cell #2
    >+                  Add 1 to Cell #3
    >-                  Subtract 1 from Cell #4
    >>+                 Add 1 to Cell #6
    [<]                 Move back to the first zero cell you find; this will
                        be Cell #1 which was cleared by the previous loop
    <-                  Decrement the loop Counter in Cell #0
]                       Loop till Cell #0 is zero; number of iterations is 8

The result of this is:
Cell No :   0   1   2   3   4   5   6
Contents:   0   0  72 104  88  32   8
Pointer :   ^

>>.                     Cell #2 has value 72 which is 'H'
>---.                   Subtract 3 from Cell #3 to get 101 which is 'e'
+++++++..+++.           Likewise for 'llo' from Cell #3
>>.                     Cell #5 is 32 for the space
<-.                     Subtract 1 from Cell #4 for 87 to give a 'W'
<.                      Cell #3 was set to 'o' from the end of 'Hello'
+++.------.--------.    Cell #3 for 'rl' and 'd'
>>+.                    Add 1 to Cell #5 gives us an exclamation point
>++.                    And finally a newline from Cell #6

To:

using System;

namespace Brainfuck_Interpreter
{
    class CompiledTemplate
    {
        static byte ReadChar()
        {
            return (byte)Console.ReadKey().KeyChar;
        }

        static void Main(string[] args)
        {
            byte[] buffer = new byte[2048];
            int index = 0;

            while (buffer[index] != 0)
            {
                buffer[index] = ReadChar();
                Console.Write((char)buffer[index]);

                while (buffer[index] != 0)
                {
                    Console.Write((char)buffer[index]);
                }

                buffer[index] = ReadChar();
                Console.Write((char)buffer[index]);
                Console.Write((char)buffer[index]);
                buffer[index] = ReadChar();
                buffer[index] = ReadChar();
                buffer[index] = ReadChar();
                buffer[index]++;
                buffer[index] = ReadChar();
                buffer[index]--;
                buffer[index] = ReadChar();
                index--;
                index++;
                buffer[index] = ReadChar();

                while (buffer[index] != 0)
                {
                }

                Console.Write((char)buffer[index]);
                Console.Write((char)buffer[index]);
            }

            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;

            while (buffer[index] != 0)
            {
                index++;
                buffer[index]++;
                buffer[index]++;
                buffer[index]++;
                buffer[index]++;

                while (buffer[index] != 0)
                {
                    index++;
                    buffer[index]++;
                    buffer[index]++;
                    index++;
                    buffer[index]++;
                    buffer[index]++;
                    buffer[index]++;
                    index++;
                    buffer[index]++;
                    buffer[index]++;
                    buffer[index]++;
                    index++;
                    buffer[index]++;
                    index--;
                    index--;
                    index--;
                    index--;
                    buffer[index]--;
                }

                index++;
                buffer[index]++;
                index++;
                buffer[index]++;
                index++;
                buffer[index]--;
                index++;
                index++;
                buffer[index]++;

                while (buffer[index] != 0)
                {
                    index--;
                }

                index--;
                buffer[index]--;
            }

            index++;
            index++;
            Console.Write((char)buffer[index]);
            index++;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            Console.Write((char)buffer[index]);
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            Console.Write((char)buffer[index]);
            Console.Write((char)buffer[index]);
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            Console.Write((char)buffer[index]);
            index++;
            index++;
            Console.Write((char)buffer[index]);
            index--;
            buffer[index]--;
            Console.Write((char)buffer[index]);
            index--;
            Console.Write((char)buffer[index]);
            buffer[index]++;
            buffer[index]++;
            buffer[index]++;
            Console.Write((char)buffer[index]);
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            Console.Write((char)buffer[index]);
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            buffer[index]--;
            Console.Write((char)buffer[index]);
            index++;
            index++;
            buffer[index]++;
            Console.Write((char)buffer[index]);
            index++;
            buffer[index]++;
            buffer[index]++;
            Console.Write((char)buffer[index]);


            Console.WriteLine("Program terminated successfully...");
            Console.ReadLine();
        }
    }
}

Do note: this programme is only an interpreter/compiler, it does not attempt to verify anything to do with the Brainfuck, except that it is compile-able to C#. If the logic in the Brainfuck is flawed, the logic in the C# programme will be as well.

Anyone is free to reuse this with appropriate attribution, as per usual.

\$\endgroup\$
  • 2
    \$\begingroup\$ So.. it eats comments for lunch then huh \$\endgroup\$ – Mathieu Guindon Oct 15 '15 at 20:05
  • \$\begingroup\$ @Mat'sMug That's how Brainfuck works...yep. :) I might add functionality in the future to support putting those comments in the generated C# code, though. \$\endgroup\$ – Der Kommissar Oct 15 '15 at 20:05
  • 1
    \$\begingroup\$ Awaiting the Roslyn-empowered answer now ;-) \$\endgroup\$ – Mathieu Guindon Oct 15 '15 at 20:14
11
\$\begingroup\$

Dayum, there's some static around here.

Why not make it an instance class?

What you have here is a workflow, where methods have a specific order. This is clearly seen in your Main method. The workflow usually have a state, and static stuff is supposed to be stateless. So I really think an instance class would be good!

Doing this would also mean you wouldn't need all those dirty out parameters, nobody loves them anyway! You could keep these ->

string sourceFile = string.Empty;
string programName = string.Empty;
string destinationFile = string.Empty;
string executableFile = string.Empty;
string dotNetVersion = string.Empty;

as class members, which would make sense. This would also let you inject the dependancies of your logger at one place, instead of having it as a parameter for each of your methods.

You didn't specify your access modifier, you should. It's always easier if they're there, this way you don't have to wonder "Did I forget to make it [internal,private,public] or was private intended?"

Your static Dictionary should be at the top of your class, in order to follow conventions about the file template!

Finally, you've made a method for every step of your "workflow" except for the one where you replace your placeholders with actual values. You could argue 2-3 lines of code don't deserve a method, but I think they do!

In the best case scenario, you should have something like this in your Main method (which, btw, would be named something else if it was in an instance class)

//All the initialization that was here is now in the constructor

AcquireInput();
string brainfuck = LoadBrainfuckCode();
string result = AcquireTemplate("CompiledTemplate.cs");
string brainfuckCode = InterpretBrainfuck(brainfuck);
result = ReplacePlaceHolders(result,brainfuckCode);

WriteToCsFile(_logger, _destinationFile, result);
CompileCsFile(_logger, _dotNetFolders[dotNetVersion], _destinationFile, _executableFile);

_logger.LogInformation("Launching programme...");
Process.Start(_executableFile);
_logger.LogInformation("Programme launched.");

Console.WriteLine("Press enter key to exit...");
Console.ReadLine();

By retyping this, I realized you have a constant in your code that isn't a.. const. You should really extract it, so it's clear does "CompiledTemplate.cs" means.

I figured I didn't need to retype all the methods as you probably get my point, but if there's something not clear about the workflow process and you'd like me to write it all, I'll do it!

\$\endgroup\$
8
\$\begingroup\$

Just a few very minor points...

There's a couple places where you do this sort of thing:

string brainfuck = string.Empty;
using (StreamReader sr = new StreamReader(filename))
{
    brainfuck = sr.ReadToEnd();
}

Which could be more easily written as:

var brainfuck = File.ReadAllText(filename);

StringBuilder's append methods return the StringBuilder so you can chain calls. AppendLine also has a parameterless version.

So this:

sb.AppendLine("}");
sb.AppendLine("");

Can become:

sb.AppendLine("}").AppendLine();

Finally, you don't need string interpolation in your WriteIndents method:

sb.Append($"{new string(' ', indents * 4)}");

Is much clearer as:

sb.Append(' ', indents * 4);
\$\endgroup\$
3
\$\begingroup\$

You should use a for loop in the indent code instead of creating a string on the fly every time.

There are the methods ReadAllText and WriteAllText in System.IO.File that could help shorten some of the code.

You should also consider using the types in System.Reflection.Emit rather than calling the compiler.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.