 Has everybody had a good RubyConf so far? Yes. Yes? OK, so I know it's Saturday afternoon. So I'll try to keep it light. So I guess they, you know, hi. My name's Tom. I work for Neuralic. I work on the mobile monitoring and Linux serve monitoring products. We're hiring. So if you're looking for a job, please feel free to spam us with your resumes. I'm here today to talk to you about rapid programming language prototypes using Ruby and Rack, which is essentially compiler construction using Ruby. Now before I get started, I just want to make it clear that I'm talking about Rack and not Rack. So Rack with a K is web server middleware. So I'm not going to be speaking about Thin or Unicorn or Sinatra or Rails or anything like that. I'm talking about Rack, which is a password generator for Ruby. OK, so when I first sort of put the proposal for this presentation together, I kind of had it in my head that my target audience would sort of be people who have maybe toyed with compiler construction in the past and had some experience using Yack and Bison and all that sort of stuff. But Matz's keynote on Thursday sort of made me realize that maybe that was a bit too ambitious and I should sort of scale it back a little bit to sort of give people who have not experienced some exposure to compiler construction fundamentals. And so the first part of this presentation, probably the first 10 to 15 minutes, is basically an intro to compiler construction, basic theory, the architecture behind a generalized compiler. And this is great information because, you know, this stuff can be applied to pretty much any, you know, modern compiler that you care to look at. So if you go digging inside the Ruby compiler, these concepts apply. Likewise Python, PHP even, all of this stuff, you know, it sort of applies across the board. Once I've sort of covered off the fundamentals, I'm going to dive into Rack and, you know, when you might want to use it instead of Yack or Bison. And I'm going to finish up with some live coding. So when I say finish up, probably 2 thirds of this presentation is live coding. So I'm going to sort of try and implement a compiler here on the stage in front of you and you all get to laugh at my horrible, horrible Ruby code. Okay, so first up, what is a compiler? A compiler takes code in a source language, then magic happens, and then out the other end, the target code comes out. And so the way I like to think of a compiler is sort of as a pipeline with several sort of steps along the way. So there's several transformations applied to your source code along the way before it gets dumped out as, you know, your target code. And the first step in that process is the scanner. So once again, if you think of your compiler as a pipeline, the scanner is like the first component in that pipeline that sort of takes your raw source code and tries to identify, you know, interesting tokens within that source code. Like this is an if keyword, this is a bracket, this is a string, semicolon, that sort of thing. So, you know, it might strip out all the white space and comments and all that sort of stuff. So it's really just sort of chunking up your input to make it easier to digest. So those tokens, you know, sort of form a stream or an array or however you want to sort of conceptualize it. And that feeds into the parser. The parser takes those tokens and sort of tries to make sense of that raw input using a set of rules. And I've got up on the projector there, you know, it outputs an intermediate representation, which is so, you know, generic as to be meaningless. So I've given the example of an abstract syntax tree, which you may have heard of. If you haven't, I'll sort of cover that in more detail in a moment. So I'm going to treat the rest of this presentation as though the parser always outputs an abstract syntax tree. But it's probably important to note that it doesn't necessarily have to do that. It can even, you know, directly generate bytecode. Now, parsers, you can manually write parsers. So it's actually pretty trivial to write, like a top-down recursive descent parser by hand. But generally, you know, what you tend to see out in the wild is people will use things called parser generators to take a grammar and automatically generate a parser for the language described in the grammar. And so a grammar, as you might suspect, describes the complete syntax of a programming language. Has everybody sort of heard of EB&F? Sort of, maybe? No, kind of? That's okay. It doesn't really matter too much. You'll sort of see an example later on in the presentation, a very, very simple example. And essentially, variants of EB&F are used for the DSLs that serve as inputs to parser generators. So an example of a parser generator would be YAC or Bison or even RAC. So before I mentioned an abstract syntax tree, so this is really just, you know, sort of giving you more detail about what an abstract syntax tree actually is. So it's an in-memory, logical representation of your source program. So it's just a data structure, basically. So you start with this raw, you know, blob of source code. It passes through the scanner. The scanner converts it into tokens. The tokens are fed into the parser. The parser structures those tokens into an AST. So it's abstract in the sense that we emit some detail as well. So by the time we get to an abstract syntax tree, we don't care that our if statement requires brackets around the test expression, for example. Contrast to a concrete syntax tree, which does include that sort of detail. Okay, so we've got our AST or, you know, quote-unquote intermediate representation. And now we want to generate code from that intermediate representation. So in the case of an AST, we traverse the tree and generate the appropriate code for each node along the way. So if you're working with an if node, for example, the code generator would look at the if node and go, aha, this is an if node. So I know to generate code for the test expression associated with that if node. And I know to generate code to test whether that expression is true. And I know I need to generate a jump over the body of the if statement, if the expression evaluates to false. And I need to generate the code for the body itself. And so that's all done sort of recursively, like the code generator can recurse into the various nodes in the AST. Okay, so I kind of spoke about the compiler essentially being a pipeline. And this is kind of the diagram that sort of shows you what's going on. So the raw source code feeds into the scanner. The scanner produces tokens. The tokens feed into the parser. The parser constructs an AST. The AST feeds into the code generator. And the code generator finally dumps out your target code. Okay, so that kind of concludes the sort of, you know, 10,000-foot view of, you know, compiler construction in general. So now for a little bit about RAC. So RAC, as you probably might have guessed by now, is a parser generator, written by a tenderlove. You write your grammar using a sort of DSL, which I'll sort of demonstrate in a minute. You run RAC over your grammar, and that will generate Ruby code that can parse the language that you describe in your grammar. So why would you use RAC over something like Yak or Bison? Well, I tend to use RAC when I'm sort of experimenting, you know, with crazy ideas or trying to learn. Basically, I'm relatively new to, you know, the whole compiler construction thing myself. And so whenever I want to spin up a new project or something like that, essentially something that I'm probably going to throw away, I get really frustrated when I have to, you know, do the magical dance required to get Yak, Bison, Flex and C and everything working together. Not to mention, you know, that Ruby gives you hashes and arrays and all that sort of stuff for free. So really, for me, it's about, you know, getting from zero to 100 as quickly as possible. Okay. This is sucklang. Sucklang is essentially a DSL for calling functions. It doesn't take any arguments at this point. So this is a really, really simple example. If I have time towards the end, which it looks like I should, I'll try to sort of expand on this and make it more interesting. So up there, you can see we've got... So this is an example of EBNF, if you've never seen it before. So very, very simple example of EBNF. So on the left-hand side, you can see we've got a call that's called a non-terminal. And on the right-hand side, we've got a sequence of tokens. So ID is essentially a token. The bracket is a token. The closing bracket is a token, which is all fed into the parser from the scanner. So I've also got a note up there. You know, that big scary regex basically just means, you know, an ID is basically, you know, a string that starts with an alphabetical character or an underscore, followed by zero or more alphanumeric or underscore characters. Okay. So this is an example of sucklang. That's exciting, right? Okay. So if you'll recall that the scanner takes raw source code, generates tokens, passes into the parser, that means there needs to be some sort of agreement between the scanner and the parser as to how that data kind of comes in. And so Rack kind of sets the rule, so to speak, by sort of saying, okay, well, I expect two element sequences from the scanner. And so the first element in, you know, the two element sequence that represents a token is the type of the token. And the second value, the second element in the token is the value. And what I mean by that is if you're passing, say, a string, if you're scanning, sorry, if you're scanning a string, you would say, okay, well, I see a quote, there's the contents of the string and another quote. Okay, so this is a string token. So the type of the token is a string. But the value of the token would be the contents of the string. And that's important to sort of expose that sort of information to the parser when you're constructing the AST, for example. Okay, so I think at this point we can sort of get started with the fun part, which is, you know, writing a compiler, a very, very simple compiler. Okay, so, oh, you can't see. Okay, so we'll open up libsucklang scanner.rb, scanner, whoops, the scanner will live in a module called sucklang, scanner. And we don't really tend to need to keep state around when we're writing a scanner. So I'm just making this a module level function, scan. So the scanner takes raw source code as input and emits an array of tokens as output. So if we now look back at our grammar, way back here, we can see that we've basically got three tokens. So we've got an ID, an opening bracket, and a closing bracket. So let's start out by passing that ID token. So while our input, so we want to consume all the input, which we'll do by just sort of stripping the front off the string as we're scanning it, so when the start of the string contains alphabetical character or an underscore followed by zero or nine alphanumeric characters or underscores. So our language isn't unicode compliant, unfortunately. Okay, so we need to generate a token. So this is an ID token. We want to set the value of this token to the pattern that we just matched here. Oh, what am I doing? So when we match that pattern, this magic variable sort of just evaluates to the text that was matched by that regex. Okay, so we've now matched an ID, but we need to strip off the front of our source string. So I'll use slice. Whoops. And that's all we need to do to sort of scan a token. So we also need to handle the open and close brackets. So once again, when the string starts with an opening bracket or a closing bracket, we can just use the token itself. So in the case of a bracket, there's no value associated with a bracket right. It's just punctuation. So we don't really care about this value on the right-hand side. It's just kind of a throwaway thing. I'm just throwing it in there, you know, more by habit than anything else. And since we're only sort of scanning a single token in this case, we can sort of just use something like that to strip off the first character in the string. Okay, now we don't want to accept anything else as input. So anything else that we see causes an error, and we'll just, you know, flat out exit. And that should pretty much be our scanner. So let's write a quick little driver program. Ruby... Ooh, started writing C. All right, we'll include lib succlang scanner. So the scanner emits an array of tokens, and we'll just pass the, you know, our sample source program in directly. So let's see how it goes. So we're sort of chunking that, you know, raw source code up into tokens, and this is in a format that RAC can kind of start to work with. And sort of just to demonstrate, you know, at this point, we're not applying any strict rules to the input, so, you know, it doesn't have to be a legal program so long as it contains characters that the scanner kind of knows about. So, you know, that sort of stuff works as well. But if we, you know, if we, for example, put in a character that the scanner doesn't like, then it will complain. That sort of thing. So the scanner, in a sense, sort of enforces some level of syntax, but, you know, it's more in terms of, you know, weird characters and stuff that you don't expect to find on the input at all. So let's modify this so that we can pass in programs from the command line. Now, this isn't going to work because you'll get an implicit carriage return, sorry, a line feed, so we try to pass that in on an input bar. So we'll go back to our scanner and we'll strip out white space. So when the string starts with white space, get rid of it, okay? So, oh geez, we're making good time. That's excellent. Okay, so now that we've got our scanner, if you think back to that diagram, the next thing we need to do is implement a parser, and the way we're going to do that is by writing a rack grammar. So the rack grammar is basically, you know, it's sort of a pseudo ruby slash, you know, EBNF-ish DSL thing, so I guess to start with, make sure you've got rack installed. Once you've done that, you're pretty much ready to go. So what we need to do next is add lib sucklang parser.rack. Now parser.rack, whoops, yep, class, parser.rack starts out like this. So the name of the module followed by the parser class. So we want this class, which we're going to call parser, to be in this module, which we're going to call sucklang. So when rack generates the parser class, it's going to dump it out inside the sucklang module. So this rule section is where we write our crazy EBNF-DSL thing. So if we go back to our grammar, we can almost use this exactly as is. So we can say, okay, well, the call non-terminal consists of, so in EBNF we use this weird, you know, colon, colon equals operator, but in the rack grammar and yak and bias and all that sort of thing, you only need to use a colon. Followed by id, open bracket, close bracket. And we can literally just write it in like that. So once again, just to make it clear, these tokens are coming from the scanner. So these are the token types from your scanner. Okay, now, the other thing we want to do is, you know, make it easy to call the parser without sort of, you know, messing around with it too much. So we'll use this thing called an intersection. And the intersection will basically, you know, it basically lets you add whatever code you want to the inside of the parser class. So pass and we take, you know, a sequence of tokens as input and we'll just save those tokens. And then we'll call the magical rack function, well, rack method called do pass, which will actually do the heavy lifting of passing our tokens. Now we need to tell rack how to get those tokens, so we need to define a method called next token, which is something that rack is internally aware of. It'll call this on your behalf. So all we need to do is shift the next token off the front of our token array. And so we'll just keep doing that until we run out of tokens and, you know, eventually return nil and rack will go, oh, I'm finished. I don't need to do anything more. Okay, and that's pretty much the parser. So now we need to make this kind of evil command line invocation here. So basically all this is doing is running rack via rbm, specifying the output file. So we want to store the output file in lib succlang parser.rb and the input file, you know, lib succlang parser.rack. Okay, so let's do that. Okay, seems to have worked. Actually, we'll open that up and edit it. lib succlang parser.rb. Okay, so this is the Ruby code that rack generates on your behalf. You can see our inner section here. So this is dumped in, you know, literally, you know, in place. And then everything else is sort of, you know, it's generated based upon the rules that we specify in our rule section. So over here. You don't have to worry too much about what this actually does. This is the point of using a parser generator. You don't have to worry about, you know, magic going on the inside. Okay, now since I'm going to have to do that a few times, I'm going to dump it into a rate file. So when we... Sorry, I can't type anything at this point. So we want to generate succlang parser.rb, and it depends on parser.rack. So just execute that. And we'll set the default task to lib succlang parser.rb. So when we run rake, that'll execute rack on our behalf. Okay, so we've implemented a parser, but what does it do at this point? So we want to construct an AST during the parse, but at this point, like, rack doesn't... rack doesn't build an AST for you. It sort of doesn't define the output of the parse phase so much, it kind of leaves it up to you to do that. So just for science, let's open up our driver program again. And we're not returning an AST at the moment, but let's see what rack does by default. So we'll parse in our tokens, and see what comes with that. Oh, I forgot to require it. Okay, so the interesting thing is from our parser here, we get this string ohi, which corresponds with the identifier value. Now, if we go into our grammar once again, you can see that, you know, the ID token is the first value in... well, the first token in the sequence of tokens that make up a call. And what you can actually do in rack is specify, you know, custom actions. And the default action for, you know, handling this rule is to set the result equal to the value of the first token. So this and this are functionally equivalent. It's the same thing. So just to prove that, I'll run rack again because I've modified the grammar, so we need to regenerate the parser. And we'll run that example program again. It's the exact same thing. Now, we need to construct an AST. So what does an AST look like? I mean, I've spoken about ASTs in, you know, sort of a really abstract sense. And I guess the best way to sort of demonstrate what an AST might look like is to sort of ask something like Rubinius. So we'll say, okay, well, whoops, RBM, Shell, oh, I'm already using Rubinius here. So we'll say RBX, compile SE. And we'll pass in, you know, because this is sort of legal Ruby syntax as well, we can use Rubinius to sort of give us a hint as to what we're doing here. Okay, so this is giving us a SEX, which is, you know, sort of a representation of an AST as a series of nested arrays. And the node type is the first value in the array and, you know, sort of children of that node kind of follow. And so here we've got a call. And a call has a target. So you can specify a target on which to call a method, right? So if you call foo.ohi, it sets the target of the method to foo. So hopefully that makes sense. It's not really important for what we're doing here, but, yeah. So then we get this symbol, ohi, which is, you know, the method that we're trying to call, followed by, you know, an empty argument list. So let's do something evil and blatantly copy this. Oh, trackpads are the bane of my existence. Okay, so we can really dump anything in here, okay? So just to make it clear, you can do, like, one, two, three, four, one, two, three, and set that to the result of your pass, and you'll get that as the output. So here we're going to literally just dump in that AST from Rubinius and, you know, without modifying it at the moment, run it again, okay? We get our AST. Now, we don't want to hard code that ohi. We want to set that to the value of the function that we're trying to call back here. So we'll set the name of the method that we're trying to call to the value of that ID token. And I've modified the grammar, regenerate the Ruby code. Okay, so there's our ohi. And just to prove that it is actually working, you know? So you can see the value of our target changing in the AST. Okay, so we don't need all this junk. So I'm just going to strip out the target. We're not using it. For now, we're going to strip out the argument list as well. We can get rid of that. And so, you know, we're just sort of simplifying the AST at this point. So we don't need any of that other junk. That's just stuff that Rubinius gave to us. Okay. So there's our simplified AST. So we've got an AST. We've got a parser returning the AST. All that's left for us to do is to implement a code generator. So I sort of undenied for a while about, you know, what language to use in the code generation side of things. And I settled on C, but you could really use, like, for this sort of thing, it's so simple you could use, you know, Ruby or JavaScript or, you know, you could probably generate, you know, the equivalent JVM byte code pretty easily as well if you wanted to. But where are you going to use C? So let's get started and just go ahead and implement the code generator. Okay. So once again, module suckling, module code generator. I'm going to call it class C, just in case we decide to add extra backends later on. So our code generator takes a compile function, which takes an AST as input. So the input will be this guy. And we want to make sure that, you know, we're getting the correct node parsing as well. So we'll match on the first item in the array and make sure that it's a script node. Otherwise, it's some sort of error. I won't bother implementing the error function for now. So when we know we've got a script, we know our language only supports a single function call. So, you know, the child node here is going to be this call. So what we're going to do is add a method called compile call, which is the second element in the array, so that guy. And then down here, lad compile call, it takes a call node. Once again, we want to make sure that we do definitely have a call node being passed in. Otherwise, it's some sort of error. Okay, so we're generating C code. So, you know, unlike Ruby, you can't just dump, you know, function calls in the middle of a source file. So what we're going to do is generate some, you know, like a prelude and a footer for the target source file. So what we're going to do is say, our output starts with int main, and then we compile our call, and the output ends with return zero close bracket, EOF. All right, so without actually generating the function call, we should get a valid C file at this point. So we'll open up our driver program, and this time we won't forget to require the code generator module. And we'll basically call the code generator directly with our AST and make sure we get some code at the end. So I'm not really interested in these calls anymore. I've taken it all the way. But the interesting thing you can see here is that we're taking, you know, our raw source code all the way through to our target code. So, you know, this is essentially the pipeline, the compiler pipeline in source code form, which, you know, is kind of nice. So if we run this guy... What have I done? Did I? Back in the code generator, right? Yep, gotcha. Okay, so there's our dumb C file without any function call. So now we can append. Okay, so remember that our AST, wherever that's gone now, so remember our AST for... our AST node for a call just contains the identifier of the method that we're trying to call as its second element. So we can say call one, open bracket, close bracket, semi-colon, slash n. And that's, you know, how we generate code for a call AST node in our target language. Okay? So we're calling ohi-man here, but we haven't defined that, you know, in our C code. So what I'm going to do is have main call itself. I'm glad you're all following along. And Kaboom. So we have successfully written a parser, a compiler, whose sole function is to crash itself, which is kind of neat. But, you know, maybe we should do something a little bit more interesting. So we're going to add a bit of a runtime to our hypothetical made-up crappy language. So we'll define a function called ohi, and ohi will call printf. And remember we're generating C code here, so we need to escape that carriage return. Now the carriage return, the backslash. And include standard.io.h and all that sort of fun stuff. I did it again. Okay. Compile it again and run it. And there's our output. So that's taking us all the way from, you know, very simple beginnings in the source code, all the way to the end generating target code and, you know, executing it. So we can take that, you know, maybe one step further and, you know, dump that out to a file. So a.out.c. Open it for writing. Write code. And then we'll use system to invoke GCC because we're bad people. Okay. So now when we run our compiler, we get no output, but we should have an a.out. Which, you know, so you can kind of hand wave and pretend like you're generating native code, which is kind of fun. Now, so I've got maybe eight minutes left, I think. So if anybody has any questions at this point, feel free to ask. Otherwise I can sort of, you know, go right here and keep going forwards. Yes. What would your recommendation be for writing sort of a more sophisticated scan? So you have like more complex tokens that you want to be fully aware of? Like is there like a tool you use? Yeah, yeah. I mean, I sort of chose to write this one by hand because it was, you know, so simple that, you know, it wasn't worth bringing in a new tool. But there's gems out there called, I think, like Lexar and Rubylex and things like that, which let you do that kind of scanning with sort of less effort. And, you know, I'm assuming, you know, more complicated tokens as well. I think I did like a gem search for Lex and that came up with a whole bunch of useful stuff. Can you compose grammars so that you can have one kind of, you know, language and then say have another SQL language over here and say this one includes that one so you can do inline SQL sort of in the first one? Sure. I think I've heard murmurings of things that have been able to do that. To the best of my knowledge, you can't do it with something like RAC. I mean, I could be wrong. I honestly don't know, to be honest. But, yeah, I do know that some research has been done into it because it's the next obvious step, right? You know, when you've got, like, SQL in your code, it just makes sense to kind of, you know, have it in there rather than, you know, encode it in a string or something. EBNF will only do context-free grammars, so if you're changing your parsing rules based on where you are in the program, you can't do that with a context-free grammar. So, like, you could use multiple parsers to switch between them based on what you're doing, but you can't write, like, an EBNF ground that describes something where the rules are different based on the last thing you put to show. Yeah. Could you repeat that? So, could you repeat that? So, essentially, sorry, what's your name? Glenn. So, what Glenn was saying was that you could potentially chunk it up into, you know, different sections of source code and use different parsers for each of those sections. But, you know, composing EBNF grammars doesn't really work. Does that kind of sum it up pretty much? Cool. Yeah. Could you talk a little bit about the tools provided by Rubinius to write your implementation or your parser in the on language? Sorry, I can't hear. Could you talk a little bit about the tools provided by Rubinius to do some of that and to implement your language? Honestly, I don't know a whole lot about Rubinius. I just kind of used it here to, you know, as an example to generate the AST. You're taking a peek under the hood at what Rubinius is doing internally. It's not a tool for writing your own grant. Right, right, yeah. I'm not using Rubinius, you know, as anything more just to demonstrate what an AST was like, essentially. Yep. Just on that topic, Rubinius does have numerous EBNF points in developing your own language on top of it. The Ruby store in Rubinius is just one plugin, basically, building and installing it. It's racially cool, right? If you're doing this sort of thing, you should definitely go get the Rubinius as well. And the first thing is your grant for the eight days in Rubinius. Cool. That's awesome. Let's check that out. Yeah. Can you talk a little bit about all the diversifications other than the massive diversification in Rubinius that's become a lot more popular? I don't know what else Rubinius provides. But, you know, another example is like a, I think I mentioned like a concrete syntax tree where, you know, it doesn't ditch, you know, all the extra syntax that's kind of included. You know, you can directly generate bytecode or make code or whatever, directly from within your parser if you're so inclined. On the top of my head, I can't think of anything else. You can also build a tree on this. Yeah. So it's a huge mess of frames. I don't know the types that are like classes in the same thing, but there's a little tree down there. So it's a very lovely thing. Yeah. Sorry, how does that, how does that differ from, like, an ASD or a concrete syntax tree? You would have an ASD where you know it's for objects and instances and arrays with a symbol as the person. So like a array with a symbol as the person on it, it's sort of like a foreground and it's like way of tagging the recipe. Oh, sure. Yeah. Actually, I kind of prefer that style, to be honest. It's just one of those things where, you know, it's so simple that it's just so much easier to use arrays. Test for this kind of thing. Sorry, welcome Tom. Testing, how do you test something like this? How do you test something like this? You write programs. You know, you can test all of the individual components in isolation, so you can verify that your scanner breaks up tokens in a way that you would expect. You can verify that given a set of tokens that your parser constructs, an ASD that you would expect, and, you know, given, you know, some ASD, you could verify that you get the source code that you would expect. So each of those individual, you know, sections can be, you know, tested in isolation. Sure. The risk of shilling for Ravinius, the compiler test suite is a good example of how you can get from the starting point to where you want it to be. Check out the test with the compiler, and then it has MSpec, which is this easily bootstrap, sort of more to more specs, so once you've done those, you can prove the next thing. Okay, so I think the answer to that question was use Ravinius. Check out the kinds of things it tests, and it's a power test to show you the slide. Yeah, definitely. I mean, I want to look at Ravinius now. So... Any more questions? Oh, yes. Have you built any web switches of your own to use in this kind of process if you've open sourced? I mean, if you're keen to see a specific example, come and see me afterwards, and I can give you some links. It's going to be good to see a more kind of pretty... Right, right. I'm kind of just about out of time, but I understand. I was kind of disappointed with the sort of simple example that I kind of had to stick with in the end, mainly due to time constraints, right? So I guess... Are there any more questions or things up? Yeah, sure. Just, from an educational standpoint, do you know any tools that could convert a normal grammar like definition into this workflow so you could start from, like, a period of a period of class, and learn about my history grammars and have a normal definition and you want that sort of site to develop the... Sorry, do you mean, like, going back from... backwards from a language to a grammar kind of thing, or... So, basically just some examples kind of thing, like some example grammars. Yeah. Once again, if you want to come and speak to me afterwards, I can give you some links to stuff like that. Firstly, build grammars per person like that. Okay. You can take, like, that south long stuff and, like, units, I don't know if there's a ruby thing like that that would be cool to work with. Oh, you mean, like, something that isn't like, necessarily a DSL or... Yeah, so you could just take, like, BNF formats or one of the standard grammar formats because if something could just take that and see put and spit out, you know, the effectively starting point for what you did. Okay, sorry, I think I misunderstood your question. Yeah, I personally don't know anything that does exactly that. The best, I mean, the best I could offer is sort of, you know, example, rack grammars or yack grammars or something like that. All right, so just winding up quickly, stuff I didn't cover, more complex grammars being the most obvious one. And there's a whole bunch of other stuff that kind of comes into compilers but isn't sort of core to, you know, implementing a very basic compiler if you're just sort of starting out or looking to experiment. So that's it. Once again, we're hiring. Come and work for us. Thank you very much.