module tlang.compiler.codegen.emit.dgen;

import tlang.compiler.codegen.emit.core : CodeEmitter;
import tlang.compiler.typecheck.core;
import std.container.slist : SList;
import tlang.compiler.codegen.instruction;
import std.stdio;
import std.file;
import std.conv : to;
import std.string : cmp;
import tlang.misc.logging;
import std.range : walkLength;
import std.string : wrap;
import std.process : spawnProcess, Pid, ProcessException, wait;
import tlang.compiler.typecheck.dependency.core : Context, FunctionData, DNode;
import tlang.compiler.codegen.mapper.core;
import tlang.compiler.symbols.data : SymbolType, Variable, Function, VariableParameter;
import tlang.compiler.symbols.check : getCharacter;
import tlang.misc.utils : Stack;
import tlang.compiler.symbols.typing.core;
import tlang.compiler.configuration : CompilerConfiguration;
import tlang.compiler.symbols.containers : Module;
import std.format : format;
import std.datetime.stopwatch : StopWatch, AutoStart;
import std.datetime.stopwatch : Duration, dur;

public final class DCodeEmitter : CodeEmitter
{
    /** 
     * Whether or not symbol mappi g should
     * apply to identifiers
     */
    private bool symbolMapping;

    // NOTE: In future store the mapper in the config please
    this(TypeChecker typeChecker, File file, CompilerConfiguration config, SymbolMapper mapper)
    {
        super(typeChecker, file, config, mapper);

        // By default symbols will be mapped
        enableSymbolMapping();
    }

    /** 
     * Enables symbol mapping
     */
    private void enableSymbolMapping()
    {
     	this.symbolMapping = true;
    }

    /** 
     * Disables symbol mapping
     */
    private void disableSymbolMapping()
    {
     	this.symbolMapping = false;
    }

    private ulong transformDepth = 0;

    private string genTabs(ulong count)
    {
        string tabStr;

        /* Only generate tabs if enabled in compiler config */
        if(config.getConfig("dgen:pretty_code").getBoolean())
        {
            for(ulong i = 0; i < count; i++)
            {
                tabStr~="\t";
            }
        }
        
        return tabStr;
    }

    /** 
     * Given an instance of a Type this will transform it to a string
     *
     * Params:
     *   typeIn = The Type to transform
     *
     * Returns:  The string representation of the transformed type
     */
    public string typeTransform(Type typeIn)
    {
        string stringRepr;

        // TODO: Some types will ident transform

        /* Pointer types */
        if(cast(Pointer)typeIn)
        {
            /* Extract type being pointed to */
            Pointer pointerType = cast(Pointer)typeIn;       
            Type referType = pointerType.getReferredType();

            /* The type is then `transform(<refertype>)*` */
            return typeTransform(referType)~"*";
        }
        /* Integral types transformation */
        else if(cast(Integer)typeIn)
        {
            Integer integralType = cast(Integer)typeIn;

            /* u<>_t or <>_t (Determine signedness) */
            string typeString = integralType.isSigned() ? "int" : "uint";

            /* Width of integer */
            typeString ~= to!(string)(integralType.getSize()*8);

            /* Trailing `_t` */
            typeString ~= "_t";

            return typeString;
        }
        /* Void type */
        else if(cast(Void)typeIn)
        {
            return "void";
        }
        /* Stack-based array type */
        else if(cast(StackArray)typeIn)
        {
            // TODO: Still implement stakc-based arrays
            // we won't be able to tyoe transform just here
            // ... as we need <componentType> <varName>[<arraySize>]
            // ... hence this must be performed in avriable declaration
            StackArray stackArray = cast(StackArray)typeIn;
            
            return typeTransform(stackArray.getComponentType());
            // return "KAK TODO";
        }

        ERROR("Type transform unimplemented for type '"~to!(string)(typeIn)~"'");
        assert(false);
        // return stringRepr;
    }


