Hello and welcome, everybody.
In this lecture we're going to implement some scope functionality.
So just create a new file called Scope C and we're going to include compilers, we're going to include
memory and we're going to include SD lib dosage
and finally assert that h nice.
So we're now going to create a new structure called scope and we will put this in their header file
eventually just doing in the source file now.
So it's easy for you to see the code we're using and reference it with the structure and we're just
going to go in it.
Flags So every scope allowed flags struc vector entities
has a pointer.
Okay.
So these entities will be a pointer of void.
That's the entities the vector will take.
And this just allows some abstraction, because now we can basically push anything to our scope.
We're not limited to nodes or anything like that.
Obviously, the person that pops from the from the scope vector would then need to know what that type
is so they can cast it back.
But yeah, this abstraction is quite important.
So our entities will hold a void pointer which will allow us to point to any element in memory.
So when I was going to say size, tw size.
So this is the total number of bytes this scope uses and it's aligned to 16 bytes because in C we are
required to align our stack frames to 16 bytes each.
It has to be aligned to a 16 by boundary.
And now we're going to have a pointer to our parent scope null if no parent.
Nice.
Okay.
So we're not going to worry about defining any scope flags at the moment.
We're just going to have the variable there in case we use it later on.
So we're going to start by creating our scope allocation function so that returns the scope.
We're going to call it scope o'clock.
So that's going to create a scope
and we're going to say scope entities equals vector, create sizeof, and we are going to have to include
the vector as well guys.
So include helpers, vector dot, size of void pointer.
And we're going to set the peak pointer to the end
and we're going to set the decrement flag.
Now, as you're probably all aware already, the peak pointer allows us to just sort of like peak OC.
So we can set the peak pointer to a position and index and the vector and we can just peak ahead.
And it will basically bring back what the element is without actually removing it.
So you can think of it as kind of like a pop, but it doesn't pop, it only peaks.
It gets that element from the vector and then increments the peak pointer.
So when we call vector peak again, it will give us the next element and we can always change the peak
pointer to point back to where we were at at any time.
Okay.
So what we're doing here is we're setting the flag to decrement, which means that if we have a vector,
it will peak from the index we're pointing at backwards.
So it's better that I just show you the code so you can see here vector peak.
If the vector flag, peak decrement flag is set, then our p index goes minus minus instead of plus
plus.
So basically peeking backwards back through our vector.
So if we push 50 to a vector of integers and then we push 30 and then 20, if we call peak and our peak
index is on three, it is on index two.
So then we're going to get the, the last element we just pushed and then we peak again.
We get the element before that and we peak again.
We get the element before that.
So that's how that's that that's how the peak decrement works.
And a peak decrement is exactly what we're looking for, for scopes.
So we're going to return the scope now.
We're going to say struct scope, scope, create route, struct, compile process,
process.
So what we're doing now is we're creating functionality for the root scope.
So the root scope is the same as the global scope.
If you remember in the presentation, I mentioned the global scope, right?
It's the same sort of thing.
So this function will create the global scope essentially here called the root scope.
So we're actually going to have to modify our compile process structure to hold the root scope.
So we're going to have to copy and paste the structure we created now, and we're going to put it in
compiler H above compile process.
So I asked to be above that.
And we're just going to scroll down to compile process.
And just by here we're going to say struct scope, struct scope, roots, struct scope current.
Okay.
So the current scope will be the scope we're currently on.
So for example, inside our main function, when we come into here, the current scope will be the function
body scope.
So that will be set to that, whereas the root scope would be set to the actual main scope here.
Where in it be might go?
So back to scope.
See, we're now going to continue this root function.
So we're going to say assert that there is no root scope and said that there is no current scope because
we're creating the root.
Right.
So there shouldn't be a current scope or a root scope at this point.
So we're going to say struct scope, root scope equals scope, lock process scope root equals root scope
process scope current equals root scope because that's currently the current scope because it's the
only one that exists.
And then we're just going to return the root scope.
So that creates our root scope call.
So now we're going to have a scope dialog and it isn't going to delete any memory at all because like
I said already, we're not concerned with filling memory in this project because because we're using
so many nodes and we're mixing so many things, the chance of memory corruption is very high.
So we're not going to concern ourselves with memory management in this project because compiler goes
from A to B to C to D to E, and then it finishes the program and everything gets duplicated anyway.
So we're not going to concern ourselves with D allocation in this project.
It will.
It could bring more harm than good if we freeze something that we're still using a lot of pointers that
being shared around and stuff like that.
So we need to be careful with the allocating memory in this project.
So we're now going to say void scope, free root, struct, scope, scope.
You're welcome to make those to fill those allocation functions in.
I recommend you wait until you finish the whole course just in case there is something like double freeze
or anything like that that come along.
So we're going to go scope the scope, process, scope the root equals null.
And yeah, this needs to be a struct compile process
because we're freeing the root, not that particular scope, process, scope.
So current equals null.
Yeah.
And we're just going to free the root scope like that.
Nice.
Do nothing for now.
Okay, nice.
So now we're going to create another function and it's going to return a scope and we're going to call
it scope new.
It's going to accept a compile process and some flags nice.
So we need to assert that we do actually already have a root scope and a current scope, because if
we don't, then that means this scope new was called before we created the root, which is not allowed.
So we're going to say struct scope new scope equals scope i loc new scope flags equals flags new scope
parent equals process scope current.
So if you remember in the presentation I showed you the triangle with the hierarchy this this basically
creates a new scope at the bottom of the triangle and the parent is the one above it.
So we will change the current scope in this function as well, and we're going to do that now.
So scope, current equals new scope.
So the new scope becomes the new current and we use the previous current as the parent of the new scope.
So if we were searching for variable a, it would it would, it would say okay let's go into the current
scope is variable A in the new scope.
No, it isn't.
Okay, let's check the scope above it.
Okay.
So then it'll go to this scope the parent but okay.
Variable A I found it cool.
All right.
And that's how that's working.
So essentially here we create the hierarchy that I showed you, create a new scope.
The parent gets set to the previous scope.
So we're going to return new scope.
So we will have some scope, iteration functions to allow us to iterate through the scope.
So just go void scope, iteration, start struct scope, scope.
And we're going to say vector set pointer scope entities zero.
And then we're going to say if the scope entity has a flag of decrement, then set the peak.
Pointer to the end.
Okay.
So that just basically resets iteration.
So it allows us to iterate from the start of our scope again.
We're going to have a scope iteration end here as well that it's not going to do anything.
It's just going to be there for show essentially, so that we can call, start and then call.
And it just shows a bit of order, you know, it shows a bit of order of what we're doing.
So it's tidy.
So we're going to say void pointer scope.
Iterate back struct scope.
Scope.
So we're going to say if the vector count of the scope entities is equal to zero, then basically just
return null because there's nothing in in that scope.
We can't do anything.
Otherwise we're going to return vector peak pointer scope entities and peak pointer should be the right
one.
Let me check.
Yes, it is the correct one.
Great.
So you might also want to know what the last element was that was pushed to the scope, regardless of
where the point is pointing.
So we're going to make a function for that scope, last entity at scope,
and we're just going to copy and paste this.
Okay.
If it's if it's zero, we want to return null and now we're just going to do return vector back pointer
and we're going to put in the scope entities.
If we look into there, space is just going to do a vector back and pull out that pointer for us.
Okay, nice.
So there's plenty of times where we're going to want to grab a variable from a scope.
And if it doesn't exist in that scope, check the parent scope and pull it from there.
Okay.
I mean, as I discussed in the presentation, when we look for variable A, it will check all the scopes
up and up and up.
We haven't yet made a function to do that, so we're going to do that now.
So just to void scope last entity from scope stop at and we're going to say struct scope, scope, strut,
scope, stop scope.
So what our function will do is look for the last element.
So if we can't find the last element on this scope, it will check the parent scope and it will keep
doing this until it reaches the stop scope.
So rather than rather than what I just said, where we look for a variable A and we check the parents
until we find it in this particular function we're making, we're going to keep looking until we find
any entity at all and then we're going to return.
So first things first.
If the scope is equal to the stop scope, we're going to return null because we we should be stopping.
Now we're going to go void last equals scope.
Last entity at scope.
We're going to pass in our scope.
Now, if this is not null, then we found an element.
So we can now just return.
Return that element if we're still here on line 96 and we couldn't find an element.
So we're we're going to have to check our parent scope.
So we're going to extract scope parent equals scope parent.
And we're going to say if parent, then return scope last entity from scope stop at and we're going
to pass the parent and the stop scope.
So this is kind of recursive.
Otherwise we're going to return null.
See, I what's going to happen is we're going to come in here with our scope.
It's going to grab an element from the scope.
If that element's not null, then we've already found the last entity, so we just return it.
If that entity is not on that scope, i.e. the scope is empty.
We'll check the parent and we'll call that function again, which will go through here and grab the
last density of the parent scope.
Again, if it can't find anything, it'll call the parent scope of that parent.
And if it still can't find anything after doing all of this work, it will return.
NULL So that's how that's working.
So now going to create an alias of this function that just takes in the compiler process and uses the
current scope.
So we're going to go void scope last entity, stop at struck compile process
struct scope stop stop scope.
I should say not stop at.
And we're going to say return scope, last entity from scope, stop at process, scope, current, and
then we're going to provide the stop scope calls.
So that allows us not to be aware of the starting scope.
It's a nice bit of an alias there, but of abstraction.
So then we can do things like void, scope, last entity
and that will take a compile process.
So then we're just going to go return scope, last entity, stop at process and null.
So that will allow us to get the last entity of the current scope and basically all parent scopes.
It'll just keep going until it finds the last entity.
It'll go through all the parents until it finds a vector that isn't null, essentially a vector that
has a count of above zero, and then it will pull that last element from it.
So that's all good.
So now for our portion, so we're going to go void scope, push, struct, compile process, void pointer
size, DT element size.
So this is for pushing elements to our scope.
So we're going to go back to push process scope, current ampersand pointer.
So I'll push that pointer value they provided there to that vector.
And now we're going to say process, scope, current size plus equals element size.
So we're actually calculating this size for every element we push.
So if we're pushing like some sort of variable node here and the element size is four because there's
an integer that fall will go in there.
Okay.
So now we're going to say void, scope, finish, struck, compile process
and we're going to say struct scope, new current scope equals process scope, current parent.
Because obviously we're finishing a scope, aren't we?
So if we leave these brackets, of course the function scope ends.
So we need to finish the scope so we grab our parent and that becomes the new current scope.
Okay, that's what we're doing here.
So we're first kind of de allocate the current scope.
We're going to say process, scope current is equal to the new current scope.
And if we have a root scope but we don't have a current scope, that means we literally just popped
off the root scope.
So the root scope now has to be set to null.
So we go process scope root equals null.
And you could probably get away with just with just something, something like that.
Okay.
That'd probably be absolutely fine, to be honest with you, but we'll just keep it like that just to
be more specific, because it's kind of more understandable, you know what I mean?
It's more understandable if we have that, because then you can clearly see, Oh, we're checking if
there's a root and there's not a current.
It's more cleaner, in my opinion.
So now here we're going to say struct scope, scope, current struct compile process
and we'll let you just going to return process, scope, current.
Okay.
And that is all the functionality we need for a successful scope system.
So you might be wondering, well, we haven't done any functionality that tries to pull variables from
the scopes.
And you'd be correct because this is an abstraction.
This scope template works with any element.
I could push integers to this thing if I wanted to, and that's exactly what we want.
It's more like a stack, more of an abstraction, whereas the resolver that will be creating that will
be more for things like going through expressions.
So we do a equals 50, it'll go through our scope and essentially resolve that, right?
This is more of an abstraction layer.
So yeah, the last thing we need to do is add this scope to the Makefile.
So let's just go in here.
We're going to copy and paste something and a scope oh scope dot C and we're going to scroll up.
We're just going to add that in our build script and as you know, make files, they require you to
use tabs.
So don't forget that build scope co.
TAPS is in these rules here.
So we're going to go clear, make, clean, make.
Oh, and there's one mistake on scope.
Line 117.
You can see that we we don't we don't actually use the entities should be accessing the entities they're
not just scoped out current try make again we can see that that compiles absolutely fine.
So thanks very much for watching.