Lesson Description

The "Name Resolution Exercise" Lesson is part of the full, Write a Compiler That Understands Types course featured in this preview video. Here's what you'd learn in this lesson:

Richard introduces the name resolution exercise, focusing on implementing naming conventions and scoping rules. He also walks through the solutions by updating functions like declare variable, adding variables to scope, and visiting binary expressions and arrow functions.

Preview
Close

Transcript from the "Name Resolution Exercise" Lesson

[00:00:00]
>> Richard Feldman: All right, so for this one, we are not going formatter.js, in fact, formatter.js was just a part one thing, and we're not gonna talk about formatting again until very end of the workshop. So in part 2 we don't have formatter.js but we do have a new one called naming.js, so let's take a look at that.

[00:00:14]
And basically, inside, sorry, that's the solutions, whoops, cheating, this one, naming.js. So at first, we have the same exact instructions to run this, so in your terminal, you can basically do the exact same command to run every single one of these exercises, you just need to do it a different directory.

[00:00:30]
So if you see the into 2, then now you can do node index.js and once again, you're gonna see the exact same thing that we see at the beginning of all these exercises, which is some failing tests. So some of these are passing, some of these are failing, and in order to change the failing ones to passing ones, we are going to, once again, do the same thing that we did previously.

[00:00:45]
Where we just scroll down until we find little arrows here, and yeah, again, we're not gonna go through what all this code does. But you can see this same kind of some similar patterns, like some big switch statements. One thing that is worth noting, this is something that is gonna come up not only naming resolution, but also in type checking, we're gonna use the same function naming convention, which is basically just like visit node.

[00:01:07]
Now, in the previous exercise, we didn't call it visit, we called it, what we call it, format node, something like that. And this is like a case of what some people would say is, and some people would say is not the visitor pattern, where you're basically just going through every single node in the tree and like doing something with it.

[00:01:23]
So we're gonna do a whole lot of going through nodes and trees and visiting them, doing different things to them. And so in this case, we are choosing to name it visitNode, but you could also say name check node, that would be a fine name for that too or canonicalize node, if you were making a canonicalization pass.

[00:01:39]
But visitNode is kind of the generic name, it's just like whatever we're going to do the thing to this node. And then that's where then right after that you get your big switch statement. Okay, so basically we have a couple of different little to-dos here that need to be resolved.

[00:01:57]
So inside declare variable, right now we have your classic if false, which always lets that either some debugging or some teaching materials was happening. Basically what we wanna do is only report this error if the name has been declared. So right now we're just never reporting the error, but this is one of those duplicate declaration errors that we talked about several slides ago.

[00:02:15]
So this is not the basic, am I in scope or not, this is actually I am declaring a variable right now, and we wanna see, is this variable already declared in the same scope? And if so, that's a duplicate declaration error, and if not, it's fine. Now, I mentioned this in the hint here, but like there are two ways you could implement this.

[00:02:35]
One is I support shadowing, and so that is basically where I'm not gonna look at all the scopes, I'm just gonna, we report an error if it's in exactly the same current scope. And if it's in any other scope, it's fine, it's allowed, that's shadowing. And then the other way to implement that would be to say shadowing is disallowed and like duplicate declarations is disallowed, I guess the third way would be they're both always allowed.

[00:02:54]
But the test runner expects shadowing to be supported, so what you wanna do here is do the presentation that supports shadowing right here for this condition. And what that means is look in the exact current scope that we're in, not any other scopes. And if we are only in the exact current scope that, sorry, only within the exact current scope, does this name already appear in that set, then we wanna report the error and otherwise we don't.

[00:03:20]
And then basically, this is the part where we are actually gonna add the variable to scope. So of course, the main thing that declare variable needs to do is it needs to actually say, okay, I got this name, I need to add this to a scope set, the current scope set.

[00:03:31]
Otherwise, if I don't add it to the current scope set, that later on, when somebody does a lookup, they're not gonna see it and they're just gonna get an error. So this is the second thing that we are doing here. And then there's two more things that we're doing here.

[00:03:42]
One is visit binary expression, so this is just to kind of give you a sense of like in that big switch statement, it's doing all this branching. Okay, what happens if we're going down the parse tree and we hit a binary expression just like we did in the last one?

[00:03:54]
That's plus or times. Basically what we need to do here is to visit both nodes in the parse tree, the trickier one is the arrow function. So the arrow function is basically where you have params and a body, we need to check that the body has access to all of the things in the params as well as the other scopes.

[00:04:16]
But for the params, we're actually defining them in scope and and there needs to be a nested scope here for the body. So inside the body, the params should be in scope but we don't wanna add the params to the current scope or else we have that same problem that we talked about before we got into nesting, where you have a function that has an argument named n.

[00:04:34]
And now everybody in the same scope of that function is declared can access n because we didn't remember to push the scope and then pop it at the end. So this is where we're gonna wanna actually do that nesting scope. Okay, so yeah, one change to declare variable, one change to add it to scope, visiting the binary expression.