    public override string transform(const Instruction instruction)
    {
        writeln("\n");
        DEBUG("transform(): "~to!(string)(instruction));
        transformDepth++;

        // The data to emit
        string emmmmit;

        // At any return decrement the depth
        scope(exit)
        {
            transformDepth--;
        }

        /* VariableAssignmentInstr */
        if(cast(VariableAssignmentInstr)instruction)
        {
            DEBUG("type: VariableAssignmentInstr");

            VariableAssignmentInstr varAs = cast(VariableAssignmentInstr)instruction;
            Context context = varAs.getContext();

            DEBUG("Is ContextNull?: "~to!(string)(context is null));
            DEBUG("Wazza contect: "~to!(string)(context.container));
            auto typedEntityVariable = typeChecker.getResolver().resolveBest(context.getContainer(), varAs.varName); //TODO: Remove `auto`
            DEBUG("Hi"~to!(string)(varAs));
            DEBUG("Hi"~to!(string)(varAs.data));
            DEBUG("Hi"~to!(string)(varAs.data.getInstrType()));

            // NOTE: For tetsing issue #94 coercion (remove when done)
            string typeName = (cast(Type)varAs.data.getInstrType()).getName();
            DEBUG("VariableAssignmentInstr: The data to assign's type is: "~typeName);


            /* If it is not external */
            if(!typedEntityVariable.isExternal())
            {
                // FIXME: Set proper scope type
                string renamedSymbol = mapper.map(typedEntityVariable, ScopeType.GLOBAL);

                emmmmit = renamedSymbol~" = "~transform(varAs.data)~";";
            }
            /* If it is external */
            else
            {
                emmmmit = typedEntityVariable.getName()~" = "~transform(varAs.data)~";";
            }
        }
        /* VariableDeclaration */
        else if(cast(VariableDeclaration)instruction)
        {
            DEBUG("type: VariableDeclaration");

            VariableDeclaration varDecInstr = cast(VariableDeclaration)instruction;
            Context context = varDecInstr.getContext();

            Variable typedEntityVariable = cast(Variable)typeChecker.getResolver().resolveBest(context.getContainer(), varDecInstr.varName); //TODO: Remove `auto`

            /* If the variable is not external */
            if(!typedEntityVariable.isExternal())
            {
                //NOTE: We should remove all dots from generated symbol names as it won't be valid C (I don't want to say C because
                // a custom CodeEmitter should be allowed, so let's call it a general rule)
                //
                //simple_variables.x -> simple_variables_x
                //NOTE: We may need to create a symbol table actually and add to that and use that as these names
                //could get out of hand (too long)
                // NOTE: Best would be identity-mapping Entity's to a name
                // FIXME: Set proper scope type
                string renamedSymbol = mapper.map(typedEntityVariable, ScopeType.GLOBAL);


                // Check if the type is a stack-based array
                // ... if so then take make symbolName := `<symbolName>[<stackArraySize>]`
                if(cast(StackArray)varDecInstr.varType)
                {
                    StackArray stackArray = cast(StackArray)varDecInstr.varType;
                    renamedSymbol~="["~to!(string)(stackArray.getAllocatedSize())~"]";
                }

                // Check to see if this declaration has an assignment attached
                if(typedEntityVariable.getAssignment())
                {
                    Value varAssInstr = varDecInstr.getAssignmentInstr();
                    DEBUG("VarDec(with assignment): My assignment type is: "~varAssInstr.getInstrType().getName());

                    // Generate the code to emit
                    emmmmit = typeTransform(cast(Type)varDecInstr.varType)~" "~renamedSymbol~" = "~transform(varAssInstr)~";";
                }
                else
                {
                    emmmmit = typeTransform(cast(Type)varDecInstr.varType)~" "~renamedSymbol~";";
                }
            }
            /* If the variable is external */
            else
            {
                emmmmit = "extern "~typeTransform(cast(Type)varDecInstr.varType)~" "~typedEntityVariable.getName()~";";
            }
        }
        /* LiteralValue */
        else if(cast(LiteralValue)instruction)
        {
            DEBUG("type: LiteralValue");

            LiteralValue literalValueInstr = cast(LiteralValue)instruction;

            emmmmit = to!(string)(literalValueInstr.getLiteralValue());
        }
        /* FetchValueVar */
        else if(cast(FetchValueVar)instruction)
        {
            DEBUG("type: FetchValueVar");

            FetchValueVar fetchValueVarInstr = cast(FetchValueVar)instruction;
            Context context = fetchValueVarInstr.getContext();

            Variable typedEntityVariable = cast(Variable)typeChecker.getResolver().resolveBest(context.getContainer(), fetchValueVarInstr.varName); //TODO: Remove `auto`

            /* If it is not external */
            if(!typedEntityVariable.isExternal())
            {
                //TODO: THis is giving me kak (see issue #54), it's generating name but trying to do it for the given container, relative to it
                //TODO: We might need a version of generateName that is like generatenamebest (currently it acts like generatename, within)

                // FIXME: Set proper scope type
                string renamedSymbol = mapper.map(typedEntityVariable, ScopeType.GLOBAL);

                emmmmit = renamedSymbol;
            }
            /* If it is external */
            else
            {
                emmmmit = typedEntityVariable.getName();
            }
        }
        /* BinOpInstr */
        else if(cast(BinOpInstr)instruction)
        {
            DEBUG("type: BinOpInstr");

            BinOpInstr binOpInstr = cast(BinOpInstr)instruction;

            // TODO: I like having `lhs == rhs` for `==` or comparators but not spaces for `lhs+rhs`

            /**
             * C compiler's do this thing where:
             *
             * If `<a>` is a pointer and `<b>` is an integer then the
             * following pointer arithmetic is allowed:
             *
             * int* a = (int*)2;
             * a = a + b;
             *
             * But it's WRONG if you do
             *
             * a = a + (int*)b;
             *
             * Even though it makes logical sense coercion wise.
             *
             * Therefore we need to check such a case and yank
             * the cast out me thinks.
             * 
             * See issue #140 (https://deavmi.assigned.network/git/tlang/tlang/issues/140#issuecomment-1892)
             */
            Type leftHandOpType = (cast(Value)binOpInstr.lhs).getInstrType();
            Type rightHandOpType = (cast(Value)binOpInstr.rhs).getInstrType();

            if(typeChecker.isPointerType(leftHandOpType))
            {
                // Sanity check the other side should have been coerced to CastedValueInstruction
                CastedValueInstruction cvInstr = cast(CastedValueInstruction)binOpInstr.rhs;
                assert(cvInstr);

                DEBUG("CastedValueInstruction relax setting: Da funk RIGHT ");

                // Relax the CV-instr to prevent it from emitting explicit cast code
                cvInstr.setRelax(true);
            }
            else if(typeChecker.isPointerType(rightHandOpType))
            {
                // Sanity check the other side should have been coerced to CastedValueInstruction
                CastedValueInstruction cvInstr = cast(CastedValueInstruction)binOpInstr.lhs;
                assert(cvInstr);

                DEBUG("CastedValueInstruction relax setting: Da funk LEFT ");

                // Relax the CV-instr to prevent it from emitting explicit cast code
                cvInstr.setRelax(true);
            }

            emmmmit = transform(binOpInstr.lhs)~to!(string)(getCharacter(binOpInstr.operator))~transform(binOpInstr.rhs);
        }
        /* FuncCallInstr */
        else if(cast(FuncCallInstr)instruction)
        {
            DEBUG("type: FuncCallInstr");

            FuncCallInstr funcCallInstr = cast(FuncCallInstr)instruction;
            Context context = funcCallInstr.getContext();
            assert(context);

            Function functionToCall = cast(Function)typeChecker.getResolver().resolveBest(context.getContainer(), funcCallInstr.functionName); //TODO: Remove `auto`

            // TODO: SymbolLookup?

            string emit = functionToCall.getName()~"(";

            //TODO: Insert argument passimng code here
            //NOTE: Typechecker must have checked for passing arguments to a function that doesn't take any, for example

            //NOTE (Behaviour): We may want to actually have an preinliner for these arguments
            //such to enforce a certain ordering. I believe this should be done in the emitter stage,
            //so it is best placed here
            if(functionToCall.hasParams())
            {
                Value[] argumentInstructions = funcCallInstr.getEvaluationInstructions();
                string argumentString;
                
                for(ulong argIdx = 0; argIdx < argumentInstructions.length; argIdx++)
                {
                    Value currentArgumentInstr = argumentInstructions[argIdx];
                    argumentString~=transform(currentArgumentInstr);

                    if(argIdx != (argumentInstructions.length-1))
                    {
                        argumentString~=", ";
                    }
                }

                emit~=argumentString;
            }

            emit ~= ")";

            // If this is a statement-level function call then tack on a `;`
            if(funcCallInstr.isStatementLevel())
            {
                emit ~= ";";
            }

            emmmmit = emit;
        }
        /* ReturnInstruction */
        else if(cast(ReturnInstruction)instruction)
        {
            DEBUG("type: ReturnInstruction");

            ReturnInstruction returnInstruction = cast(ReturnInstruction)instruction;
            Context context = returnInstruction.getContext();
            assert(context);

            /* Get the return expression instruction */
            Value returnExpressionInstr = returnInstruction.getReturnExpInstr();

            emmmmit = "return "~transform(returnExpressionInstr)~";";
        }
        /**
        * If statements (IfStatementInstruction)
        */
        else if(cast(IfStatementInstruction)instruction)
        {
            IfStatementInstruction ifStatementInstruction = cast(IfStatementInstruction)instruction;

            BranchInstruction[] branchInstructions = ifStatementInstruction.getBranchInstructions();
            DEBUG("Holla"~to!(string)(branchInstructions));

            string emit;

            for(ulong i = 0; i < branchInstructions.length; i++)
            {
                BranchInstruction curBranchInstr = branchInstructions[i];

                if(curBranchInstr.hasConditionInstr())
                {
                    Value conditionInstr = cast(Value)curBranchInstr.getConditionInstr();

                    string hStr = (i == 0) ? "if" : genTabs(transformDepth)~"else if";

                    emit~=hStr~"("~transform(conditionInstr)~")\n";

                    emit~=genTabs(transformDepth)~"{\n";

                    foreach(Instruction branchBodyInstr; curBranchInstr.getBodyInstructions())
                    {
                        emit~=genTabs(transformDepth)~"\t"~transform(branchBodyInstr)~"\n";
                    }

                    emit~=genTabs(transformDepth)~"}\n";
                }
                else
                {
                    emit~=genTabs(transformDepth)~"else\n";

                    emit~=genTabs(transformDepth)~"{\n";

                    foreach(Instruction branchBodyInstr; curBranchInstr.getBodyInstructions())
                    {
                        emit~=genTabs(transformDepth)~"\t"~transform(branchBodyInstr)~"\n";
                    }

                    emit~=genTabs(transformDepth)~"}\n";
                }
            }

            emmmmit = emit;
        }
        /**
        * While loops (WhileLoopInstruction)
        *
        * TODO: Add do-while check
        */
        else if(cast(WhileLoopInstruction)instruction)
        {
            WhileLoopInstruction whileLoopInstr = cast(WhileLoopInstruction)instruction;

            BranchInstruction branchInstr = whileLoopInstr.getBranchInstruction();
            Value conditionInstr = branchInstr.getConditionInstr();
            Instruction[] bodyInstructions = branchInstr.getBodyInstructions();

            string emit;

            /* Generate the `while(<expr>)` and opening curly brace */
            emit = "while("~transform(conditionInstr)~")\n";
            emit~=genTabs(transformDepth)~"{\n"; 

            /* Transform each body statement */
            foreach(Instruction curBodyInstr; bodyInstructions)
            {
                emit~=genTabs(transformDepth)~"\t"~transform(curBodyInstr)~"\n";
            }

            /* Closing curly brace */
            emit~=genTabs(transformDepth)~"}";

            emmmmit = emit;
        }
        /**
        * For loops (ForLoopInstruction)
        */
        else if(cast(ForLoopInstruction)instruction)
        {
            ForLoopInstruction forLoopInstr = cast(ForLoopInstruction)instruction;

            BranchInstruction branchInstruction = forLoopInstr.getBranchInstruction();
            Value conditionInstr = branchInstruction.getConditionInstr();
            Instruction[] bodyInstructions = branchInstruction.getBodyInstructions();

            string emit = "for(";

            // Emit potential pre-run instruction
            emit ~= forLoopInstr.hasPreRunInstruction() ? transform(forLoopInstr.getPreRunInstruction()) : ";";

            // Condition
            emit ~= transform(conditionInstr)~";";

            // NOTE: We are leaving the post-iteration blank due to us including it in the body
            // TODO: We can hoist bodyInstructions[$] maybe if we want to generate it as C-for-loops
            // if(forLoopInstr.hasPostIterationInstruction())
            emit ~= ")\n";

            // Open curly (begin body)
            emit~=genTabs(transformDepth)~"{\n"; 

            /* Transform each body statement */
            foreach(Instruction curBodyInstr; bodyInstructions)
            {
                emit~=genTabs(transformDepth)~"\t"~transform(curBodyInstr)~"\n";
            }

            // Close curly (body end)
            emit~=genTabs(transformDepth)~"}"; 

            emmmmit = emit;
        }
        /**
        * Unary operators (UnaryOpInstr)
        */
        else if(cast(UnaryOpInstr)instruction)
        {
            UnaryOpInstr unaryOpInstr = cast(UnaryOpInstr)instruction;
            Value operandInstruction = cast(Value)unaryOpInstr.getOperand();
            assert(operandInstruction);

            string emit;
            
            /* The operator's symbol */
            emit ~= getCharacter(unaryOpInstr.getOperator());

            /* Transform the operand */
            emit ~= transform(operandInstruction);

            emmmmit = emit;
        }
        /**
        * Pointer dereference assignment (PointerDereferenceAssignmentInstruction)
        */
        else if(cast(PointerDereferenceAssignmentInstruction)instruction)
        {
            PointerDereferenceAssignmentInstruction pointerDereferenceAssignmentInstruction = cast(PointerDereferenceAssignmentInstruction)instruction;
            Value lhsPtrAddrExprInstr = pointerDereferenceAssignmentInstruction.getPointerEvalInstr();
            assert(lhsPtrAddrExprInstr);
            Value rhsAssExprInstr = pointerDereferenceAssignmentInstruction.getAssExprInstr();
            assert(rhsAssExprInstr);

            string emit;

            /* Star followed by transformation of the pointer address expression */
            string starsOfLiberty;
            for(ulong i = 0; i < pointerDereferenceAssignmentInstruction.getDerefCount(); i++)
            {
                starsOfLiberty ~= "*";
            }
            emit ~= starsOfLiberty~"("~transform(lhsPtrAddrExprInstr)~")";

            /* Assignment operator follows */
            emit ~= " = ";

            /* Expression to be assigned on the right hand side */
            emit ~= transform(rhsAssExprInstr)~";";


            emmmmit = emit;
        }
        /**
        * Discard instruction (DiscardInstruction)
        */
        else if(cast(DiscardInstruction)instruction)
        {
            DiscardInstruction discardInstruction = cast(DiscardInstruction)instruction;
            Value valueInstruction = discardInstruction.getExpressionInstruction();

            string emit;

            /* Transform the expression */
            emit ~= transform(valueInstruction)~";";

            emmmmit = emit;
        }
        /**
        * Type casting instruction (CastedValueInstruction)
        */
        else if(cast(CastedValueInstruction)instruction)
        {
            CastedValueInstruction castedValueInstruction = cast(CastedValueInstruction)instruction;
            Type castingTo = castedValueInstruction.getCastToType();

            // TODO: Dependent on type being casted one must handle different types, well differently (as is case for atleast OOP)

            Value uncastedInstruction = castedValueInstruction.getEmbeddedInstruction();


            string emit;


            /**
             * Issue #140
             *
             * If relaxed then just emit the uncasted instruction
             */
            if(castedValueInstruction.isRelaxed())
            {
                /* The original expression */
                emit ~= transform(uncastedInstruction);
            }
            else
            {
                /* Handling of primitive types */
                if(cast(Primitive)castingTo)
                {
                    /* Add the actual cast */
                    emit ~= "("~typeTransform(castingTo)~")";

                    /* The expression being casted */
                    emit ~= transform(uncastedInstruction);
                }
                else
                {
                    // TODO: Implement this
                    ERROR("Non-primitive type casting not yet implemented");
                    assert(false);
                }
            }

            emmmmit = emit;
        }
        /** 
         * Array indexing (pointer-based arrays)
         *
         * Handles `myArray[<index>]` where `myArray` is
         * of type `int[]` (i.e. `int*`)
         */
        else if(cast(ArrayIndexInstruction)instruction)
        {
            ArrayIndexInstruction arrAssInstr = cast(ArrayIndexInstruction)instruction;

            ERROR("TODO: Implement Pointer-array index emit");

            DEBUG("ArrayInstr: "~arrAssInstr.getIndexedToInstr().toString());
            DEBUG("ArrayIndexInstr: "~to!(string)(arrAssInstr.getIndexInstr()));

            
            /* Obtain the entity being indexed */
            Value indexed = arrAssInstr.getIndexedToInstr();

            /* Obtain the index */
            Value index = arrAssInstr.getIndexInstr();

            
            /** 
             * Emit *(<indexedEval>+<index>)
             */
            string emit;
            emit ~= "*(";

            
            emit ~= transform(indexed);
            emit ~= "+";
            emit ~= transform(index);
            emit ~= ")";



            // return "*("~transform(indexed)~"+"~transform(index)~")";
            emmmmit = emit;
        }
        /** 
         * Array assignments (pointer-based arrays)
         *
         * Handles `myArray[<index>] = <expression>` where `myArray`
         * is of type `int[]` (i.e. `int*`)
         */
        else if(cast(ArrayIndexAssignmentInstruction)instruction)
        {
            ArrayIndexAssignmentInstruction arrayAssignmentInstr = cast(ArrayIndexAssignmentInstruction)instruction;

            /** 
             * Obtain the array pointer evaluation
             */
            ArrayIndexInstruction arrayPtrEval = arrayAssignmentInstr.getArrayPtrEval();

            // NOTE: See above
            // /** 
            //  * Obtain the index being assigned at
            //  */
            // Value index = arrayAssignmentInstr.getIndexInstr();

            /** 
             * Obtain the expression being assigned
             */
            Value assignmentInstr = arrayAssignmentInstr.getAssignmentInstr();


            /** 
             * Emit *(<arrayPtrVal>+<indexInstr>) = <assignmentInstr>;
             */
            string emit;
            // NOTE: Below is done by ArrayIndexInstruction
            // emit ~= "*(";
            // emit ~= transform(arrayPtrEval);
            // emit ~= "+";
            // emit ~= transform(index);
            // emit ~= ")";
            emit ~= transform(arrayPtrEval);

            emit ~= " = ";
            emit ~= transform(assignmentInstr);
            emit ~= ";";


            emmmmit = emit; 
        }
        /** 
         * Array indexing (stack-based arrays)
         *
         * Handles `myArray[<index>]` where `myArray` is
         * of type `int[<size>]` (i.e. a stack-array)
         */
        else if(cast(StackArrayIndexInstruction)instruction)
        {
            StackArrayIndexInstruction stackArrInstr = cast(StackArrayIndexInstruction)instruction;
            Context context = stackArrInstr.getContext();
            
            /* Obtain the stack array variable being indexed */
            // TODO: Investigate, nroamlly we do a `FetchValueVar` as like the instr which is fine actually
            FetchValueVar array = cast(FetchValueVar)stackArrInstr.getIndexedToInstr();
            assert(array);
            Variable arrayVariable = cast(Variable)typeChecker.getResolver().resolveBest(context.getContainer(), array.varName);

            /* Perform symbol mapping */
            // FIXME: Set proper scope type
            string arrayName = mapper.map(arrayVariable, ScopeType.GLOBAL);

            /* Obtain the index expression */
            Value indexInstr = stackArrInstr.getIndexInstr();

            /** 
             * Emit <arrayName>[<index>]
             */
            string emit = arrayName;
            emit ~= "[";
            emit ~= transform(indexInstr);
            emit ~= "]";


            ERROR("TODO: Implement Stack-array index emit");

            
            

            // return "(TODO: Stack-array index emit)";
            emmmmit = emit;
        }
        /** 
         * Array assignments (stack-based arrays)
         *
         * Handles `myArray[<index>] = <expression>` where `myArray`
         * is of type `int[<size>]` (i.e. a stack-array)
         */
        else if(cast(StackArrayIndexAssignmentInstruction)instruction)
        {
            StackArrayIndexAssignmentInstruction stackArrAssInstr = cast(StackArrayIndexAssignmentInstruction)instruction;
            Context context = stackArrAssInstr.getContext();
            assert(context);

            /** 
             * Obtain the stack array being assigned to
             */
            string arrayName = stackArrAssInstr.getArrayName();
            Variable arrayVariable = cast(Variable)typeChecker.getResolver().resolveBest(context.getContainer(), arrayName);

            /* Perform symbol mapping */
            // FIXME: Set proper scope type
            string arrayNameMapped = mapper.map(arrayVariable, ScopeType.GLOBAL);

            /* Obtain the index expression */
            Value indexInstr = stackArrAssInstr.getIndexInstr();

            /* Obtain the expresison being assigned */
            Value assignmentInstr = stackArrAssInstr.getAssignedValue();

            /** 
             * Emit <arrayName>[<index>] = <expression>;
             */
            string emit = arrayNameMapped;
            emit ~= "[";
            emit ~= transform(indexInstr);
            emit ~= "]";

            emit ~= " = ";
            emit ~= transform(assignmentInstr);
            emit ~= ";";


            // return "(StackArrAssignmentInstr: TODO)";

            emmmmit = emit;
        }
        // TODO: MAAAAN we don't even have this yet
        // else if(cast(StringExpression))
        /** 
         * Unsupported instruction
         *
         * If you get here then normally it's because
         * you didn't implement a transformation for
         * an instruction yet.
         */
        else
        {
            emmmmit = "<TODO: Base emit: "~to!(string)(instruction)~">";
        }

        return emmmmit;
    }


