Hello and welcome, everybody.
In the last lecture we implemented function substructures.
In this lecture we're going to start implementing our function parsing code.
Please head parsed out, see?
And just by here we're going to say void parse function.
This will obviously be in charge of parsing functions in our compiler.
Okay.
Once you've made the parse function function, we're going to head over to Node C and we're just going
to create a make function node.
So you can see here we do make struct node and so on.
So we're going to say make func node, we'll call it function node.
Actually, it's going to accept a return type.
So we can call that a type.
It's going to accept a function name, it's going to accept a vector of argument nodes.
So what the arguments vector will be, it will essentially be a vector of no pointers.
And obviously the type of those nodes will be no type variable.
What the functions finally have a body, they have body nodes.
So yeah, so we have return type A function name and argument vector and our body node.
So what we're going to do guys we're going to say struck node func node equals know create
ampersand struct node
dot type equals node type function dot func dot name equals name solve func to args dot vector equals
arguments dot func top body node equals body node.
Just like that.
Finally, when missing the return data type.
So we're going to say dot func dot type
equals ret type and we're going to put an asterisk just before it.
OC because this doesn't accept a pointer expects the full struct data type.
So by put an asterisk, there will take the pointer and it'll copy it all over to the R type.
Nice.
So now we just return the func node.
We're also going to put a warning here because we do want to come back here when we go to implement
the code generator, don't forget to build the frame elements.
So like I said in the presentation of the course regarding functions, I didn't tell you the whole picture
because it's not time for us to discuss what frame elements are yet.
And obviously I don't want to overwhelm you.
We're just going to focus on parsing for now and as we get to the code generator, we'll come back here
and implement these essential things for code generation.
Okay.
Copy and paste, make function.
No, we're going to put it in the header file.
Just done to make struct node will be absolutely fine.
Back to parse dot c and we see our parse function that we made earlier.
We're going to now fill it in.
Okay.
So the very first thing we're going to do, we're going to make a arguments vector and we're going to
set that to null.
So this will be like formally parse function arguments such as struct history by here, right?
We're now going to say parse the scope new and parse the scope finish.
We're now going to go to our parse function arguments and we're going to create some arguments here.
So a positive function should obviously take a D type or a return type we'll call a return type issue.
It'll take a name token and it might also take a history pointer.
So just by here, we're going to say make function node, ret type name token asphalt to get the string.
Arguments will be null for now, and the body note will also be no for now because we haven't yet parsed
it.
So when we go to call, parse function, we know the return type and we know the function name at that
point in time.
How do we know the return type and the function name at that point in time?
Because if we did in it a that is a variable.
Soon as we have some brackets, that is a function.
So obviously we've already parsed the data type and the name before we know if it's a variable or a
function.
So that's why the parse function accepts the return type and the name, because by the time we know
it's a function, we've already parsed the data type and the function name.
So now we're going to say struct no function node equals no peak.
And obviously this should be above the scope finish, guys.
Now we're going to say parse the current function equals function node and we haven't yet created that.
Let's just search for parse of current body for a moment and we can see that's declared at the top here
in the node.
We're just going to put here struck node, parse the current function.
Okay, guys, so once you've done that, it's now kind of time for us to set the pause, comb body and
parse the function for the nodes that we're creating.
So we just scroll down to no create.
You'll see we left a little warning for ourselves.
This is why the warnings are good guys, so we don't forget things, right?
So, yeah, essentially we're going to set the binded owner and the binded function.
So what is the owner?
The owner is the current body node that this node is in.
Okay.
So in this scenario, the owner would be the body of the node create function if we were to parse this
source file.
So the owner of the node that we're creating here.
So now we need to set the current function and there we go.
Now we can get rid of that warning.
So what that allows us to do that allows us to know at any point in the program which body a particular
node was, is in and which function it's in, which is really good because for things like function
calls, we can know exactly which function that call was done in, which can be really handy for things
like debugging.
So when you've done that, we can head back to parse dot C.
And we can head back to pass function
and you'll see it's also still complaining.
That is because we need to mark it as external
pause, current function.
And now finally we can go back to parse function.
So yeah, so just mark, it is external in the same source file.
Okay.
Just by there.
Okay.
So you can just define it there in the in the node dot C file and then impose a dot C, just assign
it as external so that it's able to be found.
Okay.
Right then back to it.
Okay.
So the next step is to check whether the return type is a structure or union.
Now, I'm actually talking about structural union, not a structural union pointer.
Are they returning a structure or a union?
Okay.
So that's what we need to check for if data type is struct or union.
Right type.
And again, everything's subject to change.
You know, if we if later on in the course we decide to change this, we will.
But essentially, if we're returning things such as structures, okay, then obviously we need a way
to be able to access that data if it's not a structure pointer.
So essentially, usually when you're dealing with return arguments when generating code, you would
set the EAX register in the processor to the return value.
And obviously a structure that has, I don't know, 30 bytes, you're going to have a hard time returning
that, right?
So there's a special way that it's done and that's what we're going to deal with here now.
So essentially the stack addition
needs to be added by a size of.
DWORD
okay.
So basically a pointer of the actual structure address is going to be pushed to the actual stack, which
is why our stack additions remember I explained what the stack condition was in the presentation, right?
Check that video.
If you're confused by that stack addition needs to be higher to take into account those extra four byte
pointer that was put on the stack.
So what I've done is I've created a simple program just to demonstrate this a little further.
I know I did show you in the presentation, but it's best just to cover it a little bit more so you
can see in it main we call test OC test returns a dog.
Okay, so we create a dog here and we do D dot x equals 50 dot y equals 20 return D.
Okay, so let's look from the, the main functions perspective on what's happening.
So we push the base pointer, move the stack pointer to the base pointer, subtract 16 bytes from the
stack pointer to make enough room for struct dog here.
And then we do something special.
We load the address of the dog that we're pushing, Struck Dog DX and we push it to the stack.
So that's a pointer.
We've just made a pointer out of this.
So even though there's no pointers here, it's kind of like an invisible pointer, right?
So that gets pushed to the stack and then we call test.
Now look in test here we grab the value of epi plus eight.
Now what is at plus eight, the address that was pushed here.
Once we have that address, we now have access to struct doc dx from within the test function, which
means we can change it directly and that's exactly what we do.
We set the x of struct dog dx on line 18 here we set it to 50 OC you can see by here and then we grab
the same pointer, put it back in here x and then we once again do x plus four to access integer y and
we put 20 into there.
And then finally we set the eax register to basically a pointer of the EVP plus eight that was passed
to us.
Okay.
So that is how we return the structures and see, that's the type of assembly that gets generated.
So now that I've gone over that in more detail, okay, so hopefully now you can understand why we add
four bytes to the stack addition.
Okay.
The Stack addition will be set to eight by default.
We haven't yet set the default value.
We'll do that now in a moment.
So essentially when we do eight plus four, we get 12.
So that means that for anyone who wants to access any function arguments at all, such as this, they
would need to ensure that they plus the stack addition first, which would be 12 bytes after this is
run.
And that's essentially why we have the stack addition.
So if something special needs to happen, such as like an invisible pointer, we can just adjust the,
the amount of bytes that need to be added to access the first function argument so that they offset
correctly.
Okay.
So let's just set this stack addition, which I forgot to do.
Let's go to note dot C and back to our make function node.
We're going to do it by there.
So we're going to go dot func, dot, dot stack addition equals data size D word.
And then that gives us that nice eight bytes that we're looking for.
So yeah, just do that in the actual creation process to make function node OC nice.
And now if you go back to passive dot C, we can continue creating this function parsing.
Okay.
So what do we have after we've checked all of this while we do interject?
A And there's our function, we've already passed in it and A So we have our left bracket.
So that's what we need to check for.
So we're going to say expect op left bracket, expect same right bracket.
Because remember I said earlier on in the course during lexical analysis that our left brackets will
be operated as our rate brackets will be symbols, and that's the same for arrays as well.
And just by here, we just can put a warning for now, parse the function arguments and we will come
back to that because I don't want to overwhelm you too much.
So currently our pass function will only parse functions that have no arguments.
And once we parse the arguments, then we can obviously set the actual argument vector to the arguments
factor, which is currently null because we haven't parsed anything.
But obviously when we've parsed it, it won't be null.
So that would be fine.
So we now need to check if it's a native function because if it is, we need to assign that special
flag I was talking about earlier in the presentation.
So we're going to say sim resolver get symbol for native function and we haven't yet implemented that
or exported it.
So let's just head to the symbol resolver a minute.
Okay.
We can see it was defined, but it wasn't exported.
It's copy and paste that into the header file
just by that.
And then we have access to it backed paths dot C and we can finish this.
So we're going to put current process and what is the name going to be?
It's going to be name token as Val, and that should actually be an F statement.
So rather than it just being a one line statement, we're going to check to see if it can get the symbol
of the native function.
If it can, we're going to say function node func dot flags or equals.
Function node flag is native and that's a special flag I was talking about in the presentation.
So essentially, if we know that this function that is being parsed is defined as a symbol, as a native
function symbol in the system, then we'll just set this flag to it.
So I know what you're thinking.
Why would we bother parsing a function that's already registered in the system while our parse function
will also be responsible for parsing prototypes?
So if we registered something like a test function called init test in the system, the parse function
here will still be responsible for parsing any prototypes we declared such as this.
Obviously, if you then decided to declare a body and it's already registered in the system, that's
obviously a problem.
But obviously you might want to forward declare a prototype and that's why it's valid here.
So if we did in a test like that and we run the parse function over it, this flag would be set once
it parsed in it.
Test the prototype hope that makes sense.
So we're now going to check for the function body token.
Next is symbol.
We're going to look for the the curly brace.
Okay.
If we find that, then obviously we now know this is not a prototype.
We're actually parsing a full on function.
Now, if that's the case, we're going to go parse function body and we're going to pass in a history
into there.
So we're just going to say history begin and we haven't created this function yet.
Don't worry, we'll do it.
So we're going to scroll up now to make that function.
So we're going to say void, parse, function, body, struct history.
And this is going to be very simple.
We just go parse body and we're going to pass null for the variable size because we don't really care
about getting the variable size from from this body.
And then we're going to say history down history and we're going to pass in the original history flags
odd with history flag inside function body, which we haven't yet created.
We'll do that now.
So we're going to say history flag
inside unit and we're just going to click that to get to it.
Now we're going to just copy and paste, move the one one place to the left
and we're going to rename.
It can't stress enough that you need to check those bits.
If you get those bits wrong, then you risk and ending up the system thinking it's inside a structure
or something, right?
So when you copy and paste this, you always need to shift the one to the left.
And if you run out of places, that's fine.
You just need to ensure that whatever the.
Is being set to such as a CHA.
It needs to be up to a shot or an integer, you understand?
So that will pause our function body correctly.
It'll take in a history that might have all sorts of flags set.
It'll call history down, which will create a new history object with this adjusted flag set to it.
History flag and say function body.
And, you know, it's it is quite memory heavy because we don't care about memory management in this
compiler.
But obviously this is to teach you compiler development.
We don't want to overwhelm you with having to worry about double free glitches and bugs and all that
as well.
And, you know, compiler runs from A to A to B to C to D and then it terminates, you know what I mean?
So it's going to be it's going to be pretty fine.
It's going to run from up to down, and then it'll be done.
So here we're going to say history, but again, we're going to pass zero for the flags back on our
line, seven, eight, eight, where we parse the actual body, only parse the body.
We obviously want to pop off the body node.
So now we're going to set the body node on the function.
So we're going to say function node, funk, top body and equals body node.
Okay.
So then there's a direct link from the function substructure to the actual body.
Okay.
We don't have to parse a right bracket here because obviously the parse body function handles that.
Okay.
So you won't have to worry about anything like that because you can see in multiple statements like
nine, seven three, it says expect symbol.
So it does it there.
Okay.
Because I know some some of you might have wondered what we're doing about that.
Okay.
So here we just were peeking, you know, we're not actually popping anything off the stream.
And we do token next is symbol.
It's a peak.
Okay.
All right, nice.
So we're going to do an LCS.
So if there's no curly brace, then it must be a prototype, which means it's going to have a semicolon
such as in a test, for example, you get me.
Okay, finally, we're going to say parse the current function equals null.
Now we don't have to worry about saving the old version because in C functions can't nest.
You know, it's not like in C++ where you can have classes and methods, methods and they can be overridden
and all sorts of stuff like that.
And C as you know, we can't have a function within a function, so we're guaranteed to only have one
function at a time when parsing.
So for that reason we can just set it to null when we're done.
No need to keep some sort of stack.
Okay, let's go to parse variable functional struct union and we now need to just basically call our
function by here.
All right, call our parse function.
So this is very easy to do just by here.
We're going to say token is operator or token next I should say token next is operator.
And we're going to look for the left bracket.
And if it's found, then this must be a function.
So we're going to say parse function and they'll I'll obviously parse the full function or the prototype,
whatever it was.
And obviously then we're going to go Ampersand D type, we're going to say name token and we're going
to pass in the history.
The final step is to just do a return because we've now parsed the function.
Okay, so that concludes this lecture.
Let's just make clean and make.
It didn't even work.
Okay.
Give us a second.
Hold on.
Okay, guys, there's just one other thing.
Go to A.C..
And just by here, I've put two dots by accident.
I'm sure you guys never made the same mistake.
But just in case anyone did.
Should be one.
Yeah.
For the actual make function node function.
So back to positive that see everything looks fine.
Let's test this so we're going to go make clean make we're going to run main and we can see that everything
is working fine.
So let's put in a actual function.
Remember, don't put any arguments because our system can't handle that yet, okay?
If we now run that, we can see everything was fine.
So when I was just going to put in a function argument, we're going to run it again and we can see
expected symbol right bracket.
So yeah, on the next lecture we're going to implement function arguments, so it's able to parse those
correctly.
Thank you for watching.