Hello, everybody, and welcome to this lecture.
In this lecture, we're going to implement our symbol resolver that we discussed in the last lecture.
So what I want us to do is, is I just want us to create a new file called Sym Resolver C.
So in our Sym resolver file, we're just going to include compiler dot H.
I now want us to go to compilers H and just in compiler dot HAGE.
The compile process structure, we're going to say struct.
Cymbals.
And that's going to have a vector to a symbol table.
And it's also going to have another vector of the tables.
So it's going to be a vector of vector tables.
So this is the current active symbol table, which holds essentially symbols.
So this vector will essentially hold struct symbol pointers.
This vector will hold struck vector and essentially the vectors it will be holding at the table vectors
here to allow us to have multiple symbol tables.
So now that you've implemented that, we are ready to go back to our symbol resolver dot C file so we
can create some functions.
So we're going to start with void Sym resolver initialize.
So what this will do, this will initialize our symbol resolver.
It's going to accept a compile process as function argument.
And we're just going to go process symbols, tables, not table.
Be careful.
Dot tables equals vector create size of struct vector.
And obviously that'll be a vector pointer, not just a vector.
So that initializes our symbol tables vector, which accepts table vectors.
This allows us to have multiple symbol tables that hold multiple symbols.
So now we're just going to go static void sym resolver push symbol struct, compile process
struct symbol SEM.
And we're just going to go back to push process symbols.
Dot table, ampersand, sim.
And I know we have an initialized table yet.
Don't worry, we'll get to that.
So this allows us to push symbols to the current active symbol table, the one that's currently active.
So now we're going to create a function for create new tables.
So we're going to avoid symbols over new table struck, compile process, process, and we need to save
the current table.
So all we do is we go process, we go vector push process, symbols, dot tables, ampersand process,
symbols, table.
So all that will do that will push the current vector table, the active one to our tables vector.
So it's basically taking the pointer of our vector table and storing it in this vector here so that
we can access that table later on.
So now we're going to override the active table.
So how do we do that process?
Symbols.
Table equals vector create size of struct symbol.
So what we're basically doing is we're creating a table that accepts symbol pointers.
So it's a vector that accepts symbol pointers, essentially.
And we haven't lost our old table because it's been saved on this table's vector.
So now we're going to create a function for ending tables.
So we're going to assume resolver and table struct, compile process, process.
So all this will do this will basically pop off the tables vector here and it will basically set the
table back to that to this table here, which essentially discards the active table.
Right.
So we're going to say struck vector last table equals vector back pointer.
And we're going to need to include some vector functionality here.
Guys include helpers vector
and we're just going to go process symbols, dot tables.
So that'll peak on our save tables and find the last one that we pushed essentially.
So let's imagine we called New Table.
It would push the current table to the table's vector, set the new active table or what end table is
doing.
It's grabbing from that vector where we saved and it's going to restore it.
So now we're just going to go process symbols.
Table equals last table, and that's essentially overwritten the current active table.
And then finally, we're going to pop from the symbol tables vector, which will ensure that the last
table pointer is no longer on the safe table's vector.
Okay.
There we go.
So now we have the ability to initialize the vector tables.
We have the B to push symbols to the active table, and we have the B to make a new active table and
end a current active table.
So all of that in 28 lines code, that's not bad.
So you know what we've forgotten to do?
We haven't created this struct symbol yet.
So not to worry.
Let's do it.
Go back to compact h.
Scroll up here and just by here we're going to say struct symbol.
And what is our symbol going to have?
It's going to have a bunch of name which we'll use to identify it.
Remember I said all symbols need a unique name.
They cannot share the same name.
We're going to have a numeric type and we're going to have a void pointer to the actual symbol data.
So now let's make the types we're going to make an enumeration symbol type node, symbol type, native
function.
So what is a native function?
A native function is essentially a macro function that exists only in our binary.
So it exists only in our compiler binary, and it can be called from our code that we're compiling the
C code we're compiling OC.
More will be discussed on native functions later on.
And finally, symbol type unknown, which is just where we just have no idea what the symbol is that's
being registered.
So someone writing very specific functionality might use this type.
Abstraction is very important.
Okay, so now we can go back to symbols over and continue writing now that we've created our symbol
structure.
So we're going to make the way to get symbols by name now.
So we're just going to go struct symbol sym resolver get symbol struct compile process process conj
char name.
So this this allows us to get symbols by name, essentially.
So we're going to go back to SEP pointer
and we're going to set the peak pointer to the current table to zero.
Make sure you don't do tables by accident.
It's so easy to miss type.
That must be the table, the current active table.
So we're going to say struct symbol, symbol equals vector peak pointer process, symbols, table while
symbol.
So while we have symbols, we're going to say if s equal string, equal symbol, name, name.
So if the name of the symbol we've just found has the same name, then this is the symbol we were looking
for, in which case we just break.
And then under here we do a symbol vector peg pointer process symbols table to go to the next symbol.
So the idea is when we find the correct symbol, we just break and now we just do return the symbol.
So that's how that's how easy it is to find a symbol by name.
So now we're going to say struct symbol, sym resolver get symbol for native function that's going to
accept compile process again and also char name.
So essentially what this function will do is it will get the symbol, but only if the symbol is a native
function symbol.
Otherwise it will return null.
So now we're just going to go struct symbol sym equals.
So now we just do some resolver, get symbol, process, name, and now we're going to say if the symbol
is null, then return null.
And then we're going to say if the symbol type does not equal the native function type return null,
otherwise we'll return CIM.
So I ensures the symbol we've resolved has to be a native function symbol.
If it isn't, we can just return null.
So let's now make it so we can register symbols.
So we're going to say struct symbol, sim resolver register symbol and we're going to say struck compile
process process, const char sim name and IT type void data.
And we're going to say if the sim resolver has the symbol already OC, then we're just going to return
null because like I said, what was the rule of symbols?
They can never share the same name.
And just so we fix that typo.
So if they ever do share the same name, we have to just return null.
We refuse to register it.
Symbols can only have unique names.
Okay, so if we've passed this point, then we have symbols.
Great.
So we're going to say struct symbol, sim equals clock size of struct symbol, and we're going to just
put a one by here.
Okay, nice.
So we're going to say sim name equals sim name sim data equals data, sim type equals type.
And I'd rather put those the other way around.
So now we're just going to say sim resolver, push symbol.
Just like that.
And we're just going to return the SIM.
So now we're going to say struct node sim resolver, node struct symbol sim for the argument.
And now we're going to say if the sim type does not equal symbol type node return null, otherwise return
the SIM data.
So while this does anyone calling this function knows that the symbol data is actually a node, so therefore
they call this to resolve the node from the symbol.
So returns the void pointer of data.
But because the return type here has struck no pointer, that's what the caller of this function receives.
And if they call this function and it turns out the symbol is not actually a node, a node type, then
we return null because symbol type node basically means that the private data which is.
Here for a point of data contains a node, and that's what symbol type node means.
Okay, cool.
So now we're going to make some special functions.
We're going to say void sim resolver, build for variable node, and that's going to take a compile
process and it's going to take a variable node.
Okay.
And as we don't have variable nodes yet, we're just going to make a compiler error.
Variables not yet supported because we have to actually make them right before we can we can write write
code on them.
Right.
So we're going to do the same here.
We're going to say void sim resolver, build for function node struct, compile process, process,
struc node.
And, you know, we just we just don't have them implemented right now, right?
We're just writing the skeleton code.
So functions are not yet supported.
We're just going to do this for quite a few.
So we're going to say void SIM resolver, build for structure, node struct, compile process, struct,
node, node.
And once again, we're just going to say compiler error structures are not yet supported and we'll fill
all this in as we go through the course, these functions.
Right.
It's just important we get the basic skeleton work done so we don't have to worry about that later on.
So we're going to say sim resolver, build for union node, same sort of thing.
And I know this is a lot of work, but it's going to be worth it, guys.
Compiler development is not easy.
Unions are not yet supported.
Nice.
So now we're going to say void sim resolver built for node struck, compile processed
struct node and this one is not going to error out as you think that it might.
We're going to say switch no type
case, no type variable break, and we're just going to go build for variable node.
So this is kind of a generic function here, right?
So if someone doesn't know what, whether it's a union node structure, node function node or variable
node, rather than having to check that every time, they can just pass it to this function here, which
will figure out the correct node for them.
So we're going to go case node type function.
We're going to go sim resolver
for function node
case node type struct
sim resolver.
Build struct node.
Nice.
Finally, case for unions.
Call.
Cool.
There we go.
Nice.
And we're going to ignore all other node types because they can't become symbols.
Okay, nice.
So we don't error out if we don't support the current node that they've passed here.
And the reason for that is because then the programmer can just cause sim resolvable for node and they
won't actually have to care about checking if it's a valid node to process.
Right.
They can just call this every time they encounter a node and they'll be guaranteed that if it's applicable,
it will be registered.
So that's why we don't show an error here.
If if an invalid node is passed, we just ignore it.
So that is our symbol resolver.
Let's add it to the Makefile.
So we're just going to copy and paste anything here.
SIM resolver, sim resolver, sim resolver, sim resolver, scroll up and mine was that scope todo.
So I'm going to put mine in order to build some resolver.
Dot I do recommend you keep the same order you use them in.
It's not mandatory, but it helps keep your makefile clean.
So now let's just do a make clean and a make and we can see that compiles absolutely fine.
Okay.
Brilliant.
Congratulations.
You've just implemented a symbol resolver.
We will fill in these functions that we couldn't finish today.
We'll fill those in as we go through the course.
So when we implement variables, well then implement sim resolvable for.
Note when we implement functions will then implement sim resolver, build the function node and so on.
So yeah, hopefully that makes sense to you.
If you have any questions, please let me know.