Let’s Build a Simple Interpreter, Part 1
ruslanspivak.com
ruslanspivak.com
What nonsense. If you don't know how quantum mechanics works, you don't know how transistors work. If you don't know how transistors work, you don't know how computers work, etcetera.
But I get that you can understand a lot about computation without understanding compilers. Just as you can understand a lot about circuits without understanding quantum mechanics.
I agree with you, as yes there's a line somewhere to be drawn and I didn't exactly like the tone of the quote.
But there is some truth in the quote. Knowing how abstraction layers work, a couple of levels below the one you are working with, even if it is not something you would use in your day-to-day job, is extremely rewarding.
> Writing Go took me a little longer since I had to implement a lot of functionality provided for me in Ruby. However, for every task it also made me think about what the computer actually does.
Building a naive cost model for Ruby isn't so difficult if you know how compilers work. Believing that Go provides a model of what the computer actually does is rather naive. Both of these can be addressed by spending a bit of time studying compilers.
You also need to know all of ruby. In particular, if you do not know how flexible the language is, you may make the mistake of thinking that function call targets can be resolved at compilation time. With duct typing, that gets considerably harder.
Another example is that you may have to know the way integer overflow works. Wrap around, throw, or silently convert to bigint all have different performance characteristics (luckily that's an edge case for most programs)
Also, if you aren't naive, it gets really hard to build a naive cost model for Ruby. You start thinking "will they have complicated things by doing X (e.g. assuming that x.f is a function, and recompiling if that assumption gets invalidated) to get performance here?" all the time.
Your hypercorrection from duct tape is bleeding into your duck typing. So named because "if it looks like a duck, it must be a duck."
http://www.zeusedit.com/tools.html
Is it any good? Not really. There are many far better interpreters out there to choose from.
But it was my first and last ever interpreter, written some 20+ years ago, written when I was fresh out of university.
Was it hard to write?
I did not think so and I only had an engineering degree ;)
Now lets try to write a compiler. Suddenly things get far more difficult!!!
The hard part is making the generated code perform well, which requires much more work.
It was a basic exercise for our CS compiler classes in 1998, written in the newly introduced Java language and generates bytecode and native.
The code is actually pretty basic, and I did some improvements (Makefile to Maven, Java 1.0 to 7, replaced C runtime by pure Asm) in the last couple of years just for fun.
If anything, it taught me a very interesting trick for writing compilers as if they were interpreters.
Create bytecodes as intermediate language that can be easily mapped into Assembly macros. Then instead of interpreting, just make them go through the macro assembler.
It won't generate the fastest code in the world, the register allocator will be pretty stupid, but hey it compiles straight to native.
Of course, this makes the implementation simple while arguably pushing some of the work onto the programmer using the language - but RPN languages can be rather fun in a Lisp kind of way.
- read a line
- do a switch on the first word of the line
- inside each switch, make up your own syntax, parse the
remainder of the line, and execute it or issue an error
message and abort.
Feel free to cheat by using scanf to parse integers.
Iteration 2 can add printing the line number in error messages.Iteration 3 can add if/then and looping: keep the lines in an array, and add 'goto' to the list of commands, and hack the jump into the interpreter loop by adjusting a global variable from inside the 'goto' statement.
Iteration 4 can add the 'let' command and variables. Naming them A through Z for simplicity is an option.
Then, add simple assignments of the form
let a = x <op> y
where x and y are liberals or variables. For now, don't bother with expressions with multiple operators.Next, add statements of the form
y = f(x)
That gives you a sort-of 70's Basic that can be quite useful and fun to use.Only then would I start introducing grammars, the word 'parse', etc.
If you start with a grammar, you lose half your audience within 5 minutes (numbers made up)
If you start with a program that reads a text file and plays notes for lines contaning do, re, and mi, part of that lost audience will get drawn in, complete the scale, add 'fart' commands, etc.
Also, smart kids may figure out they can easily add single-statement loops on their own, by adding a case 'repeat' that scans for the number of iterations, removes the 'repeat' and that number of iterations from the line, and then calls the 'processALine' code.
It's "learn by playing" vs "learn through study".
For initialized arrays with unknown size, a first pass is done to count the number of elements.
For architectures where arguments are evaluated in reverse order, a first pass is done to reverse the argument order.
From: http://bellard.org/tcc/tcc-doc.htmlThis great code to read. I need to get back to playing with it again.
ps: it's also still under development http://lists.nongnu.org/archive/html/tinycc-devel/ .. impressive.
----
"tcc still compiles around 10x faster but runs x3.5 times slower." from tcc homepage 3rd benchmark URL http://lists.nongnu.org/archive/html/tinycc-devel/2013-02/ms...
tcc can actually be made to build Windows executables in the 'old-school' "Programming Windows" way, which is quite fun.
http://lafo.ssw.uni-linz.ac.at/papers/2012_DLS_SelfOptimizin...
Its a natural extension to derive objects optimized for common cases e.g. integer increment vs generic add expression. The bytecode idea adds complexity and doesn't actually speed up unless you add JIT conversion to native, which makes it a compiler again.
The virtual method penalty is way overstated. Its still 10X faster than an interpreter. And 10X slower than a compiler. A decent compromise, and easily written in a weekend for any simple language.
Besides, not all bytecode interpreters are made equal: http://vvm.lip6.fr/publications/9806PLDI.ps.gz.
Go ahead and implement a bytecode machine, but you lose two ways: the time to write that extra engine, and the loss of semantics. In the interplet (ok, AST) interpreter, you can add debugging features that 'understand' the intent e.g. still know a loop is a loop. That's cool and powerful.
In every language implementation I've worked with, bytecode led to a large improvement in performance.
> The virtual method penalty is way overstated.
This makes me think your data is outdated. It's more about the loss of locality today than it is virtual method dispatch. With a tree-walk interpreter, your "code" is spread all throughout memory and executing involves jumping all over the address space. That kills your cache.
> Its still 10X faster than an interpreter. And 10X slower than a compiler.
Those numbers don't mean anything without context. What are the semantics of the language? What's the object representation? The memory model? How are local variables managed?
> easily written in a weekend for any simple language.
Call me crazy, but I don't think a bytecode compiler takes any more time than that either.
JIT compilers are indeed fast, but AST interpreters are usually slower than bytecode interpreters.
This way you can save on parsing the same structure multiple times, and it makes it simpler to implement certain optimizations like compile-time lexical binding.
it's in javascript and does a good job at teaching writing interpreters withtout regex tricks. There is even a compiler tutorial at the end.
Another neat thing about this approach is that its super easy to add metadata to the syntax tree (for example, line numbers for error messages).
For example, JJTree on JavaCC.
I am talking about prototyping, not going with a state of art implementation.