mirror of https://github.com/tbklang/tlang.git
1495 lines
46 KiB
D
1495 lines
46 KiB
D
module compiler.typecheck.dependency.core;
|
|
|
|
import compiler.symbols.check;
|
|
import compiler.symbols.data;
|
|
import std.conv : to;
|
|
import std.string;
|
|
import std.stdio;
|
|
import gogga;
|
|
import compiler.parsing.core;
|
|
import compiler.typecheck.resolution;
|
|
import compiler.typecheck.exceptions;
|
|
import compiler.typecheck.core;
|
|
import compiler.symbols.typing.core;
|
|
import compiler.symbols.typing.builtins;
|
|
|
|
|
|
|
|
/**
|
|
* Passed around
|
|
*
|
|
* 1. Contains containership (some Statements are not contained) so we need to track this
|
|
* 2. InitScope, STATIC or VIRTUAL permission
|
|
* 3. `allowUp`, when resolving names in this Context use
|
|
* resolveBest instead of resolveWithin (stay inside Context solely
|
|
* don't travel up parents)
|
|
*/
|
|
public final class Context
|
|
{
|
|
// Required for cases where we need the functionality of the type checker
|
|
static TypeChecker tc;
|
|
|
|
InitScope initScope;
|
|
Container container;
|
|
bool allowUp = true;
|
|
|
|
this(Container container, InitScope initScope)
|
|
{
|
|
this.initScope = initScope;
|
|
this.container = container;
|
|
}
|
|
|
|
public bool isAllowUp()
|
|
{
|
|
return allowUp;
|
|
}
|
|
|
|
public void noAllowUp()
|
|
{
|
|
allowUp = false;
|
|
}
|
|
|
|
public Container getContainer()
|
|
{
|
|
return container;
|
|
}
|
|
|
|
public override string toString()
|
|
{
|
|
return "Context [ContPtr(valid?): "~to!(string)(!(container is null))~", InitScope: "~to!(string)(initScope)~"]";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FunctionData
|
|
*
|
|
* Contains the dependency tree for a function,
|
|
* it's name, context as to where it is declared
|
|
*
|
|
*TODO: TO getn this to work DNode and DNoeGenerator
|
|
* must become one to house `private static DNode root`
|
|
* and `private static DNode[] pool`, which means FunctionData
|
|
* may remain completely seperated from Module's DNode
|
|
*
|
|
* Of course DNode must have a FunctionData[] array irrespective
|
|
* of the sub-type of DNode as we look up data using it
|
|
* techncially it could be seperate, yeah, global function
|
|
*
|
|
* The FunctionData should, rather than Context perhaps,
|
|
* take in the DNode of the Modulle, to be able to idk
|
|
* maybe do some stuff
|
|
*/
|
|
public struct FunctionData
|
|
{
|
|
public string name;
|
|
public DNodeGenerator ownGenerator;
|
|
public Function func;
|
|
|
|
public DNode generate()
|
|
{
|
|
return ownGenerator.generate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* All declared functions
|
|
*/
|
|
private FunctionData[string] functions;
|
|
|
|
|
|
/**
|
|
* Returns the declared functions
|
|
*/
|
|
public FunctionData[string] grabFunctionDefs()
|
|
{
|
|
return functions;
|
|
}
|
|
|
|
/**
|
|
* Creates a new FunctionData and adds it to the
|
|
* list of declared functions
|
|
*
|
|
* Requires a TypeChecker `tc`
|
|
*/
|
|
private void addFunctionDef(TypeChecker tc, Function func)
|
|
{
|
|
/* (Sanity Check) This should never be called again */
|
|
foreach(string cFuncKey; functions.keys())
|
|
{
|
|
FunctionData cFuncData = functions[cFuncKey];
|
|
Function cFunc = cFuncData.func;
|
|
|
|
if(cFunc == func)
|
|
{
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create the FunctionData, coupled with it own DNodeGenerator
|
|
* context etc.
|
|
*/
|
|
FunctionData funcData;
|
|
funcData.ownGenerator = new DFunctionInnerGenerator(tc, func);
|
|
funcData.name = func.getName();
|
|
funcData.func = func;
|
|
|
|
|
|
functions[funcData.name] = funcData;
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* DNode
|
|
*
|
|
* Represents a dependency node which contains sub-dependencies,
|
|
* an associated Statement (to be initialized) and status flags
|
|
* as to whether the node has been visited yet and whether or
|
|
* not it has been initialized
|
|
*/
|
|
public class DNode
|
|
{
|
|
/* The Statement to be initialized */
|
|
protected Statement entity;
|
|
|
|
protected string name;
|
|
|
|
protected DNodeGenerator dnodegen;
|
|
protected Resolver resolver;
|
|
|
|
private bool visited;
|
|
private bool complete;
|
|
private DNode[] dependencies;
|
|
|
|
|
|
//TODO: Commen this
|
|
//NOTE: This is the linearized version
|
|
public static DNode[] poes;
|
|
|
|
this(DNodeGenerator dnodegen, Statement entity)
|
|
{
|
|
this.entity = entity;
|
|
this.dnodegen = dnodegen;
|
|
this.resolver = dnodegen.resolver;
|
|
|
|
initName();
|
|
}
|
|
|
|
public void needs(DNode dependency)
|
|
{
|
|
dependencies ~= dependency;
|
|
}
|
|
|
|
public bool isVisisted()
|
|
{
|
|
return visited;
|
|
}
|
|
|
|
public void markVisited()
|
|
{
|
|
visited = true;
|
|
}
|
|
|
|
public void markCompleted()
|
|
{
|
|
complete = true;
|
|
}
|
|
|
|
public bool isCompleted()
|
|
{
|
|
return complete;
|
|
}
|
|
|
|
public Statement getEntity()
|
|
{
|
|
return entity;
|
|
}
|
|
|
|
public static ulong count(string bruh)
|
|
{
|
|
ulong i = 0;
|
|
foreach(char character; bruh)
|
|
{
|
|
if(character == '.')
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
public static ulong c = 0;
|
|
|
|
|
|
public final string getName()
|
|
{
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Should be overriden or have something set
|
|
* inherited variable, this should make the
|
|
* implementation of `print()` a lot more
|
|
* cleaner
|
|
*/
|
|
private void initName()
|
|
{
|
|
name = "bruh";
|
|
}
|
|
|
|
public string print()
|
|
{
|
|
string spaces = " ";
|
|
/* The tree */ /*TODO: Make genral to statement */
|
|
string tree = " ";
|
|
|
|
// if(cast(Entity)entity || cast(VariableAssignment)entity)
|
|
// {
|
|
// tree ~= name;
|
|
// }
|
|
// else
|
|
// {
|
|
// tree ~= entity.toString();
|
|
// }
|
|
|
|
tree ~= name;
|
|
|
|
tree ~= "\n";
|
|
c++;
|
|
foreach(DNode dependancy; dependencies)
|
|
{
|
|
if(!dependancy.isCompleted())
|
|
{
|
|
dependancy.markCompleted();
|
|
|
|
|
|
|
|
tree ~= spaces[0..(c)*3]~dependancy.print();
|
|
}
|
|
|
|
}
|
|
|
|
markCompleted();
|
|
|
|
/* TODO: I think using `isDone` we can linearise */
|
|
gprintln("Done/Not-done?: "~to!(string)(isDone));
|
|
|
|
if(isDone)
|
|
{
|
|
poes ~= this;
|
|
}
|
|
|
|
c--;
|
|
return tree;
|
|
}
|
|
|
|
private bool isDone()
|
|
{
|
|
bool done = false;
|
|
|
|
foreach(DNode dependency; dependencies)
|
|
{
|
|
if(!dependency.isCompleted())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* DNodeGenerator (Next-generation) base
|
|
*
|
|
* This is a base class for a DNode generator,
|
|
* all it requires to construct is:
|
|
*
|
|
* 1. Context (to know what we are in or so)
|
|
* 2. Statements[] (to know what to process)
|
|
* 3. TypeChecker (to know how to resolve names)
|
|
*
|
|
*/
|
|
public class DNodeGeneratorBase
|
|
{
|
|
/* Type checker (for name lookups) */
|
|
private TypeChecker tc;
|
|
|
|
/* Statements to process */
|
|
private Statement[] statements;
|
|
|
|
/* Information about our current container for said statements (and initscope) */
|
|
private Context context;
|
|
|
|
this(TypeChecker tc, Statement[] statements, Context context)
|
|
{
|
|
this.tc = tc;
|
|
this.statements = statements;
|
|
this.context = context;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public final class DFunctionInnerGenerator : DNodeGenerator
|
|
{
|
|
private Function func;
|
|
|
|
this(TypeChecker tc, Function func)
|
|
{
|
|
super(tc);
|
|
this.func = func;
|
|
}
|
|
|
|
public override DNode generate()
|
|
{
|
|
/* Recurse downwards */
|
|
/* FIXME: We need to no use modulle, but use some fsort of Function Container */
|
|
Context context = new Context(func, InitScope.STATIC);
|
|
DNode funcDNode = generalPass(func, context);
|
|
|
|
return funcDNode;
|
|
}
|
|
}
|
|
|
|
|
|
public class DNodeGenerator
|
|
{
|
|
/**
|
|
* Type checking utilities
|
|
*/
|
|
private TypeChecker tc;
|
|
public Resolver resolver;
|
|
|
|
|
|
/**
|
|
* Supporting string -> DNode map for `saveFunctionDefinitionNode`
|
|
* and `retrieveFunctionDefintionNode`
|
|
*/
|
|
private DNode[string] functionDefinitions;
|
|
|
|
/**
|
|
* Given a DNode generated by a Function (function definition)
|
|
* this will extract the name of the function and save the DNode
|
|
* into the map for later retrieval by `retrieveFunctionDefinitionNode`
|
|
*/
|
|
private void saveFunctionDefinitionNode(DNode funcDefNode)
|
|
{
|
|
gprintln("saveFunctionDefinitionNode: Implement me please");
|
|
|
|
// Extract the name of the function
|
|
Function functionDefinition = cast(Function)funcDefNode.getEntity();
|
|
assert(functionDefinition);
|
|
string functionNameAbsolutePath = resolver.generateName(cast(Container)root.getEntity(), cast(Entity)functionDefinition);
|
|
|
|
// Save to the map
|
|
functionDefinitions[functionNameAbsolutePath] = funcDefNode;
|
|
}
|
|
|
|
/**
|
|
* Given the absolute path to a function, this will retrieve the
|
|
* Function (function definition) DNode from the map
|
|
*/
|
|
private DNode retrieveFunctionDefinitionNode(string functionAbsolutePath)
|
|
{
|
|
gprintln("retrieveFunctionDefinitionNode: Implement me please");
|
|
|
|
// TODO: Add an assertion for failed lookup
|
|
return functionDefinitions[functionAbsolutePath];
|
|
}
|
|
|
|
|
|
/**
|
|
* DNode pool
|
|
*
|
|
* This holds unique pool entries
|
|
*/
|
|
private DNode[] nodePool;
|
|
|
|
this(TypeChecker tc)
|
|
{
|
|
// /* NOTE: NEW STUFF 1st Oct 2022 */
|
|
// Module modulle = tc.getModule();
|
|
// Context context = new Context(modulle, InitScope.STATIC);
|
|
// super(tc, context, context.getContainer().getStatements());
|
|
|
|
|
|
|
|
|
|
this.tc = tc;
|
|
this.resolver = tc.getResolver();
|
|
|
|
/* TODO: Make this call in the TypeChecker instance */
|
|
//generate();
|
|
}
|
|
|
|
public DNode root;
|
|
|
|
|
|
public DNode generate()
|
|
{
|
|
/* Start at the top-level container, the module */
|
|
Module modulle = tc.getModule();
|
|
|
|
/* Recurse downwards */
|
|
Context context = new Context(modulle, InitScope.STATIC);
|
|
DNode moduleDNode = generalPass(modulle, context);
|
|
|
|
/* Print tree */
|
|
// gprintln("\n"~moduleDNode.print());
|
|
|
|
return moduleDNode;
|
|
}
|
|
|
|
private DNode pool(Statement entity)
|
|
{
|
|
foreach(DNode dnode; nodePool)
|
|
{
|
|
if(dnode.getEntity() == entity)
|
|
{
|
|
return dnode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If no DNode is found that is associated with
|
|
* the provided Entity then create a new one and
|
|
* pool it
|
|
*/
|
|
DNode newDNode = new DNode(this, entity);
|
|
nodePool ~= newDNode;
|
|
|
|
return newDNode;
|
|
}
|
|
|
|
/**
|
|
* Templatised pooling mechanism
|
|
*
|
|
* Give the node type and entity type (required as not all take in Statement)
|
|
*/
|
|
private DNodeType poolT(DNodeType, EntityType)(EntityType entity)
|
|
{
|
|
foreach(DNode dnode; nodePool)
|
|
{
|
|
if(dnode.getEntity() == entity)
|
|
{
|
|
return cast(DNodeType)dnode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If no DNode is found that is associated with
|
|
* the provided Entity then create a new one and
|
|
* pool it
|
|
*/
|
|
DNodeType newDNode = new DNodeType(this, entity);
|
|
nodePool ~= newDNode;
|
|
|
|
return newDNode;
|
|
}
|
|
|
|
|
|
|
|
|
|
import compiler.typecheck.dependency.expression;
|
|
import compiler.typecheck.dependency.classes.classObject;
|
|
import compiler.typecheck.dependency.classes.classVirtualInit;
|
|
|
|
/* TODO: As mentioned in classObject.d we should static init the class type here */
|
|
private ClassVirtualInit virtualInit(Clazz clazz)
|
|
{
|
|
/* TODO: Pass over variables but we need own pool as instance variable a, must be unique per object */
|
|
|
|
/* TODO: COnstructor dependency, implicit super, climb class virtual hierachy */
|
|
|
|
/* TODO: Constructor run remainders */
|
|
|
|
/* TODO: Init classes, vars (check order) */
|
|
|
|
|
|
|
|
return null;
|
|
}
|
|
|
|
private ObjectInitializationNode objectInitialize(Clazz clazz, NewExpression newExpression)
|
|
{
|
|
/* We don't pool anything here - a constructor call is unique */
|
|
|
|
ObjectInitializationNode node = new ObjectInitializationNode(this, clazz, newExpression);
|
|
|
|
|
|
/* TODO: Call a virtual pass over the class */
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
/**
|
|
* Used for maintaining dependencies along a trail of `x.y.z`
|
|
*/
|
|
private DNode[][string] pathTrailDeps;
|
|
private void addToPathTrail(string finalEntityName, DNode dep)
|
|
{
|
|
bool found = false;
|
|
foreach(string entityName; pathTrailDeps.keys)
|
|
{
|
|
if(cmp(entityName, finalEntityName) == 0)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(found == false)
|
|
{
|
|
pathTrailDeps[finalEntityName] = [];
|
|
}
|
|
|
|
pathTrailDeps[finalEntityName] ~= dep;
|
|
|
|
}
|
|
|
|
|
|
private DNode expressionPass(Expression exp, Context context)
|
|
{
|
|
ExpressionDNode dnode = poolT!(ExpressionDNode, Expression)(exp);
|
|
|
|
gprintln("expressionPass(Exp): Processing "~exp.toString(), DebugType.WARNING);
|
|
gprintln("expressionPass(Exp): Context coming in "~to!(string)(context));
|
|
|
|
/* TODO: Add pooling */
|
|
|
|
/**
|
|
* Number literal
|
|
*/
|
|
if(cast(NumberLiteral)exp)
|
|
{
|
|
/* TODO: Make number LiteralNode */
|
|
return dnode;
|
|
}
|
|
/**
|
|
* Function calls (and struct constrctors)
|
|
*/
|
|
else if (cast(FunctionCall)exp)
|
|
{
|
|
/* TODO: Implement argument expression dependency */
|
|
FunctionCall funcCall = cast(FunctionCall)exp;
|
|
gprintln("FuncCall: "~funcCall.getName());
|
|
|
|
|
|
/* TODO: We need to fetch the cached function definition here and call it */
|
|
Entity funcEntity = resolver.resolveWithin(context.container, funcCall.getName());
|
|
DNode funcDefDNode = retrieveFunctionDefinitionNode(tc.getResolver().generateName(tc.getModule(), funcEntity));
|
|
gprintln("FuncCall (FuncDefNode): "~to!(string)(funcDefDNode));
|
|
dnode.needs(funcDefDNode); /* NOTE: New code as of 4th October 2022 */
|
|
|
|
|
|
/**
|
|
* Go through each argument generating a fresh DNode for each expression
|
|
*/
|
|
foreach(Expression actualArgument; funcCall.getCallArguments())
|
|
{
|
|
ExpressionDNode actualArgumentDNode = poolT!(ExpressionDNode, Expression)(actualArgument);
|
|
// dnode.needs(actualArgumentDNode);
|
|
|
|
// gprintln("We need to add recursion here", DebugType.ERROR);
|
|
// gprintln("Func?: "~to!(string)(cast(FunctionCall)actualArgument));
|
|
// gprintln("Literal?: "~to!(string)(cast(NumberLiteral)actualArgument));
|
|
// gprintln("Hello baba", DebugType.ERROR);
|
|
|
|
/* TODO: Ensure the correct context */
|
|
dnode.needs(expressionPass(actualArgument, context));
|
|
}
|
|
}
|
|
/**
|
|
* `new A()` expression
|
|
*/
|
|
else if(cast(NewExpression)exp)
|
|
{
|
|
/* The NewExpression */
|
|
NewExpression newExpression = cast(NewExpression)exp;
|
|
|
|
/* Get the FunctionCall */
|
|
FunctionCall constructorCall = newExpression.getFuncCall();
|
|
|
|
/* Get the name of the class the function call referes to */
|
|
string className = constructorCall.getName();
|
|
Type type = tc.getType(context.container, className);
|
|
|
|
if(type)
|
|
{
|
|
Clazz clazz = cast(Clazz)type;
|
|
|
|
if(clazz)
|
|
{
|
|
/* TODO: Process class static initialization */
|
|
/* Get the static class dependency */
|
|
ClassStaticNode classDependency = classPassStatic(clazz);
|
|
|
|
/* Make this expression depend on static initalization of the class */
|
|
dnode.needs(classDependency);
|
|
|
|
/* TODO: Process object initialization */
|
|
ObjectInitializationNode objectDependency = objectInitialize(clazz, newExpression);
|
|
dnode.needs(objectDependency);
|
|
|
|
/* TODO: Process function call argument */
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("Only class-type may be used with `new`");
|
|
assert(false);
|
|
}
|
|
gprintln("Poe naais");
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("Invalid ryp");
|
|
assert(false);
|
|
}
|
|
// FunctionCall
|
|
}
|
|
/**
|
|
* Variable expression
|
|
*
|
|
* Example: `p`, `p.p.l`
|
|
*
|
|
* First example, `p`, would have expressionNode.needs(AccessNode)
|
|
* Second example, `p.p.l`, would have expressionNode.needs(AccessNode.needs(AccessNode.needs(AccessNode)))
|
|
*/
|
|
else if(cast(VariableExpression)exp)
|
|
{
|
|
/* TODO: Figure out where the variable lies */
|
|
|
|
/* TODO: Change this later */
|
|
// return new DNode(this, exp);
|
|
|
|
/**
|
|
* Extract the variable name
|
|
*/
|
|
VariableExpression varExp = cast(VariableExpression)exp;
|
|
|
|
string path = varExp.getName();
|
|
long nearestDot = indexOf(path, ".");
|
|
|
|
|
|
gprintln("VariableExpressionPass(): Path: "~path, DebugType.WARNING);
|
|
gprintln("VarExp Context set? (before): "~to!(string)(varExp.getContext()));
|
|
|
|
/* See issue #9 on Gitea */
|
|
/* FIXME: We only set context in some situations - we MUST fix this */
|
|
/* NOTE: I think THIS is wrong - varExp.setContext(context); */
|
|
/* What we need to do is set the variable itself me thinks */
|
|
/* NOTE: But the above seems to also be needed */
|
|
|
|
/* NOTE: Fix is below I think (it doesn't crash then) */
|
|
/* Set context for expression and the variable itself */
|
|
varExp.setContext(context);
|
|
Entity bruh = tc.getResolver().resolveBest(context.getContainer(), path);
|
|
bruh.setContext(context);
|
|
|
|
/* Has two dots? */
|
|
bool hasTwoDots = indexOf(path, ".", nearestDot+1) == lastIndexOf(path, ".") && indexOf(path, ".", nearestDot+1) > -1;
|
|
gprintln(indexOf(path, ".", nearestDot+1));
|
|
|
|
/**
|
|
* Current named entity
|
|
*/
|
|
string nearestName;
|
|
|
|
/**
|
|
* If the `path` has no dots
|
|
*
|
|
* Example: `variableX`
|
|
*/
|
|
if(nearestDot == -1)
|
|
{
|
|
/* The name is exactly the path */
|
|
nearestName = path;
|
|
|
|
/* Resolve the Entity */
|
|
Entity namedEntity = tc.getResolver().resolveWithin(context.getContainer(), nearestName);
|
|
|
|
|
|
|
|
/**
|
|
* NEW CODE!!!! (Added 25th Oct)
|
|
*
|
|
* Update name for later typechecking resolution of var
|
|
*
|
|
*/
|
|
varExp.setContext(context);
|
|
gprintln("Kont: "~to!(string)(context));
|
|
|
|
|
|
|
|
if(namedEntity)
|
|
{
|
|
/* FIXME: Below assumes basic variable declarations at module level, fix later */
|
|
|
|
/**
|
|
* Get the Entity as a Variable
|
|
*/
|
|
Variable variable = cast(Variable)namedEntity;
|
|
|
|
if(variable)
|
|
{
|
|
/* Pool the node */
|
|
VariableNode varDecNode = poolT!(VariableNode, Variable)(variable);
|
|
|
|
/**
|
|
* Check if the variable being referenced has been
|
|
* visited (i.e. declared)
|
|
*
|
|
* If it has then setup dependency, if not then error
|
|
* out
|
|
*/
|
|
if(varDecNode.isVisisted())
|
|
{
|
|
dnode.needs(varDecNode);
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("Cannot reference variable "~nearestName~" which exists but has not been declared yet");
|
|
}
|
|
|
|
|
|
/* Use the Context to make a decision */
|
|
}
|
|
else if(cast(Function)namedEntity)
|
|
{
|
|
/**
|
|
* FIXME: Yes it isn't a funcall not, and it is not a variable and is probably
|
|
* being returned as the lookup, so a FUnction node i guess
|
|
*/
|
|
Function funcHandle = cast(Function)namedEntity;
|
|
|
|
/**
|
|
* FIXME: Find the best place for this. Functions will always
|
|
* be declared (atleast for basic examples as like now) in
|
|
* the module level
|
|
*/
|
|
Context cont = new Context(tc.getModule(), InitScope.STATIC);
|
|
// cont.container = tc.getModule();
|
|
// cont.
|
|
funcHandle.setContext(cont);
|
|
|
|
// funcHandle
|
|
|
|
|
|
/**
|
|
* FIXME: Do we have to visit the function, I am not sure, like maybe declaration
|
|
* or surely it is already declared??!?!?
|
|
*
|
|
* Does pooling it make sense? Do we force a visitation?
|
|
*/
|
|
FuncDecNode funcDecNode = poolT!(FuncDecNode, Function)(funcHandle);
|
|
dnode.needs(funcDecNode);
|
|
|
|
gprintln("Muh function handle: "~namedEntity.toString(), DebugType.WARNING);
|
|
}
|
|
else
|
|
{
|
|
/* TODO: Add check ? */
|
|
}
|
|
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("No entity by the name "~nearestName~" exists (at all)");
|
|
}
|
|
|
|
|
|
}
|
|
/**
|
|
* If the `path` has dots
|
|
*
|
|
* Example: `container.variableX`
|
|
*
|
|
* We want to start left to right, first look at `variableX`,
|
|
* take that node, then recurse on `container.` (everything
|
|
* without the last segment) as this results in the correct
|
|
* dependency sub-tree
|
|
*
|
|
* FIXME: We should stop at `x.y` and not go further as we need
|
|
* to know what we are acessing
|
|
*/
|
|
else
|
|
{
|
|
/* Chop off the last segment */
|
|
long lastDot = lastIndexOf(path, ".");
|
|
string remainingSegment = path[0..(lastDot)];
|
|
|
|
/* TODO: Check th container passed in */
|
|
/* Lookup the name within the current entity's context */
|
|
gprintln("Now looking up: "~remainingSegment);
|
|
Entity namedEntity = tc.getResolver().resolveBest(context.getContainer(), remainingSegment);
|
|
gprintln("namedEntity: "~to!(string)(namedEntity));
|
|
gprintln("Context used for resolution: "~to!(string)(context.getContainer()));
|
|
|
|
/* The remaining segment must EXIST */
|
|
if(namedEntity)
|
|
{
|
|
/* The remaining segment must be a CONTAINER */
|
|
Container container = cast(Container)namedEntity;
|
|
if(container)
|
|
{
|
|
/* If we have a class then it needs static init */
|
|
if(cast(Clazz)container)
|
|
{
|
|
Clazz containerClass = cast(Clazz)container;
|
|
DNode classStaticAllocate = classPassStatic(containerClass);
|
|
dnode.needs(classStaticAllocate);
|
|
gprintln("Hello "~remainingSegment, DebugType.ERROR);
|
|
}
|
|
|
|
/**
|
|
* FIXME: Decide what requires new dep and what doesn't, instance vs class access etc
|
|
*
|
|
* How detailed we need to be? Will we combine these and consume later, we need to take these things
|
|
* into account. I am erring on the side of one single access, the only things along the way are possible static
|
|
* allocations, but that is my feeling - each path segment doesn't need something for simply existing
|
|
*/
|
|
|
|
/* If we only have one dot left s(TODO: implement ) */
|
|
bool hasMoreDot = indexOf(remainingSegment, ".") > -1;
|
|
if(hasMoreDot)
|
|
{
|
|
gprintln("has mor dot");
|
|
|
|
/**
|
|
* Create a VariableExpression for the remaining segment,
|
|
* run `passExpression()` on it (recurse) and make the CURRENT
|
|
* DNode (`dnode`) depend on the returned DNode
|
|
*
|
|
* TOOD: Double check the Context passed in
|
|
*/
|
|
// Context varExpRemContext = new Context(tc.getModule(), InitScope.STATIC);
|
|
VariableExpression varExpRem = new VariableExpression(remainingSegment);
|
|
DNode varExpRemDNode = expressionPass(varExpRem, context);
|
|
|
|
/* TODO: Double check if we need this, problems lie here and when we NEED to do and and when NOT */
|
|
dnode.needs(varExpRemDNode);
|
|
}
|
|
else
|
|
{
|
|
/* Do access operation here */
|
|
gprintln("No more dot");
|
|
|
|
gprintln("No mord to accevssor(): "~to!(string)(dnode));
|
|
|
|
/* TODO: We now have `TestClass.P` so accessor op or what? */
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("Could not acces \""~remainingSegment~"\" as it is not a container");
|
|
}
|
|
|
|
}
|
|
/**
|
|
* If an entity by that name doesn't exist then
|
|
* this is a typechecking error and we should
|
|
* break
|
|
*/
|
|
else
|
|
{
|
|
Parser.expect("Could not find an entity named "~remainingSegment);
|
|
}
|
|
}
|
|
|
|
/* TODO:C lean up and mke DNode */
|
|
|
|
/* TODO: Process `nearestName` by doing a tc.resolveWithin() */
|
|
|
|
|
|
|
|
|
|
/* TODO: SPlit the path up and resolve the shit */
|
|
|
|
/* TODO: gte start of path (TODO)*/
|
|
/* TODO: Then check that within current context, then we shift context for another call */
|
|
string currentName;
|
|
|
|
/**
|
|
* If we can resolve anywhere (TODO: Perhaps module level was better
|
|
*/
|
|
if(context.isAllowUp())
|
|
{
|
|
/* TODO: Use normal resolveBest */
|
|
}
|
|
/**
|
|
* Only within resolution allowed
|
|
*/
|
|
else
|
|
{
|
|
gprintln("87er78fgy678fyg678g6f8gfyduhgfjfgdjkgfdhjkfgdhjfkgdhgfdjkhgfjkhgfdjkhgfdjkhgfdjkfgdhjkfgdhjkfdghjgkfdhgfdjkhgfdjkhgfdjkhfgdjkhfgd");
|
|
Entity entity = tc.getResolver().resolveWithin(context.getContainer(), nearestName);
|
|
|
|
/* TODO: If dots remain then make sure cast(Container)entity is non-zero, i.e. is a container, else fail, typecheck error! */
|
|
}
|
|
|
|
|
|
gprintln("VarExp Context set? (after): "~to!(string)(varExp.getContext()));
|
|
|
|
}
|
|
/**
|
|
* Binary operator
|
|
*/
|
|
else if(cast(BinaryOperatorExpression)exp)
|
|
{
|
|
/* Get the binary operator expression */
|
|
BinaryOperatorExpression binOp = cast(BinaryOperatorExpression)exp;
|
|
|
|
|
|
|
|
/**
|
|
* If the operator is a dot operator
|
|
*
|
|
* We then treat that as an accessor
|
|
*
|
|
* Example: func().p1
|
|
* Example: new A().p1
|
|
*/
|
|
if(binOp.getOperator() == SymbolType.DOT)
|
|
{
|
|
/**
|
|
* Get the left-node (the thing being accessed)
|
|
*
|
|
* Either a `new A()`, `A()`
|
|
*/
|
|
Expression leftExp = binOp.getLeftExpression();
|
|
|
|
|
|
/**
|
|
* Process the right-hand side expression
|
|
* but we should give it the Context that
|
|
* it is accessing some sort of class for example
|
|
* such that resolution can work properly
|
|
* (hence the need for `Context` in this function)
|
|
*
|
|
* 1. The Container is the type of the object and
|
|
* we then call expresssionPass on it which
|
|
* will eensure static init of class type etc
|
|
*/
|
|
|
|
/* The NewExpression */
|
|
NewExpression newExpression = cast(NewExpression)leftExp;
|
|
|
|
/* Get the FunctionCall */
|
|
FunctionCall constructorCall = newExpression.getFuncCall();
|
|
|
|
/* Get the name of the class the function call referes to */
|
|
string className = constructorCall.getName();
|
|
Type type = tc.getType(context.container, className);
|
|
|
|
Clazz clazzType = cast(Clazz)type;
|
|
Container clazzContainer = cast(Container)clazzType;
|
|
|
|
|
|
|
|
|
|
Context objectContext = new Context(clazzContainer, InitScope.VIRTUAL);
|
|
/* Also, only resolve within */
|
|
objectContext.noAllowUp();
|
|
|
|
|
|
/**
|
|
* Pass the newExpression and static init the class
|
|
* using current context
|
|
*
|
|
* We now know the class is static inited, and also
|
|
* the object
|
|
*/
|
|
DNode lhsNode = expressionPass(leftExp, context);
|
|
|
|
/**
|
|
* Now using this pass the right-hand side with context
|
|
* being that the object access has virtual (static and
|
|
* non-static access as it is, well, an object `new A()`)
|
|
*
|
|
* Context being eithin the object and its class
|
|
*/
|
|
DNode rhsNode = expressionPass(binOp.getRightExpression(), objectContext);
|
|
|
|
|
|
// if(cast(NewExpression)leftExp)
|
|
|
|
/**
|
|
* TODO
|
|
*
|
|
* 1. Split up and recurse down the path (rhsExpression)
|
|
* 2. Above is done already in varExp (well needs to be implemented)
|
|
* 3. Make the rhsNode finanly depend on lhsNode
|
|
* 4. dnode (whole expression, dot operator expresiosn) relies on rhsNode
|
|
*
|
|
*/
|
|
dnode.needs(lhsNode);
|
|
lhsNode.needs(rhsNode);
|
|
|
|
|
|
|
|
}
|
|
/**
|
|
* Anything else are mutually exlsuive (i.e. not chained)
|
|
*
|
|
* FIXME: For now
|
|
*/
|
|
else
|
|
{
|
|
/* Process left and right */
|
|
DNode leftNode = expressionPass(binOp.getLeftExpression(), context);
|
|
DNode rightNode = expressionPass(binOp.getRightExpression(), context);
|
|
|
|
/* Require the evaluation of these */
|
|
/* TODO: Add specific DNode type dependent on the type of operator */
|
|
dnode.needs(leftNode);
|
|
dnode.needs(rightNode);
|
|
}
|
|
}
|
|
/**
|
|
* Unary operator
|
|
*/
|
|
else if(cast(UnaryOperatorExpression)exp)
|
|
{
|
|
/* Get the unary operator expression */
|
|
UnaryOperatorExpression unaryOp = cast(UnaryOperatorExpression)exp;
|
|
|
|
/* Process the expression */
|
|
DNode expressionNode = expressionPass(unaryOp.getExpression(), context);
|
|
|
|
|
|
/* Require the evaluation of the expression */
|
|
/* TODO: Add specific DNode type dependent on the type of operator */
|
|
dnode.needs(expressionNode);
|
|
}
|
|
else
|
|
{
|
|
// dnode = new DNode(this, exp);
|
|
|
|
|
|
|
|
// dnode.needs()
|
|
gprintln("Interesting", DebugType.ERROR);
|
|
}
|
|
|
|
|
|
|
|
|
|
return dnode;
|
|
}
|
|
|
|
|
|
import compiler.typecheck.dependency.variables;
|
|
private ModuleVariableDeclaration pool_module_vardec(Variable entity)
|
|
{
|
|
foreach(DNode dnode; nodePool)
|
|
{
|
|
if(dnode.getEntity() == entity)
|
|
{
|
|
return cast(ModuleVariableDeclaration)dnode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If no DNode is found that is associated with
|
|
* the provided Entity then create a new one and
|
|
* pool it
|
|
*/
|
|
ModuleVariableDeclaration newDNode = new ModuleVariableDeclaration(this, entity);
|
|
nodePool ~= newDNode;
|
|
|
|
return newDNode;
|
|
}
|
|
|
|
private DNode generalPass(Container c, Context context)
|
|
{
|
|
Entity namedContainer = cast(Entity)c;
|
|
assert(namedContainer);
|
|
|
|
DNode node = pool(namedContainer);
|
|
|
|
/* FIXME: Fix this later, currently using it for Function definitions */
|
|
bool ignoreInitScope = true;
|
|
|
|
/* If this is a Module then it must become the root */
|
|
if(cast(Module)namedContainer)
|
|
{
|
|
root = node;
|
|
}
|
|
/* NOTE: 1st October: Just for now ignore funciton stuff InitScvope? */
|
|
else if(cast(Function)namedContainer)
|
|
{
|
|
ignoreInitScope=false;
|
|
root=pool(tc.getModule());
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the statements of this Container
|
|
*/
|
|
Statement[] entities;
|
|
foreach(Statement statement; c.getStatements())
|
|
{
|
|
if(!(statement is null))
|
|
{
|
|
entities ~= cast(Statement)statement;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process each Entity
|
|
*
|
|
* TODO: Non entities later
|
|
*/
|
|
foreach(Statement entity; entities)
|
|
{
|
|
gprintln("generalPass(): Processing entity: "~entity.toString());
|
|
|
|
Entity ent = cast(Entity)entity;
|
|
if(ent && ent.getModifierType() != InitScope.STATIC && ignoreInitScope)
|
|
{
|
|
writeln("Did we just skip someone?");
|
|
writeln(ent);
|
|
continue;
|
|
}
|
|
|
|
/**
|
|
* Variable declarations
|
|
*/
|
|
if(cast(Variable)entity)
|
|
{
|
|
/* Get the Variable and information */
|
|
Variable variable = cast(Variable)entity;
|
|
|
|
/* TODO: 25Oct new */
|
|
// Context d = new Context( cast(Container)modulle, InitScope.STATIC);
|
|
entity.setContext(context);
|
|
/* TODO: Above 25oct new */
|
|
|
|
Type variableType = tc.getType(c, variable.getType());
|
|
assert(variableType); /* TODO: Handle invalid variable type */
|
|
DNode variableDNode = poolT!(StaticVariableDeclaration, Variable)(variable);
|
|
writeln("Hello");
|
|
writeln("VarType: "~to!(string)(variableType));
|
|
|
|
/* Basic type */
|
|
if(cast(Primitive)variableType)
|
|
{
|
|
/* Do nothing */
|
|
}
|
|
/* Class-type */
|
|
else if(cast(Clazz)variableType)
|
|
{
|
|
writeln("Literally hello");
|
|
|
|
/* Get the static class dependency */
|
|
ClassStaticNode classDependency = classPassStatic(cast(Clazz)variableType);
|
|
|
|
/* Make this variable declaration depend on static initalization of the class */
|
|
variableDNode.needs(classDependency);
|
|
}
|
|
/* Struct-type */
|
|
else if(cast(Struct)variableType)
|
|
{
|
|
|
|
}
|
|
/* Anything else */
|
|
else
|
|
{
|
|
/* This should never happen */
|
|
assert(false);
|
|
}
|
|
|
|
|
|
/* Set this variable as a dependency of this module */
|
|
// node.needs(variableDNode);
|
|
|
|
/* Set as visited */
|
|
variableDNode.markVisited();
|
|
|
|
|
|
/**
|
|
* FIXME
|
|
*
|
|
* I propose a major restructuring of variable and assignments
|
|
* along with typecheck and the way it does things. It doesn't
|
|
* make sense that varNode -> (depends on) varAss. No, varAss
|
|
* depends on varNode. Doing so would also then give us
|
|
* the correct ordering and NO swapping would be required.
|
|
*
|
|
*/
|
|
|
|
|
|
/* If there is an assignment attached to this */
|
|
if(variable.getAssignment())
|
|
{
|
|
/* Extract the assignment */
|
|
VariableAssignment varAssign = variable.getAssignment();
|
|
|
|
/* Set the Context of the assignment to the current context */
|
|
varAssign.setContext(context);
|
|
|
|
/* Pool the assignment to get a DNode */
|
|
DNode expressionNode = expressionPass(varAssign.getExpression(), context);
|
|
|
|
/* This assignment depends on an expression being evaluated */
|
|
VariableAssignmentNode varAssignNode = new VariableAssignmentNode(this, varAssign);
|
|
varAssignNode.needs(expressionNode);
|
|
|
|
/* This assignment is now dependent on the variable declaration */
|
|
varAssignNode.needs(variableDNode);
|
|
|
|
/* The current container is dependent on this running */
|
|
node.needs(varAssignNode);
|
|
continue;
|
|
}
|
|
/* If there is no assignment */
|
|
else
|
|
{
|
|
/* The current container is dependent on this variable declaration */
|
|
node.needs(variableDNode);
|
|
}
|
|
|
|
|
|
}
|
|
/**
|
|
* Variable asignments
|
|
*/
|
|
else if(cast(VariableAssignmentStdAlone)entity)
|
|
{
|
|
VariableAssignmentStdAlone vAsStdAl = cast(VariableAssignmentStdAlone)entity;
|
|
|
|
/* Set the Context */
|
|
vAsStdAl.setContext(context);
|
|
|
|
/* TODO: CHeck avriable name even */
|
|
gprintln("YEAST ENJOYER");
|
|
|
|
|
|
// FIXME: The below assert fails for function definitions trying to refer to global values
|
|
// as a reoslveBest (up) is needed. We should firstly check if within fails, if so,
|
|
// resolveBest, if that fails, then it is an error (see #46)
|
|
assert(tc.getResolver().resolveWithin(c, vAsStdAl.getVariableName()));
|
|
gprintln("YEAST ENJOYER");
|
|
Variable variable = cast(Variable)tc.getResolver().resolveWithin(c, vAsStdAl.getVariableName());
|
|
assert(variable);
|
|
/* Pool the variable */
|
|
DNode varDecDNode = pool(variable);
|
|
|
|
/* TODO: Make sure a DNode exists (implying it's been declared already) */
|
|
if(varDecDNode.isVisisted())
|
|
{
|
|
/* Pool varass stdalone */
|
|
DNode vStdAlDNode = pool(vAsStdAl);
|
|
node.needs(vStdAlDNode);
|
|
|
|
DNode expression = expressionPass(vAsStdAl.getExpression(), context);
|
|
vStdAlDNode.needs(expression);
|
|
}
|
|
else
|
|
{
|
|
Parser.expect("Cannot reference variable "~vAsStdAl.getVariableName()~" which exists but has not been declared yet");
|
|
}
|
|
}
|
|
/**
|
|
* Function declarations
|
|
* Status: Not done (TODO)
|
|
*/
|
|
else if(cast(Function)entity)
|
|
{
|
|
// /* Grab the function */
|
|
Function func = cast(Function)entity;
|
|
|
|
// /* Set the context to be STATIC and relative to this Module */
|
|
// Context d = new Context( cast(Container)modulle, InitScope.STATIC);
|
|
// func.setContext(d);
|
|
|
|
// /* Pass the function declaration */
|
|
// DNode funcDep = FunctionPass(func);
|
|
|
|
// /* TODO: Surely we only require the module, it doesn't need us? */
|
|
// /* TODO: Perhaps, no, it needs us to make it into the tree */
|
|
// /* TODO: But NOT it's subcompnents */
|
|
// funcDep.needs(moduleDNode);
|
|
// moduleDNode.needs(funcDep); /* TODO: Nah fam looks weird */
|
|
|
|
/**
|
|
* TODO:
|
|
*
|
|
* Perhaps all function calls should look up this node
|
|
* via pooling it and then they should depend on it
|
|
* which depends on module init
|
|
*
|
|
* Then whatever depends on function call will have module dependent
|
|
* on it, which does this but morr round about but seems to make more
|
|
* sense, idk
|
|
*/
|
|
|
|
/**
|
|
* SOLUTION
|
|
*
|
|
* DOn;'t process declarations
|
|
* Process function calls, then look up the Function (declaration)
|
|
* and go through it pooling and seeing it's needs
|
|
*/
|
|
|
|
/**
|
|
* Other SOLUTION
|
|
*
|
|
* We go through and process the declaration and get
|
|
* what each variable depends on, we then return this
|
|
* And we have a function that does that for us
|
|
* but WE DON'T IMPLEMENT THAT HERE IN modulePass()
|
|
*
|
|
* Rather each call will do it, and because we pool
|
|
* we will add DNOdes that then flatten out
|
|
*/
|
|
|
|
/**
|
|
* EVEN BETTER (+PREVIOUS SOLUTION)
|
|
*
|
|
* We process it here yet we do not
|
|
* add thre entity themselves as dnodes
|
|
* only their dependents and return that
|
|
* Accounting ONLY for external dependencies
|
|
* WE STORE THIS INA FUNCTIONMAP
|
|
*
|
|
* We DO call this here
|
|
*
|
|
* On a FUNCTION **CALL** do a normal pass on
|
|
* the FUNCTIONMAP entity, in a way that doesn't
|
|
* add to our tree for Modulle. Effectively
|
|
* giving us a uniue dependecny tree per call
|
|
* which is fine for checking things and also
|
|
* for (what is to come - code generation) AS
|
|
* THEN we want duplication. Calling something
|
|
* twice means two sets of instructions, not one
|
|
* (as a result from pooled dependencies or USING
|
|
* the same pool)
|
|
*/
|
|
|
|
/* Add funtion definition */
|
|
gprintln("Hello");
|
|
addFunctionDef(tc, func);
|
|
|
|
/* TODO: Hoist stuff from pre-processing */
|
|
|
|
/* TODO: New code from 1st October */
|
|
/* Recurse downwards */
|
|
/* FIXME: The context container must be fixed, see passClazz, we pass the euiavlent of `func` in there */
|
|
Context funcContext = new Context(tc.getModule(), InitScope.STATIC);
|
|
DNode funcDefDNode = generalPass(func, funcContext);
|
|
|
|
|
|
/**
|
|
* Save the function dnode for lookup later
|
|
*/
|
|
saveFunctionDefinitionNode(funcDefDNode);
|
|
}
|
|
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
import compiler.typecheck.dependency.classes.classStaticDep;
|
|
private ClassStaticNode poolClassStatic(Clazz clazz)
|
|
{
|
|
/* Sanity check */
|
|
if(clazz.getModifierType() != InitScope.STATIC)
|
|
{
|
|
Parser.expect("SanityCheck: poolClassStatic(): Cannot pool a non-static class");
|
|
// assert(clazz.getModifierType() == InitScope.STATIC);
|
|
}
|
|
|
|
|
|
foreach(DNode dnode; nodePool)
|
|
{
|
|
Statement entity = dnode.getEntity();
|
|
if(entity == clazz && cast(ClassStaticNode)dnode)
|
|
{
|
|
return cast(ClassStaticNode)dnode;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If no DNode is found that is associated with
|
|
* the provided Entity then create a new one and
|
|
* pool it
|
|
*/
|
|
ClassStaticNode newDNode = new ClassStaticNode(this, clazz);
|
|
nodePool ~= newDNode;
|
|
|
|
return newDNode;
|
|
}
|
|
|
|
/**
|
|
* Passes through the given Class to resolve
|
|
* dependencies, creates DNode(s) for them,
|
|
* adds them to a DNode created for the Class
|
|
* given and then returns it
|
|
*
|
|
* This is called for static initialization
|
|
*/
|
|
private ClassStaticNode classPassStatic(Clazz clazz)
|
|
{
|
|
/* Get a DNode for the Class */
|
|
ClassStaticNode classDNode = poolClassStatic(clazz);
|
|
|
|
gprintln("classPassStatic(): Static init check for?: "~to!(string)(clazz));
|
|
|
|
/* Make sure we are static */
|
|
if(clazz.getModifierType()!=InitScope.STATIC)
|
|
{
|
|
gprintln("classPassStatic(): Not static class", DebugType.ERROR);
|
|
assert(false);
|
|
}
|
|
|
|
/* Crawl up the static initialization tree of parent static classes */
|
|
if(clazz.parentOf() && cast(Clazz)clazz.parentOf())
|
|
{
|
|
/* Get the dependency node for the parent class */
|
|
ClassStaticNode parentClassDNode = classPassStatic(cast(Clazz)clazz.parentOf());
|
|
|
|
/* Make ourselves dependent on its initialization */
|
|
classDNode.needs(parentClassDNode);
|
|
}
|
|
|
|
|
|
/* TODO: visiation loop prevention */
|
|
/**
|
|
* If we have been visited then return nimmediately
|
|
*/
|
|
if(classDNode.isVisisted())
|
|
{
|
|
return classDNode;
|
|
}
|
|
else
|
|
{
|
|
/* Set as visited */
|
|
classDNode.markVisited();
|
|
}
|
|
|
|
generalPass(clazz, new Context(clazz, InitScope.STATIC));
|
|
|
|
return classDNode;
|
|
}
|
|
|
|
} |