    public override void emit()
    {
        // TODO: We must figure out how we decide to generate
        // multiple emits here for the many modules within the
        // `Program`
        import tlang.compiler.symbols.data : Program;
        import tlang.compiler.symbols.containers : Module;
        Program program = this.typeChecker.getProgram();
        Module[] programsModules = program.getModules();
        DEBUG("emit() has found modules '"~to!(string)(programsModules)~"'");

        foreach(Module curMod; programsModules)
        {
            DEBUG("Begin emit process for module '"~to!(string)(curMod)~"'...");

            File modOut;
            modOut.open(format("%s.c", curMod.getName()), "w");

            // Emit header comment (NOTE: Change this to a useful piece of text)
            emitHeaderComment(modOut, curMod, "Place any extra information by code generator here"); // NOTE: We can pass a string with extra information to it if we want to

            // Emit standard integer header import
            emitStdint(modOut, curMod);

            // Emit make-available's (externs)
            emitExterns(modOut, curMod);

            // Emit static allocation code
            emitStaticAllocations(modOut, curMod);

            // Emit globals
            emitCodeQueue(modOut, curMod);

            // Emit function definitions
            emitFunctionPrototypes(modOut, curMod);
            emitFunctionDefinitions(modOut, curMod);

            // Close (and flush anything not yet written)
            modOut.close();
            DEBUG("Emit for '"~to!(string)(curMod)~"'");
        }
        
        // If enabled (default: yes) then emit entry point (TODO: change later)
        Module mainModule;
        Function mainFunction;
        if(findEntrypoint(mainModule, mainFunction))
        {
            // FIXME: Disable (needed "a", because "w" overwrote previous writes)
            File entryModOut;
            entryModOut.open(format("%s.c", mainModule.getName()), "a");

            // Emit entry point
            emitEntrypoint(entryModOut, mainModule);

            entryModOut.close();
        }
        else
        {
            // If enabled (default: yes) then emit a testing
            // entrypoint (if one if available for the given
            // test case)
            //
            // In such test cases we assume that the first module
            // is the one we care about
            if(config.getConfig("dgen:emit_entrypoint_test").getBoolean())
            {
                WARN("Generating a testcase entrypoint for this program");

                Module firstMod = programsModules[0];
                File firstModOut;
                firstModOut.open(format("%s.c", firstMod.getName()), "a");
                
                // Emit testing entrypoint
                emitTestingEntrypoint(firstModOut, firstMod);

                firstModOut.close();
            }
            else
            {
                ERROR("Could not find an entry point module and function. Missing a main() maybe?");
            }
        }   
    }

