Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
1–10 of 20 posts
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#2Bootstrapping proceeds as follows:
1. Implement a simple Forth interpreter (really) by handwriting ELF.
2. Grow it into a relatively expressive language through self-extension.
Since writing a program in machine language by hand is a tough work, the first interpreter is designed to be very simple. Every built-in word is single-letter and the interpreter just repeats that reads a character, looks it up from the dictionary and executes it. Also there is no error checking.
This is the actual code for the first interpreter, which is a 136-byte implementation of the interpreter followed by a built-in dictionary of 888 bytes.
https://github.com/nineties/planckforth/blob/main/planck.xxd
The first interpreter and language is so esoteric that, for example, the Hello World looks like this.
$ ./planck
kHtketkltkltkotk tkWtkotkrtkltkdtk!tk:k0-tQ
bootstrap.fs self-extends this. After running bootstrap.fs, the Hello World program can be written like this: $ ./planck
At the beginning of bootstrap.fs, you can only use one-letter words, so the code
is difficult to read as shown below. $ head bootstrap.fs
h@l@h@!h@C+h!k1k0-h@$k:k0-h@k1k0-+$h@C+h!ih@!h@C+h!kefh@!h@C+h!l!
h@l@h@!h@C+h!k1k0-h@$k h@k1k0-+$h@C+h!ih@!h@C+h!kefh@!h@C+h!l!
h@l@ h@!h@C+h! k1k0-h@$ k\h@k1k0-+$ h@C+h!
i h@!h@C+h!
kkf h@!h@C+h!
kLf h@!h@C+h!
k:k0- h@!h@C+h!
k=f h@!h@C+h!
kJf h@!h@C+h!
However, at the end, you can write a normal Forth program as follows.
In this way, the interpreter and the language are self-extended during the
execution of the script. $ tail bootstrap.fs
next-arg dup argv @ !
included
else
." Welcome to PlanckForth " version type
." [" runtime type ." ]" cr
copyright
." Type 'bye' to exit." cr
s" /dev/tty" included
then
; execute
Various functionalities like compile mode, immediate mode, control-flow structures, literals, variables, constants, file I/O, heap memory, etc. will be available after running bootstrap.fs.Also, the language and bootstrap.fs are designed to be as environment-independent as possible, so you can implement the runtime in other languages as well. As an example, I added an implementation of the runtime in C and Python 3.
There is no space for detailed explanation here. So please read the following source code and comments.
https://github.com/nineties/planckforth/blob/main/bootstrap....
Also, there is a commentary article in Japanese here. I hope you can read it with google translate. (I will write it in English if I have time. someday.)
https://qiita.com/9_ties/items/349b2ed65b7cd8a7d580
Feedbacks and pull requests such as runtime implementations in other languages are very welcome.
Thank you for reading.
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#3[1] https://bootstrapping.miraheze.org/ (also a great trove of resources if you are into this kind of things)
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#4Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#5Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#6It would make a great addition to the Bootstrapping Wiki [1]! [1] https://bootstrapping.miraheze.org/ (also a great trove of resources if you are into this kind of things)
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#7Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#8This is so over my head I couldn't grasp it with a jetpack.
It has a rather minimal set of commands in that dictionary to start with. The next thing you want to do is give it a program that adds new commands to the dictionary to make the language less annoying and more capable. This is in the bootstrap.fs file. The first two lines define a command called `\`, which reads and discards bytes from the program until it finds a newline byte. This allows the language to include comments. It also defines a command called ` ` which does nothing except allow the program to include spaces for formatting. :)
Commands are added by writing to memory to add new entries to the dictionary. The dictionary is a linked list, and the bootstrap program “allocates” memory by simply starting at the beginning of free memory and writing values consecutively. That’s good enough to extend the dictionary as long as it never goes backwards. I haven’t read the whole bootstrap program, but presumably it implements something more sophisticated than that. On the other hand, memory is cheap. You can do a lot even if you never deallocate anything.
Since the language is so simple, you can learn to decypher it pretty easily. You’ll pretty quickly start to notice elements that are frequently repeated, such as `h@!h@C+h!`, which stores the byte on the stack to the current address in memory, then moves the current address to point to the next location in memory. In C that would be `*ptr++ = foo`. These repeated bits are soon defined as new commands, so that the program gradually gets less repetitive.
My favorite trick is actually one he does to improve readability. The command to read input, `k`, reads a single byte as a signed integer. If you want to read in something like -5, you could include that as a single byte with the value 251 (or 0xFB). Instead, he writes it as `k0k5-`. This reads the byte `0`, which has value 48 (0x30), then the value 53 (0x35), and subtracts them. But _visually_, it looks almost exactly like `0-5` to the programmer which is indeed the same as negative 5. This is used in the `\` command to jump backwards by 5 instructions after every read that doesn’t find a newline, for example. On line 645 he finally gets around to defining a command that negates a number, on line 1153 he defines one to read in a number in either decimal or hexadecimal, and on line 1304 is the command that reads in a double–quoted string. After that he can start writing out numbers almost directly: `s" -5" >number` reads in a decimal -5, but now it can be trivially modified to contain any number you need.
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#9@nineties, I'm curious why you included things like 'mul', 'divmod', 'and', 'or' in the initial instruction set? These can all be composed, so would make your initial ELF binary a bit smaller.
Re: Show HN: PlanckForth – Bootstrapping an interpreter from handwritten 1kb binary
#10https://github.com/nineties/planckforth/blob/main/bootstrap....
It starts off looking like line noise (the very simple interpreter defined in hex) and gradually turns into the forth we know and love.
Fantastic!