字幕列表 影片播放 列印英文字幕 - Hello, welcome to Coding Challenge: Logo Interpreter, part 2, I'm a little bit scared of this. (clears throat) All right, so what's going on? Where are we, why am I here? You might remember me from Logo Interpreter Coding Challenge number 121, look that's me, I'm almost wearing the same clothes. This is a challenge where I looked at the Logo programming language, a language from designed in 1967, turtle graphics, you might be familiar with, I talked a bit more about it in that video. I encourage you to read about the history and also to check out the Logo Foundation which is the foundation that supports a variety of wonderful initiatives, including Scratch Day. So check that out but what I did was I made this Logo interpreter that is able to interpret simple commands like forward 60, left 90, forward 100. So while this implementation of Logo interpreting these Logo commands like forward, left, forward, et cetera works, it's missing crucial components. And so there are many commands in Logo and I implemented pen up, pen down, some of these, but one of the most important ones that I did not implement is repeat. So this is a tutorial from a Brown University page about the history of Logo that I'll also link to in this video's description, but what I want to attempt to do is add the repeat functionality which you would think would be a really simple thing, but it's a little bit more complicated than, well, there are some simple ways probably to do it, and I will point out, in fact, that when I posted this code, 16 pull requests came in with suggestions and techniques for how to implement repeat, and I would encourage you to go check all these out. I want to highlight one of them that was pretty interesting to me, I'm going to look for it, Regex implementation, is this the one I'm looking for? Yeah, this one, oh yeah, I love this, which is just basically using a regular expression to search for the repeat command and then just replace the repeat command in the string itself with what's the thing to repeat multiple times. So kind of like unrolling the string recursively with a regular expression. Pretty wild implementation. I'm not going to use that one, I'm going to try to spin up my own. So this is my idea. So, one thing that I think will help is object oriented programming. What if I have this idea of a command class? So I make this idea of a command class and it has the name of the command. That could be forward, left, picks the pen up, et cetera. It has an argument so if the command is forward, maybe I would have an argument like 100. And by the way, the command could be repeat and I know I'm about to leave your view here. Repeat and then the argument could be a number also 'cause you're going to repeat something a certain number of times. So you have this idea of a command class. And then maybe what I think would be useful is to create a parser object. And actually, instead of using split, so I used split as a way of looking at the Logo instructions and just splitting it into a big array, but I think what I want to try to do in the most absurd thing ever is just actually step through the string one character at a time so that I have the idea of a parser is keeping track of a index. So the parser is always has basically a pointer into the command and can step through and if it finds repeat, it can then pull out a chunk, it can do all sorts of stuff. So, if what I'm kind of thinking about here is this idea of a command. A command could just be a thing that you execute with an argument but if the command is repeat, then it could also have a list of, it could have an array of its own commands, that those are the things you repeat. And then inside of this array, there could be a command that is a repeat command so it could nested and recursive. So this is my idea, I have not actually, I thought about this quite a bit and probably helped by looking at everybody's pull requests so I don't know to what extent this is an original idea or it just came from suggestions, but it through osmosis, my brain has come up with this idea but I have not tried to implement it so what you are about to watch is me actually trying to implement this for the first time, and it might be a terrible idea and things will go wrong and you'll probably notice this video is 342 hours long, but if you want to stick with me, here we go. Okay, so let me go to the code. I'm going to keep the code that I had before, I'm going to add a new JavaScript file called parser. Maybe it should be interpreter, I don't know why I'm going to call it parser. Parser.js, I'm going to write a parser class and the idea is that when you create the parser, you give it some text. So this is the full text that needs to parse and then also it has an index which starts at 0, okay. And, one of the functions that I would have here is nextToken so it could maybe look and sort of find the next token or something in this text by stepping through it with the index, okay. That's what I'm thinking about. Now, I am also going to then create another JavaScript class called command, and this would be, this is what I'm talking about, it would have a name and an argument. And then it could also have potentially subcommands. Recursively, a command could also include a list of other commands. So this is my idea, now, let's go to sketch.js and basically, this go turtle function, I'm actually going to just basically take out what I did before 'cause I don't think this, I'm not going to use split anymore and I'm not going to have my own index here. I'm going to say let parser = new parser with that code. So let's just for example say console.log and parser.nextToken. So let me at least see if I can get parser.nextToken to work, and so... And this should probably return the token so return "test". So if I just run this now, parser's not defined, right, I always forget this. I've added a bunch of new JavaScript classes so I want to make them separate, reference those files here. Probably should get into bundling and building and all that sort of stuff at some point. Parser and command. Let me go back to sketch, refresh, okay, so, great, I see test there. I have got my command but I didn't really get the token. What I need to do is here, I really need to say all right, so I'm going to step through the text one character at a time. So what I mean by that is I'm going to say let char = this.text charAt this.index, okay. So, while the character is not a, I guess I could use a regular expression here. So, I want to... Let me just say, let me just do it this way for a second. While the character is not a space. What I want to do is I'm going to start with an empty token, and I am going to say, oh I don't need a... While the character is not a space, token += the character, character = this.text.charAt, charAt ++this.index. So what's going on here? The idea here and, again, I could use regular expression and substring but I'm trying to do this in a very manual way to understand it. What I'm doing is I'm saying let me look at the first character and as long as that character is not a space, I'm going to add that character to my token and then look at the next character. And this is basically, remember, this is something I covered in the first part of the challenge. This.index ++ this.index is increasing the index by 1 and giving back that new value. So now if I say return token, let's see what happens. Refresh. This text chatAt, charAt, a little typo there. There we go, look. (bell dings) I got the first token, that's great. Now, what if I now want to say here next token? Ooh, it got a space, so look at this, a space is not a valid token so what I can actually do here, back into parser, is I can actually say if, if char = a space, return next, increase the index, go to the next one and return this.nextToken. So this is a way of basically skipping and, again, this is definitely a clear example of I'll refactor this later, I need a little theme music there for that, but, this is a start, okay, so this is basically saying ignore the space and so now, we can say I've got forward 60, but here's what I want to do really. Here, I want to say... And let me actually, let me use a regular expression here. So I'm going to say, there's a category of command which is like a movement command, forward, backward, right, left, so I'm going to say that would be a regular expression that is forward or backward, this is fb followed by d or left or right followed by a t and probably, I should probably also make sure it's the full string, so put this beginning and end. So now I could say let token = parser.nextToken if movement test(token) so if it's a movement command, then create a new command with token and token and parser.nextToken. Right, 'cause I think I said the command is, I said the name of the command and the argument and the argument always comes next. So, again, I'm not doing a lot of error checking for people making mistakes in their Logo code but we'll get to that later, I suppose. Okay, I'm setting myself up for hopefully being able to do a repeat. Where am I here? So let's just see and actually let's make an array of commands, I mean I might want to put all the commands into an array or I might want to process them one at a time, there's various ways of doing this but let's actually put it into an array right now, I think that'll be helpful. Oops, okay, so let's see and now let's look at commands. Okay, refresh, look at that, I've got a command with arguments, the name is forward, the argument is 60 and it doesn't have any subcommands because it's not a repeat. Woo, I like this so far. So now, hmm, this is where I'm going to get into trouble. Let's do something like while parser is not empty, while parser has remainingTokens, I don't know, come up with a better name for that. While parser has remainingTokens, keep doing this. So what does it mean here in the parser to say remeainingTokens, well, on the one hand, I can just return this.index is less than this.text.length because if the index has moved past the end of the text, then there's nothing left. Hmm. I'm wondering if this is going, I bet you I'm going to get an infinite loop here but let's just give it a try anyway. Yes. So I have an infinite loop, I could tell because everything went blank, this is just there, so I'm going to actually, unfortunately, have to kill this page, whoops, ah. Next token, there are situations where if it hits the bracket, bracket is not a space, bracket is not a... So, ah, I guess what I could do here is if char, I mean this is really terrible or if it's one of these brackets, then, return, then also go up by an index and return that character. So this definitely accounts for another thing. So, again, this could be condensed and I could maybe use some more clever ways of doing this but if it's a space, ignore. If it's a bracket, whoops, if it's... And actually, I'm going to want to do something for it's a repeat, but anyway, if it's a bracket, send that back. And, otherwise, accumulate until a space or bracket, but, okay, there's so many problems with this but I have to build it one step at a time. If you're willing to stick with me and keep watching this video, then more power to you, okay. Let's see if I manage to not have an infinite loop. Still infinite loop. While character's not a space, keep going up and this could get stuck. Or, while character space and this.index is less than this.text.length. So I've also got to check to make sure I'm not hitting the end, oh there we go, okay (laughing). All right, so that was definitely the problem. Now at least my infinite loop is gone. Let's look at these commands. So forward 60, right 120, so this is good. This is what I wanted it to be. But I need to be able to account for the repeat. So this is good, this is giving me a list of commands but what if the token that I get back, now I need to deal with repeat. So, if it is a movement, I should also have something for a pen command which would really just be matching a p at the beginning, right, any of the pen commands match just a p so I should say else if pen test token, so a pen command is just a command with just the token, no argument. And now I could, in theory, if I added a pen up command, we would see pen up is in there with no argument but that's fine, so that's good, so this would work, oh why isn't it, oh it's not drawing anything 'cause I'm not bothering to draw, sorry, I keep opening up terminal by accident. Okay, so I have now written a parser that can go from token to token and can create commands and knows how to handle any movement command or any pen command. Oh and this.remaining tokens. Ah, yes, great suggestion from the chat. Abdillah Baghat suggests that here, I like this suggestion, that this is already a test which is this.remaining, remaining tokens, 'cause I have that function, right, that already tests that here. Great, okay, still working, all right. Now it's time to deal with the repeat. So I need to handle the repeat in a very unique way and this is where I'm really going to get lost. So repeat is just matching the repeat command. So this could be a regular expression here. So else if repeat test token. So now, what if I get a repeat command? Then what I need to do is say, so let command is a new command with the token. Oh, and also the next token. So I first get the number, then what I need to do is I need to pull out, hmm, I need to pull out everything within the brackets so then what I want to say is the to repeat is the parser, get repeat. So I want to write a new function that then in the parser looks for that starting bracket, looks for the end bracket and just gives everything back (claps), okay. So, in the parser now, I'm going to say getRepeat. So I should first, basically find that first bracket. So I want to, in theory, there could be spaces, so char = this.text, this is really hard, the way that I've gotten to this text.charAt this.index. I should probably put that in a function and then if while char, I guess I could say, you know what, I could do this. While this is not equal to this opening bracket, while it's not equal to this opening bracket, oh ++, right, I want to go ahead to the next one but stick where I am, then just keep going, and, and I guess need to make sure, and this.remainingTokens. Now, I have found... Basically I have found... I'm going to say index, I'm just going to say this.index. This should console.log where the index is. So I need to put a repeat in here. So let's start with something really simple like repeat three this. And let's refresh, all right. It got forward, it got right and it got, ooh weird, and it said 10. Is that 0 1 2 3 4 5 6 7 8 9, oh great, so now it's at index 10, oh, perfect. So now, ah, I love this. Okay, so. Commands push. So this is actually a function, I got to make a subparser of the sub thing. Ooh. Ah, yikes, I mean the recursion aspect of this, right, because a repeat could have a repeat inside of it. If a repeat couldn't have a repeat inside of it, I'd kind of be done, maybe I should do that first. This is going to be a really long video. I know I keep saying that but it just is. So now what I want is to find the last one. So... Let's start = this.index. Let end, now I need to do this again, move along here, and let end = this.index and then return this.text substring start, end. Let's see if this does what I think it should do. And then, let's see if it does what I think it should do. Okay. Command, token, whew, parser, nextToken, let's just console.log this. Console.log to repeat, okay. Yes, oh okay, look at that, this is the thing to repeat and the command is the repeat, perfect, ah, yes, okay, but minus one, minus one, minus one. So here, in parser, end minus one 'cause the index is actually the index past that final bracket. So now, there we go. The command is repeat, it's named repeat and now I just need to fill the commands with an array of the stuff that's inside of that to repeat. So this, ah, okay, I have an idea now. This function... Parse, let commands. I'm trying to think here, parse. I need to write a recursive function now where I come back and do this, then for those subcommands, okay so let commands equal, this is going to be weird, let me just try to do parse, parser.parse, is this what I want to let commands = parser.parse, should all this be in the parser? And then, this would, I don't know about putting this in the parser. If this is in the parser, parse, oh this is hard. Okay. While this remaining tokens, next, so this I'm not so sure about putting this in here but I'm going to. So everywhere I said parser, I'm now saying this. And then, I am saying let commands is be an array and then basically, at the end here, I'm going to say return commands, just give me a second, I'm not going to do the recursion yet. So let's see, let's see what happens here. And then in Sketch, let commands = parser.parse, console.log commands. This is crazy. All right, sketch. All right, that's good, that's good, hold on, hold on, hold on, I'm thinking, I'm thinking, oh there's a console.log here. Where is there extra console.log? Uh, it's 25 and 31. Oh I have it twice, okay. Okay, so now, okay, okay, now, now, this is the hard part. All right, so I have this to repeat and then what I want to say is command.commands equals (gasps) parse, this.parse to, this is what I want to do, to repeat. Okay. So this isn't exactly going to work, this is the idea of what I want to do. But it's not right yet. So close, it's not right because the parse command inside, it's working with this particular, the sort of global block of text. Maybe it makes more sense for that not to be, oh wait, no, no, no, I think I have an idea (gasps). All I have to do is make a new parser here. With the to repeat stuff and then that equals parser.parse, right? Because then it would have, I think this might work. Could this really work? Let's think about this. I'm basically saying hey, make a parser with all this code, parse it and give me the commands. The parser is going to do that and return that array of commands but internally, oh, wait, wait, wait, wait, yeah, it's going to make. I think this, I mean, it's so hard to speak this. Let's run it, let's run it. This is going to produce a lot of errors or an infinite loop, for sure. (drum roll) (buzzer buzzing) Cannot read property length of undefined parser.js line 7. (vocalizing) This.text, oh whoops, I took that out when I was messing around with this idea. Okay, not playing the drums but okay, command, repeat, ah! Ohhh. (clapping) I think that worked, okay, so I think this is a case where I was figuring it out, trying to explain it, all at the same time but I don't know that I did that super successfully so let me take a minute to try to unpack what just happened. Okay, and the idea here is that I have something like this, repeat three, forward 60, can you see that? Yes, so the parser finds the first token and makes a command repeat with three. Then the parser, because it found a repeat, asks for this as basically a substring and it makes a new parser object with only this. So the first parser object started with everything, the new parser object just works on this and makes a command. So I really should put this in a json way. So the idea here, and let's add some other stuff. The way that it should work, imagine if this had forward 60 here. So the idea is that I would have an array with commands and the first command would have name forward, argument 60, then the next command would have name, right, repeat, argument three but it knows it's a repeat, it then goes and makes a parser for this and the same way that this is a big array, the parser now works on this to put it in an array here with objects like forward 60, et cetera. So in theory, this could even have another repeat inside and that repeat would be in this array with its own subcommands. Hopefully that helps a little bit more. But let's actually test this theory. So let me go now to, let me, I'm afraid to type stuff in here and break so I think what I'm going to do is just, so let's start with forward 60, repeat, let's just start with that first. So we can see we got a forward 60, no commands and a second command that says repeat three with two other commands. Okay. Now what if I then put in here, let me do it out here 'cause, what if I do another repeat forward, oh so repeat four, forward 10. Let's look at this. Okay, so. There's still forward 60, then there's a repeat and the repeat argument is three, there are three commands, one of which is a repeat which has a subcommands in it. (bell dings) (train whistle) All right, I think this works, I mean I'm sure this needs error handling but the basic idea is actually working, wow this is very exciting to me. All right, so what I need to do now is to follow the instructions. This should be, in theory, this should be the easy part. So now that I have this commands, I can just say for let command of commands and now I forgot how I did this in the first place 'cause I have this, oh boy, this is bad, I have a, I have this dictionary thing that I made to execute these different functions, hmm, so what do I want to do, this is called commands, so I'm going to call this command dictionary. Well I'll just call this commands, command list. Command list, it's not a list. Command lookup. Command look up, boy that's a awkward variable name. So what I want to do is now I want to say the name is command.name and the argument is command.argument and then I'm going to say, I have to figure out if it's a repeat. If name = repeat, then for let i = 0, i is less than arg, i++, right, 'cause I want to do whatever is, and then I'm going to go through its commands. I guess I could say, oh boy, oh, ah, I need another recursive function to traverse it. Aah. Let me go back to something simpler. Without anything nested. Forward 60, repeat three times, okay so this idea works. But... This needs to be a function, I need more recursion or maybe I should be just doing this as I'm going. So I'm going to call this execute and executes a list of commands. So if you execute a list of commands and then you get the repeat, you need to execute, command.commands. Right, so in other words, I'm looking at the commands. If it is a repeat, then I want to execute the sub commands that are in these sub commands list a certain number of times. Otherwise, I can now say basically what I did in the very first version of this challenge which is to do what? Command list name arg. So the idea is I'm looking up the command list, in the command list, by the name with an argument. So that will then go here and say if it's forward, it will execute this function, moving the turtle forward by an amount. I think there's a lot of redundant extra code now but this is fine. Let's see what happens. Syntax error. Oh this has to say function. I'm not in an object anymore. All right, mm, so, hmm, what is that 100 100, so what's, oh, oh I didn't execute anything. Execute commands. Command list is not defined. Command lookup, okay, I can't remember what I call anything, command lookup. Hey! (clapping) (bell dings) This is working. Wow, okay. Dare I say this is complete? Does anybody have access to the code that produces this design? I'm going to take a break and try to find it. (train whistle) Okay, I'm back. What you don't realize is about an hour passed of me doing some horrific debugging and if you want to find that, I will link to the livestream version of this edited challenge in the video description, the thing that you're watching right now, which is not, even though I am live, it's not this, ah, okay, this, this is the code using only repeats, left, pen up and pen downs that will create this particular pattern and if I go and grab this, I've pasted it here and if I go back to my example and I paste it in... (drum roll) (buzzer buzzing) It doesn't work. What's wrong? Well, weirdly, I'm going to show you something weird. If I put a space here and a space here, ah, all of a sudden. (horns playing) We have the pattern we were looking for all long. But there's some weird buggy stuff going on, why is it a space but no space, here is the issue. So it took a while to debug, I will show you a way of finding, the way that I found the error was by looking through, first of all the chat found this way before me and kept suggesting it and I had to look and look and look. Where I found it all of a sudden in this last repeat here, this command right, right, it didn't pick up the argument three, it's blank so that's certainly why I'm not getting the pattern that I'm expecting to get. Now why is it doing that? Well the secret to that, it's not a secret so much, lies in the fact of the way that I parsed this bracket, right, remember if I have a repeat, if I go here, right, I look for the first opening bracket, that's the start, the first ending bracket and that's the end but what if there's a nested repeat? If there's a nested repeat, this is the starting bracket, this is the ending bracket. So it completely ignores, it pulls it off here and then loses the way it's parsing, it loses that three and somehow having a space in between there, it kind of is able to still get that three by accident. So what I really need to do is match the number of brackets. All right, I have a different idea now. What if, I think this finding the first one and finding the last one in this weird double wild loop way is kind of silly because all I need to do is keep track of, I mean I do need to find the first one, so let's stick with finding the first one and then basically once I found that first one, bracket, bracket count, ah, equals one. So now, what I want to do is while bracket count is greater than zero, I'm going to look at the character. If character = and opening bracket, then, ah, then increase the bracket count, else if it = a closing bracket, then decrease the bracket count. And then I should have, end should just now be wherever the index is, wherever it last left off, I might need to do minus 1, I'm not sure, right, so this should be like, I'm basically finding the first bracket, in this case, there's noise and spaces there and then I'm going to pull out, I'm going to, I'm basically looking for where that last bracket is and the last bracket is and as the last bracket comes, bracket count will be zero, I'll be done but if there are other brackets, it's got to keep counting, okay. (drum roll) I should never use my drum sound because it always means it's not going to work. (clapping) (train whistle) All right, there we go. All right so this is complete, it works, I'm very excited about that. People have pointed out some examples that I found, for example, I could try to make this design, let's see what happens if I post this in here. Right, we could say, oh I'm kind of off where my starting point is of but now, I could start to really be creative and the line is sort of thick here so I also feel like now that I'm doing more complex stuff, I probably want to change the stroke weight back to one. And we can see that looks a little bit nicer. I just want to show you something though amazing. If we go back to the Logo repo which this is the state before I've done this video and I go to pull requests and I go, for example, to pull request number three from TheTastefulToastie, you can see here are a whole bunch of other ones that you can see that I could now try that should work with my, and I'm going to show you something that's going to blow your mind, this. Ah, let's make this work, we have to make this work. So I'm going to go, just before I leave, think about other creative things you could add. These are all the commands. I am going to now go into index and HTML, I'm going to put that in here, hold on, what's going on here? Let me give a carriage return here, let me paste in all of those commands. Now I could now go back to here and do this. We don't see anything. So one thing that I really definitely need to do is give it a much bigger canvas. So let's go back to create canvas. Have to finish with this. 900 by 900, something like that, refresh. (gasps) There it is. Oh my goodness, this is nuts. But it's a little off center, oh let's change it to 20 20 or zero, let's actually just change it to 0, 0. And make it a little bit wider. Whoops. There we go. (train whistle) it is The Coding Train logo made with literally with Logo and all of these numbers here. I hope you enjoyed this video, there's so many things missing, right, there are all these Logo, if you look at the history of Logo and you look at the language of Logo, there are lots of commands that I missed. So I'll list some of those in the video's description, try to implement those or also, color, how would you incorporate color with this? How would you think of other clever ways to make this interactive in realtime to allow people to play with it? So, we now have a full interpreter here and I'm finished with this project. I can't wait to see what you make, I'm going to add this code to the This Coding Train Logo repository and I will accept pull requests for implementing other commands, bug fixes, adding things like color, making the interface a little nicer so I'm probably going to close out most of those pull requests that were implementations of repeat, I thank you for them but this will be now a community Logo interpreter project that will start from the code that is in this video. Goodbye, everyone, and see you in future coding challenges. (upbeat music)
B1 中級 編碼挑戰#121.2。徽標解釋器第二部分 (Coding Challenge #121.2: Logo Interpreter Part 2) 3 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字