    /** 
     * Attempts to find an entry point within the `Program`,
     * when it is found the ref parameters are filled in
     * and `true` is returned, else they are left untouched
     * and `false` is returned
     *
     * Params:
     *   mainModule = the found main `Module` (if any)
     *   mainFunc = the found main `Function` (if any)
     * Returns: `true` if an entrypoint is found, else
     * `false`
     */
    private bool findEntrypoint(ref Module mainModule, ref Function mainFunc)
    {
        import tlang.compiler.symbols.data : Program, Entity;
        import tlang.compiler.typecheck.resolution : Resolver;
        Program program = this.typeChecker.getProgram();
        Resolver resolver = this.typeChecker.getResolver();
        foreach(Module curMod; program.getModules())
        {
            Entity potentialMain = resolver.resolveWithin(curMod, "main");

            if(potentialMain !is null)
            {
                Function potentialMainFunc = cast(Function)potentialMain;
                if(potentialMainFunc !is null)
                {
                    // TODO: Ensure that it is void or int? (Our decision)
                    // TODO: Ensure arguments (choose what we allow)
                    mainModule = curMod;
                    mainFunc = potentialMainFunc;
                    return true;
                }
            }
        }

        return false;
    }

    /** 
     * Emits the header comment which contains information about the source
     * file and the generated code file
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     *   headerPhrase = Optional additional string information to add to the header comment
     */
    private void emitHeaderComment(File modFile, Module mod, string headerPhrase = "")
    {
        // NOTE: We could maybe fetch input fiel info too? Although it would have to be named similiarly in any case
        // so perhaps just appending a `.t` to the module name below would be fine
        string moduleName = typeChecker.getResolver().generateName(mod, mod); //TODO: Lookup actual module name (I was lazy)
        string outputCFilename = modFile.name();

        modFile.write(`/**
 * TLP compiler generated code
 *
 * Module name: `);
        modFile.writeln(moduleName);
        modFile.write(" * Output C file: ");
        modFile.writeln(outputCFilename);

        if(headerPhrase.length)
        {
            modFile.write(wrap(headerPhrase, 40, " *\n * ", " * "));
        }
        
        modFile.write(" */\n");
    }

