Join the Compiler Creation Club
tech.pro
tech.pro
At the beginning it was very traditional c; symbol and AST manipulation were a PITA (at that point it was a malloc'd arrays of `expressions`). After I had a base language working I started to use the language I had implemented so far to further the implementation, this finally peaked where this weekend I did a large refactor to remove most of my c arrays and instead replace them with scheme pairs and lists.
For example, here [1] I implement define function form in terms of lambda, specifically new_lambda(env, cons(args, cons(body, null))).
In hindsight this seems so obvious, but I have found the whole process extremely interesting, specifically looking at how the implemented languages starts to influence the implementation language.
I really cannot stress enough how enjoyable the process of writing my interpreter has been, I thoroughly recommend it to anyone who is interesting in programming languages.
[1] https://github.com/mkfifo/plot/commit/07272bd69e51979ab71fa0...
Symbolic manipulation of data structures is plain joy, compared to what is required in C or Pascal family of languages.
This advice is especially true if you think syntax and parsing are the boring parts of writing a compiler.
I haven't considered if I will go down the compiler route, but either way that resource looks very interesting; especially the coverage on tail call optimisation at the assembly level.
Does anyone have any further resources discussing tail call optimisation? I would like something a bit more in depth.
The other topic I would like to read more on is continuations, as a still newbie-schemer I find the idea of implementing them to be quite daunting.
[1] https://github.com/mkfifo/plot [2] https://mitpress.mit.edu/sicp/
Dybvig himself, you might recognize him as the author of the Scheme specification.
Oddly following that link gives me a 404 ('The requested URL /~dyb/papers/3imp.pdf‎ was not found on this server.', seems there is a trailing character '%E2%80%8E').
I was able to find it on google and open it, yielding the link http://www.cs.indiana.edu/~dyb/papers/3imp.pdf
I like that way of thinking of it, I see it as just reusing the existing stack frame as a call in a tail call position doesn't need anything from the current stack (returning to this frame adds no extra meaning to it).
Having never implemented them though I feel like my ideas still need some fleshing out, my naive approach seems to be similar to the idea of trampolining.
Really looking forward to reading the linked document, very thorough and looks to be just what I was looking for (it even covers architecture dependent aspects!).
The basic idea is to compile every call in a tail position to a jump. You just need to clean up your stack before making the call, and make sure to arrange your calling convention such that A can call B, which can jump to C, which can return directly to A.
In the A -> B -> C example, this means:
1) Using a callee-pops convention so that callees pop arguments off the stack rather than callers. If B and C take different numbers of arguments, A doesn't know how many arguments to pop. So functions should pop their arguments off the stack themselves before they return.
2) B must restore its callee-save registers before jumping to C, which will save those registers itself if it clobbers them.
3) To accommodate variadic functions, there has to be a hidden argument to let callees know how many arguments were actually pushed by the caller, so they can pop the right number.
It's tiny and way more powerful than yet another C dialect.
For example, I have some books about writing Pascal compilers in 48K ZX Spectrum Basic.
Quite an interesting read about code optimization.
And depending on the style of compiler you're writing, it is not a given that you'll do much symbolic manipulation of data structures. E.g. a Wirth style compiler pretty much only maintains a very simple, light-weight symbol table.
(My first proper compiler started out in M68000 assembler, btw.; the amount of symbolic manipulation of data structures was more than small enough that this was not all that much of a challenge. Once the language started taking shape, one of the first things I added was support for inline assembly, and then I gradually started using the language constructs to rewrite the compiler in the language I wrote; implementation language really is not a huge deal)
I would not call that building a compiler.
Are there any modern tutorials/references of hand-generating the grammar, hand-coding the parser, and hand-coding whatever comes next (because I have no idea, thanks to these new-age tutorials) - without a toolchain, so that the entire process can be seen from start to finish?
If you want more depth, you probably want to pick up a compiler textbook. I liked "Modern Compiler Implementation" (I've used both the C and ML versions) in my undergrad.
What I really mean to say is that your parser doesn't have a lot of effect on the rest of your compiler design. In the end, it's a function that takes input text to an AST. You can go back and replace a YACC generated one later, if you want to know more.
Of course if you choose a simple to parse input language (like lisp) then you can write a simple handwritten parser off the bat.
https://www.coursera.org/course/automata https://www.coursera.org/course/compilers
Though, without reading Modern Compiler Implementation or something similar the use of shift-reduce tables in generated code might not make sense. Also, my computer science curriculum included a course where we wrote a top-down LL(1) parser which is organized very differently with a function call for parsing each rule which calls other functions for parsing sub-rules etc.
I've hand-written a few mangled char-by-char state machines as well, which served as lexers for various DSLs.
http://www.ethoberon.ethz.ch/WirthPubl/CBEAll.pdf
Of course, the best is to do both. :)
https://en.wikipedia.org/wiki/Shift-reduce_parsing
https://en.wikipedia.org/wiki/LALR_parser
If you want lots of detail, there is always this:
http://www.amazon.com/Compilers-Principles-Techniques-Tools-...
My series is here: http://www.hokstad.com/compiler/
(I just finished part 34 last night, and the next part goes up on my site in a week or so; I'll shorten my buffer quite a bit over the next two months, I think - I currently have a lead time of about 5 months... )
It starts out fairly generic in terms of code generation, but I then got the stupid or brilliant (you choose) idea of writing a Ruby compiler, which on one hand is very interesting, on the other hand is exceedingly frustrating, and which I think lost me some focus - I really should extract the simple/non-Ruby specific sections into a separate text and clean it up.
In terms of other peoples series, look up Crenshaws "Let's write a compiler" and Niklaus Wirths "Compiler Construction". Bother are available for free online. Frankly, if you google 'How to write a compiler' the results are actually very useful (both books show up on the first page for me).
My Google SoC project (ages ago) involved generating code to be ABI compatible with C++ virtual table layouts. This spec gave me nightmares: http://mentorembedded.github.io/cxx-abi/abi.html#vtable.
My favorite part is this sentence: "The rules for constructing virtual tables of the class are a combination of the rules from Categories 2 and 3, and can generally be determined inductively."
Ah yes, very comforting. The rules can be generally determined inductively (except of course, when they can't be!)
antlr "schemas" are extremely close to ebnf's (some exceptions obviously) and the compiler creation club could benefit from that. Antlr 3 is usable in a variaty of languages (although 4 is a hellavalot simpler and solves a fair bit around left recursive grammers, but non jvm support is non existent still I suspect)
However I'm having a bit of trouble right now with my grammar, I'm using Jison (http://jison.org) and the error messages are kind of confusing (I didn't even know they were error messages at first, I thought they were some kind of logging). I apparently have shift-reduce conflicts just about everywhere, but I didn't bother solving them as I was building the rest of the language since everything is working fine! (well I'm sure there are edge cases that I haven't ran into yet)
So if anyone here has a good link on the basics of parsing, grammars, LR(1) and whatnot, I'd like to understand what I'm doing ;)
It links to the original source. Why are we not giving the original author credit, instead of linking to blog spam on tech.pro?
For the lexer and parser, we used SableCC, an object oriented framework that generates a compiler in Java. I've never used anything else (yacc, lex, etc), so I can't compare the tool, but it provides a rich, useful and easy interface to use.
yacc and lex are handy to have around, but feel like Jurassic tools when compared to more modern parser generators tooling like ANTLR.
FYI: My generator allowed for dynamic resolution of shift/reduce conflicts which allows you to compile some languages yacc wont let you (languages that let you change the priority of operators for example)
I implemented a left recursive parser in x86 Assembly for MS-DOS systems.
As I said, yacc and lex are nice to have, but nowadays there is little incentive to keep using them.
Specially as you say, they are not able to parse all types of languages.
I actually like yacc/bison - they're good for most purposes and deliberately designing a non-LALR (or more a non-LR) language on purpose (rather than because you don't know any better) is usually silly - you do need to 'get' the concept of building a parse tree from bottom up - assembling it from larger and larger snippets as you go
Oh and yacc/bison run about 10 times faster than equivalent I wrote 10 years before they existed so I'm not complaining
I tend to use the same bespoke lexical analyser I've used for years and hack it to suit - it includes support for symbol tables/etc and runs really fast, no need to reinvent the wheel