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".
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.
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.
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.