    private struct ModuleExternSet
    {
        private Module originator;
        private Variable[] pubVars;
        private Function[] pubFns;

        this(Module originator, Variable[] publicVars, Function[] publicFunctions)
        {
            this.originator = originator;
            this.pubVars = publicVars;
            this.pubFns =  publicFunctions;
        }

        public Variable[] vars()
        {
            return this.pubVars;
        }

        public Function[] funcs()
        {
            return this.pubFns;
        }

        public Module mod()
        {
            return this.originator;
        }
    }

    private ModuleExternSet generateExternsForModule(Module mod)
    {
        DEBUG(format("Generating extern statements for module '%s'", mod.getName()));

        Entity[] allPubFunc;
        Entity[] allPubVar;

        import tlang.compiler.typecheck.resolution : Resolver;

        Resolver resolver = this.typeChecker.getResolver();

        auto funcAccPred = derive_functionAccMod(AccessorType.PUBLIC);
        auto varAccPred = derive_variableAccMod(AccessorType.PUBLIC);

        bool allPubFuncsAndVars(Entity entity)
        {
            return funcAccPred(entity) || varAccPred(entity);
        }

        Entity[] entities;
        resolver.resolveWithin(mod, &allPubFuncsAndVars, entities);
        DEBUG(format("Got %d many entities needing extern statements emitted", entities.length));
        import niknaks.arrays : filter;
        import niknaks.functional : predicateOf;

        // Filter variables
        bool onlyVar(Entity entity) { return cast(Variable)entity !is null; }
        filter!(Entity)(entities, predicateOf!(onlyVar), allPubVar);

        // Filter functions
        bool onlyFunc(Entity entity) { return cast(Function)entity !is null; }
        filter!(Entity)(entities, predicateOf!(onlyFunc), allPubFunc);
        
        ModuleExternSet modExtSet = ModuleExternSet(mod, cast(Variable[])allPubVar, cast(Function[])allPubFunc);
        return modExtSet;
    }

