ed89e0bde8
FossilOrigin-Name: 0b0cd20ed69027a7d9b5f2d905d7b9e033e2976208385652d888da35432a4482
331 lines
9.9 KiB
Markdown
331 lines
9.9 KiB
Markdown
========================
|
|
An Introduction to RETRO
|
|
========================
|
|
|
|
---------------
|
|
Getting Started
|
|
---------------
|
|
|
|
Building the VM
|
|
===============
|
|
RETRO runs on a virtual machine. This has been implemented in many
|
|
languages, and allows easy portability to most platforms. The primary
|
|
implementation is in C.
|
|
|
|
Building it on FreeBSD, Linux or macOS is just a matter of running:
|
|
|
|
make
|
|
|
|
It'll build with the standard C compilers (tested with gcc and clang),
|
|
and requires a standard `make` (tested with BSD and GNU variants).
|
|
|
|
When done you will have a few files of interest:
|
|
|
|
ngaImage Contains the RETRO system as a memory image
|
|
|
|
bin/repl Interactive interface, requires ngaImage in
|
|
the current directory
|
|
|
|
bin/rre Batch/Scripting interface; this is self-
|
|
contained and embeds the ngaImage into itself
|
|
during the build process
|
|
|
|
I symlink the `bin/rre` to a location in my $PATH so I can refer to
|
|
it from anywhere in my environment.
|
|
|
|
|
|
Basic Interactions with the REPL
|
|
================================
|
|
When you start RETRO via `bin/repl`, you should see something like
|
|
the following:
|
|
|
|
RETRO 12 (rx-2017.11)
|
|
8388608 MAX, TIB @ 1025, Heap @ 9374
|
|
|
|
At this point you are at the *listener*, which reads and processes
|
|
your input. You are now set to begin exploring RETRO.
|
|
|
|
To exit, run `bye`:
|
|
|
|
bye
|
|
|
|
Basic Interactions with RRE
|
|
===========================
|
|
`rre` (short for *run retro and exit*) is my preferred interface. It
|
|
takes a filename and runs the code in the file. E.g.,
|
|
|
|
rre example/99Bottles.forth
|
|
|
|
Source files for `rre` are written in a literate format, with code
|
|
stored in Markdown-style fenced blocks. E.g.,
|
|
|
|
Define a word that returns the cube of a number.
|
|
|
|
~~~
|
|
:n:cube (n-n) dup dup * * ;
|
|
~~~
|
|
|
|
`rre` adds significant features to the base language, including
|
|
support for keyboard input, file i/o, fetching resources via
|
|
gopher, and floating point support.
|
|
|
|
----------------------
|
|
Exploring the Language
|
|
----------------------
|
|
|
|
Names And Numbers
|
|
=================
|
|
At a fundamental level, the RETRO language consists of whitespace
|
|
delimited tokens.
|
|
|
|
The interpreter takes a look at the first character of the token. If
|
|
this matches a known *prefix*, the rest of the token is passed to a
|
|
function which handles that group of tokens. If no valid prefix is
|
|
found, RETRO tries to find the word in the dictionary. If successful
|
|
the information in the *dictionary header* is used to carry out the
|
|
actions specified in the name's *definition*. If this also fails,
|
|
RETRO calls `err:notfound` to report the error.
|
|
|
|
A flowchart of the interpreter process:
|
|
|
|
+-------------+ +------------------------------------+
|
|
| Get a Token | ==> | Is first character a valid prefix? |
|
|
+-------------+ +------------------------------------+
|
|
| |
|
|
YES NO
|
|
| |
|
|
+-----------------------+ +---------------------------+
|
|
| Pass rest of token to | | Is entire token a name in |
|
|
| prefix handler | | the dictionary? |
|
|
+-----------------------+ +---------------------------+
|
|
| |
|
|
YES NO
|
|
| |
|
|
+------------------+ +-------------------+
|
|
| Push XT to stack | | Call err:notfound |
|
|
+------------------+ +-------------------+
|
|
|
|
|
+--------------------+
|
|
| Call class handler |
|
|
+--------------------+
|
|
|
|
In RETRO, the *prefix handlers* and *class handlers* are responsible
|
|
for dealing with the tokens and words. This includes both interpret
|
|
and compilation as necessary.
|
|
|
|
RETRO permits names to contain any characters other than space, tab,
|
|
cr, dnd lf. Names are *case sensitive*, so the following are three
|
|
*different* names from RETRO's perspective:
|
|
|
|
foo Foo FOO
|
|
|
|
Note that a name should not start with a *prefix* as prefixes are
|
|
checked prior to dictionary lookups.
|
|
|
|
The Compiler
|
|
============
|
|
To create new functions, you use the compiler. This is generally
|
|
started by using the `:` (pronounced *colon*) prefix. A simple
|
|
example:
|
|
|
|
:foo #1 #2 + n:put ;
|
|
|
|
Breaking this apart:
|
|
|
|
:foo
|
|
|
|
RETRO sees that `:` is a prefix and calls the handler for it. The
|
|
handler creates a new word named *foo* and starts the compiler.
|
|
|
|
#1
|
|
|
|
RETRO sees that `#` is a prefix and calls the handler for it. The
|
|
handler converts the token to a number and then pushes the value to
|
|
the stack. Since the `Compiler` is now active, it then pops the
|
|
value off the stack and compiles it into the current definition.
|
|
|
|
#2
|
|
|
|
And again, but compile a *2* instead of a *1*.
|
|
|
|
|
|
+
|
|
|
|
RETRO does not find a `+` prefix, so it searches the dictionary.
|
|
Upon finding `+`, it pushes the address (*xt*) to the stack and
|
|
calls the corresponding class handler. The class handler for
|
|
normal words calls the code at the address if interpreting, or
|
|
compiles a call to it if the `Compiler` is active.
|
|
|
|
n:put
|
|
|
|
The process is repeated for `n:put`.
|
|
|
|
;
|
|
|
|
The last word has a slight difference. Like `+` and `n:put`, this
|
|
is a word, not a prefixed token. But the class handler for this
|
|
one always calls the associated code. In this case, `;` is the
|
|
word which ends a definition and turns off the `Compiler`.
|
|
|
|
Hyperstatic Global Environment
|
|
==============================
|
|
This now brings up an interesting subpoint. RETRO provides what is
|
|
sometimes called a *hyper-static global environment.* This can be
|
|
difficult to explain, so let's take a quick look at how it works:
|
|
|
|
:scale (x-y) A fetch * ;
|
|
>>> A ?
|
|
#1000 'A var<n>
|
|
:scale (x-y) A fetch * ;
|
|
#3 scale n:put
|
|
>>> 3000
|
|
#100 A store
|
|
#3 scale n:put
|
|
>>> 300
|
|
#5 'A var<n>
|
|
#3 scale n:put
|
|
>>> 300
|
|
A fetch n:put
|
|
>>> 5
|
|
|
|
Output is marked with **>>>**.
|
|
|
|
Note that we create two variables with the same name (*A*). The
|
|
definition for *scale* still refers to the old variable, even
|
|
though we can no longer directly manipulate it.
|
|
|
|
In a hyper-static global environment, functions continue to refer to
|
|
the variables and functions that existed when they were defined. If
|
|
you create a new variable or function with the same name as an existing
|
|
one, it only affects future code.
|
|
|
|
Take advantage of this to reuse short names whenever it helps to
|
|
make your code easier to understand.
|
|
|
|
Classes
|
|
=======
|
|
Getting back to function creation, it's time for a clarification: in
|
|
RETRO, the interpreter is unaware of how to handle a dictionary entry
|
|
and has no concept of the difference between compiling and interpreting.
|
|
|
|
The actual work is handled by something we call *class handlers*.
|
|
|
|
Each dictionary header contains a variety of information:
|
|
|
|
+--------+------------------+
|
|
| Offset | Description |
|
|
+========+==================+
|
|
| 0 | link to previous |
|
|
+--------+------------------+
|
|
| 1 | class handler |
|
|
+--------+------------------+
|
|
| 2 | xt |
|
|
+--------+------------------+
|
|
| 3+ | name of function |
|
|
+--------+------------------+
|
|
|
|
When a token is found, the listener pushes the contents of the xt field
|
|
to the stack and then calls the function that the class handler field
|
|
points to.
|
|
|
|
This model differs from Forth (and most other languages) in that the
|
|
interpreter is unaware of how tokens are handled. All actions are
|
|
performed by the class handlers, which allows for easy addition of new
|
|
categories of words and functionality.
|
|
|
|
|
|
Data Structures
|
|
===============
|
|
You can create named data structures using `d:create`, `var`, `var<n>`,
|
|
and `const`.
|
|
|
|
|
|
Constants
|
|
---------
|
|
These are the simplest data structure. The *xt* is set to a value, which
|
|
is either left on the stack or compiled into a definition.
|
|
|
|
#100 'ONE-HUNDRED const
|
|
|
|
By convention, constants in RETRO should have names in all uppercase.
|
|
|
|
|
|
Variables
|
|
---------
|
|
A variable is a named pointer to a memory location holding a value that
|
|
may change over time. RETRO provides two ways to create a variable:
|
|
|
|
'A var
|
|
|
|
The first, using `var`, creates a name and allocates one cell for storage.
|
|
The memory is initialized to zero.
|
|
|
|
#10 'B var<n>
|
|
|
|
The second, `var<n>`, takes a value from the stack, and creates a name,
|
|
allocates one cell for storage, and then initializes it to the value
|
|
specified.
|
|
|
|
This is cleaner than doing:
|
|
|
|
'A var
|
|
#10 &A store
|
|
|
|
|
|
Custom Structures
|
|
-----------------
|
|
You can also create custom data structures by creating a name, and
|
|
allocating space yourself. For instance:
|
|
|
|
'Test d:creat
|
|
#10 , #20 , #30 ,
|
|
|
|
This would create a data structure named `Test`, with three values,
|
|
initialized to 10, 20, and 30. The values would be stored in
|
|
consecutive memory locations.
|
|
|
|
If you want to allocate a buffer, you could use `allot` here:
|
|
|
|
'Buffer d:create
|
|
#2048 allot
|
|
|
|
The use of `allot` reserves space, but does not initialize the space.
|
|
|
|
|
|
Strings
|
|
-------
|
|
In addition to the basic data structures above, RETRO also provides
|
|
support for string data.
|
|
|
|
Creating a string simply requires using the `'` prefix:
|
|
|
|
'this_is_a_string
|
|
'__this_string_has_leading_and_trailing_spaces__
|
|
|
|
When creating strings, RETRO uses a floating, rotating buffer for
|
|
temporary strings. Strings created in a definition are considered
|
|
permanent.
|
|
|
|
Note the use of underscores in place of spaces. Since strings are
|
|
handled by a prefix handler, the token can not contain whitespace
|
|
characters. RETRO will convert the underscores into spaces by
|
|
default.
|
|
|
|
You can obtain the length of a string using `s:length`:
|
|
|
|
'this_is_a_string s:length
|
|
|
|
|
|
Comparisons
|
|
-----------
|
|
Strings can be compared using `s:eq?`:
|
|
|
|
'test_1 'test_2 s:eq? n:put
|
|
>>> 0
|
|
'test_3 'test_3 s:eq? n:put
|
|
>>> -1
|
|
|
|
The comparisons are case sensitive.
|
|
|
|
============= To Be Continued ...
|