Introduction
Brainf*ck is a simple Turing-complete language which only has eight commands. Structure of brainf*ck program is simple, it consists of certain number of cells for storing program data and data pointer (dataptr
) that is used for referencing data by program's commands. Size of a cell is not defined but it is usually a single byte or a word for easier handling of EOF marker when dealing with I/O commands.
brainf*ck commands: |
> | moves dataptr to the next cell |
< | moves dataptr to the previous cell |
+ | increments cell referenced by dataptr |
- | decrements cell referenced by dataptr |
[ | test value of cell referenced by dataptr if it is equal to 0 , command moves execution forward to the command after the matching ] command. |
] | moves execution backward to the matching [ command |
. | writes single byte stored in a cell referenced by the data pointer to the standard output |
, | reads single byte from the standard input and stores it in a cell referenced by the data pointer |
Converting a brainf*ck source code to C# equivalent is an easy task using the following mappings:
> | dataptr = dataptr + 1; |
< | dataptr = dataptr - 1; |
+ | cells[dataptr] = cells[dataptr] + 1; |
- | cells[dataptr] = cells[dataptr] - 1; |
[ | for(; cells[dataptr] != 0;) { |
] | } |
. | Console.Write(cells[dataptr]); |
, | cells[dataptr] = Console.Read(); |
Once the brainf*ck program is converted to C# code, C# compiler can be invoked dynamically to produce executable file.
Generating C# Code using CodeDOM
CodeDOM is part of .NET Framework and allows programmers to build source code tree dynamically. Unlike Expression Trees and Lambda Expressions, code models built using CodeDOM can be used to feed compiler and produce assembly files at run-time.
C# code template that is used for converting looks like this:
namespace BfApplication {
using System;
public class Program {
public static void Main() {
short[] cells = new short[1000];
int ptr = 0;
System.IO.Stream @in = Console.OpenStandardInput();
System.IO.Stream @out = Console.OpenStandardOutput();
}
}
}
cells
array represents cells used for storing data by brainf*ck program, ptr
is data pointer, @in
and @out
are standard input and output streams which are used by ,
and .
commands.
To represent this template as CodeDOM graph, we need to create compilation unit which will store namespaces, imports and classes. Template building is done in Compile
method of Compiler
class.
CodeCompileUnit unit = new CodeCompileUnit();
CodeNamespace ns = new CodeNamespace("BfApplication");
unit.Namespaces.Add(ns);
ns.Imports.Add(new CodeNamespaceImport("System"));
CodeTypeDeclaration cs = new CodeTypeDeclaration("Program");
ns.Types.Add(cs);
CodeEntryPointMethod main = new CodeEntryPointMethod();
cs.Members.Add(main);
main.Statements.Add(new CodeVariableDeclarationStatement(typeof(short[]),
"cells", new CodeArrayCreateExpression(typeof(short), cellCount)));
main.Statements.Add(new CodeVariableDeclarationStatement(typeof(int),
"ptr", new CodePrimitiveExpression(0)));
main.Statements.Add(new CodeVariableDeclarationStatement(typeof(Stream),
"in", new CodeMethodInvokeExpression(ConAcc("OpenStandardInput"))));
main.Statements.Add(new CodeVariableDeclarationStatement(typeof(Stream),
"out", new CodeMethodInvokeExpression(ConAcc("OpenStandardOutput"))));
Stack<codestatementcollection> blocks = new Stack<codestatementcollection />();
blocks.Push(main.Statements);
CSharpCodeProvider
class gives the programmer access to C# compiler. GenerateCodeFromCompileUnit
method generates C# code from CodeDOM graph of compilation unit and CompileAssemblyFromDom
method produces assembly file.
if (codeGen != null)
provider.GenerateCodeFromCompileUnit(unit, codeGen,
new CodeGeneratorOptions());
CompilerParameters p = new CompilerParameters(new string[] { "System.dll" },
outputFileName);
p.GenerateExecutable = true;
CompilerResults results = provider.CompileAssemblyFromDom(p, unit);
CodeGeneratorOptions
and CompilerParameters
classes are used to sets various parameters for code generation and compilation.
Brainf*ck Parser
Parsing is implemented by Parse
method and it is using mappings that are described earlier:
for (int i = 0; i < code.Length; i++)
{
if (char.IsWhiteSpace(code[i])) continue;
switch (code[i])
{
case '>': blocks.Peek().Add(IncSt(typeof(int), PtrAcc())); break;
case '<': blocks.Peek().Add(DecSt(typeof(int), PtrAcc())); break;
case '+': blocks.Peek().Add(IncSt(typeof(byte), CellAcc())); break;
case '-': blocks.Peek().Add(DecSt(typeof(byte), CellAcc())); break;
case '.': blocks.Peek().Add(OutExp()); break;
case ',':
blocks.Peek().Add(new CodeAssignStatement(CellAcc(), InExp()));
break;
case '[':
CodeIterationStatement loop = new CodeIterationStatement(
new CodeSnippetStatement(), new CodeBinaryOperatorExpression(
CellAcc(), CodeBinaryOperatorType.IdentityInequality,
new CodePrimitiveExpression(0)), new CodeSnippetStatement());
blocks.Peek().Add(loop);
blocks.Push(loop.Statements);
break;
case ']':
blocks.Pop();
break;
default: break;
}
}
private static CodeMethodReferenceExpression ConAcc(string method)
{
return new CodeMethodReferenceExpression(
new CodeTypeReferenceExpression("Console"), method);
}
private static CodeExpression InExp()
{
return new CodeCastExpression(typeof(short),
new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("in"), "ReadByte")));
}
private static CodeExpression OutExp()
{
return new CodeMethodInvokeExpression(
new CodeMethodReferenceExpression(
new CodeVariableReferenceExpression("out"), "WriteByte"),
new CodeCastExpression(typeof(byte), CellAcc()));
}
private static CodeExpression PtrAcc()
{
return new CodeVariableReferenceExpression("ptr");
}
private static CodeExpression CellAcc()
{
return new CodeArrayIndexerExpression(
new CodeVariableReferenceExpression("cells"), PtrAcc());
}
private static CodeExpression CastExp(Type type, CodeExpression exp)
{
return new CodeCastExpression(type, exp);
}
private static CodeStatement IncSt(Type type, CodeExpression exp)
{
return new CodeAssignStatement(exp, CastExp(type,
new CodeBinaryOperatorExpression(exp, CodeBinaryOperatorType.Add,
new CodePrimitiveExpression(1))));
}
private static CodeStatement DecSt(Type type, CodeExpression exp)
{
return new CodeAssignStatement(exp, CastExp(type,
new CodeBinaryOperatorExpression(exp, CodeBinaryOperatorType.Subtract,
new CodePrimitiveExpression(1))));
}
Error/warning reporting is removed from the code sample.
OnReport
event is used for notification about compiler events like compile errors, warnings and state of compilation process.
History
- 15th November, 2011: Initial post