Whitespace Language Compiler for .NET

February 16, 2017 ยท View on GitHub

using System; using System.IO; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit;

using Whitespace.Utils;

namespace Whitespace { public delegate void CompiledMethod(TextReader input, TextWriter output);

public sealed class Compiler {
    #region Prefetched Reflection Info
    private static readonly Type stackType = typeof(Stack<int>);
    private static readonly Type heapType = typeof(Heap<int>);
    private static readonly Type helperType = typeof(Helper);
    private static readonly Type textReaderType = typeof(TextReader);
    private static readonly Type textWriterType = typeof(TextWriter);

    private static readonly Type[] intParams = new[] { typeof(int) };
    private static readonly Type[] charParams = new[] { typeof(char) };
    private static readonly Type[] textReaderParams = new[] { typeof(TextReader) };

    private static readonly MethodInfo stackPopFn = stackType.GetMethod("Pop", Type.EmptyTypes);
    private static readonly MethodInfo stackPushFn = stackType.GetMethod("Push", intParams);
    private static readonly PropertyInfo heapIndexer = heapType.GetProperty("Item", intParams);
    private static readonly MethodInfo heapStoreFn = heapType.GetMethod("Store", intParams);
    private static readonly MethodInfo heapStoreSpecificFn = heapIndexer.GetSetMethod();
    private static readonly MethodInfo heapRestoreFn = heapIndexer.GetGetMethod();
    private static readonly MethodInfo readCharFn = textReaderType.GetMethod("Read", Type.EmptyTypes);
    private static readonly MethodInfo readIntFn = helperType.GetMethod("ReadInteger", textReaderParams);
    private static readonly MethodInfo writeCharFn = textWriterType.GetMethod("Write", charParams);
    private static readonly MethodInfo writeIntFn = textWriterType.GetMethod("Write", intParams);
    #endregion

    #region Local Variables
    private readonly Parser parser;
    private readonly ILGenerator il;

    private readonly Dictionary<int, Label> labels = new Dictionary<int, Label>();
    private readonly List<Label> returnLabels = new List<Label>();

    private LocalBuilder swap1, swap2, cbStack, heap;
    private Label returnSelLabel, defaultReturnLabel;
    #endregion

    #region Constructors
    private Compiler() { }

    public Compiler(Parser parser, ILGenerator il) {
        this.il = il;
        this.parser = parser;
    }
    #endregion

    #region Compiler
    private void Compile() {
        il.Emit(OpCodes.Nop);

        swap1 = il.DeclareLocal(typeof(int));
        if(parser.hasSwap)
            swap2 = il.DeclareLocal(typeof(int));

        if(parser.hasCbStack) {
            cbStack = il.DeclareLocal(stackType);
            returnSelLabel = il.DefineLabel();
            defaultReturnLabel = il.DefineLabel();

            il.Emit(OpCodes.Newobj, stackType.GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Stloc, cbStack);
        }

        heap = il.DeclareLocal(heapType);

        il.Emit(OpCodes.Newobj, heapType.GetConstructor(Type.EmptyTypes));
        il.Emit(OpCodes.Stloc, heap);

        il.Emit(OpCodes.Nop);
        foreach(OpParam op in parser.Instructions)
            Emit(op.op, op.parameter);

        if(parser.hasCbStack) {
            il.Emit(OpCodes.Br, defaultReturnLabel);

            il.MarkLabel(returnSelLabel);
            il.Emit(OpCodes.Ldloc, cbStack);
            il.Emit(OpCodes.Call, stackPopFn);
            returnLabels.Add(defaultReturnLabel);
            il.Emit(OpCodes.Switch, returnLabels.ToArray());

            il.MarkLabel(defaultReturnLabel);
            il.Emit(OpCodes.Nop);
        }

        il.Emit(OpCodes.Ret);
    }