    /** 
     * TODO: Re-do
     *
     * Generates a bunch of extern statements
     * for symbols such as variables and
     * function which are to be exposed
     * in the generated object file such
     * that they can be linked externally
     * to other object files.
     *
     * The method for this is to resolve
     * all `Entity`(s) which are either
     * a `Function` or `Variable` which
     * have an access modifier of `public`
     * and lastly which are only at the
     * module-level in terms of declaration
     *
     * Params:
     *   modOut = the `File` to write the
     * emitted source code to
     *   mod = the current `Module` being
     * processed
     */
    private void emitExterns(File modOut, Module mod)
    {
        // Find all modules except ourselves
        import tlang.compiler.typecheck.resolution : Resolver;
        import niknaks.arrays : filter;
        Module[] everyoneElse;
        bool justNotMe(Module modI) { return modI !is mod; }
        filter!(Module)(this.typeChecker.getProgram().getModules(), predicateOf!(justNotMe), everyoneElse);

        // Now grab each other modules' extern data
        ModuleExternSet[] externSets;
        foreach(Module omod; everyoneElse)
        {
            externSets ~= generateExternsForModule(omod);
        }

        /**
         * Emit for each
         */
        foreach(ModuleExternSet mos; externSets)
        {
            DEBUG(format("Emitting extern(...) statements for module %s...", mos.mod()));

            // Emit public functions
            foreach(Function func; mos.funcs())
            {
                // Generate signature
                string signature = generateSignature(func);

                // Decide whether or not `extern` is needed
                string externPart = func.isExternal() ? "" : "extern ";

                // Generate the emit
                string externEmit = format("%s%s;", externPart, signature);

                DEBUG(format("FuncExternEmit: '%s'", externEmit));
                modOut.writeln(externEmit);
            }

            // Emit public variables
            foreach(Variable var; mos.vars())
            {
                // Generate signature
                string signature = generateSignature_Variable(var);

                // Decide whether or not `extern` is needed
                string externPart = var.isExternal() ? "" : "extern ";

                // Generate the emit
                string externEmit = format("%s%s;", externPart, signature);

                DEBUG(format("VarExternEmit: '%s'", externEmit));
                modOut.writeln(externEmit);
            }
        }
    }

    /** 
     * Emits the static allocations provided
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     */
    private void emitStaticAllocations(File modOut, Module mod)
    {
        // Select the static initializations code queue for
        // the given module
        selectQueue(mod, QueueType.ALLOC_QUEUE);
        DEBUG("Static allocations needed: "~to!(string)(getQueueLength()));

        modOut.writeln();
    }

    /** 
     * Emits the function prototypes
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     */
    private void emitFunctionPrototypes(File modOut, Module mod)
    {
        DEBUG("Function definitions needed: "~to!(string)(getFunctionDefinitionsCount(mod)));

        // Get complete map (should we bypass anything in CodeEmitter for this? Guess it is fair?)
        Instruction[][string] functionBodyInstrs = typeChecker.getFunctionBodyCodeQueues(mod);
        string[] functionNames = getFunctionDefinitionNames(mod);

        DEBUG("WOAH: "~to!(string)(functionNames));

        foreach(string currentFunctioName; functionNames)
        {
            emitFunctionPrototype(modOut, mod, currentFunctioName);
            modOut.writeln();
        }
    }

    /** 
     * Emits the function definitions
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     */
    private void emitFunctionDefinitions(File modOut, Module mod)
    {
        DEBUG("Function definitions needed: "~to!(string)(getFunctionDefinitionsCount(mod)));

        // Get the function definitions of the current module
        Instruction[][string] functionBodyInstrs = typeChecker.getFunctionBodyCodeQueues(mod);

        string[] functionNames = getFunctionDefinitionNames(mod);

        DEBUG("WOAH: "~to!(string)(functionNames));

        foreach(string currentFunctioName; functionNames)
        {
            emitFunctionDefinition(modOut, mod, currentFunctioName);
            modOut.writeln();
        }
    }

    /** 
     * Generates the signature emit for a given
     * variable.
     *
     * This is something of the form:
     *
     *    `<type> <name>`
     *
     * Where the `<name>` has been symbol
     * mapped.
     *
     * Params:
     *   var = the `Variable`
     * Returns: a string
     */
    private string generateSignature_Variable(Variable var)
    {
        string signature;

        // Extract the Variable's type
        Type varType = typeChecker.getType(var.context.container, var.getType());

        // Decide on the symbol's name
        string symbolName;

        // If it is NOT extern then map it
        if(!var.isExternal())
        {
            // FIXME: Set proper scope type
            symbolName = mapper.map(var, ScopeType.GLOBAL);
        }
        // If it is extern, then leave it as such
        else
        {
            symbolName = var.getName();
        }

        // <type> <name>
        signature = typeTransform(varType)~" "~symbolName;

        // If if is external then it needs `extern ...`
        if(var.isExternal())
        {
            signature = "extern "~signature;
        }

        return signature;
    }

    private string generateSignature(Function func)
    {
        string signature;

        // Extract the Function's return Type
        Type returnType = typeChecker.getType(func.context.container, func.getType());

        // <type> <functionName> (
        signature = typeTransform(returnType)~" "~func.getName()~"(";

        // Generate parameter list
        if(func.hasParams())
        {
            VariableParameter[] parameters = func.getParams();
            string parameterString;
            
            for(ulong parIdx = 0; parIdx < parameters.length; parIdx++)
            {
                Variable currentParameter = parameters[parIdx];

                // Extract the variable's type
                Type parameterType = typeChecker.getType(currentParameter.context.container, currentParameter.getType());

                // Generate the symbol-mapped names for the parameters
                Variable typedEntityVariable = cast(Variable)typeChecker.getResolver().resolveBest(func, currentParameter.getName()); //TODO: Remove `auto`
                // FIXME: Set proper scope type
                string renamedSymbol = mapper.map(typedEntityVariable, ScopeType.GLOBAL);


                // Generate <type> <parameter-name (symbol mapped)>
                parameterString~=typeTransform(parameterType)~" "~renamedSymbol;

                if(parIdx != (parameters.length-1))
                {
                    parameterString~=", ";
                }
            }

            signature~=parameterString;
        }

        // )
        signature~=")";

        // If the function is marked as external then place `extern` infront
        if(func.isExternal())
        {
            signature = "extern "~signature;
        }

        return signature;

    }

