tlang/source/tlang/compiler/typecheck/core.d

2550 lines
90 KiB
D

module tlang.compiler.typecheck.core;
import tlang.compiler.symbols.check;
import tlang.compiler.symbols.data;
import std.conv : to, ConvException;
import std.string;
import std.stdio;
import gogga;
import tlang.compiler.parsing.core;
import tlang.compiler.typecheck.resolution;
import tlang.compiler.typecheck.exceptions;
import tlang.compiler.symbols.typing.core;
import tlang.compiler.typecheck.dependency.core;
import tlang.compiler.codegen.instruction;
import std.container.slist;
import std.algorithm : reverse;
/**
* The Parser only makes sure syntax
* is adhered to (and, well, partially)
* as it would allow string+string
* for example
*
*/
public final class TypeChecker
{
private Module modulle;
/* The name resolver */
private Resolver resolver;
public Module getModule()
{
return modulle;
}
this(Module modulle)
{
this.modulle = modulle;
resolver = new Resolver(this);
/* TODO: Module check?!?!? */
}
/**
* I guess this should be called rather
* when processing assignments but I also
* think we need something like it for
* class initializations first rather than
* variable expressions in assignments
* (which should probably use some other
* function to check that then)
*/
public void dependencyCheck()
{
/* Check declaration and definition types */
checkDefinitionTypes(modulle);
/* TODO: Implement me */
checkClassInherit(modulle);
/**
* Dependency tree generation
*
* Currently this generates a dependency tree
* just for the module, the tree must be run
* through after wards to make it
* non-cyclic
*
*/
DNodeGenerator dNodeGenerator = new DNodeGenerator(this);
/* Generate the dependency tree */
DNode rootNode = dNodeGenerator.generate(); /* TODO: This should make it acyclic */
/* Perform the linearization to the dependency tree */
rootNode.performLinearization();
/* Print the tree */
string tree = rootNode.getTree();
gprintln(tree);
/* Get the action-list (linearised bottom up graph) */
DNode[] actionList = rootNode.getLinearizedNodes();
doTypeCheck(actionList);
/**
* After processing globals executions the instructions will
* be placed into `codeQueue`, therefore copy them from the temporary
* scratchpad queue into `globalCodeQueue`.
*
* Then clean the codeQueue for next use
*/
foreach(Instruction curGlobInstr; codeQueue)
{
globalCodeQueue~=curGlobInstr;
}
codeQueue.clear();
assert(codeQueue.empty() == true);
/* Grab functionData ??? */
FunctionData[string] functionDefinitions = grabFunctionDefs();
gprintln("Defined functions: "~to!(string)(functionDefinitions));
foreach(FunctionData funcData; functionDefinitions.values)
{
assert(codeQueue.empty() == true);
/* Generate the dependency tree */
DNode funcNode = funcData.generate();
/* Perform the linearization to the dependency tree */
funcNode.performLinearization();
/* Get the action-list (linearised bottom up graph) */
DNode[] actionListFunc = funcNode.getLinearizedNodes();
//TODO: Would this not mess with our queues?
doTypeCheck(actionListFunc);
gprintln(funcNode.getTree());
// The current code queue would be the function's body instructions
// a.k.a. the `codeQueue`
// functionBodies[funcData.name] = codeQueue;
// The call to `doTypeCheck()` above adds to this queue
// so we should clean it out before the next run
//
// NOTE: Static allocations in? Well, we don't clean init queue
// so is it fine then? We now have seperate dependency trees,
// we should make checking methods that check the `initQueue`
// whenever we come past a `ClassStaticNode` for example
// codeQueue.clear();
/**
* Copy over the function code queue into
* the function code queue respective key.
*
* Then clear the scratchpad code queue
*/
functionBodyCodeQueues[funcData.name]=[];
foreach(Instruction curFuncInstr; codeQueue)
{
//TODO: Think about class funcs? Nah
functionBodyCodeQueues[funcData.name]~=curFuncInstr;
gprintln("FuncDef ("~funcData.name~"): Adding body instruction: "~to!(string)(curFuncInstr));
}
codeQueue.clear();
gprintln("FUNCDEF DONE: "~to!(string)(functionBodyCodeQueues[funcData.name]));
}
}
/**
* Function definitions
*
* Holds their action lists which are to be used for the
* (later) emitting of their X-lang emit code
*/
//FUnctionDeifnition should couple `linearizedList` but `functionEntity`
// private FunctionDefinition[string] functionDefinitions2; //TODO: Use this
/**
* Concrete queues
*
* These queues below are finalized and not used as a scratchpad.
*
* 1. Global code queue
* - This accounts for the globals needing to be executed
* 2. Function body code queues
* - This accounts for (every) function definition's code queue
*/
private Instruction[] globalCodeQueue;
private Instruction[][string] functionBodyCodeQueues;
public Instruction[] getGlobalCodeQueue()
{
return globalCodeQueue;
}
public Instruction[][string] getFunctionBodyCodeQueues()
{
return functionBodyCodeQueues;
}
/* Main code queue (used for temporary passes) */
private SList!(Instruction) codeQueue; //TODO: Rename to `currentCodeQueue`
/* Initialization queue */
private SList!(Instruction) initQueue;
//TODO: CHange to oneshot in the function
public Instruction[] getInitQueue()
{
Instruction[] initQueueConcrete;
foreach(Instruction currentInstruction; initQueue)
{
initQueueConcrete~=currentInstruction;
}
return initQueueConcrete;
}
/* Adds an initialization instruction to the initialization queue (at the back) */
public void addInit(Instruction initInstruction)
{
initQueue.insertAfter(initQueue[], initInstruction);
}
/*
* Prints the current contents of the init-queue
*/
public void printInitQueue()
{
import std.range : walkLength;
ulong i = 0;
foreach(Instruction instruction; initQueue)
{
gprintln("InitQueue: "~to!(string)(i+1)~"/"~to!(string)(walkLength(initQueue[]))~": "~instruction.toString());
i++;
}
}
/* Adds an instruction to the front of code queue */
public void addInstr(Instruction inst)
{
codeQueue.insert(inst);
}
/* Adds an instruction to the back of the code queue */
public void addInstrB(Instruction inst)
{
codeQueue.insertAfter(codeQueue[], inst);
}
/* Removes the instruction at the front of the code queue and returns it */
public Instruction popInstr()
{
Instruction poppedInstr;
if(!codeQueue.empty)
{
poppedInstr = codeQueue.front();
codeQueue.removeFront();
}
return poppedInstr;
}
/* Pops from the tail of the code queue and returns it */
public Instruction tailPopInstr()
{
Instruction poppedInstr;
if(!codeQueue.empty)
{
// Perhaps there is a nicer way to tail popping
codeQueue.reverse();
poppedInstr = codeQueue.front();
codeQueue.removeFront();
codeQueue.reverse();
}
return poppedInstr;
}
public bool isInstrEmpty()
{
return codeQueue.empty;
}
// public Instruction[] getCodeQueue()
// {
// Instruction[] codeQueueConcrete;
// foreach(Instruction currentInstruction; codeQueue)
// {
// codeQueueConcrete~=currentInstruction;
// }
// return codeQueueConcrete;
// }
/*
* Prints the current contents of the code-queue
*/
public void printCodeQueue()
{
import std.range : walkLength;
ulong i = 0;
foreach(Instruction instruction; codeQueue)
{
gprintln(to!(string)(i+1)~"/"~to!(string)(walkLength(codeQueue[]))~": "~instruction.toString());
i++;
}
}
/**
* There are several types and comparing them differs
*/
private bool isSameType(Type type1, Type type2)
{
bool same = false;
/* Handling for pointers */
if(typeid(type1) == typeid(type2) && cast(Pointer)type1 !is null)
{
Pointer p1 = cast(Pointer)type1, p2 = cast(Pointer)type2;
/* Now check that both of their referred types are the same */
return isSameType(p1.getReferredType(), p2.getReferredType());
}
/* Handling for Integers */
else if(typeid(type1) == typeid(type2) && cast(Integer)type1 !is null)
{
Integer i1 = cast(Integer)type1, i2 = cast(Integer)type2;
/* Both same size? */
if(i1.getSize() == i2.getSize())
{
/* Matching signedness ? */
same = i1.isSigned() == i2.isSigned();
}
/* Size mismatch */
else
{
same = false;
}
}
gprintln("isSameType("~to!(string)(type1)~","~to!(string)(type2)~"): "~to!(string)(same), DebugType.ERROR);
return same;
}
/**
* Given a type to try coerce towards and a literal value
* instruction, this will check whether the literal itself
* is within the range whereby it may be coerced
*
* Params:
* variableType = the type to try coercing towards
* assignmentInstruction = the literal to apply a range
* check to
*/
private bool isCoercibleRange(Type toType, Value literalInstr)
{
// You should only be calling this on either a `LiteralValue`
// ... or a `LiteralValueFloat` instruction
// TODO: Add support for UnaryOpInstr (where the inner type is then)
// ... one of the above
assert(cast(LiteralValue)literalInstr || cast(LiteralValueFloat)literalInstr || cast(UnaryOpInstr)literalInstr);
// LiteralValue (integer literal instructions)
if(cast(LiteralValue)literalInstr)
{
LiteralValue integerLiteral = cast(LiteralValue)literalInstr;
string literal = integerLiteral.getLiteralValue();
// NOTE (X-platform): For cross-platform sake we should change the `ulong` to `size_t`
ulong literalValue = to!(ulong)(literal);
if(isSameType(toType, getType(null, "ubyte")))
{
if(literalValue >= 0 && literalValue <= 255)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "ushort")))
{
if(literalValue >= 0 && literalValue <= 65_535)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "uint")))
{
if(literalValue >= 0 && literalValue <= 4_294_967_295)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "ulong")))
{
if(literalValue >= 0 && literalValue <= 18_446_744_073_709_551_615)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
// Handling for signed bytes [0, 127]
else if(isSameType(toType, getType(null, "byte")))
{
if(literalValue >= 0 && literalValue <= 127)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
// Handling for signed shorts [0, 32_767]
else if(isSameType(toType, getType(null, "short")))
{
if(literalValue >= 0 && literalValue <= 32_767)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
// Handling for signed integers [0, 2_147_483_647]
else if(isSameType(toType, getType(null, "int")))
{
if(literalValue >= 0 && literalValue <= 2_147_483_647)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
// Handling for signed longs [0, 9_223_372_036_854_775_807]
else if(isSameType(toType, getType(null, "long")))
{
if(literalValue >= 0 && literalValue <= 9_223_372_036_854_775_807)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
}
// LiteralValue (integer literal instructions)
else if(cast(LiteralValueFloat)literalInstr)
{
}
// Unary operator
else
{
UnaryOpInstr unaryOpLiteral = cast(UnaryOpInstr)literalInstr;
assert(unaryOpLiteral.getOperator() == SymbolType.SUB);
Value operandInstr = unaryOpLiteral.getOperand();
// LiteralValue (integer literal instructions) with subtraction infront
if(cast(LiteralValue)operandInstr)
{
LiteralValue theLiteral = cast(LiteralValue)operandInstr;
// Then the actual literal will be `-<value>`
string negativeLiteral = "-"~theLiteral.getLiteralValue();
gprintln("Negated literal: "~negativeLiteral);
// NOTE (X-platform): For cross-platform sake we should change the `long` to `ssize_t`
long literalValue = to!(long)(negativeLiteral);
if(isSameType(toType, getType(null, "byte")))
{
if(literalValue >= -128 && literalValue <= 127)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "short")))
{
if(literalValue >= -32_768 && literalValue <= 32_767)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "int")))
{
if(literalValue >= -2_147_483_648 && literalValue <= 2_147_483_647)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
else if(isSameType(toType, getType(null, "long")))
{
if(literalValue >= -9_223_372_036_854_775_808 && literalValue <= 9_223_372_036_854_775_807)
{
// Valid coercion
return true;
}
else
{
// Invalid coercion
return false;
}
}
}
// LiteralValue (integer literal instructions) with subtraction infront
else
{
}
}
return false;
}
/**
* Attempts to perform coercion of the provided Value-instruction
* with respect to the provided variable type.
*
* This should only be called if the types do not match.
* This will update the provided instruction's type-field
*
* Params:
* variableType = the type to attempt coercing the instruction to
* assignmentInstruction = instruction to coerce
*/
private void attemptCoercion(Type variableType, Value assignmentInstruction)
{
gprintln("VibeCheck?");
/* Extract the type of the assignment instruction */
Type assignmentType = assignmentInstruction.getInstrType();
// If it is a LiteralValue (integer literal) (support for issue #94)
if(cast(LiteralValue)assignmentInstruction)
{
// TODO: Add a check for if these types are both atleast integral (as in the Variable's type)
// ... THEN (TODO): Check if range makes sense
bool isIntegral = !(cast(Integer)variableType is null); // Integrality check
if(isIntegral)
{
bool isCoercible = isCoercibleRange(variableType, assignmentInstruction); // TODO: Range check
if(isCoercible)
{
// TODO: Coerce here by changing the embedded instruction's type (I think this makes sense)
// ... as during code emit that is what will be hoisted out and checked regarding its type
// NOTE: Referrring to same type should not be a problem (see #96 Question 1)
assignmentInstruction.setInstrType(variableType);
}
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (range violation)");
}
}
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
}
}
// If it is a LiteralValueFloat (support for issue #94)
else if(cast(LiteralValueFloat)assignmentInstruction)
{
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
assert(false);
}
// Unary operator (specifically with a minus)
else if(cast(UnaryOpInstr)assignmentInstruction)
{
UnaryOpInstr unaryOpInstr = cast(UnaryOpInstr)assignmentInstruction;
if(unaryOpInstr.getOperator() == SymbolType.SUB)
{
Value operandInstr = unaryOpInstr.getOperand();
// If it is a negative LiteralValue (integer literal)
if(cast(LiteralValue)operandInstr)
{
bool isIntegral = !(cast(Integer)variableType is null);
if(isIntegral)
{
LiteralValue literalValue = cast(LiteralValue)operandInstr;
bool isCoercible = isCoercibleRange(variableType, assignmentInstruction); // TODO: Range check
if(isCoercible)
{
// TODO: Coerce here by changing the embedded instruction's type (I think this makes sense)
// ... as during code emit that is what will be hoisted out and checked regarding its type
// NOTE: Referrring to same type should not be a problem (see #96 Question 1)
assignmentInstruction.setInstrType(variableType);
}
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (range violation)");
}
// TODO: Implement things here
// gprintln("Please implement coercing checking for negative integer literals", DebugType.ERROR);
// assert(false);
}
}
// If it is a negative LiteralValueFloat (floating-point literal)
else if(cast(LiteralValueFloat)operandInstr)
{
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
assert(false);
}
// If anything else is embedded
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
}
}
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Cannot coerce a non minus unary operation");
}
}
else
{
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
}
}
/**
* Given two Value-based instructions this will firstly check if
* at least one of the two is of type Pointer, then checks if the
* remaining instruction is an of type Integer - the remaining instruction
* will then be coerced into a pointer.
*
* If both are Pointers, neither are pointers or one or the other is
* a Pointer and another is non-Integer then nothing will be coerced.
* and this function is effectively a no-op.
*
* Params:
* vInstr1 = the first instruction
* vInstr2 = the second instruction
*/
private void attemptPointerAriehmeticCoercion(Value vInstr1, Value vInstr2)
{
// Get the types of `vInstr1` and `vInstr2` respectively
Type t1 = vInstr1.getInstrType();
Type t2 = vInstr2.getInstrType();
// TODO: Check if T1 is a pointer and then if T2 is an integer make it a pointer
if(cast(Pointer)t1 && cast(Integer)t2)
{
Pointer t1Ptr = cast(Pointer)t1;
Type coercedType = new Pointer(t1Ptr.getReferredType());
vInstr2.setInstrType(coercedType);
}
// TODO: Else check if T2 is a pointer and then if T1 is an integer and make it a pointer
else if(cast(Pointer)t2 && cast(Integer)t2)
{
Pointer t2Ptr = cast(Pointer)t2;
Type coercedType = new Pointer(t2Ptr.getReferredType());
vInstr1.setInstrType(coercedType);
}
else if(cast(Pointer)t1 && cast(Pointer)t2)
{
// Do nothing
// TODO: Remove this branch
}
else
{
// Do nothing
}
}
public void typeCheckThing(DNode dnode)
{
gprintln("typeCheckThing(): "~dnode.toString());
/* ExpressionDNodes */
if(cast(tlang.compiler.typecheck.dependency.expression.ExpressionDNode)dnode)
{
tlang.compiler.typecheck.dependency.expression.ExpressionDNode expDNode = cast(tlang.compiler.typecheck.dependency.expression.ExpressionDNode)dnode;
Statement statement = expDNode.getEntity();
gprintln("Hdfsfdjfds"~to!(string)(statement));
/* Dependent on the type of Statement */
if(cast(NumberLiteral)statement)
{
/**
* Codegen
*
* TODO: We just assume (for integers) byte size 4?
*
* Generate the correct value instruction depending
* on the number literal's type
*/
Value valInstr;
/* Generate a LiteralValue (IntegerLiteral) */
if(cast(IntegerLiteral)statement)
{
IntegerLiteral integerLitreal = cast(IntegerLiteral)statement;
/**
* Determine the type of this value instruction by finding
* the encoding of the integer literal (part of doing issue #94)
*/
Type literalEncodingType;
if(integerLitreal.getEncoding() == IntegerLiteralEncoding.SIGNED_INTEGER)
{
literalEncodingType = getType(modulle, "int");
}
else if(integerLitreal.getEncoding() == IntegerLiteralEncoding.UNSIGNED_INTEGER)
{
literalEncodingType = getType(modulle, "uint");
}
else if(integerLitreal.getEncoding() == IntegerLiteralEncoding.SIGNED_LONG)
{
literalEncodingType = getType(modulle, "long");
}
else if(integerLitreal.getEncoding() == IntegerLiteralEncoding.UNSIGNED_LONG)
{
literalEncodingType = getType(modulle, "ulong");
}
assert(literalEncodingType);
// TODO: Insert getEncoding stuff here
LiteralValue litValInstr = new LiteralValue(integerLitreal.getNumber(), literalEncodingType);
valInstr = litValInstr;
// TODO: Insert get encoding stuff here
}
/* Generate a LiteralValueFloat (FloatingLiteral) */
else
{
FloatingLiteral floatLiteral = cast(FloatingLiteral)statement;
gprintln("We haven't sorted ouyt literal encoding for floating onts yet (null below hey!)", DebugType.ERROR);
Type bruhType = null;
assert(bruhType);
LiteralValueFloat litValInstr = new LiteralValueFloat(floatLiteral.getNumber(), bruhType);
valInstr = litValInstr;
// TODO: Insert get encoding stuff here
}
addInstr(valInstr);
}
/* String literal */
else if(cast(StringExpression)statement)
{
gprintln("Typecheck(): String literal processing...");
/**
* Add the char* type as string literals should be
* interned
*/
gprintln("Please implement strings", DebugType.ERROR);
// assert(false);
// addType(getType(modulle, "char*"));
// /**
// * Add the instruction and pass the literal to it
// */
// StringExpression strExp = cast(StringExpression)statement;
// string strLit = strExp.getStringLiteral();
// gprintln("String literal: `"~strLit~"`");
// StringLiteral strLitInstr = new StringLiteral(strLit);
// addInstr(strLitInstr);
// gprintln("Typecheck(): String literal processing... [done]");
}
else if(cast(VariableExpression)statement)
{
gprintln("Yaa, it's rewind time");
auto g = cast(VariableExpression)statement;
/* FIXME: It would seem that g.getContext() is returning null, so within function body's context is not being set */
gprintln("VarExp: "~g.getName());
gprintln(g.getContext());
auto gVar = cast(TypedEntity)resolver.resolveBest(g.getContext().getContainer(), g.getName());
gprintln("gVar nullity?: "~to!(string)(gVar is null));
/* TODO; Above crashes when it is a container, eish baba - from dependency generation with `TestClass.P.h` */
string variableName = resolver.generateName(modulle, gVar);
gprintln("VarName: "~variableName);
gprintln("Halo");
gprintln("Yaa, it's rewind time1: "~to!(string)(gVar.getType()));
gprintln("Yaa, it's rewind time2: "~to!(string)(gVar.getContext()));
/* TODO: Above TYpedEntity check */
/* TODO: still wip the expresison parser */
/* TODO: TYpe needs ansatz too `.updateName()` call */
Type variableType = getType(gVar.getContext().getContainer(), gVar.getType());
gprintln("Yaa, it's rewind time");
/**
* Codegen
*
* FIXME: Add type info, length
*
* 1. Generate the instruction
* 2. Set the Context of it to where the VariableExpression occurred
*/
FetchValueVar fVV = new FetchValueVar(variableName, 4);
fVV.setContext(g.getContext());
addInstr(fVV);
/* The type of a FetchValueInstruction is the type of the variable being fetched */
fVV.setInstrType(variableType);
}
// else if(cast()) !!!! Continue here
else if(cast(BinaryOperatorExpression)statement)
{
BinaryOperatorExpression binOpExp = cast(BinaryOperatorExpression)statement;
SymbolType binOperator = binOpExp.getOperator();
/**
* Codegen/Type checking
*
* Retrieve the two Value Instructions
*
* They would be placed as if they were on stack
* hence we need to burger-flip them around (swap)
*/
Value vRhsInstr = cast(Value)popInstr();
Value vLhsInstr = cast(Value)popInstr();
/**
* Attempt to coerce the types of both instructions if one is
* a pointer and another is an integer, else do nothing
*/
attemptPointerAriehmeticCoercion(vLhsInstr, vRhsInstr);
Type vRhsType = vRhsInstr.getInstrType();
Type vLhsType = vLhsInstr.getInstrType();
/**
* TODO
* Types must either BE THE SAME or BE COMPATIBLE
*/
Type chosenType;
if(isSameType(vLhsType, vRhsType))
{
/* Left type + Right type = left/right type (just use left - it doesn't matter) */
chosenType = vLhsType;
}
else
{
throw new TypeMismatchException(this, vLhsType, vRhsType, "Binary operator expression requires both types be same");
}
BinOpInstr addInst = new BinOpInstr(vLhsInstr, vRhsInstr, binOperator);
addInstr(addInst);
/* Set the Value instruction's type */
addInst.setInstrType(chosenType);
}
/* Unary operator expressions */
else if(cast(UnaryOperatorExpression)statement)
{
UnaryOperatorExpression unaryOpExp = cast(UnaryOperatorExpression)statement;
SymbolType unaryOperator = unaryOpExp.getOperator();
/* The type of the eventual UnaryOpInstr */
Type unaryOpType;
/**
* Typechecking (TODO)
*/
Value expInstr = cast(Value)popInstr();
Type expType = expInstr.getInstrType();
/* TODO: Ad type check for operator */
/* If the unary operation is an arithmetic one */
if(unaryOperator == SymbolType.ADD || unaryOperator == SymbolType.SUB)
{
/* TODO: I guess any type fr */
if(unaryOperator == SymbolType.SUB)
{
// TODO: Note below is a legitimately good question, given a type
// ... <valueType>, what does applying a `-` infront of it (`-<valueType>`)
// ... mean in terms of its type?
//
// ... Does it remain the same type? We ask because of literal encoding.
// ... I believe the best way forward would be specifically to handle
// ... cases where `cast(LiteralValue)expInstr` is true here - just
// ... as we had the special handling for it in `NumberLiteral` statements
// ... before.
if(cast(LiteralValue)expInstr)
{
LiteralValue literalValue = cast(LiteralValue)expInstr;
string literalValueStr = literalValue.getLiteralValue();
ulong literalValueNumber = to!(ulong)(literalValueStr); // TODO: Add a conv check for overflow
if(literalValueNumber >= 9_223_372_036_854_775_808)
{
// TODO: I don't think we are meant to be doing the below, atleast for coercive cases
// TODO: make this error nicer
// throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Cannot represent -"~literalValueStr~" as too big");
}
// TODO: Check case of literal being 9223372036854775808 or above
// ... and having a `-` infront of it, then disallow
// TODO: Remove the below (just for now)
unaryOpType = expType;
}
else
{
// Else just copy the tyoe of the expInstr over
unaryOpType = expType;
}
}
else
{
// Else just copy the tyoe of the expInstr over
unaryOpType = expType;
}
}
/* If pointer dereference */
else if(unaryOperator == SymbolType.STAR)
{
gprintln("Type popped: "~to!(string)(expType));
// Okay, so yes, we would pop `ptr`'s type as `int*` which is correct
// but now, we must a.) ensure that IS the case and b.)
// push the type of `<type>` with one star less on as we are derefrencing `ptr`
Type derefPointerType;
if(cast(Pointer)expType)
{
Pointer pointerType = cast(Pointer)expType;
// Get the type being referred to
Type referredType = pointerType.getReferredType();
unaryOpType = referredType;
}
else
{
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "You cannot dereference a type that is not a pointer type!");
}
}
/* If pointer create `&` */
else if(unaryOperator == SymbolType.AMPERSAND)
{
/**
* If the type popped from the stack was `<type>` then push
* a new type onto the stack which is `<type>*`
*/
Type ptrType = new Pointer(expType);
unaryOpType = ptrType;
}
/* This should never occur */
else
{
gprintln("UnaryOperatorExpression: This should NEVER happen: "~to!(string)(unaryOperator), DebugType.ERROR);
assert(false);
}
// TODO: For type checking and semantics we should be checking WHAT is being ampersanded
// ... as in we should only be allowing Ident's to be ampersanded, not, for example, literals
// ... such a check can be accomplished via runtime type information of the instruction above
UnaryOpInstr addInst = new UnaryOpInstr(expInstr, unaryOperator);
gprintln("Made unaryop instr: "~to!(string)(addInst));
addInstr(addInst);
addInst.setInstrType(unaryOpType);
}
/* Function calls */
else if(cast(FunctionCall)statement)
{
// gprintln("FuncCall hehe (REMOVE AFTER DONE)");
FunctionCall funcCall = cast(FunctionCall)statement;
/* TODO: Look up func def to know when popping stops (types-based delimiting) */
Function func = cast(Function)resolver.resolveBest(modulle, funcCall.getName());
assert(func);
VariableParameter[] paremeters = func.getParams();
/* TODO: Pass in FUnction, so we get function's body for calling too */
FuncCallInstr funcCallInstr = new FuncCallInstr(func.getName(), paremeters.length);
gprintln("Name of func call: "~func.getName(), DebugType.ERROR);
/* If there are paremeters for this function (as per definition) */
if(!paremeters.length)
{
gprintln("No parameters for deez nuts: "~func.getName(), DebugType.ERROR);
}
/* Pop all args per type */
else
{
ulong parmCount = paremeters.length-1;
gprintln("Kachow: "~to!(string)(parmCount),DebugType.ERROR);
while(!isInstrEmpty())
{
Instruction instr = popInstr();
Value valueInstr = cast(Value)instr;
/* Must be a value instruction */
if(valueInstr && parmCount!=-1)
{
/* TODO: Determine type and match up */
gprintln("Yeah");
gprintln(valueInstr);
Type argType = valueInstr.getInstrType();
// gprintln(argType);
Variable parameter = paremeters[parmCount];
// gprintln(parameter);
Type parmType = getType(func.parentOf(), parameter.getType());
// gprintln("FuncCall(Actual): "~argType.getName());
// gprintln("FuncCall(Formal): "~parmType.getName());
// gprintln("FuncCall(Actual): "~valueInstr.toString());
/* Match up types */
//if(argType == parmType)
if(isSameType(argType, parmType))
{
gprintln("Match type");
/* Add the instruction into the FunctionCallInstr */
funcCallInstr.setEvalInstr(parmCount, valueInstr);
gprintln(funcCallInstr.getEvaluationInstructions());
}
else
{
printCodeQueue();
gprintln("Wrong actual argument type for function call", DebugType.ERROR);
gprintln("Cannot pass value of type '"~argType.getName()~"' to function accepting '"~parmType.getName()~"'", DebugType.ERROR);
throw new TypeMismatchException(this, parmType, argType, "The actual argument's type does not match that of the function's parameter type");
}
parmCount--;
}
else
{
// TODO: This should enver happen, see book and remove soon (see Cleanup: Remove any pushbacks #101)
/* Push it back */
addInstr(instr);
break;
}
}
}
/**
* TODO:
*
* 1. Create FuncCallInstr
* 2. Evaluate args and process them?! wait done elsewhere yeah!!!
* 3. Pop arts into here
* 4. AddInstr(combining those args)
* 5. DOne
*/
funcCallInstr.setContext(funcCall.getContext());
addInstr(funcCallInstr);
/* Set the Value instruction's type */
Type funcCallInstrType = getType(func.parentOf(), func.getType());
funcCallInstr.setInstrType(funcCallInstrType);
}
/* Type cast operator */
else if(cast(CastedExpression)statement)
{
CastedExpression castedExpression = cast(CastedExpression)statement;
gprintln("Context: "~to!(string)(castedExpression.context));
gprintln("ParentOf: "~to!(string)(castedExpression.parentOf()));
/* Extract the type that the cast is casting towards */
Type castToType = getType(castedExpression.context.container, castedExpression.getToType());
/**
* Codegen
*
* 1. Pop off the current value instruction corresponding to the embedding
* 2. Create a new CastedValueInstruction instruction
* 3. Set the context
* 4. Add to front of code queue
*/
Value uncastedInstruction = cast(Value)popInstr();
assert(uncastedInstruction);
/* Extract the type of the expression being casted */
Type typeBeingCasted = uncastedInstruction.getInstrType();
gprintln("TypeCast [FromType: "~to!(string)(typeBeingCasted)~", ToType: "~to!(string)(castToType)~"]");
printCodeQueue();
// TODO: Remove the `castToType` argument, this should be solely based off of the `.type` (as set below)
CastedValueInstruction castedValueInstruction = new CastedValueInstruction(uncastedInstruction, castToType);
castedValueInstruction.setContext(castedExpression.context);
addInstr(castedValueInstruction);
/* The type of the cats expression is that of the type it casts to */
castedValueInstruction.setInstrType(castToType);
}
}
/* VariableAssigbmentDNode */
else if(cast(tlang.compiler.typecheck.dependency.variables.VariableAssignmentNode)dnode)
{
import tlang.compiler.typecheck.dependency.variables;
/* Get the variable's name */
string variableName;
VariableAssignmentNode varAssignDNode = cast(tlang.compiler.typecheck.dependency.variables.VariableAssignmentNode)dnode;
Variable assignTo = (cast(VariableAssignment)varAssignDNode.getEntity()).getVariable();
variableName = resolver.generateName(modulle, assignTo);
gprintln("VariableAssignmentNode: "~to!(string)(variableName));
/* Get the Context of the Variable Assigmnent */
Context variableAssignmentContext = (cast(VariableAssignment)varAssignDNode.getEntity()).context;
/**
* FIXME: Now with ClassStaticAllocate we will have wrong instructoins for us
* ontop of the stack (at the beginning of the queue), I think this leads us
* to potentially opping wrong thing off - we should filter pop perhaps
*/
/**
* Codegen
*
* 1. Get the variable's name
* 2. Pop Value-instruction
* 3. Generate VarAssignInstruction with Value-instruction
* 4. Set the VarAssignInstr's Context to that of the Variable assigning to
*/
Instruction instr = popInstr();
assert(instr);
Value valueInstr = cast(Value)instr;
assert(valueInstr);
gprintln("VaribleAssignmentNode(): Just popped off valInstr?: "~to!(string)(valueInstr), DebugType.WARNING);
Type rightHandType = valueInstr.getInstrType();
gprintln("RightHandType (assignment): "~to!(string)(rightHandType));
gprintln(valueInstr is null);/*TODO: FUnc calls not implemented? Then is null for simple_1.t */
VariableAssignmentInstr varAssInstr = new VariableAssignmentInstr(variableName, valueInstr);
varAssInstr.setContext(variableAssignmentContext);
// NOTE: No need setting `varAssInstr.type` as the type if in `getEmbeddedInstruction().type`
addInstr(varAssInstr);
}
/* TODO: Add support */
/**
* TODO: We need to emit different code dependeing on variable declaration TYPE
* We could use context for this, ClassVariableDec vs ModuleVariableDec
*/
else if(cast(tlang.compiler.typecheck.dependency.variables.StaticVariableDeclaration)dnode)
{
/* TODO: Add skipping if context is within a class */
/* We need to wait for class static node, to do an InitInstruction (static init) */
/* It probably makes sense , IDK, we need to allocate both classes */
/**
* Codegen
*
* Emit a variable declaration instruction
*/
Variable variablePNode = cast(Variable)dnode.getEntity();
gprintln("HELLO FELLA");
string variableName = resolver.generateName(modulle, variablePNode);
gprintln("HELLO FELLA (name): "~variableName);
Type variableDeclarationType = getType(variablePNode.context.container, variablePNode.getType());
// Check if this variable declaration has an assignment attached
Value assignmentInstr;
if(variablePNode.getAssignment())
{
Instruction poppedInstr = popInstr();
assert(poppedInstr);
// Obtain the value instruction of the variable assignment
// ... along with the assignment's type
assignmentInstr = cast(Value)poppedInstr;
assert(assignmentInstr);
Type assignmentType = assignmentInstr.getInstrType();
// TODO: We should add a typecheck here where we update the type of the valInstr if it is of
// ... type NumberLiteral and coerce it to the variable referred to by the VariableAssignment
// ... see issue #94 part on "Coercion"
// If the types match then everything is fine
if(isSameType(variableDeclarationType, assignmentType))
{
gprintln("Variable's declared type ('"~to!(string)(variableDeclarationType)~"') matches that of assignment expression's type ('"~to!(string)(assignmentType)~"')");
}
// If the types do not match
else
{
// Then attempt coercion
attemptCoercion(variableDeclarationType, assignmentInstr);
}
}
/* Generate a variable declaration instruction and add it to the codequeue */
VariableDeclaration varDecInstr = new VariableDeclaration(variableName, 4, variableDeclarationType, assignmentInstr);
varDecInstr.setContext(variablePNode.context);
addInstrB(varDecInstr);
}
/* TODO: Add class init, see #8 */
else if(cast(tlang.compiler.typecheck.dependency.classes.classStaticDep.ClassStaticNode)dnode)
{
/* Extract the class node and create a static allocation instruction out of it */
Clazz clazzPNode = cast(Clazz)dnode.getEntity();
string clazzName = resolver.generateName(modulle, clazzPNode);
ClassStaticInitAllocate clazzStaticInitAllocInstr = new ClassStaticInitAllocate(clazzName);
/* Add this static initialization to the list of global allocations required */
addInit(clazzStaticInitAllocInstr);
}
/* It will pop a bunch of shiiit */
/* TODO: ANy statement */
else if(cast(tlang.compiler.typecheck.dependency.core.DNode)dnode)
{
/* TODO: Get the STatement */
Statement statement = dnode.getEntity();
gprintln("Generic DNode typecheck(): Begin (examine: "~to!(string)(dnode)~" )");
/* VariableAssignmentStdAlone */
if(cast(VariableAssignmentStdAlone)statement)
{
VariableAssignmentStdAlone vasa = cast(VariableAssignmentStdAlone)statement;
string variableName = vasa.getVariableName();
/* Extract information about the variable declaration of the avriable being assigned to */
Context variableContext = vasa.getContext();
Variable variable = cast(Variable)resolver.resolveBest(variableContext.container, variableName);
Type variableDeclarationType = getType(variableContext.container, variable.getType());
/**
* Codegen
*
* 1. Get the variable's name
* 2. Pop Value-instruction
* 3. Generate VarAssignInstruction with Value-instruction
*/
Instruction instr = popInstr();
assert(instr);
Value assignmentInstr = cast(Value)instr;
assert(assignmentInstr);
Type assignmentType = assignmentInstr.getInstrType();
assert(assignmentType);
if(isSameType(variableDeclarationType, assignmentType))
{
gprintln("Variable's declared type ('"~to!(string)(variableDeclarationType)~"') matches that of assignment expression's type ('"~to!(string)(assignmentType)~"')");
}
// If the type's do not match
else
{
// Then attempt coercion
attemptCoercion(variableDeclarationType, assignmentInstr);
}
/* Generate a variable assignment instruction and add it to the codequeue */
VariableAssignmentInstr vAInstr = new VariableAssignmentInstr(variableName, assignmentInstr);
vAInstr.setContext(vasa.getContext());
addInstrB(vAInstr);
}
/**
* Return statement (ReturnStmt)
*/
else if(cast(ReturnStmt)statement)
{
ReturnStmt returnStatement = cast(ReturnStmt)statement;
/**
* Codegen
*
* 1. Pop the expression on the stack
* 2. Create a new ReturnInstruction with the expression instruction
* embedded in it
* 3. Set the Context of the instruction
* 4. Add this instruction back
*/
Value returnExpressionInstr = cast(Value)popInstr();
assert(returnExpressionInstr);
ReturnInstruction returnInstr = new ReturnInstruction(returnExpressionInstr);
returnInstr.setContext(returnStatement.getContext());
addInstrB(returnInstr);
}
/**
* If statement (IfStatement)
*/
else if(cast(IfStatement)statement)
{
IfStatement ifStatement = cast(IfStatement)statement;
BranchInstruction[] branchInstructions;
/* Get the if statement's branches */
Branch[] branches = ifStatement.getBranches();
assert(branches.length > 0);
/**
* 1. These would be added stack wise, so we need to pop them like backwards
* 2. Then a reversal at the end (generated instructions list)
*
* FIXME: EIther used siggned or the hack below lmao, out of boounds
*/
for(ulong branchIdx = branches.length-1; true; branchIdx--)
{
Branch branch = branches[branchIdx];
// Pop off an expression instruction (if it exists)
Value branchConditionInstr;
if(branch.hasCondition())
{
Instruction instr = popInstr();
gprintln("BranchIdx: "~to!(string)(branchIdx));
gprintln("Instr is: "~to!(string)(instr));
branchConditionInstr = cast(Value)instr;
assert(branchConditionInstr);
}
// Get the number of body instructions to pop
ulong bodyCount = branch.getBody().length;
ulong i = 0;
Instruction[] bodyInstructions;
while(i < bodyCount)
{
Instruction bodyInstr = tailPopInstr();
bodyInstructions~=bodyInstr;
gprintln("tailPopp'd("~to!(string)(i)~"/"~to!(string)(bodyCount-1)~"): "~to!(string)(bodyInstr));
i++;
}
// Reverse the body instructions (correct ordering)
bodyInstructions=reverse(bodyInstructions);
// Create the branch instruction (coupling the condition instruction and body instructions)
branchInstructions~=new BranchInstruction(branchConditionInstr, bodyInstructions);
if(branchIdx == 0)
{
break;
}
}
// Reverse the list to be in the correct order (it was computed backwards)
branchInstructions=reverse(branchInstructions);
/**
* Code gen
*
* 1. Create the IfStatementInstruction containing the BranchInstruction[](s)
* 2. Set the context
* 3. Add the instruction
*/
IfStatementInstruction ifStatementInstruction = new IfStatementInstruction(branchInstructions);
ifStatementInstruction.setContext(ifStatement.getContext());
addInstrB(ifStatementInstruction);
gprintln("If!");
}
/**
* While loop (WhileLoop)
*/
else if(cast(WhileLoop)statement)
{
WhileLoop whileLoop = cast(WhileLoop)statement;
// FIXME: Do-while loops are still being considered in terms of dependency construction
if(whileLoop.isDoWhile)
{
gprintln("Still looking at dependency construction in this thing (do while loops )");
assert(false);
}
Branch branch = whileLoop.getBranch();
/* The condition `Value` instruction should be on the stack */
Value valueInstrCondition = cast(Value)popInstr();
assert(valueInstrCondition);
/* Process the body of the while-loop with tail-popping followed by a reverse */
Instruction[] bodyInstructions;
ulong bodyLen = branch.getBody().length;
ulong bodyIdx = 0;
while(bodyIdx < bodyLen)
{
Instruction bodyInstr = tailPopInstr();
bodyInstructions~=bodyInstr;
bodyIdx++;
}
// Reverse the list to be in the correct order (it was computed backwards)
bodyInstructions=reverse(bodyInstructions);
// Create a branch instruction coupling the condition instruction + body instructions (in corrected order)
BranchInstruction branchInstr = new BranchInstruction(valueInstrCondition, bodyInstructions);
/**
* Code gen
*
* 1. Create the WhileLoopInstruction containing the BranchInstruction
* 2. Set the context
* 3. Add the instruction
*/
WhileLoopInstruction whileLoopInstruction = new WhileLoopInstruction(branchInstr);
whileLoopInstruction.setContext(whileLoop.getContext());
addInstrB(whileLoopInstruction);
}
/**
* For loop (ForLoop)
*/
else if(cast(ForLoop)statement)
{
ForLoop forLoop = cast(ForLoop)statement;
/* Pop-off the Value-instruction for the condition */
Value valueInstrCondition = cast(Value)popInstr();
assert(valueInstrCondition);
/* Calculate the number of instructions representing the body to tailPopInstr() */
ulong bodyTailPopNumber = forLoop.getBranch().getStatements().length;
gprintln("bodyTailPopNumber: "~to!(string)(bodyTailPopNumber));
/* Pop off the body instructions, then reverse final list */
Instruction[] bodyInstructions;
for(ulong idx = 0; idx < bodyTailPopNumber; idx++)
{
bodyInstructions ~= tailPopInstr();
}
bodyInstructions = reverse(bodyInstructions);
// Create a branch instruction coupling the condition instruction + body instructions (in corrected order)
BranchInstruction branchInstr = new BranchInstruction(valueInstrCondition, bodyInstructions);
/* If there is a pre-run instruction */
Instruction preRunInstruction;
if(forLoop.hasPreRunStatement())
{
preRunInstruction = tailPopInstr();
}
/**
* Code gen
*
* 1. Create the ForLoopInstruction containing the BranchInstruction and
* preRunInstruction
* 2. Set the context
* 3. Add the instruction
*/
ForLoopInstruction forLoopInstruction = new ForLoopInstruction(branchInstr, preRunInstruction);
forLoopInstruction.setContext(forLoop.context);
addInstrB(forLoopInstruction);
}
/* Branch */
else if(cast(Branch)statement)
{
Branch branch = cast(Branch)statement;
gprintln("Look at that y'all, cause this is it: "~to!(string)(branch));
}
/**
* Dereferencing pointer assignment statement (PointerDereferenceAssignment)
*/
else if(cast(PointerDereferenceAssignment)statement)
{
PointerDereferenceAssignment ptrDerefAss = cast(PointerDereferenceAssignment)statement;
/* Pop off the pointer dereference expression instruction (LHS) */
Value lhsPtrExprInstr = cast(Value)popInstr();
assert(lhsPtrExprInstr);
/* Pop off the assignment instruction (RHS expression) */
Value rhsExprInstr = cast(Value)popInstr();
assert(rhsExprInstr);
/**
* Code gen
*
* 1. Create the PointerDereferenceAssignmentInstruction containing the `lhsPtrExprInstr`
* and `rhsExprInstr`. Also set the pointer depth.
* 2. Set the context
* 3. Add the instruction
*/
PointerDereferenceAssignmentInstruction pointerDereferenceAssignmentInstruction = new PointerDereferenceAssignmentInstruction(lhsPtrExprInstr, rhsExprInstr, ptrDerefAss.getDerefCount());
pointerDereferenceAssignmentInstruction.setContext(ptrDerefAss.context);
addInstrB(pointerDereferenceAssignmentInstruction);
}
/**
* Discard statement (DiscardStatement)
*/
else if(cast(DiscardStatement)statement)
{
DiscardStatement discardStatement = cast(DiscardStatement)statement;
/* Pop off a Value instruction */
Value exprInstr = cast(Value)popInstr();
assert(exprInstr);
/**
* Code gen
*
* 1. Create the DiscardInstruction containing the Value instruction
* `exprInstr`
* 2. Set the context
* 3. Add the instruction
*/
DiscardInstruction discardInstruction = new DiscardInstruction(exprInstr);
discardInstruction.setContext(discardStatement.context);
addInstrB(discardInstruction);
}
/* Case of no matches */
else
{
gprintln("NO MATCHES FIX ME FOR: "~to!(string)(statement), DebugType.WARNING);
}
}
}
/**
* Perform type-checking and code-generation
* on the provided linearized dependency tree
*/
private void doTypeCheck(DNode[] actionList)
{
/* Print the action list provided to us */
gprintln("Action list: "~to!(string)(actionList));
/**
* Loop through each dependency-node in the action list
* and perform the type-checking/code generation
*/
foreach(DNode node; actionList)
{
gprintln("Process: "~to!(string)(node));
/* Print the code queue each time */
gprintln("sdfhjkhdsfjhfdsj 1");
printCodeQueue();
gprintln("sdfhjkhdsfjhfdsj 2");
/* Type-check/code-gen this node */
typeCheckThing(node);
writeln("--------------");
}
writeln("\n################# Results from type-checking/code-generation #################\n");
/* Print the init queue */
gprintln("<<<<< FINAL ALLOCATE QUEUE >>>>>");
printInitQueue();
/* Print the code queue */
gprintln("<<<<< FINAL CODE QUEUE >>>>>");
printCodeQueue();
}
/**
* Given a type as a string this
* returns the actual type
*
* If not found then null is returned
*/
public Type getType(Container c, string typeString)
{
Type foundType;
/* Check if the type is built-in */
foundType = getBuiltInType(this, typeString);
/* If it isn't then check for a type (resolve it) */
if(!foundType)
{
foundType = cast(Type)resolver.resolveBest(c, typeString);
}
return foundType;
}
// TODO: What actually is the point of this? It literally generates a `Class[]`
// ... and then tosses it after returning. (See issue "Dead code tracking" #83)
private void checkDefinitionTypes(Container c)
{
/* Check variables and functions (TypedEntities) declarations */
// checkTypedEntitiesTypeNames(c);
/* Check class inheritance types */
Clazz[] classes;
foreach (Statement statement; c.getStatements())
{
if (statement !is null && cast(Clazz) statement)
{
classes ~= cast(Clazz) statement;
}
}
}
/**
* Begins the type checking process
*/
public void beginCheck()
{
/* Process all pseudo entities of the given module */
processPseudoEntities(modulle);
/**
* Make sure there are no name collisions anywhere
* in the Module with an order of precedence of
* Classes being declared before Functions and
* Functions before Variables
*/
checkContainerCollision(modulle); /* TODO: Rename checkContainerCollision */
/* TODO: Now that everything is defined, no collision */
/* TODO: Do actual type checking and declarations */
dependencyCheck();
}
private void processPseudoEntities(Container c)
{
/* Collect all `extern` declarations */
ExternStmt[] externDeclarations;
foreach(Statement curStatement; c.getStatements())
{
if(cast(ExternStmt)curStatement)
{
externDeclarations ~= cast(ExternStmt)curStatement;
}
}
// TODO: We could remove them from the container too, means less loops in dependency/core.d
/* Add each Entity to the container */
foreach(ExternStmt curExternStmt; externDeclarations)
{
SymbolType externType = curExternStmt.getExternType();
string externalSymbolName = curExternStmt.getExternalName();
Entity pseudoEntity = curExternStmt.getPseudoEntity();
/* Set the embedded pseudo entity's parent to that of the container */
pseudoEntity.parentTo(c);
c.addStatements([pseudoEntity]);
assert(this.getResolver().resolveBest(c, externalSymbolName));
}
}
private void checkClassInherit(Container c)
{
/* Get all types (Clazz so far) */
Clazz[] classTypes;
foreach (Statement statement; c.getStatements())
{
if (statement !is null && cast(Clazz) statement)
{
classTypes ~= cast(Clazz) statement;
}
}
/* Process each Clazz */
foreach (Clazz clazz; classTypes)
{
/* Get the current class's parent */
string[] parentClasses = clazz.getInherit();
gprintln("Class: " ~ clazz.getName() ~ ": ParentInheritList: " ~ to!(
string)(parentClasses));
/* Try resolve all of these */
foreach (string parent; parentClasses)
{
/* Find the named entity */
Entity namedEntity;
/* Check if the name is rooted */
string[] dotPath = split(parent, '.');
gprintln(dotPath.length);
/* Resolve the name */
namedEntity = resolver.resolveBest(c, parent);
/* If the entity exists */
if (namedEntity)
{
/* Check if it is a Class, if so non-null */
Clazz parentEntity = cast(Clazz) namedEntity;
/* Only inherit from class or (TODO: interfaces) */
if (parentEntity)
{
/* Make sure it is not myself */
if (parentEntity != clazz)
{
/* TODO: Add loop checking here */
}
else
{
Parser.expect("Cannot inherit from self");
}
}
/* Error */
else
{
Parser.expect("Can only inherit from classes");
}
}
/* If the entity doesn't exist then it is an error */
else
{
Parser.expect("Could not find any entity named " ~ parent);
}
}
}
/* Once processing is done, apply recursively */
foreach (Clazz clazz; classTypes)
{
checkClassInherit(clazz);
}
}
private void checkClasses(Container c)
{
/**
* Make sure no duplicate types (classes) defined
* within same Container
*/
checkClassNames(c);
/**
* Now that everything is neat and tidy
* let's check class properties like inheritance
* names
*/
checkClassInherit(c);
}
public Resolver getResolver()
{
return resolver;
}
/**
* Given a Container `c` this will check all
* members of said Container and make sure
* none of them have a name that conflicts
* with any other member in said Container
* nor uses the same name AS the Container
* itself.
*
* Errors are printed when a member has a name
* of a previously defined member
*
* Errors are printed if the memeber shares a
* name with the container
*
* If the above 2 are false then a last check
* happens to check if the current Entity
* that just passed these checks is itself a
* Container, if not, then we do nothing and
* go onto processing the next Entity that is
* a member of Container `c` (we stay at the
* same level), HOWEVER if so, we then recursively
* call `checkContainer` on said Entity and the
* logic above applies again
*/
private void checkContainerCollision(Container c)
{
/**
* TODO: Always make sure this holds
*
* All objects that implement Container so far
* are also Entities (hence they have a name)
*/
Entity containerEntity = cast(Entity)c;
assert(containerEntity);
/**
* Get all Entities of the Container with order Clazz, Function, Variable
*/
Entity[] entities = getContainerMembers(c);
gprintln("checkContainer(C): " ~ to!(string)(entities));
foreach (Entity entity; entities)
{
/**
* Absolute root Container (in other words, the Module)
* can not be used
*/
if(cmp(modulle.getName(), entity.getName()) == 0)
{
throw new CollidingNameException(this, modulle, entity, c);
}
/**
* If the current entity's name matches the container then error
*/
else if (cmp(containerEntity.getName(), entity.getName()) == 0)
{
throw new CollidingNameException(this, containerEntity, entity, c);
}
/**
* If there are conflicting names within the current container
* (this takes precedence into account based on how `entities`
* is generated)
*/
else if (findPrecedence(c, entity.getName()) != entity)
{
throw new CollidingNameException(this, findPrecedence(c,
entity.getName()), entity, c);
}
/**
* Otherwise this Entity is fine
*/
else
{
string fullPath = resolver.generateName(modulle, entity);
string containerNameFullPath = resolver.generateName(modulle, containerEntity);
gprintln("Entity \"" ~ fullPath
~ "\" is allowed to be defined within container \""
~ containerNameFullPath ~ "\"");
/**
* Check if this Entity is a Container, if so, then
* apply the same round of checks within it
*/
Container possibleContainerEntity = cast(Container) entity;
if (possibleContainerEntity)
{
checkContainerCollision(possibleContainerEntity);
}
}
}
}
/**
* TODO: Create a version of the below function that possibly
* returns the list of Statement[]s ordered like below but
* via a weighting system rather
*/
public Statement[] getContainerMembers_W(Container c)
{
/* Statements */
Statement[] statements;
/* TODO: Implement me */
return statements;
}
/**
* Returns container members in order of
* Clazz, Function, Variable
*/
private Entity[] getContainerMembers(Container c)
{
/* Entities */
Entity[] entities;
/* Get all classes */
foreach (Statement statement; c.getStatements())
{
if (statement !is null && cast(Entity) statement)
{
entities ~= cast(Entity) statement;
}
}
// /* Get all classes */
// foreach (Statement statement; c.getStatements())
// {
// if (statement !is null && cast(Clazz) statement)
// {
// entities ~= cast(Clazz) statement;
// }
// }
// /* Get all functions */
// foreach (Statement statement; c.getStatements())
// {
// if (statement !is null && cast(Function) statement)
// {
// entities ~= cast(Function) statement;
// }
// }
// /* Get all variables */
// foreach (Statement statement; c.getStatements())
// {
// if (statement !is null && cast(Variable) statement)
// {
// entities ~= cast(Variable) statement;
// }
// }
return entities;
}
/**
* Finds the first occurring Entity with the provided
* name based on Classes being searched, then Functions
* and lastly Variables
*/
public Entity findPrecedence(Container c, string name)
{
foreach (Entity entity; getContainerMembers(c))
{
/* If we find matching entity names */
if (cmp(entity.getName(), name) == 0)
{
return entity;
}
}
return null;
}
/**
* Starting from a Container c this makes sure
* that all classes defined within that container
* do no clash name wise
*
* Make this general, so it checks all Entoties
* within container, starting first with classes
* then it should probably mark them, this will
* be so we can then loop through all entities
* including classes, of container c and for
* every entity we come across in c we make
* sure it doesn't have a name of something that
* is marked
*/
private void checkClassNames(Container c)
{
/**
* TODO: Always make sure this holds
*
* All objects that implement Container so far
* are also Entities (hence they have a name)
*/
Entity containerEntity = cast(Entity)c;
assert(containerEntity);
/* Get all types (Clazz so far) */
Clazz[] classTypes;
foreach (Statement statement; c.getStatements())
{
if (statement !is null && cast(Clazz) statement)
{
classTypes ~= cast(Clazz) statement;
}
}
/* Declare each type */
foreach (Clazz clazz; classTypes)
{
// gprintln("Name: "~resolver.generateName(modulle, clazz));
/**
* Check if the first class found with my name is the one being
* processed, if so then it is fine, if not then error, it has
* been used (that identifier) already
*
* TODO: We cann add a check here to not allow containerName == clazz
* TODO: Call resolveUp as we can then stop class1.class1.class1
* Okay top would resolve first part but class1.class2.class1
* would not be caught by that
*
* TODO: This will meet inner clazz1 first, we need to do another check
*/
if (resolver.resolveUp(c, clazz.getName()) != clazz)
{
Parser.expect("Cannot define class \"" ~ resolver.generateName(modulle,
clazz) ~ "\" as one with same name, \"" ~ resolver.generateName(modulle,
resolver.resolveUp(c, clazz.getName())) ~ "\" exists in container \"" ~ resolver.generateName(
modulle, containerEntity) ~ "\"");
}
else
{
/* Get the current container's parent container */
Container parentContainer = containerEntity.parentOf();
/* Don't allow a class to be named after it's container */
// if(!parentContainer)
// {
if (cmp(containerEntity.getName(), clazz.getName()) == 0)
{
Parser.expect("Class \"" ~ resolver.generateName(modulle,
clazz) ~ "\" cannot be defined within container with same name, \"" ~ resolver.generateName(
modulle, containerEntity) ~ "\"");
}
/* TODO: Loop througn Container ENtitys here */
/* Make sure that when we call findPrecedence(entity) == current entity */
// }
/* TODO: We allow shaddowing so below is disabled */
/* TODO: We should however use the below for dot-less resolution */
// /* Find the name starting in upper cotainer */
// Entity clazzAbove = resolveUp(parentContainer, clazz.getName());
// if(!clazzAbove)
// {
// }
// else
// {
// Parser.expect("Name in use abpve us, bad"~to!(string)(clazz));
// }
/* If the Container's parent container is Module then we can have
/* TODO: Check that it doesn;t equal any class up the chain */
/* TODO: Exclude Module from this */
// /* Still check if there is something with our name above us */
// Container parentContainer = c.parentOf();
// /* If at this level container we find duplicate */
// if(resolveUp(parentContainer, clazz.getName()))
// {
// Parser.expect("Class with name "~clazz.getName()~" defined in class "~c.getName());
// }
}
}
/**
* TODO: Now we should loop through each class and do the same
* so we have all types defined
*/
//gprintln("Defined classes: "~to!(string)(Program.getAllOf(new Clazz(""), cast(Statement[])marked)));
/**
* By now we have confirmed that within the current container
* there are no classes defined with the same name
*
* We now check each Class recursively, once we are done
* we mark the class entity as "ready" (may be referenced)
*/
foreach (Clazz clazz; classTypes)
{
gprintln("Check recursive " ~ to!(string)(clazz), DebugType.WARNING);
/* Check the current class's types within */
checkClassNames(clazz);
// checkClassInherit(clazz);
}
/*Now we should loop through each class */
/* Once outerly everything is defined we can then handle class inheritance names */
/* We can also then handle refereces between classes */
// gprintln("checkTypes: ")
}
/* Test name resolution */
unittest
{
//assert()
}
}
/* Test name colliding with container name (1/3) [module] */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/collide_container_module1.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity container = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y");
Entity colliderMember = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y.y");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member y.y collided with root container (module) y */
assert(e.defined == container);
}
}
/* Test name colliding with container name (2/3) [module, nested collider] */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/collide_container_module2.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity container = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y");
Entity colliderMember = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y.a.b.c.y");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member y.a.b.c.y collided with root container (module) y */
assert(e.defined == container);
}
}
/* Test name colliding with container name (3/3) [container (non-module), nested collider] */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/collide_container_non_module.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity container = typeChecker.getResolver().resolveBest(typeChecker.getModule, "a.b.c");
Entity colliderMember = typeChecker.getResolver().resolveBest(typeChecker.getModule, "a.b.c.c");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member a.b.c.c collided with a.b.c container */
assert(e.defined == container);
}
}
/* Test name colliding with member */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/collide_member.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity memberFirst = typeChecker.getResolver().resolveBest(typeChecker.getModule, "a.b");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member a.b.c.c collided with a.b.c container */
assert(e.attempted != memberFirst);
}
}
/* Test name colliding with member (check that the member defined is class (precendence test)) */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/precedence_collision_test.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity ourClassA = typeChecker.getResolver().resolveBest(typeChecker.getModule, "a");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member attempted was Variable and defined was Clazz */
assert(cast(Variable)e.attempted);
assert(cast(Clazz)e.defined);
}
}
/* Test name colliding with container name (1/2) */
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/collide_container.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Setup testing variables */
Entity container = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y");
Entity colliderMember = typeChecker.getResolver().resolveBest(typeChecker.getModule, "y.y");
try
{
/* Perform test */
typeChecker.beginCheck();
/* Shouldn't reach here, collision exception MUST occur */
assert(false);
}
catch (CollidingNameException e)
{
/* Make sure the member y.y collided with root container (module) y */
assert(e.defined == container);
}
}
/* Test name colliding with container name (1/2) */
// TODO: Re-enable this when we take a look at the `discards` - for now discards at module level are not allowed
// ... therefore this unittest fails - otherwise it would have normally passed
// unittest
// {
// import std.file;
// import std.stdio;
// import compiler.lexer;
// import compiler.parsing.core;
// string sourceFile = "source/tlang/testing/typecheck/simple_dependence_correct7.t";
// File sourceFileFile;
// sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
// ulong fileSize = sourceFileFile.size();
// byte[] fileBytes;
// fileBytes.length = fileSize;
// fileBytes = sourceFileFile.rawRead(fileBytes);
// sourceFileFile.close();
// string sourceCode = cast(string) fileBytes;
// Lexer currentLexer = new Lexer(sourceCode);
// currentLexer.performLex();
// Parser parser = new Parser(currentLexer.getTokens());
// Module modulle = parser.parse();
// TypeChecker typeChecker = new TypeChecker(modulle);
// /* Perform test */
// typeChecker.beginCheck();
// /* TODO: Insert checks here */
// }
/**
* Code generation and typechecking
*
* Testing file: `simple_function_call.t`
*/
unittest
{
import std.file;
import std.stdio;
import tlang.compiler.lexer.core;
import tlang.compiler.parsing.core;
string sourceFile = "source/tlang/testing/typecheck/simple_function_call.t";
File sourceFileFile;
sourceFileFile.open(sourceFile); /* TODO: Error handling with ANY file I/O */
ulong fileSize = sourceFileFile.size();
byte[] fileBytes;
fileBytes.length = fileSize;
fileBytes = sourceFileFile.rawRead(fileBytes);
sourceFileFile.close();
string sourceCode = cast(string) fileBytes;
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
/* Perform test */
typeChecker.beginCheck();
/* TODO: Actually test generated code queue */
}