    private void Emit(InstOpCode op, int parameter) {
        switch(op) {
            case InstOpCode.Pus:
                il.Emit(OpCodes.Ldc_I4, parameter);
                break;
            case InstOpCode.Dup:
                il.Emit(OpCodes.Dup);
                break;
            case InstOpCode.Swp:
                il.Emit(OpCodes.Stloc, swap2);
                EmitSwap(swap2);
                break;
            case InstOpCode.Pop:
                il.Emit(OpCodes.Pop);
                break;

            case InstOpCode.Add:
                il.Emit(OpCodes.Add);
                break;
            case InstOpCode.Sub:
                il.Emit(OpCodes.Sub);
                break;
            case InstOpCode.Mul:
                il.Emit(OpCodes.Mul);
                break;
            case InstOpCode.Div:
                il.Emit(OpCodes.Div);
                break;
            case InstOpCode.Mod:
                il.Emit(OpCodes.Rem);
                break;

            case InstOpCode.Sto:
                EmitHeapStoreFn();
                break;
            case InstOpCode.Ret:
                EmitSwap(heap);
                il.Emit(OpCodes.Call, heapRestoreFn);
                break;

            case InstOpCode.Mrk:
                il.MarkLabel(GetLabel(parameter));
                break;
            case InstOpCode.Cas:
                int returnId;
                Label returnLabel = GetReturnLabel(out returnId);
                il.Emit(OpCodes.Ldloc, cbStack);
                il.Emit(OpCodes.Ldc_I4, returnId);
                il.Emit(OpCodes.Call, stackPushFn);
                il.Emit(OpCodes.Br, GetLabel(parameter));
                il.MarkLabel(returnLabel);
                break;
            case InstOpCode.Jmp:
                il.Emit(OpCodes.Br, GetLabel(parameter));
                break;
            case InstOpCode.Jmz:
                il.Emit(OpCodes.Brfalse, GetLabel(parameter));
                break;
            case InstOpCode.Jmn:
                il.Emit(OpCodes.Ldc_I4_0);
                il.Emit(OpCodes.Clt);
                il.Emit(OpCodes.Brtrue, GetLabel(parameter));
                break;
            case InstOpCode.Ens:
                il.Emit(OpCodes.Br, returnSelLabel);
                break;
            case InstOpCode.End:
                il.Emit(OpCodes.Ret);
                break;

            case InstOpCode.Wrc:
                EmitSwap(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Callvirt, writeCharFn);
                break;
            case InstOpCode.Wrn:
                EmitSwap(OpCodes.Ldarg_1);
                il.Emit(OpCodes.Callvirt, writeIntFn);
                break;
            case InstOpCode.Rec:
                EmitSwap(heap);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Callvirt, readCharFn);
                il.Emit(OpCodes.Call, heapStoreSpecificFn);
                break;
            case InstOpCode.Ren:
                EmitSwap(heap);
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, readIntFn);
                il.Emit(OpCodes.Call, heapStoreSpecificFn);
                break;
        }
    }

    private void EmitHeapStoreFn() {
        EmitSwap(heap);
        il.Emit(OpCodes.Call, heapStoreFn);
    }

    private void EmitSwap(OpCode ldOpcode) {
        il.Emit(OpCodes.Stloc, swap1);
        il.Emit(ldOpcode);
        il.Emit(OpCodes.Ldloc, swap1);
    }

    private void EmitSwap(LocalBuilder loc) {
        il.Emit(OpCodes.Stloc, swap1);
        il.Emit(OpCodes.Ldloc, loc);
        il.Emit(OpCodes.Ldloc, swap1);
    }
    #endregion

    #region Helpers
    private Label GetLabel(int id) {
        Label label;
        if(!labels.TryGetValue(id, out label)) {
            label = il.DefineLabel();
            labels.Add(id, label);
        }
        return label;
    }

    private Label GetReturnLabel(out int returnId) {
        Label label = il.DefineLabel();
        returnId = returnLabels.Count;
        returnLabels.Add(label);
        return label;
    }
    #endregion

    #region Public API
    // Generated method's parameters: Compiled(TextReader reader, TextWriter writer);
    public static void Compile(string source, ILGenerator il) {
        if(source == null)
            throw new ArgumentNullException("source");
        if(il == null)
            throw new ArgumentNullException("il");

        Parser parser = new Parser(source);
        Compiler compiler = new Compiler(parser, il);
        parser.Parse();
        compiler.Compile();
    }

    public static CompiledMethod Compile(string source) {
        if(source == null)
            throw new ArgumentNullException("source");

        DynamicMethod method = new DynamicMethod("_Compiled_", null, new[] {
            textReaderType, textWriterType
        });

        method.DefineParameter(0, ParameterAttributes.In, "input");
        method.DefineParameter(1, ParameterAttributes.In, "output");

        Parser parser = new Parser(source);
        Compiler compiler = new Compiler(parser, method.GetILGenerator());

        parser.Parse();
        compiler.Compile();

        return method.CreateDelegate(typeof(CompiledMethod)) as CompiledMethod;
    }
    #endregion
}

}