    /** 
     * Emits the function prototype for the `Function`
     * of the given name
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     *   functionName = the name of the function
     */
    private void emitFunctionPrototype(File modOut, Module mod, string functionName)
    {
        // Select the function definition code queue by module and function name
        // TODO: Is this needed for protptype def? I think not (REMOVE PLEASE)
        selectQueue(mod, QueueType.FUNCTION_DEF_QUEUE, functionName);

        DEBUG("emotFunctionDefinition(): Function: "~functionName~", with "~to!(string)(getSelectedQueueLength())~" many instructions");
    
        //TODO: Look at nested definitions or nah? (Context!!)
        //TODO: And what about methods defined in classes? Those should technically be here too
        Function functionEntity = cast(Function)typeChecker.getResolver().resolveBest(mod, functionName); //TODO: Remove `auto`
        
        // Emit the function signature
        modOut.writeln(generateSignature(functionEntity)~";");
    }

    /** 
     * Emits the function definition for the `Function`
     * of the given name
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     *   functionName = the name of the function
     */
    private void emitFunctionDefinition(File modOut, Module mod, string functionName)
    {
        // Select the function definition code queue by module and function name
        selectQueue(mod, QueueType.FUNCTION_DEF_QUEUE, functionName);

        DEBUG("emotFunctionDefinition(): Function: "~functionName~", with "~to!(string)(getSelectedQueueLength())~" many instructions");
    
        //TODO: Look at nested definitions or nah? (Context!!)
        //TODO: And what about methods defined in classes? Those should technically be here too
        Function functionEntity = cast(Function)typeChecker.getResolver().resolveBest(mod, functionName); //TODO: Remove `auto`
        
        // If the Entity is NOT external then emit the signature+body
        if(!functionEntity.isExternal())
        {
            // Emit the function signature
            modOut.writeln(generateSignature(functionEntity));

            // Emit opening curly brace
            modOut.writeln(getCharacter(SymbolType.OCURLY));

            // Emit body
            while(hasInstructions())
            {
                Instruction curFuncBodyInstr = getCurrentInstruction();

                string emit = transform(curFuncBodyInstr);
                DEBUG("emitFunctionDefinition("~functionName~"): Emit: "~emit);
                modOut.writeln("\t"~emit);
                
                nextInstruction();
            }

            // Emit closing curly brace
            modOut.writeln(getCharacter(SymbolType.CCURLY));
        }
        // If the Entity IS external then don't emit anything as the signature would have been emitted via a prorotype earlier with `emitPrototypes()`
        else
        {
            // Do nothing
        }
    }

    /** 
     * Emits the code queue of the given `Module`
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     */
    private void emitCodeQueue(File modOut, Module mod)
    {
        // Select the global code queue of the current module
        selectQueue(mod, QueueType.GLOBALS_QUEUE);
        DEBUG("Code emittings needed: "~to!(string)(getQueueLength()));

        while(hasInstructions())
        {
            Instruction currentInstruction = getCurrentInstruction();
            modOut.writeln(transform(currentInstruction));

            nextInstruction();
        }

        modOut.writeln();
    }

    /** 
     * Emits the standard imports of the given
     * `Module`
     *
     * Params:
     *   modFile = the `File` to write the emitted source code to
     *   mod = the current `Module` being processed
     */
    private void emitStdint(File modOut, Module mod)
    {
        modOut.writeln("#include<stdint.h>");
    }

    private void emitEntrypoint(File modOut, Module mod)
    {
        ERROR("IMPLEMENT ME");
        ERROR("IMPLEMENT ME");
        ERROR("IMPLEMENT ME");
        ERROR("IMPLEMENT ME");
        ERROR("We have NOT YET implemented the init method");

        // modOut.writeln("fok");

        // TODO: In future, for runtime init,
        // I will want to co-opt main(int, args)
        // for use for runtime init to then
        // call ANOTHER REAL main (specified)
        // by the user

        // Therefore there must be some sort
        // of renaming stage somewhere
    }

    private void emitTestingEntrypoint(File modOut, Module mod)
    {
        // TODO: Implement me

        // Test for `simple_functions.t` (function call testing)
        if(cmp(mod.getName(), "simple_functions") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    assert(t_7b6d477c5859059f16bc9da72fc8cc3b == 22);
    printf("k: %u\n", t_7b6d477c5859059f16bc9da72fc8cc3b);
    
    banana(1);
    assert(t_7b6d477c5859059f16bc9da72fc8cc3b == 72);
    printf("k: %u\n", t_7b6d477c5859059f16bc9da72fc8cc3b);

    return 0;
}`);
        }
        // Test for `simple_function_recursion_factorial.t` (recursive function call testing)
        else if(cmp(mod.getName(), "simple_function_recursion_factorial") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = factorial(3);
    assert(result == 6);
    printf("factorial: %u\n", result);
    
    return 0;
}`);
        }
        // Test for `simple_direct_func_call.t` (statement-level function call)
        else if(cmp(mod.getName(), "simple_direct_func_call") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    // Before it should be 0
    assert(t_de44aff5a74865c97c4f8701d329f28d == 0);

    // Call the function
    function();

    // After it it should be 69
    assert(t_de44aff5a74865c97c4f8701d329f28d == 69);
    
    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_while") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function(3);
    printf("result: %d\n", result);
    assert(result == 3);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_for_loops") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function(3);
    printf("result: %d\n", result);
    assert(result == 3);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_pointer") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int retValue = thing();
    assert(t_87bc875d0b65f741b69fb100a0edebc7 == 4);
    assert(retValue == 6);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_pointer_array_syntax") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int retValue = thing();
    assert(t_9d01d71b858651e520c9b503122a1b7a == 4);
    assert(retValue == 6);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_pointer_cast_le") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int retValue = thing();
    assert(t_e159019f766be1a175186a13f16bcfb7 == 256+4);
    assert(retValue == 256+4+2);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_pointer_malloc") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    test();
    
    // TODO: Test the value

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_extern") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    test();
   
    

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_stack_array_coerce") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function();
    assert(result == 420+69);
    printf("stackArr sum: %d\n", result);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "complex_stack_array_coerce") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function();
    assert(result == 69+420);

    printf("val1: %d\n", t_596f49b2a2784a3c1b073ccfe174caa0);
    printf("val2: %d\n", t_4233b83329676d70ab4afaa00b504564);
    printf("stackArr sum: %d\n", result);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_stack_array_coerce_ptr_syntax") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function();
    assert(result == 420+69);
    printf("stackArr sum: %d\n", result);

    return 0;
}`);
        }
        else if(cmp(mod.getName(), "simple_stack_arrays4") == 0)
        {
            modOut.writeln(`
