B
OK, hier der erste lauffähige Versuch. CompilingEvaluator nimmt die Rolle des Interpreters ein, d.h. starten mit new TestSession<Brainfuck.CompilingEvaluator>().Run(); . Das Interface S musste ich public machen. Ob es auch ohne geht, weiß ich nicht, dazu müsste man den Code ja in dasselbe Assembly generieren.
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
namespace Brainfuck
{
class SimpleCompiler
{
private static void GenerateCode(string program, MethodBuilder mBuilder)
{
ILGenerator ilgen = mBuilder.GetILGenerator();
// Note: the types can't be changed arbitrarily. The Load and Store instructions have to be adjusted as well.
Type pointerType = typeof(uint);
Type dataType = typeof(short), dataArrayType = typeof(short[]);
int dataSize = 32768;
// Declare and initialize data pointer
LocalBuilder pointer = ilgen.DeclareLocal(pointerType, false);
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Stloc, pointer);
// Declare and initialize data array
LocalBuilder dataArray = ilgen.DeclareLocal(dataArrayType, false);
ilgen.Emit(OpCodes.Ldc_I4, dataSize);
ilgen.Emit(OpCodes.Newarr, dataType);
ilgen.Emit(OpCodes.Stloc, dataArray);
// Declare parameter
ParameterBuilder systemParam = mBuilder.DefineParameter(1, ParameterAttributes.In, "s");
// Manage labels:
// [ creates two labels: one for the current instruction and one for the instruction after the loop
// ] jumps to the corresponding top of stack and marks the other one
Stack<Label> labelStack = new Stack<Label>();
for (int i = 0; i < program.Length; ++i)
{
switch (program[i])
{
case '<':
// Decrease data pointer
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Sub);
ilgen.Emit(OpCodes.Stloc, pointer);
break;
case '>':
// Increase data pointer
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Add);
ilgen.Emit(OpCodes.Stloc, pointer);
break;
case '+':
// Increase current data
// Prepare Store
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
// Load current data
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldelem_I2);
// Increase
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Add);
// Now the result is on top of the stack, store it
ilgen.Emit(OpCodes.Stelem_I2);
break;
case '-':
// Decrease current data
// Prepare Store
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
// Load current data
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldelem_I2);
// Increase
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Sub);
// Now the result is on top of the stack, store it
ilgen.Emit(OpCodes.Stelem_I2);
break;
case '[':
{
// Enter loop if current data != 0, skip loop otherwise
Label thisInstruction = ilgen.DefineLabel();
Label followingInstruction = ilgen.DefineLabel();
// Push two labels:
// thisInstruction: label to which the corresponding ] has to jump
// followingInstruction: label of the instruction after the corresponding ]
// only thisInstruction will be marked here
labelStack.Push(thisInstruction);
labelStack.Push(followingInstruction);
ilgen.MarkLabel(thisInstruction);
// Load current data
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldelem_I2);
// Compare with 0 and branch
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Beq, followingInstruction);
}
break;
case ']':
{
// Jump to the beginning of the loop
// (Alternative implementation: Exit loop if current data == 0, jump to the beginning otherwise)
// Pop two labels:
// nextInstruction is the instruction after the loop, marked here
// loopBegin is the label that was marked when translating the corresponding [
Label nextInstruction = labelStack.Pop();
Label loopBegin = labelStack.Pop();
ilgen.Emit(OpCodes.Br, loopBegin);
ilgen.MarkLabel(nextInstruction);
}
break;
case '.':
{
// Write current data:
// Call s.W(dataArray[pointer])
MethodInfo writeMethod = typeof(S).GetMethod("W");
ilgen.Emit(OpCodes.Ldarg, 1);
// Load current data
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
ilgen.Emit(OpCodes.Ldelem_I2);
// Call shell.W
ilgen.Emit(OpCodes.Call, writeMethod);
}
break;
case ',':
{
// Read into current data:
// Call s.R() and assign the result to dataArray[pointer]
MethodInfo readMethod = typeof(S).GetMethod("R");
// Prepare Store
ilgen.Emit(OpCodes.Ldloc, dataArray);
ilgen.Emit(OpCodes.Ldloc, pointer);
// Call shell.R()
ilgen.Emit(OpCodes.Ldarg, 1);
ilgen.Emit(OpCodes.Call, readMethod);
// Assign result
ilgen.Emit(OpCodes.Stelem_I2);
}
break;
}
}
ilgen.Emit(OpCodes.Ret);
}
public static void CompileAndRun(S system)
{
AssemblyName asmName = new AssemblyName("Brainfuck");
AssemblyBuilder asmBuilder =
System.Threading.Thread.GetDomain().DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("Brainfuck.exe");
TypeBuilder typeBuilder = modBuilder.DefineType(
"Brainfuck",
TypeAttributes.Public | TypeAttributes.Class);
MethodBuilder runMethodBuilder = typeBuilder.DefineMethod(
"Run",
MethodAttributes.HideBySig | MethodAttributes.Public,
typeof(void),
new Type[] { typeof(S) });
GenerateCode(system.P, runMethodBuilder);
Type brainfuckTempType = typeBuilder.CreateType();
MethodInfo runMethod = brainfuckTempType.GetMethod("Run");
ConstructorInfo ctor = brainfuckTempType.GetConstructor(Type.EmptyTypes);
Object compiledProgram = ctor.Invoke(new object[] {});
runMethod.Invoke(compiledProgram, new object[] { system });
}
}
class CompilingEvaluator
{
public void R(S s)
{
SimpleCompiler.CompileAndRun(s);
}
}
}
hustbaer: Das sagst du jetzt! Ich hatte mir das erst auch ein bisschen anders vorgestellt, nicht daran gedacht, dass das ja eine Stackmaschine ist.