 Okay, let's get started today. We've got a lot of material to cover that's really important for homework assignment 1, so we've really got to get into it. Questions about project 2, homework assignment number 1? Questions? Yeah. I would prefer typed out for homework 1, but I think the TAs and I would also agree. I think what makes it easier for us to give you points, right? So if it's handwritten and it's hard to read your handwriting, that does not put the people grading your homework in a good mood. Yeah, it'll just be easier for everyone. Unless you need to draw like a parse tree, that's probably the exception that you can't do. More questions? Homeworks? Homework projects? For homework? If you want credit, right? That's fine. You can put your answer there and say whatever your notes are attached. You don't have to re-type your notes, but it's up to you if you think we can read it, right? If your answer is wrong, we're going to have to go look at your derivation and everything that you did, right? If you can't read that, then you might want to type it up. Other questions? Cool. Alright, so we're going to start where we left off on Monday. So we've been talking about can we use a regular expression to define grammars and the programming constructs that we're really interested in here. And hopefully this example at the bottom here kind of shows you that, yeah, maybe regular expressions aren't the right tool for the job because this looks like a proper regular expression that we just defined up here, right? To define what a program is, it's statements. One statement that we're interested in is an expression, an expression we're defining as numbers or IDs or decimals followed by an operator followed by either a number or an ID or a decimal. But that fails on this case of the string 1 plus 2 plus 3. And so it turns out we're not going to get into the exact specifics. But it turns out that regular expressions aren't actually sufficient or expressive enough to capture all of the programming constructs that we want to be able to define when talking about and analyzing a language. So we're not going to go into the details, but you can, if you look, you can find proofs of this that regular expressions are just simply not expressive enough. They can't express or describe the language that we want to be able to match. And so if you think about it kind of the example I like to give here, and once again I'll reiterate, we're talking about the regular expressions as we define them in this class, right? So we're not thinking about any of the extensions to regular expressions that might exist in current programming languages. So how would you, so kind of the motivating example is how do you write a regular expression to match balanced parentheses, right? So if we think about it from the end, working back, what we want to define is we want to define some regular expression R that the language that's described by R is the, let's say, the empty string, two parentheses, one left parentheses, one right parentheses, two left parentheses, two right parentheses, three left parentheses, three right parentheses. So can we, can somebody write this regular expression? Yeah. It would require predicting the future. It would require predicting the future, the future. It would require you to know this, like, beyond the next token. Because you don't know. Beyond the next token, yeah. That's one of the reasons I would say. Right, so any other, anybody think you can? Yeah. So the only thing we can use, right, is the concatenation operator, the dot operator, the OR operator, the bar, or the star operator. This is the only three operators we can use to construct a regular expression for this. All right, so the bar means zero, or the star means zero or more, right? But that's not a finite number, right? It just means zero or more repetitions. So maybe you can kind of think about it. This is why we're not going to get into the details. But the regular expressions we've defined have no concept or no idea of counting. They can't say one left parentheses followed by one right parentheses, or two left parentheses followed by two right parentheses, right? So we can define those individual regular expressions, but you can't write a finite regular expression that captures that idea of we want balance. So the same number of left parentheses followed by the same number of right parentheses. So kind of it's really about this idea of counting. We can't, which kind of goes to the look ahead, right? We can't, when we see the left parentheses, we can't look ahead and see, oh, there's three or four left parentheses, and that means I want three or four right parentheses to close those out. Do we kind of see that? Questions? So still, regular expressions are very powerful, right? But the things that we want to describe like match balance parentheses can't be described by regular expressions. So that leads us to our next topic, which is context-free grammars. And these are grammars that are going to allow us to express these program language constructs that we're really interested in. And so we're going to find these. We're going to go super formal like we did on regular expressions, but I want to give you an idea of what's involved here. So the syntax for context-free grammars, so they're going to be defined by a series of rows, and each row is going to be called a production. And we're going to have what we're going to call non-terminals on the left side of the production, followed by a right arrow, followed by non-terminals and terminals on the right. So in all of our examples, non-terminals are going to start with an uppercase letter, and terminals are going to be lowercase, and we're going to think of our terminals as tokens, right? So tokens are defined by regular expressions, and so we already know how to take a raw input string and using our lexer given a set of tokens to be able to pull the tokens out of their sequence. So now what we're going to define this grammar on is essentially the tokens. And so s will typically be the starting non-terminal. So okay, this is kind of high level, very abstract. So let's look at an example. This maybe hopefully makes it a little bit clearer. So the example for matching parentheses. So we have one production rule that says s produces the epsilon, the empty string. Okay, so that'd be one production rule. So in this case, what's the non-terminal here? Yeah, it's on the left. You know it's non-terminal, it's on the left, and you also know because I've said that all the non-terminals will be uppercase. Okay, we have another rule that says s produces left parentheses, s right parentheses. So this is it. This is our grammar, our context free grammar for defining that language, for defining the language of equal amounts of left parentheses followed by equal amounts of right parentheses. And this kind of makes sense. So the way I kind of try to think about this is I look at the grammar and I say, okay, well, whenever I see an s, I can either replace it with the empty string, nothing. Or if I see s, I can replace it with a left parentheses, an s, and a right parentheses. And I can continue doing that. But that, so left parentheses, s right parentheses, that's not a shrink. And this is where we get the term non-terminal, right? It's literally in the name. So you can't stop and generate a string with a non-terminal in it. The non-terminals are part of the context free grammar, but not part of the strings that are defined by this grammar. So what can we do? Well, we can replace that s with an empty string, an epsilon. And so I'll show an example in a second. But I just want to clarify. So we're going to actually reuse the bar operation one more time. So we're going to be able to write these context free grammars a little bit more distinctly by combining the same production rules with the bar. So this means s can produce either left parentheses, s right parentheses, or it can produce the empty string. Does that make sense? Questions? Okay, we can go back. We'll go back here. So let's walk through an example to see if we can understand intuitively what this grammar means and what kind of strings it describes. So when we have this grammar, here in this case, the same grammar, right? s produces either left parentheses, s right parentheses, or s produces epsilon, the empty string. So we're going to define derivations of the CFG. So these are going to describe how we use the CFG to generate a valid string that is defined by this grammar. And this is going to be pretty simple. We're going to start on the very left with the start non-terminal. So in this case it would be only one non-terminal here. So you get that like a, I guess, 100% shoddy guessing. Well, maybe 50 of you also traded epsilon. That's not a non-terminal. So we're going to start on the left with s, and we're going to show the series of steps where we apply one of these rules to s and how we end up with a string at the end. So, and we're going to separate these. So for notation purposes, the single arrow here is going to be, describe the grammar, and a thick double arrow is going to describe a step in our derivation. So for instance, we can go s derives epsilon. So why can we do that? Yeah. Because one of the options is s produces epsilon. Exactly. So one of the options in the context free grammar is s produces epsilon. So we start with s. We can derive epsilon just by applying that rule of the context free grammar. So I just made a mistake here. Okay. So this is wrong. Why is this wrong? Let's go. Somebody right there? Yeah. Don't let it get out to the right side of the production. Right. So these, at every step, at every double, these double thick right arrows, it has to be a rule from our rules, right? So do we have the rule of s produces left parentheses, epsilon right parentheses? No. No. Right. So that's wrong. Let's say I did it on purpose. So I'll go ahead and fix it in a second, and you can tell me how to fix it. So this isn't right, but this is a string that we will generate. So let's go to the next one. So we first, okay, this one is more right. So here we have three steps. We're going to start with s. We're going to apply the first rule, s produces left parentheses, s right parentheses. So we're going to apply that rule once. Then we still have an s left, right? So we still have a non-terminal, so we can't stop. So now we replace that s with which rule? One or two. You can put your fingers in there. So one would be this one. One, two would be this one. Can you guys actually see this? Okay. Make sure I wasn't gesturing myself. So yeah, one. So we're going to replace this s with the same rule, left parentheses and s right parentheses. And then we're going to replace s with what? Which rule in this case? Epsilon. Epsilon, the second one. Right. And so doing this, we can generate strings that are represented by this context-free grammar. Right? We can keep doing this. We can keep choosing rule one. And every time we then choose the rule two, now we have a string. And so we say, okay, that strings in our context, the language described by our context-free grammar. Questions? So do you agree that this actually describes that language that we wanted to of matching parentheses, numbers and parentheses? No. No? Why no? Because it only covers the instance of a single matched case, as in you have one statement that just has an increasingly large, useless parentheses. Okay, crap. You're not actually using the parentheses to do anything. That's a good point. So I guess I should be more specific. So does this describe, let's go back. Sorry, I hate it when I have to do this. Does this describe this language? Yes. So it describes a language that we could not define using regular expressions, right? So it defines the language of equal numbers of left parentheses followed by equal number of right parentheses. But yeah, it does not match what we kind of want, is any expression with balance left and right parentheses. Those will be more advanced and those will come later. Okay. So derivations. Okay, so let's look at another example of questions before we go on to the next example. Okay, let's look at this example. So now I'm going to cheat a little bit and we're not going to start with S or start with the expression. So here we're defining expressions. So we're going to find the expression as an expression can produce either an expression followed by a plus sign followed by an expression. Or an expression can produce an expression followed by a multiplication sign or the star followed by an expression and an expression can produce a number. So here, right, the star is not the regular expression operator, right? Because now we're kind of past the regular expression point where we're dealing with tokens. So you can think of the star as a token or representing the actual textual star and the same with the plus. So does that make sense? So this actually, so what does this describe? What does this expression describe? You want to say it up? Yeah. Yeah, so the question is, does this match our earlier example of regular expressions of one plus two plus three? Yes. So I guess the other way to phrase that would be, does the language described by this conduct free grammar contain that string one plus two plus three? Yes. Yeah. Okay, so let's look at some derivations. So how can we, so we're going to look at, we're actually going to do it slightly different. We're going to think it's one plus two times three is what we're going to use. And so if we derive it, so we start with an expression, right? We're going to apply one of these three rules. So if we apply the second rule, what's going to be after this double right arrow? Expression star expression. Expression star expression, right. And then let's say we want to then, we're going to, so now we have two non-terminals in this, this rule or this derivation, right? We have the first expression and then the second expression. So we can, oh once again I cheated, okay. So once again, if we wanted to, let's say, replace that last expression by three, the string three, which of these three rules would we choose? Three. The third one, number, right? So we may say that, okay, expression, multiply, blah, blah. Expression, the star operator and three. So is this, are we done? Is this a string in our language? We still have non-terminals. Right, we still have a non-terminal here, exactly. So let's say we replace that by rule number one, then what's the string going to look like? Expression plus expression times three. Right, so at each step we're applying one of these rules. And then let's say we replace the second expression here with two, the string two. So now we're going to replace it there. And then we're going to, let's say we replace the first expression by one. So we're going to have, so are we done here? Yes, no? Anybody say no? Got more to do? So yeah, now here we have a string that's in the language described by our convex regrammer. We have the string one plus two times three. Pretty cool, right? So questions on convex regrammers, how they work, how we can derive strings, yeah. Do we need the parentheses here? I don't know, what does everyone think? Technically we don't. Technically we don't? Why? There is a mathematical, there is already mathematical precedence set. Who's talking about math? So right now we don't need parentheses, right? Because all we're doing is generating a string described by this grammar, right? So at this point it doesn't mean anything. It all depends on how we describe the grammar and then later how we want to interpret that grammar. So yeah, so I guess phrase it another way. If we intended this to represent expressions of mathematical operators, do we need parentheses? Hold on to that thought. We're going to leave it for a little bit because we'll talk about it in a second. So the question kind of is, well then what are we doing with these, what are these derivations for? Right, so that would be one thing. So it's important, so the way I think about it is these derivations describe kind of how the grammar, in some sense, proof that this string is in this language, is in this grammar. So if I say show me that the string 1 plus 2 times 3 is described by this grammar, well how can you do that? Well, you start at the start and you keep deriving applying rules until you end up with that string. And so that's a proof that hey, this string actually is in this language described by this context free grammar. And you can also maybe, you can kind of look at this and convince yourself that by applying these rules in proof, and even though these are infinite, right, by applying all these rules you can generate all strings described by this grammar. You can keep, maybe always choose rule 1 until you choose rule 2 and then you can kind of generate all those strings. Whether you ever stop would be another matter. Okay, there's two important concepts to get across here because they're important when talking about, so talking about doing the derivations. So how did I decide which expression to expand and apply one of the production rules to? Seems like I went with the right one, does that true, did I? Good pattern matching. I did not, it was not intended but it's kind of, I just did it arbitrarily, right? I mean there's no, I at least didn't give you any rhyme or reason. I don't have to change this example again, right? Cool. So yeah, so at this point so it's just, you're choosing a non-terminal, applying new rules. But we're going to define two different types of derivations which actually do follow that, that thing where we're either going to call it a leftmost derivation or a rightmost derivation. And these concepts are pretty easy there, very much self-explanatory. In a leftmost derivation we're going to always expand the leftmost non-terminal, right? So if we have, once again, our context-free grammar. So the question is that could be asked on a homework assignment or an exam would be like, is this a leftmost derivation of this context-free grammar? No, why? Somebody want to raise their hand? Yeah. Two, three. This one? This one. Let's count here. One, two, three. So this step or this step? The first one. Okay, this one. Yes. So in this step here? Yes. What's happening? We really want to click on something to highlight it. So in going, okay, so applying the first rule, right, we're applying the second rule, we're applying expression goes to expression times expression, right? Yes. So here we're expanding the second, the rightmost in this case, expression to three. So yeah, so in this case saying, so this is not a leftmost derivation because of this second derivation when we chose to expand the not the leftmost non-terminal. That make sense? So you just always expand the left, the leftmost. And it'll be hopefully clear why this distinction matter. Okay, is this a leftmost derivation? Is it a valid derivation? Yes. It's good. I should like have papers that are right and put sentences up there so that you can all spell check and grammar check it. It's very good at finding mistakes. I like it. Okay, so is it a valid derivation? Yes. We're always applying one of the rules in the grammar there. I hope it is. It's meant to be at least. Is it a leftmost derivation? Yes. Is it? Anybody say no? Anybody want to argue that it's not a leftmost derivation? It's a tension of non-terminals, but we are doing all the terminals and the leftmost are left. So it's about non-terminals where we're expanding terminals. We expand, well, so yes. Okay, so maybe, what was that? From the step three to step four, we are expanding the terminals as expression one as one. Oh, so you're talking here? Yes. Yeah. So in this case, we're expanding expression to a non-terminal. And a non-terminal in this case would be number. So I'm kind of making the simplification set that we don't need to go from number to one. So number is a token. You can replace number with one. And we know that one is a token and because num is the token that we defined previously, right? So we know that the string one is in the token num. So I'm making a little shortcut step. But yeah, you could replace all the numbers here with just num, the token num, and it would be the same. But yeah, we don't expand that one again to something else, right? So the whole point of Conduct-Free Grammars is there's never a terminal on the left-hand side. It's only non-terminals on the left-hand side. So anytime you see a non-terminal, you know exactly what you can replace it with. Or replace it with something on the right-hand side. But you never replace a terminal, which is implied in the name by terminal, right? So you've finished, you've terminated, derivation. Whereas non-terminals, you haven't. You have to keep going. Does that make sense? Questions on this? Okay, who wants to guess what the next slide's title is going to be? Rightmost derivation. Rightmost derivation. Awesome. Got a series of mind reviews here. Good, yes. So the opposite of the leftmost derivation would be a rightmost derivation. And so the definition is once again very similar. So you always expand the rightmost non-terminal. So once again, with our context-free grammar here, may I ask you things like, is this a valid derivation? Somebody said no. Does anyone want to say no? Which step isn't valid? It could be a no. Just text on the screen. Is it a rightmost derivation? Yes. Who wants to go no? Yeah? From, so, first step you do EHP times the exponent times the exponent. Then next step is expression. Expression times three. And next step, you expand the leftmost expression. So it's neither. So it's no. So somebody want to raise their hand? That's not rightmost, though. Yes. That expression is the only expression left for non-terminal. So that is the rightmost expression. So it's actually the same scenario in the beginning, right? So all we have is an expression. That one non-terminal is both the leftmost non-terminal and the rightmost non-terminal, right? Because it's the only one. It's a good, it's definitely something that, yeah. If it wasn't that way, that'd be like a sneaky thing to mix in there. So does everybody understand the question and the answer? Okay. Questions on derivations, leftmost derivations, rightmost derivations, yeah. Somewhere down the road do you pick up the other? Does it become negative, leftmost, or... Yes. So, for instance, if I expanded this middle, like if I expanded rightmost for two and then I decided to expand the leftmost by expanding this expression non-terminal here, then yes, that would be a... It would be a valid derivation, but it's not either leftmost or rightmost. So yeah, something can be... So one thing is, is it a valid derivation or not, right? Did we actually apply all those rules properly from the context-free grammar? And then, is it a leftmost derivation or a rightmost derivation? So where we're kind of heading and where we're trying to go, right, is we want to be able to take these sequence of tokens and turn them into what I've been calling a parse tree so that can be interpreted by the rest of our... whatever, program interpreter, I'm going to call it very broadly. So we can also represent derivations using a parse tree. And as I said, hopefully it sounds very familiar because that's the goal of what we're talking about here, is how do we take a sequence of tokens and turn it into a parse tree? Okay, so what is a parse tree? So we have some derivation, so I'm going to assume at this point we're pretty familiar with the context-free grammar rules that we've talked about with expressions. Yeah, it's a good question. I don't know, maybe I can put it up there or something. But there's three rules, it's not too terrible. So what we have, we have this derivation and so we're going to represent this derivation on a tree data structure. And the root of our tree is going to be the starting non-terminal of our derivation or of our grammar. So the root is going to be, in this case, an expression, which is a non-terminal. And all the children of this... So for any node in our parse tree, all the children of that parse tree are an application of one of the rules in our context-free grammar. So which rule did we apply first in this derivation? You have to speak up. Expression, the first derivation here? Yeah, so expression produces expression, star expression, right? So looking at the derivation we have up here, that's represented by the very first step here where we applied... I want to say it's rule two in that context-free grammar. So how many children is the... is our expression root of our parse tree going to have? Two? One? Two? Three? Two. You want to say four? Two? Okay, so some of us say three? Yeah. Yeah, so that's good. So we're going to have three, and all of the children... a node is either a terminal or non-terminal. So here we're going to have three nodes that are the children of the root node, and that's going to be expression, star expression. So judging off of the derivation, so essentially you can think of it as we've applied that first rule, that first step in our derivation above. So applying the third rule, which of these expressions are we going to look at first as having a child? Yeah? The rightmost. The rightmost, so the rightmost. And how many children are you going to have? And what's the value going to be? Three. Okay, so what's the difference between the colors? Yeah, terminals and non-terminals. So I'll try to do that. It may not always happen. And you don't have to necessarily make that distinction whenever you're creating a parse tree, but I thought it would help to explain. Yeah, so the non-terminals here are blue, and the non-terminals are blue. The terminals are green with a white border in case you're qualified. So another question would be, does any of the non-terminals, like the star, are they going to have a child? So I said no, why? Explain? Well, it doesn't ever generate anything. So the parent-child relationship here means that the children were generated by applying one of the rules in the Conductory Grammar. So there's no way to expand the star. We don't have any rules because it's a terminal. That's part of what we said the definition was. Okay, what's the next thing we're going to expand here? The left expression. The remaining non-terminal. At this point we don't really have a choice, but we can look at this next step and say, okay, we had expression times three. Now we're going to expand that expression and how many children does this expression have? This is three. What are they going to be? Expression plus sign expression. Expression plus sign expression. So yeah, here we have that. So are we done? No. Why? We still have non-terminals. Our tree isn't complete. The tree doesn't describe the derivation and so we have to continue. So which of these two expressions are we going to expand first? Rightmost. The rightmost, what's the value of that? Two. Two. Great. And then the final one? One. One on the, I guess, leftmost expression here. Cool. So you can tell you're done parsing because all of the leaves of the tree, right, so what's a leaf in a tree? Not like philosophical or like, I don't know. They don't describe a leaf in a real tree. And like mathematically, and the way we think about computer science trees, what does it mean for a leaf to be in a tree? Yeah. As no, yeah, no other nodes connected. So no children, right? Yeah, so a leaf node is a node in the tree that has no children. And we know we're done when all of the leaf nodes are non-terminals, right? So that means, huh? Terminals, yes. Okay, they are, yes. They are terminals and are not non-terminals. We want to avoid those double negatives. And so maybe you can see that we can actually generate the string represented by this harsh tree by doing a depth first search and going through this tree and going through all the non-terminals. Here you can see visually left to right. You can also do, if you think about depth first traversal, we can get the string represented by this harsh tree. So what does this represent? So this is, this harsh tree is what we're trying to go for as far as what the goal here of our parser is. So what does it represent? I mean, I know I kind of say what doesn't mean anything until we give it meaning but what would be the natural kind of assuming we're defining this mathematical language. If I gave you this harsh tree, would you be able to compute this expression? Does somebody want to briefly describe how they may go about that? And do what? So what would be the result? So you map throughout the back. Yeah. So, yeah. So you would, so the intuitive way of how to interpret this is, obviously we have to give it meaning but we'd say, okay, well this expression in this, let's say sub-tree in the lower left, why not calculate 1 plus 2? So I have an expression, the operator is plus and the values that I'm adding together are 1 plus 2. And so this expression, the result of that would be 3. And so I said, okay, what's the result of 1 plus 2 and the operator on this expression would be multiplication, multiplied by 3. Well, I can do that. 3 times 3 is 9. Yeah. Sorry, I was going back here. You go first. So it doesn't understand precedence yet, right? At this point, all we're looking at is a parse-tree. Trying to think of how might we interpret, like if I gave you this parse-tree, could you calculate it and do the calculation that it described? So I guess the question kind of maybe underneath the surface there does this parse-tree also, like is this parse-tree ambiguous in the sense that would you ever return anything else other than 9 if I gave you this parse-tree and told you to calculate it? Yes. By changing the derivation like right off the left post, would you be able to change it like if your studies ain't done, it starts to change out more than operations? So the question is if we change the derivation, will we change the order of operations? Again, yes. Absolutely. But giving this parse-tree, this parse-tree is unambiguous. So by changing the derivation, you're going to actually generate a new parse-tree. So if I gave you this parse-tree, it's unequivocally that I mean I want you to add 1 plus 2 first and then multiply that result by 3. Did we see this? Because they're farther down in the parse-tree, if you think about a mathematical expression of 1 plus 3, I know how to do that. And I know how to take the result of that and multiply that by 3 with this expression. Questions about the parse-tree in general, kind of what it means, how we can create one from the derivations? We may see in a second. It's a good question. As far as right now I'm done. Other questions? Okay. One more time. We're going to hit everything we want to. Okay. So I guess have we talked really about parsing at all so far? No, not really. What I've showed is I've tried to show how we can define a grammar. So the grammar defines some language, some set of strings. And we've seen how we can use derivations to derive a string that follows that grammar. We're going to create a parse-tree out of that so we can see maybe an alternative representation of derivation. But right now we've only been looking at how we can actually generate these strings. Applying these rules, these production rules in the context-free grammar to generate strings. But what are we really trying to do? So we want our input, we're not giving a context-free grammar trying to generate strings. Actually what we really want is we can think of it as a string or a series of tokens and we want to say, does this match our grammar? And so we need a way to turn a series of tokens into a parse-tree which then we can pass the parse-tree off to the next part of our program interpretation and it can then interpret that or execute it or transform it, do any kind of cool stuff. So parsing is really the process of determining which derivation or parse-tree is meant from a sequence of tokens. Now it's probably pretty clear because we've touched on it a little bit so far, but there's two major parsing problems here. Right? And that is one is ambiguity in the grammar. So right now we want to take a string. So think about way at the start of this process we want to throw in a sequence of bytes, just a string. And then, but at the end we want to have we want a parse-tree, a single parse-tree. And so we're going to talk about ambiguity in a second. The second biggest problem is efficient parsing, right? We want to be able to do this quickly and efficiently because, well there are practicality reasons, right? So it would be very annoying if it took your compiler like five to ten minutes to compile a small simple program. We want to be able to actually do this process efficiently. Okay. So we're going to touch on ambiguous grammar for a little bit and we're going to shift here. So it'll be clear. Okay. So going with our grammar that we've been talking about so far, this context-free grammar, the question comes up, how do we parse the string one plus two times three? Assuming that we already have tokenizers to split it up into the tokens num and the token star and the token plus. Right. So yeah, starting from the top, right? We can either apply rule one or rule two. So I'm going to show this in derivation. So this one would be applying rule two first. So applying the multiplication rule and the second one would be the similar one applying the addition rule first. So it's kind of, derivations are nice because they're very succinct, but to me they're hard to visualize. Like what is this, how would I turn this into computation where it's like a parse tree to me is much more intuitive to understanding oh this means these things happen together and these get passed up there. So we're going to look at the parse trees of these. So if we have one plus two times three, we first have the other derivation the multiplication and then plus and then plus is added, right? So this was the parse tree that we were looking at. Can you in the back read the text there more or less? Okay, cool. Thanks. Okay, so for the other tree, what's the other parse tree that we can write here? So it's going to be expression what expression? Plus, yeah, plus and then this left most expression is going to go to one and the right most expression we're going to now expand to expression star expression and the middle expression is going to go to two and the last expression is going to go to three. Awesome. Okay. So we can see that mathematically, right, if we wanted to calculate or execute this parse tree we get two completely different results from this string, right? So we get nine in the case on the left and on the right we get seven? Two times three, right? Six plus one, which is seven, right? So here we're grouping the two and the multiply first together and then adding that result there. So this is kind of, we're trying to get here, this is intuitively what we mean to say it's ambiguous. So there is, if I give you a single string, there are two valid parse trees from that string. So is this a good thing? No. Okay, so we're going to find it more or less formally and this is why we talked about left most derivations and right most derivations. So a grammar is ambiguous if one of three conditions hold and they're actually all the same. There either exists two left most derivations or so different obviously. So two different left most derivations two different right most derivations or two different parse trees for any string in the grammar. Do I have a dangling there? Yes I do. So does this make sense? Yeah, exactly. Yeah, so I like to think of it as parse trees. To me it's just a lot easier to think about that way. Yeah, because the parse tree describes really what order things happen in and what things happen together and what things, you can think about expressions again, right? These expressions are will ultimately define the computation. So if you can create two different parse trees from the same string then your grammar is ambiguous. And so the tricky thing about derivations here is just that it has to be two left most derivations or two right most derivations. So if you give me you say the grammar is ambiguous because you can do a left most derivation and a right most derivation that doesn't mean it's ambiguous, right? Because we've seen the case where if you have a single non-terminal it's both the left most and the right most derivation, right? So intuitively that kind of makes sense. So then the other question is what about just any random derivation not left or right most? And it's because of, I mean we're not going to get the details so I won't expect you to know, but a left most derivation corresponds to a unique parse tree and a right most derivation corresponds to a unique parse tree. You just do the derivations not left most or right most and you don't get that. So if you think about like these grammars the order, once you're left with expression, star expression it doesn't matter which one you derive from a non-terminal as plus or three, right? So you have two different derivations that describe the same parse tree. That makes sense? Okay. Kind of a fun little exercise we can have for like five minutes. Is English ambiguous? Maybe you want to say it's not? You could maybe have that. If you do think it's not ambiguous you probably haven't met people who English is their second language, yeah. It's going to depend on the author. So yeah, I guess your answers love you. You were very, very, very careful. You could write maybe a subset of English that is very unambiguous. Yeah. So let's look at this sentence. I saw a man on a hill with a telescope. So how many different meanings? Is there just one meaning of this sentence? So what does the sentence mean? Let me give you a meaning. Play some fun English games, yeah. Right. So one way would be I saw a man on a hill using my telescope. So I have a telescope. I'm looking through the telescope. I saw a guy on a hill. Yeah. Yeah. It could be you're sawing a person on a path with on a hill. You're on top of a hill. You're sawing a man on a path with a telescope. Probably easy if you broke the telescope first, but yeah, so that's, what about it? Yeah. Saw a man on a hill that had a telescope on it. Who has a telescope on it? The hill. The hill. Yeah. So one would be you are not looking through a telescope but you see a man on a hill and there's a there's a telescope on the hill next to the man, let's say. Yeah. Yeah. Right. The hill. The telescope is on the hill. Yeah. Yeah. So you saw the man while you're on the hill with the telescope, right? So you saw a man on a hill with a telescope. Yeah. Is there any more? I saw some. Yeah. Yeah. The man has a telescope, right? Maybe you saw a man on a hill with a telescope and the telescope is pointing at you. So like, he's using the telescope. Maybe the telescope is on top of the man. Maybe he has it on his head, right? You see a man on a hill with a telescope. Does that work? Yeah. Exactly. So yeah, when using the saw as the verb, right, now we can actually we can do the same thing where it's like, well, we're either sawing him with a telescope or you're just sawing the man on a hill and the man is holding a telescope, right? Right? Anyone want to? Yeah. Is your a convoist? Oh, yeah, yeah, like a telescope, yeah. You're like anthropomorphizing the telescope. So maybe it's even creepier because you're crazy and like the telescope is telling you what to do. All right. So you see how crazy and dark and weird we can get by dealing with ambiguity in a language. That was a lot of fun. I think we've kind of exhausted all the permutations. If you think of one, that would be really interesting. Okay, so I think it should be pretty clear from this example, right? That ambiguity is not desirable in a programming language. In English, I think maybe I'm not a philosopher or an English major, but I'd argue that it's kind of nice having the ambiguity because you can derive humor from that. You can derive, I don't know, a lot of interesting slang words that mean half of one thing or that sound like names. All kinds of cool stuff. But in a programming language, it would be a nightmare, right? So that would mean that you write one string and your compiler gets to decide how it wants to interpret that and do that execution. If you told your compiler to go, well, I guess it doesn't work with C, but if you told the compiler that you saw somebody on a hill with the telescope and it may go report the police to you, it may ask you if you want to take a picture of the the guy on the hill. If the telescope was nice, I don't know. So with the programming language, right, when we're trying to program instructions to our computer, this would be terrible. Does anyone want to take the opposite opinion? That would be good. I mean, I don't know about you, but I have enough problem programming as it is. I don't need the compiler not understanding what I mean when I tell them. So yeah, we don't want the compiler to try to infer what you meant or to have two different ideas for whatever string. Like when you say print 10, you want it to print out 10 every single time and not decide to change the print something else or maybe you meant to print it in Morris Code on a paper and not on the console, right? So that would be very nice. Okay, so there's at a very high level, there's two broad categories of parsers. There's where we can work with what we call bottom-up parsing where we start from the terminals in our language and we build up to the start. So we kind of, you can think of it as like growing from the bottom, growing the tree from the bottom up and seeing how can we construct a parse tree just from looking at the terminals. The other way, alternative there would be top-down parsing where we start from the starting non-terminal. We started, you can think of from the root of the parse tree and we think can we grow a tree structure down applying all those rules to match that string. And so in this class we're going to focus exclusively on top-down parsing. So the only parsing we're going to look at or think about is top-down parsing. Okay, so what is it? Let's look at an example. So I've got it. We're going to be totally fine on time. Okay, so we're going to look at a context-free grammar. So here you have the production rule. S produces A or B or C. So what are A, B, and C here? Non-terminal. Right, capital letters. Okay, we have the rule A can go to little A, what's little A here? Terminal. Okay, we have the rule B can go to big B, little B or C. And then we can have the rule C can go to big C, little C or Epsilon. So questions about the grammar? So is it ambiguous? So this is very similar to the S goes to left parentheses, S right parentheses, right? So it's just about replacement. So all we have to do is whenever we see a big B, we can replace it by either a little B or a big B and a little B. Obviously we won't be able to do that forever. We're going to be able to do that at some point to get a strength. It depends on what operations. No. No, ambiguity is not a property like communicative, associative, right? Those are concepts we think about afterwards. I mean those are just at the language level, right? I mean just at the contact free grammar level we don't have any of those concepts. So we're going to see later about how we can like go back and fix by essentially enforcing those rules in our grammar. Like designing our grammar in such a way that those rules are naturally come out of it. But I guess I should have mentioned we're going to set ambiguity for a side for a little bit because I have to get into some stuff so that we can talk about homework because it's covered on the homework. So that's what we're doing right now. Yeah. It is a the non-terminal B followed by the terminal lowercase B. And another way, so since I've said this is the grammar, so there's no production rules on the left side for like big B, little B. So that would be another way to clue. But yeah that's a good question. Yeah. So the question is kind of about optimization, right? So we have a rule big A goes to little A but why don't we just replace any big A's. Get rid of big A all together and just use the non-terminal little A. Yeah, you can definitely do that but it's just for examples. There's no there's no limitations on what kind of context free grammar you can describe as long as you have a single non-terminal on the left and production rules are terminals and non-terminals. Those are the only restrictions. So you can do this, very long context free grammar that boils down to this. Any questions? So what are some strings that can this language can generate? A, B, C, C, C, F's long. Okay, so we're going to think about this as a top-down parser and so right now I want to give you an overview of where we're trying to go with top-down and we're going to get more into the details later. So I'm going to define it as a function called parse s. So you can think parse s so this is kind of like pseudo-codes more like pseudo-python Python code. So you can think there's a function called parse s that we're going to call first to parse our grammar. So what does our parser use? Tokens. Yes, where do we get tokens from? The Lexar. Yeah, exactly. So we get tokens from the Lexar. So we're going to call the method on our Lexar getToken. So here we're going to get the next token in our grammar and then we're going to check hey, if the type of this token is lowercase a then I know it's a do because I know, because the problem is right now when we're trying to parse s we have three choices. Do we apply rule s goes to a? Do we apply rule s goes to b? Do we apply rule s goes to c? So what we're going to do is we're going to peak at this first token and we're going to say if that token is a little a well then we're going to put the token back and then we're going to call parse a and parse a is going to have its own parsing mechanism. It's going to be very similar to this. And then we're going to check end of file. So why do we check the end of file? You can see if that's the only one we have. Yeah, because that would be an error. So here we're going to say the next token better be an end of file otherwise we have a syntax error because in our grammar defines if you have an a it's only a single a and then there should be an end of file at the end of that. So everybody see this? Kind of get the idea here? Okay. So then if we have a b I may have made a mistake. Okay, so we're going to see if there's a b we're going to parse b I think we're fine. And then we're going to check the end of file again to see if we've matched the end of file. So we can assume that parse b knows how to go and parse all of the rules of its grammar. Okay, yeah, we're fine. Okay, so then the same thing we're going to check if that token is a little c well then we're going to put it back and then we're going to call parse c can handle how it parses its production rules and then we're going to check the end of file. So what if we get an end of file? Is it a syntax error? You want to say it louder? Yeah, no, it's epsilon, right? So is epsilon a valid string in this language? Yeah, why? Yeah, s goes to c, c goes to epsilon, right? So epsilon is in our language. So then we do some end of file stuff, whatever. What if we get any other terminal or token here? Parse error, yeah. Yeah, so if we got anything else, it's a syntax error, right? Because we know just intuitively looking at this language that string better start with the token a, b, c or end of file otherwise it's not a valid program. Does that make sense? Intuitively? So now we need to go real quick so I can give an example. So it's kind of a big word, not big but there's multiple words here, right? Predictive recursive descent parsers. So recursive descent is just what we saw where we start at the top and we're going to recurse for every rule we're going to say call parse a or parse s, you can have parse a call itself, we end the examples there. Predictive is important it's efficient and it means that we only have to look one token ahead to know which production rule to apply. So there's no backtracking, there's no guessing about which rule to apply. And so to determine if so now the question is well so how do we know if our context free grammar has a predictive recursive descent parser or can we define like is any possible context free grammar recursive descent parsers? The answer is no so we need help with the following two functions. We're going to define first of here alpha where alpha is a a sequence of grammar symbols so a sequence of either non-terminals, terminals or epsilon. And what first is going to return it's going to represent what is the set of terminals or tokens that can begin strings that are derived from alpha. So we'll see an example in a second. We're also going to discuss on Wednesday the follow set so a follow function is defined for a non-terminal which says what tokens, what non-terminals the set of terminals and here we're going to represent end of file with the dollar sign that can appear after this non-terminal. Yeah, today's Wednesday. Yeah, Wednesday. Next Wednesday. We're not going to talk about anything. We won't get to it today. Yes, first sets are on the homework that's what we've got to cover them now. So here's the intuitive example that I'm going to give first so we can think about what these mean and then we're going to step through excruciating, we're going to describe the rules and then go through an example. So here's our context grammar from earlier. So we wanted to find first sets on all of our non-terminals here. So we wanted to find first of s so okay, so what what if we just look at s can we decide what non-terminals can start a string that's derived by s? Yes, can we? Look at this rule. Not really, right? Because s can derive a, b, or c. So we really need to know what they derive. Whatever they derive first then s can also derive that first. So we're going to leave it for now and for a it should be pretty clear. What's the first? a, right? So all all strings that a can derive looking just at a start with lowercase a what about b? Small b What about a? Can b the non-terminal b by applying rules can you ever derive a string that starts with anything but a b? No So the first set of b is lowercase b. What about c? c and epsilon c and epsilon by the same reasoning so we look here and we see that c can only ever derive strings that start with a lowercase c or epsilon So now putting these all together can we define what the first set of s is? So it's going to be the union of all of them, right? So it's going to be a, b, c, and epsilon so and we're, so a little bit of preview we use these first sets to construct that parser I just gave because we can use this and say hey I know if I see an a then it's got to be applied production rule a okay so how do we actually calculate this? So we're going to do this in an iterative fashion and we're first going to start out by setting the first set for all non-terminals in the grammar to an empty set so that's where we start out and then we're going to apply the following five rules to to each of the non-terminals and we're going to continue doing that until the first sets do not change until we've applied all the rules to all the non-terminals and we get the same first sets I'm going to show an example but here's the rules so what's the first set of a terminal so the first set of x lowercase x where x is a terminal is the second containing x so if you tell me what's the first terminal that can start a terminal well it's the terminal okay the second rule is the first set of epsilon is the set containing epsilon by similar reasoning the third rule if we have a production rule of a produces big b so big b is a non-terminal here and alpha here is a sequence of terminals or non-terminals so if this is a production rule in our grammar then we add the first set of b minus epsilon so taking out epsilon if it's in the first set of b and when I say add here I mean union it with the first set of a so why does this rule make sense or does it yeah so there's two things here so why do we add b to a well the first thing is anytime I see an a I can replace it by b so whatever b starts with a has to start with that same thing right because b is the first non-terminal in that string in that production rule and I take out epsilon because there could be this alpha afterwards where there's other stuff there so we'll get to what we do here in this case but this is just yeah we'll see in a second okay so this is very complicated ish mathematical statement saying that if we have a production rule of a goes to a series of non-terminals from 0 to I and epsilon is in everything in the first sets of 0 through I all of those b's right so if a can produce these series of non-terminals and they all start with epsilon well that means that wherever the next one is that doesn't that doesn't start with an epsilon we can actually add that first set to a's first set so basically I think of it as so we this rule rule 3 says that we can add b 0 minus epsilon to a right right and this is just a special case of that the previous case so we can add b 0 to a right away so b's the first to b 0 minus epsilon we can add that to a the first set of a but if b 0 can produce an epsilon that means that the first non-terminal could be from b 1 and so we can add b 1's first set minus epsilon to the first set of a and then b 1 so this is an iterative recursive process right so b 1 has epsilon in its non-terminal that means that the next non-terminal b 2 in this case we can add its first set minus epsilon to a and we can continue going out as far as we need to okay then the last rule has to deal with yeah is in it they can produce a non-terminal which means that they might and so we have to deal with that case and go to the next non-terminal yeah so then the rule the question is well what happens if all of them are epsilon right so if a produces a bunch of non-terminals and they all can produce epsilon then we can add epsilon to a's first set does that make sense so that means yeah yeah we don't care at this point so we can so we can think we can apply these rules in isolation so yeah we'll apply so if b i plus 1 happens to have epsilon in it we apply rule 4 and then we apply rule 4 again with i plus 2 and then if i plus 2 has epsilon in it we apply it again with i plus 3 okay so let's walk through an example I'm going to show the rules as we use them so we have a slightly more complicated grammar here right we have s goes to a b c all uppercase a goes to c or d c d sorry or lowercase a big a b goes to little b c goes to little c big c or epsilon d goes to little d big d or epsilon so to calculate the first sets oh there we go we first are going to set all the first sets to the empty set and so to calculate the first set of s we're going to apply this first rule this rule 3 so we have the production s goes to b a so here s is a and a is b here right so we're going to add applying this rule we're going to add the first set of a to the first set of s so what's the first set of a so we're only doing this iteratively right so all we have right here is this initial right so what's the first set of a that we've seen so far empty set right so we add an empty set we get the empty set okay none of our other rules apply so a doesn't contain epsilon those rules apply a is also not epsilon a is not a terminal so we're done so going through it the first time the first set of s is the empty set applying this to a so we look at the production rules for a and we see okay a goes to c d well c so we add the empty the first set of c to the first set of a and but we also have another rule we also have a goes to lowercase a big a so then we add using rule number three we add the first set of little a which is little a to the first set of big a and so that's applying rule one so we get a and we're going to do the same thing with b so that same thing happens right we're going to get lowercase b we're going to do this with the first set of c so what are we adding to the first set of c c and epsilon exactly what about d D and epsilon great ok the first set of s so we apply rule three again so we apply the first we add the first set of a to the first set of s which is a right we're going to get a and then for the first we because we haven't gone all the way through. It's only if we get all epsilon's all the way through. So first we add C. But then because C contains epsilon, then we say we also have to then add the first set of D. So what's the first set of D? D epsilon, but remember by this rule number three we're gonna remove epsilon. So here we've added A, C, and D. And now we apply rule four to add D. And then we apply rule five to then add epsilon. Y, because C and D all contain epsilon. That means the first set of A contains epsilon as well. Great, so B doesn't change, should be fairly clear. C doesn't change, so we have C epsilon, D also doesn't change. Now because we've changed the first set of A and the first set of S, we have to do this again. So we go through it again and we apply okay. What's the first set of A? So what are we gonna add to S? C, D, we're not gonna add epsilon because the rule says we're gonna remove epsilon here. So we're gonna add, from A we're gonna add A, C, and D, exactly. And then we apply this rule that says, well A can contain an epsilon, so when we move to the next non-terminal, we move to B. And so we say we look and we add B's first set minus epsilon to S, which is B. So do we go on and look at C? Right, B does not have epsilon, so we stop there, we don't go on anymore. And we get A, C, D, and B. And then we apply the same reasoning to A, the first set doesn't change, we apply it to B, the first set doesn't change, we apply it to C, it doesn't change, we apply it to D, it doesn't change. So do we go one more time? Yes, there's one more column, it should be very clear. But why do we go one more time? Because we change the first set of S. So we go through and we do the same thing, we add the first set of A, okay, that's already in there, we add the first set of B, that's already in there, but B does not contain an epsilon, so we don't go to C. And so we get that set, we go the first set of A, first set of B, first set of C, and we finally hit a fixed point, we didn't change anything, so we're done with the first set. Thank you, sorry for going too late, I apologize.