Naming
TODO: Insert things here about collision detection etc. (TODO: yeah, later)
Once we have finish parsing we will effectively have a relationship
present between the AST nodes. The outermost Container
-based node, the
module has a list of children AST nodes in it. This is in the form of
a Statement[]
, one can imagine that you could loop through this array
to find a particular AST node of interest. These statements will be of
various different types, some will also implement the Container
interface, think of a class declared within a module, that’s a a
container in another container, meaning one could recurse down it.
Where does naming come into this whole picture though? That’s where we
look at the the Entity
-type of AST nodes. These are effectively any
sort of statement which has a name associated with it. If we
combine the aforementioned recursive nature of traversing the AST tree
with name matching we can effectievly build a name-based lookup system
such that given any name we can look it up.
TODO: Add diagram in moleskin here
The resolver
After we have parsed the tokens into a Module
, this is passed to a new
instance of the TypeChecker
(which you will read about next) but prior
to starting that process a new Resolver
is created which takes in the
TypeChecker
so it can access the Module
. It then has the ability to
use this to do its resolution.
Before we get into how it all works let’s first see the API offered to
us by the resolver (in source/
):
Method name | Return type | Description |
---|---|---|
generateNameBest( Entity) |
string |
Given an Entity this will return the absolute path to it |
generateName( Container, Entity) |
string |
Returns the path of the Entity relative to the Container |
isDescendant( Container, Entity) |
bool |
Checks if the given Entity can be found somewhere within the provided Container |
resolveWithin( Container, string) |
Entity |
Looksup an Entity with the given name from within the provided Container , does not recurse |
resolveUp( Container, string) |
Entity |
Looksup an Entity with the name provided, starting from the Container given but it may resolve upwards past it if it isn’t found within it |
resolveBest( Container, string) |
Entity |
Combines the above two to resolution all the way down but also, if given an anchor Container and not found it can then trickle upwards till it is found |
findContainerOfType( TypeInfo_Class, Statement) |
Container |
Given a type-of Container and a Statement this will try find a container that the provided statement apears in by traversing upwards through any nested containers and stopping at the one which exactly matches the given type |
How resolution works
Let’s start off with an example program of which we can attempt to show the resolution process:
Once we have parsed the above program we would be able to use various
methods such as resolveWithin(Container, string)
,
resolveUp(Container, string)
and resolveBest(Container, string)
.
After parsing we get a Module
object so we can make use of this if we
see fit, we can, however, also lookup other containers within this
module and use that as a lookup anhor for some of the methods.
Let’s try and resolve the class declared with the name MyClass
. For
this we can use resolveWithin
and pass it the Module
instance to
tell it to look for some entity named "My Entity"
. One thing to note
is that this method only checks members of the given Container
and not
the container itself (so you cannot lookup the Module
itself via this
method, as an example). Anyways, in this case the container is the
module and the entity, MyClass
, is within it:
// Get our resolver
Resolver res = typeChecker.getResolver();
// Get out module
Module anchor = typeChecker.getModule();
// Resolve the class
Entity foundEntity = res.resolveWithin(anchor, "MyClass");
// If found
if(foundEntity !is null)
{
Clazz myClass = cast(Clazz)foundEntity;
// Do whatever you want with the Clazz
// ...
}
When an entity is not found then null
is returned as the indicator,
hence prior to casting to the correct kind-of type we want I first do a
null check.
What if we want to now lookup the myVar
within the MyClass
class but
we don’t don’t want to have to effectively find the direct parent of
each AST node being looked up, but rather just want to give a
dotted-path (pathdot-identifier) which describes how to reach it? Well,
that’s very easy, we can use resolveBest(Container, string)
in order
to do that. We will anchor the lookup at the Module
instance and
provide it the path of "example_1.MyClass.myVar"
.
// Get our resolver
Resolver res = typeChecker.getResolver();
// Get out module
Module anchor = typeChecker.getModule();
// Resolve the class
Entity foundEntity - res.resolveBest(anchor, "example_1.MyClass.myVar");
// If found
if(foundEntity !is null)
{
Clazz myClass = cast(Clazz)foundEntity;
// Do whatever you want with the Clazz
// ...
}
It should be noted that if you have a path start with the name of a
module then it will always actually anchor to the module, so even if
we did resolveBest(foundEntity, "example_1.MyClass.myVar")
it would
start at the module anyways.
Nearest parent of a given type
There are few places in the compiler’s source code, in fact only one
actually as of writing, whereby one requires to lookup the Container
somewhere in the anscetor tree of a given AST node. For example, let us
look at the below example (this was the reason this method was actually
created in the first place). The below example can be found in
source/tlang/testing/simple_function_recursion_factorial.t
:
module simple_function_recursion_factorial;
ubyte factorial(ubyte i)
{
if(i == 0)
{
return 1;
}
else
{
return i*factorial(i-1);
}
}
What we have above is a simple recursive function but where the problem
comes in is that when we are doing typechecking on the type of the
expression contained within the return
expression, the
return i*factorial(i-1)
, if that we need to find the enclosing
Function
. Now a function is a kind-of Container
however we cannot
simply do something akin to:
// Our return expression
ReturnExpr retExp = ....
// Parent of return expression
Container retContainer = retExp.parentOf();
// Cast to Function
Function func = cast(Function)retContainer; // ERROR: Runtime type check failure
// Type check the func.getType() and retExp's expression's type
The reason for this is that the else {}
branch is a Branch
, also
a kind-of container and we’d have a runtime type check failure when we
do that cast. We need a way to travel up the parenting/container tree
until we hit a a container of a certain type - in this case of type
Function
. This is where the
findContainerOfType(TypeInfo_Class containerType, Statement startingNode)
method comes in handy. So now, repeating the above code using this
method we would have something that looks like this:
// Get the resolver
Resolver res = typeChecker.getResolver();
// Our return expression
ReturnExpr retExp = ....
// Parent of return expression
Container retContainer = res.findContainerOfType(typeid(Function), retExp)
// Cast to Function
Function func = cast(Function)retContainer; // Works!
// Type check the func.getType() and retExp's expression's type
...