#include<stdio.h>
#include<assert.h>
int main()
{
    int result = function();
    assert(result == 61);

    return 0;
}`);
        }
        else
        {
            modOut.writeln(`
int main()
{
    return 0;
}
`);
        }
    }

    /** 
     * Performs the compilation step
     *
     * This requires that the `emit()`
     * step must have already been completed
     */
    public override void finalize()
    {
        import tlang.compiler.symbols.data : Program;
        Program program = this.typeChecker.getProgram();
        Module[] programModules = program.getModules();

        string[] srcFiles;
        string[] objectFiles;

        // import tlang.compiler.configuration;
        // config.addConfig(ConfigEntry("dgen:afterexit:clean_c_files", true));
        // config.addConfig(ConfigEntry("dgen:afterexit:clean_obj_files", true));

        scope(exit)
        {
            // Clean up all generated C files
            if(config.hasConfig("dgen:afterexit:clean_c_files") && config.getConfig("dgen:afterexit:clean_c_files").getBoolean())
            {
                foreach(string srcFile; srcFiles)
                {
                    DEBUG("Cleaning up source file '"~srcFile~"'...");
                    import std.stdio : remove;
                    remove(srcFile.ptr);

                    if(!remove(srcFile.ptr))
                    {
                        ERROR("There was an error cleaning up source file '"~srcFile~"'"); // TODO: Add error code
                    }
                }
            }

            // Clean up all generates object files
            if(config.hasConfig("dgen:afterexit:clean_obj_files") && config.getConfig("dgen:afterexit:clean_obj_files").getBoolean())
            {
                foreach(string objFile; objectFiles)
                {
                    DEBUG("Cleaning up object file '"~objFile~"'...");
                    import std.stdio : remove;
                    remove(objFile.ptr);

                    if(!remove(objFile.ptr))
                    {
                        ERROR("There was an error cleaning up object file '"~objFile~"'"); // TODO: Add error code
                    }
                }
            }
        }

        try
        {
            string systemCompiler = config.getConfig("dgen:compiler").getText();
            INFO("Using system C compiler '"~systemCompiler~"' for compilation");

            // Check for object files to be linked in
            string[] objectFilesLink;
            if(config.hasConfig("linker:link_files"))
            {
                objectFilesLink = config.getConfig("linker:link_files").getArray();
                INFO("Object files to be linked in: "~to!(string)(objectFilesLink));
            }
            else
            {
                INFO("No files to link in");
            }

            // Total compilation time
            Duration total = Duration.zero();

            // TODO: Do for-each generation of `.o` files here with `-c`
            foreach(Module curMod; programModules)
            {
                string modFileSrcPath = format("%s.c", curMod.getName());
                srcFiles ~= modFileSrcPath;
                string modFileObjPath = format("%s.o", curMod.getName());

                string[] args = [systemCompiler, "-c", modFileSrcPath, "-o", modFileObjPath];

                INFO("Compiling now with arguments: "~to!(string)(args));

                StopWatch watch = StopWatch(AutoStart.yes);
                Pid ccPID = spawnProcess(args);
                int code = wait(ccPID);
                if(code)
                {
                    //NOTE: Make this a TLang exception
                    throw new Exception("The CC exited with a non-zero exit code ("~to!(string)(code)~")");
                }

                Duration compTime = watch.peek();
                INFO(format("Compiled %s in %sms", curMod.getName(), compTime.total!("msecs")()));
                total = dur!("msecs")(total.total!("msecs")()+compTime.total!("msecs")());

                // Only add it to the list of files if it was generated
                // (this guards against the clean up routines spitting out errors
                // for object files which were never generated in the first place)
                objectFiles ~= modFileObjPath;
            }

            INFO(format("Total compilation time took %s", total));

            // Now determine the entry point module
            // Module entryModule;
            // Function _;
            // if(findEntrypoint(entryModule, _))
            // {
                
            // }

            // Perform linking
            string[] args = [systemCompiler];

            // Tack on all generated object files
            args ~= objectFiles;

            // Tack on any objects to link that were specified in Config
            args ~= objectFilesLink;

            // Tack on the output filename (TODO: Fix the output file name)
            args ~= ["-o", "./tlang.out"]; 

            

            // Now link all object files (the `.o`'s) together
            // and perform linking
            Pid ccPID = spawnProcess(args);
            int code = wait(ccPID);

            if(code)
            {
                //NOTE: Make this a TLang exception
                throw new Exception("The CC exited with a non-zero exit code ("~to!(string)(code)~")");
            }
        }
        catch(ProcessException e)
        {
            ERROR("NOTE: Case where it exited and Pid now inavlid (if it happens it would throw processexception surely)?");
            assert(false);
        }
    }
}

import tlang.compiler.symbols.data : Entity, AccessorType;
import niknaks.functional : Predicate, predicateOf;

/** 
 * Derives a closure predicate which captires
 * the provided access modifier type and will
 * apply a logic which disregards any non-`Function`
 * `Entity`, however if a `Function`-typed entity
 * IS found then it will determine if its access
 * modifier matches that of the provided one
 *
 * Params:
 *   accModType = the access modifier to filter
 * by
 *
 * Returns: a `Predicate!(Entity)`
 */
private Predicate!(Entity) derive_functionAccMod(AccessorType accModType)
{
    bool match(Entity entity)
    {
        Function func = cast(Function)entity;

        // Disregard any non-Function
        if(func is null)
        {
            return false;
        }
        // Onyl care about those with a matching
        // modifier
        else
        {
            return func.getAccessorType() == accModType;
        }
    }

    return &match;
}

/** 
 * Derives a closure predicate which captires
 * the provided access modifier type and will
 * apply a logic which disregards any non-`Variable`
 * `Entity`, however if a `Variable`-typed entity
 * IS found then it will determine if its access
 * modifier matches that of the provided one
 *
 * Params:
 *   accModType = the access modifier to filter
 * by
 *
 * Returns: a `Predicate!(Entity)`
 */
private Predicate!(Entity) derive_variableAccMod(AccessorType accModType)
{
    bool match(Entity entity)
    {
        Variable var = cast(Variable)entity;

        // Disregard any non-Variable
        if(var is null)
        {
            return false;
        }
        // Onyl care about those with a matching
        // modifier
        else
        {
            return var.getAccessorType() == accModType;
        }
    }

    return &match;
}