Symbolic manipulation of data structures is plain joy, compared to what is required in C or Pascal family of languages.
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.
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)
For example, I have some books about writing Pascal compilers in 48K ZX Spectrum Basic.
Quite an interesting read about code optimization.