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

739 lines
22 KiB
D
Raw Normal View History

module compiler.typecheck.core;
2021-03-21 19:43:01 +00:00
2021-03-30 16:35:16 +01:00
import compiler.symbols.check;
import compiler.symbols.data;
2021-03-21 19:43:01 +00:00
import std.conv : to;
import std.string;
import std.stdio;
2021-03-29 18:13:39 +01:00
import gogga;
import compiler.parsing.core;
2021-03-31 11:54:34 +01:00
import compiler.typecheck.resolution;
2021-04-01 13:38:20 +01:00
import compiler.typecheck.exceptions;
2021-03-21 19:43:01 +00:00
/**
* The Parser only makes sure syntax
* is adhered to (and, well, partially)
* as it would allow string+string
* for example
*
2021-03-21 19:43:01 +00:00
*/
public final class TypeChecker
{
private Module modulle;
2021-03-21 19:43:01 +00:00
2021-03-31 11:54:34 +01:00
/* The name resolver */
private Resolver resolver;
public Module getModule()
{
return modulle;
}
this(Module modulle)
2021-03-21 19:43:01 +00:00
{
this.modulle = modulle;
2021-03-31 11:54:34 +01:00
resolver = new Resolver(this);
2021-04-01 13:28:33 +01:00
beginCheck();
2021-03-29 18:13:39 +01:00
}
2021-04-01 13:11:29 +01:00
private void beginCheck()
{
2021-04-01 14:32:05 +01:00
/**
* 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
*/
2021-04-01 14:49:02 +01:00
checkContainer(modulle); /* TODO: Rename checkContainerCollision */
2021-04-01 13:09:30 +01:00
/* TODO: Now that everything is defined, no collision */
/* TODO: Do actual type checking and declarations */
checkClassInherit(modulle);
}
2021-04-01 13:09:30 +01:00
2021-04-01 13:09:30 +01:00
2021-03-29 20:26:16 +01:00
2021-04-01 13:09:30 +01:00
2021-03-29 20:26:16 +01:00
2021-03-30 15:20:47 +01:00
2021-04-01 13:09:30 +01:00
2021-03-30 15:20:47 +01:00
2021-03-31 11:54:34 +01:00
2021-03-30 20:07:04 +01:00
private void checkClassInherit(Container c)
2021-03-30 19:05:16 +01:00
{
2021-03-30 20:07:04 +01:00
/* 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);
2021-03-30 20:07:04 +01:00
/* 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);
}
}
}
2021-03-30 19:05:16 +01:00
2021-03-30 20:07:04 +01:00
/* Once processing is done, apply recursively */
foreach(Clazz clazz; classTypes)
2021-03-30 19:05:16 +01:00
{
2021-03-30 20:07:04 +01:00
checkClassInherit(clazz);
2021-03-30 19:05:16 +01:00
}
2021-03-30 20:07:04 +01:00
2021-03-30 19:05:16 +01:00
}
private void checkClasses(Container c)
{
/**
* Make sure no duplicate types (classes) defined
2021-03-30 20:07:04 +01:00
* within same Container
2021-03-30 19:05:16 +01:00
*/
checkClassNames(c);
/**
* Now that everything is neat and tidy
2021-03-30 20:07:04 +01:00
* let's check class properties like inheritance
* names
2021-03-30 19:05:16 +01:00
*/
2021-03-30 20:07:04 +01:00
checkClassInherit(c);
2021-03-30 19:05:16 +01:00
}
/**
* 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 checkContainer(Container c)
{
/**
* 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)
{
/**
* If the current entity's name matches the container then error
*/
if(cmp(c.getName(), entity.getName()) == 0)
{
string containerPath = resolver.generateName(modulle, c);
string entityPath = resolver.generateName(modulle, entity);
2021-04-01 13:38:20 +01:00
throw new CollidingNameException(this, c, entity);
//Parser.expect("Cannot have entity \""~entityPath~"\" with same name as container \""~containerPath~"\"");
}
/**
* 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)
{
string preExistingEntity = resolver.generateName(modulle, findPrecedence(c, entity.getName()));
string entityPath = resolver.generateName(modulle, entity);
2021-04-01 13:38:20 +01:00
throw new CollidingNameException(this, findPrecedence(c, entity.getName()), entity);
//Parser.expect("Cannot have entity \""~entityPath~"\" with same name as entity \""~preExistingEntity~"\" within same container");
}
/**
* Otherwise this Entity is fine
*/
else
{
string fullPath = resolver.generateName(modulle, entity);
string containerNameFullPath = resolver.generateName(modulle, c);
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)
{
checkContainer(possibleContainerEntity);
}
}
}
}
/**
* 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(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;
}
2021-04-01 14:49:02 +01:00
/**
* 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;
}
2021-03-30 19:05:16 +01:00
/**
* 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
2021-03-30 19:05:16 +01:00
*/
private void checkClassNames(Container c)
2021-03-30 17:51:32 +01:00
{
/* 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)
{
2021-04-01 07:51:55 +01:00
// gprintln("Name: "~resolver.generateName(modulle, clazz));
2021-03-30 17:51:32 +01:00
/**
* 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
2021-03-30 19:05:16 +01:00
*
* 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
2021-03-30 17:51:32 +01:00
*/
2021-03-31 11:54:34 +01:00
if(resolver.resolveUp(c, clazz.getName()) != clazz)
2021-03-30 17:51:32 +01:00
{
2021-04-01 07:56:06 +01:00
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, c)~"\"");
2021-03-30 17:51:32 +01:00
}
else
{
/* Get the current container's parent container */
Container parentContainer = c.parentOf();
/* Don't allow a class to be named after it's container */
// if(!parentContainer)
// {
2021-04-01 07:56:06 +01:00
if(cmp(c.getName(), clazz.getName()) == 0)
{
Parser.expect("Class \""~resolver.generateName(modulle, clazz)~"\" cannot be defined within container with same name, \""~resolver.generateName(modulle, c)~"\"");
}
/* TODO: Loop througn Container ENtitys here */
/* Make sure that when we call findPrecedence(entity) == current entity */
// }
2021-03-30 20:36:35 +01:00
/* 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());
2021-03-30 20:36:35 +01:00
// if(!clazzAbove)
// {
2021-03-30 20:36:35 +01:00
// }
// 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 */
2021-03-30 19:05:16 +01:00
// /* 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());
// }
2021-03-30 17:51:32 +01:00
}
}
2021-03-30 17:51:32 +01:00
/**
* TODO: Now we should loop through each class and do the same
* so we have all types defined
*/
2021-04-01 13:09:30 +01:00
//gprintln("Defined classes: "~to!(string)(Program.getAllOf(new Clazz(""), cast(Statement[])marked)));
2021-03-30 17:51:32 +01:00
/**
* 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)
{
2021-03-30 19:05:16 +01:00
gprintln("Check recursive "~to!(string)(clazz), DebugType.WARNING);
2021-03-30 17:51:32 +01:00
/* Check the current class's types within */
2021-03-30 19:05:16 +01:00
checkClassNames(clazz);
2021-03-30 17:51:32 +01:00
2021-03-30 20:07:04 +01:00
// checkClassInherit(clazz);
2021-03-30 17:51:32 +01:00
}
2021-03-30 19:05:16 +01:00
2021-03-30 17:51:32 +01:00
/*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: ")
}
/* TODO clazz_21_211 , crashes */
private bool isNameInUse(Container relative, string name)
{
return resolver.resolveBest(relative, name) !is null;
}
2021-04-01 13:09:30 +01:00
2021-03-27 13:27:14 +00:00
/**
* Initialization order
*/
/**
* Example:
*
*
* int a;
* int b = a;
* int c = b;
* Reversing must not work
*
* Only time it can is if the path is to something in a class as those should
* be initialized all before variables
*/
private void process(Statement[] statements)
{
/* Go through each entity and check them */
/* TODO: Starting with x, if `int x = clacc.class.class.i` */
/* TODO: Then we getPath from the assignment aexpressiona nd eval it */
/**
* TODO: The variable there cannot rely on x without it being initted, hence
* need for global list of declared variables
*/
}
/* Test name resolution */
unittest
{
//assert()
}
/* TODO: We need a duplicate detector, maybe do this in Parser, in `parseBody` */
// public Entity isValidEntityTop(string path)
// {
// /* module.x same as x */
// if(cmp(path, "") == 0)
// }
2021-03-29 21:27:48 +01:00
/* TODO: Do elow functio n */
/* TODO: I also need something to get all entities with same name */
public bool entityCmp(Entity lhs, Entity rhs)
{
/* TODO: Depends on Entity */
/* If lhs and rhs are variables then if lhs came before rhs this is true */
2021-03-30 08:56:37 +01:00
return true;
}
/**
* Given a Container like a Module or Class and a path
* this will search from said container to find the Entity
* at the given path
*
* If you give it class_1 and path class_1.x or x
* they both should return the same Entity
*/
public Entity getEntity(Container container, string path)
{
/* Get the Container's name */
string containerName = container.getName();
/* Check to see if the first item is the container's name */
string[] pathItems = split(path, '.');
if(cmp(pathItems[0], containerName) == 0)
{
/* If so, then remove it */
path = path[indexOf(path, '.')+1..path.length];
}
/* Search for the Entity */
return isValidEntity(container.getStatements(), path);
2021-03-29 21:27:48 +01:00
}
/* Path: clazz_2_1.class_2_2 */
public Entity isValidEntity(Statement[] startingPoint, string path)
{ /* The entity found with the matching name at the end of the path */
// Entity foundEntity;
/* Go through each Statement and look for Entity's */
foreach(Statement curStatement; startingPoint)
{
/* Only look for Entitys */
if(cast(Entity)curStatement !is null)
{
/* Current entity */
Entity curEntity = cast(Entity)curStatement;
/* Make sure the root of path matches current entity */
string[] name = split(path, ".");
/* If root does not match current entity, skip */
if(cmp(name[0], curEntity.getName()) != 0)
{
continue;
}
// writeln("warren g had to regulate");
/**
* Check if the name fully matches this entity's name
*
* If so, return it, a match has been found
*/
if(cmp(path, curEntity.getName()) == 0)
{
return curEntity;
}
/**
* Or recurse
*/
else
{
string newPath = path[indexOf(path, '.')+1..path.length];
/* In this case it must be some sort of container */
if(cast(Container)curEntity)
{
Container curContainer = cast(Container)curEntity;
/* Get statements */
Statement[] containerStatements = curContainer.getStatements();
/* TODO: Consider accessors? Here, Parser, where? */
return isValidEntity(containerStatements, newPath);
}
/* If not, error , semantics */
else
{
return null;
}
}
}
}
return null;
2021-03-21 19:43:01 +00:00
}
/**
* This function will walk, recursively, through
* each Statement at the top-level and generate
* names of declared items in a global array
*
* This is top-level, iterative then recursive within
* each iteration
*
* The point of this is to know of all symbols
* that exist so that we can do a second pass
* and see if symbols in use (declaration does
* not count as "use") are infact valid references
*/
public void nameResolution()
{
string[] names;
foreach(Statement statement; Program.getAllOf(new Statement(), modulle.getStatements()))
{
/* TODO: Add container name */
/* TODO: Make sure all Entity type */
string containerName = (cast(Entity)statement).getName();
names ~= containerName;
string[] receivedNameSet = resolveNames(containerName, statement);
names ~= receivedNameSet;
}
}
private string[] resolveNames(string root, Statement statement)
{
/* If the statement is a variable then return */
if(typeid(statement) == typeid(Variable))
{
return null;
}
/* If it is a class */
else if(typeid(statement) == typeid(Clazz))
{
/* Get class's identifiers */
}
return null;
}
public void check()
{
checkDuplicateTopLevel();
/* TODO: Process globals */
/* TODO: Process classes */
/* TODO: Process functions */
}
/**
* Ensures that at the top-level there are no duplicate names
*/
private bool checkDuplicateTopLevel()
{
import misc.utils;
/* List of names travsersed so far */
string[] names;
/* Add all global variables */
foreach(Variable variable; Program.getAllOf(new Variable(null, null), modulle.getStatements()))
{
string name = variable.getName();
if(isPresent(names, name))
{
Parser.expect("Bruh duplicate var"~name);
}
else
{
names ~= variable.getName();
}
}
return true;
}
}
unittest
{
/* TODO: Add some unit tests */
import std.file;
import std.stdio;
import compiler.lexer;
import compiler.parsing.core;
// isUnitTest = true;
string sourceFile = "source/tlang/testing/basic1.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();
/* TODO: Open source file */
string sourceCode = cast(string)fileBytes;
// string sourceCode = "hello \"world\"|| ";
//string sourceCode = "hello \"world\"||"; /* TODO: Implement this one */
// string sourceCode = "hello;";
Lexer currentLexer = new Lexer(sourceCode);
currentLexer.performLex();
Parser parser = new Parser(currentLexer.getTokens());
Module modulle = parser.parse();
TypeChecker typeChecker = new TypeChecker(modulle);
typeChecker.check();
/* Test first-level resolution */
assert(cmp(typeChecker.isValidEntity(modulle.getStatements(), "clazz1").getName(), "clazz1")==0);
/* Test n-level resolution */
assert(cmp(typeChecker.isValidEntity(modulle.getStatements(), "clazz_2_1.clazz_2_2").getName(), "clazz_2_2")==0);
assert(cmp(typeChecker.isValidEntity(modulle.getStatements(), "clazz_2_1.clazz_2_2.j").getName(), "j")==0);
assert(cmp(typeChecker.isValidEntity(modulle.getStatements(), "clazz_2_1.clazz_2_2.clazz_2_2_1").getName(), "clazz_2_2_1")==0);
assert(cmp(typeChecker.isValidEntity(modulle.getStatements(), "clazz_2_1.clazz_2_2").getName(), "clazz_2_2")==0);
/* Test invalid access to j treating it as a Container (whilst it is a Variable) */
assert(typeChecker.isValidEntity(modulle.getStatements(), "clazz_2_1.clazz_2_2.j.p") is null);
2021-03-21 19:43:01 +00:00
}