[00:04:51]
One is very easy, and even easier if the answer is right there, and then visiting the error function, which is more non-trivial.
>> Richard Feldman: So first we had the declare variable, so the first thing we wanna do is just figure out what is our current scope. So we have this whole array of scopes, like we talked about, and since we are pushing onto that array every time we introduce a new scope, and popping when we get exit that scope.

[00:05:16]
That means that at any given moment, what the current scope is, is just gonna be the last one in the array, cuz we push onto the end of the array. So current scope is just gonna be scopes, bracket scopes out length-1. Then we can say, okay, well, if we are trying to do the shadowing rules, that means that we only want to look in this current scope for the name.

[00:05:35]
And if the current scope has that name, then that is not shadowing and therefore it's a duplicate declaration, whereas, if we wanted to disallow shadowing, then we would look at all the scopes and say, of any scope up to this point has that name, then that is disallowed and we give an error.

[00:05:51]
But since we're gonna allow shadowing here, we go with just the current scope. Finally, after we do that check, we're gonna say, okay, well, now that we have verified that it's ready to go and all set and it's not a duplicate, now we can actually add it to the current scope, and then from then on it will be available.

[00:06:09]
Any questions on that one? Yeah, question.
>> Speaker 2: We're making the implicit assumption here that the scopes array is being coherently updated, globally, right?
>> Richard Feldman: Yeah, right, elsewhere in the code, it's like, it wasn't the slide, other code is doing that, we're not going to build the whole thing from scratch in 10 minutes.

[00:06:27]
[LAUGH] All right, so let's run that again, and hey, we have fewer test failures, great. All right, next up, we're gonna do a visit binary expression. Basically, we have two things we care about here in the node argument, which is the left and the right. Now, from a naming perspective, you may recall that in the formatter section we also visited one of these binary nodes.

[00:06:49]
And there it was really important that we remember that the operator is a string and not a node, so although we were visiting, or as we called it, formatting in that section. The left and the right the operator needed to not be visited because it's just a plain old string.

[00:07:01]
Now, for our purposes, what that sort of translates to is like the operator doesn't have any names in it, so we don't care, we don't need to visit it. There's nothing to do there, we're just gonna completely disregard that field for this stage of compilation. And although it becomes important later on and just basically just say, yep, just visit the left node and the right node, and that's it, and then we're done.

[00:07:20]
And there we go, okay. So next up, we have the arrow function, so here is the one where we actually need to create a new scope, so in order to create a new scope, we're gonna just do scopes.push of a new set. If you want, you could choose to name this set and then potentially make use of it later, but since we're not actually doing the square bracket.length -1 thing like we did two fixes ago.

[00:07:45]
There's kind of no reason to name it, so I would just personally just push an anonymous set here, then we've got a for loop where you could use a for each or whatever you like here. Any of those would be all reasonable, and basically we just wanna say, okay, let's go through all the parameters that we have here and declare all of them now that we're inside the scope.

[00:08:01]
So if we did this for loop and we did not do the push, that would be where we'd create the problem that we talked about in the slides. Where we are now pushing these parameters that are supposed to be function parameters into the same scope where the function is being defined, and then they're accessible outside the end of the function.

[00:08:16]
It would be especially bad if we did the scopes.pop at the end without doing the scopes.push at the start, because then we're sort of out denting, we're leaving a scope that we didn't begin. We're just accidentally, it's as if we inserted an extra curly brace and somehow that parsed, but then did bad things in terms of scoping.

[00:08:34]
So definitely always want a pair of your pushes with your pops when doing this. And then finally, there's the very straightforward, okay, now we've got all these variables in scope, we're gonna visit the actual node body, and now that body can make use of all these things that we just declared.

[00:08:48]
As soon as we're done with the body, that's the closed curly brace, and then we do the scopes.pop to say nobody else gets to use these variables. Ordering is very important here, so if you did this first and you've visited, what do you think would happen?
>> Speaker 2: You wouldn't find any of your variables.

[00:09:03]
>> Richard Feldman: Yeah.
>> Speaker 2: You wouldn't have populated.
>> Richard Feldman: [LAUGH] Exactly, yeah, so everything other than your arguments would be in scope, everything the function closes over. But as soon as you try to reference any of your arguments, you immediately get a naming error, so yeah, definitely do those in the opposite order, yeah.

[00:09:16]
>> Speaker 2: Point of clarity, you may have already, we covered this, we are using this push pop strategy here in arrow functions because we do not intend to persist their variables, correct? Because they're ad hoc functions and we're not leaving them in our namespace.
>> Richard Feldman: Yeah, right, so these are just like function parameters that only exist in the body of the function, nobody else gets to see them, yeah.

[00:09:38]
All right, cool then, okay, so that was the actual last finger area that we needed to address.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now