retro from the top down
Table of Contents
- 1. part 1 : overview
- 2. part 2 : recursive descent
- 2.1.
listen
>: ("colon")
the word that makes new words - 2.2. TODO
:
>t:
the compiler's compiler - 2.3. TODO
t:
>label:
- 2.4. TODO
t:
>&m,
- 2.5. TODO
t:
>reclass
- 2.6. TODO
listen
>( ("paren")
the word that doesn't care what you say - 2.7. TODO
:
>create
- 2.8. TODO
:
> =' the quote - 2.9. TODO
listen
>.word
- 2.10. TODO
listen
>#
proto literals - 2.11. TODO
listen
>last
atlast
, you find the words - 2.12. TODO
listen
>@,=
assemble and follow me - 2.13. TODO
listen
>d->class
pointer arithmetic - 2.14. TODO
listen
> =!, take that, NOS! - 2.15. TODO
listen
>]]
- 2.16. TODO
listen
>vector?
vector?
I hardly know her. - 2.17. TODO
listen
>;
STOP THE PRESS! - 2.18.
listen
> ( paren ) # should have been done by this point - 2.19. TODO
listen
>repeat
the word that's just getting started - 2.20. TODO
listen
>ok
the word that lets you know everything is ok - 2.21. TODO
listen
>32
the word that wasn't here - 2.22. TODO
listen
>accept
the word that wants you to show some character - 2.23. TODO
listen
>tib
the word that holds whatlisten
heard - 2.24. TODO
listen
>find
the word that reads the dictionary - 2.25. TODO
listen
>process
the hidden word that makes the whole thing work - 2.26. TODO
listen
>again
the word that picks up afterrepeat
- 2.1.
- 3. TODO scraps (put these in the right place as we go along)
1 part 1 : overview
1.1 quick intro to retro
Retro is a concatenative programming language, related to forth, factor, and joy. Retro runs on a portable virtual machine called ngaro, and is self hosting, meaning it is written in itself.
The retro documentation includes a file, docs/Commentary.txt
, which describes the process of building a new image from the bottom up. The actual code is in the following files:
image/meta.rx |
the assembler and metacompiler |
image/meta-alt.rx |
same thing, but from vm primitives |
image/kernel.rx |
the core retro commands |
All code in the present file is taken from these sources, as they existed in October of 2012.
This is a top-down view of the bootstrap process, which means that the code will not actually compile in the order presented here. Rather, the point here is to see how all the parts fit together into a unified whole.
We will start at the "top" of the retro user interface, with the listener (the "ok" prompt), briefly discuss each word we encounter, then perform a recursive descent into its definition. The words will be introduced in breadth first order, but the definitions will be presented depth-first, so that we are implementing each word "just in time" for execution in the level above.
1.2 the listener
1.2.1 listen
the word that does what you ask it to
t: listen ( - ) repeat ok 32 # accept tib find process again ;
This is the main loop of the retro interpreter, taken from line 246 of kernel.rx
, just under halfway through the file. After this point, the code generates a fresh dictionary, copies the newly-generated image over the original, and performs a virtual reboot of the system.
Once we understand how the listener works, we can simply follow the Commentary.txt
file from the heading "Stage 3: Extend the Language" on line 1000. Therefore, we will only concern ourselves here with this the first two phases.
1.2.2 What do all those words mean?
First, note that word t:
is simply the standard word :
("colon"), which begins a definition, except that the definition gets stored in the target image. Since t:
is the first word of a definition, it should be the first sub-node that we visit, but we will treat it as :
and visit that instead.
So. What do the other words mean? The following definitions are taken from doc/Core_Functions.rst
, except for # and process, which are invisible after the reboot, and not part of standard retro.
: | "- | Calls create , changes class to .word , and turns compiler on |
---|---|---|
listen | - | Top level interpreter. Reads and process input |
( | "- | Parse for ) and ignore everything it reads |
repeat | - | Start an unconditional loop |
ok | - | Displays the "ok" prompt |
32 | -n | not found, so parsed as integer and pushed to data stack |
# | n- | meta.rx: compile a literal (unnecssary after stage 3) |
accept | c- | Read a string, ending with the specified character. The string is returned in tib |
tib | -a | Returns address of text input buffer |
find | $-af | Search for a name in the dictionary. Returns a dictionary header and a flag |
process | af- | kernel.rx: run the word's class handler if found, otherwise parse as number |
again | - | Jump to the code following the most recent repeat |
; | - | Compile an exit into a function and stop the compiler |
We will not show the lines for :
, ;
, or (...)
in other definitions, but will repeat the internal definitions.
1.2.3 Understanding the stack comment.
The second column shows the stack comment for each word. The following symbols are used:
" | indicates that the word reads input from the user or script |
---|---|
- | separates the input (on the left) from the output (on the right) |
# | beats me. :( |
n | a number |
c | a character |
a | an address |
$ | a string |
f | a flag (boolean) |
others | it's just a comment, so sometimes ad-hoc names are used |
The topmost item is always on the right, so for example ( ca-nf )
means "take an address, then a character off the stack, give back a number and then a flag". (Flags are almost always returned on top, so they can be easily tested with if
.)
- TODO what does # mean ? ( number? counted string? )
1.2.4 Summary of listen
Let's take another look at listen
then, and put it in our own words:
t: listen ( - ) repeat ok 32 # accept tib find process again ;
The listener is an infinite loop ( repeat
.. again
) that shows a prompt ( ok
), reads characters from input until encountering a space ( 32 # accept
, where 32 is the ASCII code for the space character ), looks the word up in the dictionary ( tib find
), and then performs various possible actions, depending on whether the word was found or not, and if so, what class handler is associated with it ( process
).
We will now explore each of these words in detail.
1.3 stepwise refinement and recursive descent
Having investigated the word listen
and briefly described its component words, we are now going to give each of those words the same treatment in turn.
We will proceed depth first and left to right. Note that it's possible that a word is used in multiple places, and we may therefore need to define it earlier than the order in which we expected.
2 part 2 : recursive descent
2.1 listen
> : ("colon")
the word that makes new words
t: : ( "- ) create ' .word # last # @, d->class !, ]] vector? ;
2.1.1 TODO summary
2.1.2 breakdown
: | "- | Calls create , changes class to .word , and turns compiler on |
---|---|---|
t: | "- | meta.rx: compile to target image ( bootstrap version of : ) |
create | "- | Parse for a name and call header |
' | n- | Place TOS here and increment heap by 1 |
.word | a- | Class for normal functions |
# | ||
last | -a | Variable; pointer to most recent dictionary header |
@, | - | meta.rx: assembler for @ (which fetches a value from the address at TOS) |
d->class | a-a | Given a dictionary header, return the address of the class handler. Use @ to get the class handler. |
!, | - | meta.rx: assembler for ! (which stores a value to the address at TOS) |
]] | - | Turn compiler on |
vector? | - | kernel.rx: either revectors the word immediately or writes two NOPs for revectoring later |
2.2 TODO :
> t:
the compiler's compiler
: t: ( "- ) label: nop, nop, &m, reclass ;
2.3 TODO t:
> label:
2.4 TODO t:
> &m,
2.5 TODO t:
> reclass
2.6 TODO listen
> ( ("paren")
the word that doesn't care what you say
2.7 TODO :
> create
2.8 TODO :
> =' the quote
2.9 TODO listen
> .word
2.10 TODO listen
> #
proto literals
2.11 TODO listen
> last
at last
, you find the words
2.12 TODO listen
> @,=
assemble and follow me
2.13 TODO listen
> d->class
pointer arithmetic
2.14 TODO listen
> =!, take that, NOS!
2.15 TODO listen
> ]]
2.16 TODO listen
> vector?
vector?
I hardly know her.
2.17 TODO listen
> ;
STOP THE PRESS!
2.18 listen
> ( paren ) # should have been done by this point
2.19 TODO listen
> repeat
the word that's just getting started
2.20 TODO listen
> ok
the word that lets you know everything is ok
t: ok ( - ) compiler # @, not 0; drop, cr okmsg # puts ;
2.21 TODO listen
> 32
the word that wasn't here
2.22 TODO listen
> accept
the word that wants you to show some character
2.23 TODO listen
> tib
the word that holds what listen
heard
2.24 TODO listen
> find
the word that reads the dictionary
2.25 TODO listen
> process
the hidden word that makes the whole thing work
i: process ( af- ) 0 # !if xt:class jump: withClass then drop jump: number
2.26 TODO listen
> again
the word that picks up after repeat
3 TODO scraps (put these in the right place as we go along)
i: number ( - ) tib isNumber? 0 # !if jump: build# then jump: notFound i: build# ( - ) tib toNumber ' .data # jump: withClass