RetroForth : User Guide Welcome to Retro, a modern, pragmatic Forth. Retro is Forth. It is untyped, using a stack to pass data between functions called words, and a dictionary which tracks the word names and data structures. But it's not a traditional Forth. Retro draws influences from many sources and takes a unique approach to the language. Retro has a large vocabulary of words. Keeping a copy of this manual and the dictionary on hand is highly recommended as you learn to use Retro. Welcome The system described here is RetroForth/ilo, running on the ilo virtual computer. There are related implementations available for the napia and nga systems as well. Those systems have separate documentation. This guide will refer to the system as Retro or RetroForth throughout the text. Overview of RetroForth/ilo A RetroForth system consists of three parts: a virtual machine (named "ilo"), an image file (named "ilo.rom"), and a set of data blocks (named "ilo.blocks"). The virtual machine is designed to be small, portable, and easy to implement. It's the only part that's specific to your system. All ilo implementations share the same image and blocks. Obtaining RetroForth Snapshots You can obtain the latest version of RetroForth from ilo.retroforth.org via the Gemini or HTTP(s) protocols. * gemini://ilo.retroforth.org * https://ilo.retroforth.org Daily snapshots are provided. These include ilo, the image, a set of data blocks, and documentation. Source Repositories The snapshots do not include the full source tree. To obtain this you will need to use either the Fossil or Git version control systems. Via Fossil: fossil clone http://fossils.retroforth.org:8000/ilo \ retro-ilo.fossil mkdir retro-ilo; cd retro-ilo fossil open ../retro-ilo.fossil Via Git: git clone https://git.sr.ht/~crc_/retro-ilo Building The System (C) Requirements: * C89 (or newer) compiler Process: cc ilo/ilo.c -o ilo/ilo Building The System (C) Requirements: * C89 (or newer) compiler Process: cc ilo.c -o ilo Building The System (C++) Requirements: * C++ compiler Process: c++ vm/ilo.cpp -o ilo Building The System (C#) Requirements: * C# compiler (mcs) Process: mcs vm/ilo.cs -out:ilo.exe Building The System (Go) Requirements: * Go Process: go build vm/ilo.cs Building The System (Kotlin) Requirements: * Kotlin compiler Process: kotlinc vm/ilo.kt -include-runtme -d ilo.jar Building The System (Nim) Requirements: * Nim Process: nim c vm/ilo.nim -o:ilo Building The System (Rust) Requirements: * Rust compiler Process: rustc vm/ilo.rs -o ilo Building The System (Swift) Requirements: * Swift compiler Process: swiftc vm/ilo.swift -o ilo Running RetroForth/ilo Make sure you have an ilo vm (build the C or Nim ones, or use ilo.py or ilo.lua), ilo.rom, and ilo.blocks in your working directory, then run the ilo vm. The Listener The listener is Retro's interaction loop. It reads and evaluates your input in a continual loop. By itself, the listener is not feature rich. It does not provide any history or editing (apart from backspace). If you are using a Unix host, I recommend running Retro under rlwrap. An example (ri.sh) is in the source repository; this shows how to use the rlwrap tool to add line editing, history, and tab completion. Basic Interactions At the listener you can enter code, and Retro will run what you type. The code consists of tokens, which are sequences of characters, seperated by whitespace. The listener reads and processes tokens as they are entered. On most Unix hosts, the system will buffer input until ENTER is pressed, but this should not be assumed to be the case. If not using rlwrap, on a Unix host, you can run `stty cbreak` before starting the ilo binary to disable the input buffering. As an example interaction, enter the following and press ENTER (or SPACE if using character breaking) to exit Retro: bye An Overview of Blocks & The Block Editor Retro uses blocks to store code and data. These are fixed size buffers of 1,024 values. For the purposes of code and text, they are displayed as 16 rows, with 64 columns per row. A minimal block editor is included. (If you are reading this in Retro, then you are probably using the block editor to do so). This is not a complex program, and learning to use it will help you make Retro into a productive system. What Are Blocks In RetroForth, blocks are 1,024 values (typically characters) that are used for long-term storage. The ilo computer provides a block storage device, and Retro has words to read blocks into memory and write them back to the storage device. I like to think of blocks as being similar to index cards. You can write, draw, etc onto a card, and then file them away until needed. Format of Code & Text Blocks While technically freeform, I like to follow a simple structure for my blocks. The first line gets a description of the block contents. For code blocks, this starts with a comment with the block group, followed by comments for the description. I leave the line after the description blank. This is not necessary, but I find it to aid in readability a bit. The rest of the block is filled with the source code or text. I recommend having each block focus on a particular word (or small group of related words). Format of Binary Blocks You can store any data in a block. I still recommend designing the data format to reserve the first line for a human readable description. Not doing so may cause problems with some of the supplied tools (e.g., `titles` or `needs`, which I will cover a little later) E.g., for a binary format of drawing data I have the first row contain a type ID, title for the drawing, and other metadata. This is repeated for each block needed. Commenting Code Blocks Writing comments on your code is a good idea. There are a few strategies you may wish to consider. You can write inline comments. I do this for stack comments in words, and to occasionally explain what I'm doing. For general comments, I like to have some blocks prior to the code that explain anything relevant. For word-specific comments, a traditional approach is to use shadow blocks. This would involve alternating between blocks with code and blocks with descriptions of the code on the previous block. Interacting With The Editor You begin by telling Retro that you want to edit a block. This is done by providing a block number followed by `edit`. For example, to edit block 689: #689 edit Retro will show the block contents, along with the line numbers, rulers to aid in determining columns, and some status info. Run `list` to redisplay the contents of the current block. To go to the next block, run `next`. And to go to the previous block, run `prev`. Interacting With The Editor (cont.) Entering text into the block is easy. Type the line number and then the new contents of the line. For instance, to replace line 9 with "Hello" (sans quotes): 9 Hello Run `list` to see the changes. When you are happy with the edits, run `save` to write the block to storage. If you need to revert to the currently saved copy, run `load`. Running Code In A Block You can run the code in the current block with `run`. You should `save` first. If you know the number of a block you wish to load and run, you can enter the block number followed by `use` to do this. Block Groups Often you will need more than one block for your programs. Retro provides a means of loading multiple blocks via a block group. For this, the first row of a block must contain the group name as a comment, separated by whitespace from any other text. To load these, pass a string with the group name comment to `needs`. E.g., to load the standard library, you can do: '(std) needs All blocks matching the group name will be located and run. They will run in the physical block order, so ensure that the blocks are laid out correctly. Finding Blocks Retro does not have many facilities for locating blocks. The block editor includes a single word, `titles`, for displaying a list of the blocks and their block numbers. You can run this and then read through the output to find the ones you need. The RetroForth Language Retro is a Forth, but it's not a conventional one. The major differences arise from the use of sigils and quotes. All code consists of a series of whitespace separated tokens. Retro will check the first character of the token to see if it matches a known sigil. If it does, the token (sans sigil) is passed to the sigil handler. If no sigil was found, Retro tries to find the word in the dictionary. If successful, it will either call or compile a call to the word. If both of the above fail, an error is diplayed. Sigils & The Interpreter If you are familiar with traditional Forth, you likely notice that no number handling is done. In Retro, these are handled by a sigil. The sigil system is a central part of Retro's design. If you are familiar with ColorForth, it might make sense to consider sigils to be colors, but done via characters instead of needing special editor support. Let's take a look at how they work. Sigils & The Interpreter (cont.) Retro maintains a table of sigil handlers. Any character can be a sigil. If the interpreter matches the first character in a token to a sigil, the sigil is removed and the token is passed on the stack to the sigil handler for processing. This is used in lieu of parsing words. For instance, a : sigil starts a new colon definition. A # sigil processes the token as a number. An & sigil is used to get a pointer. The ( sigil begins a comment. The next block has a table of the sigils provided by Retro. The Sigils +-------+---------------------------------------------------+ | Sigil | Use | +=======+===================================================+ | : | Begin a colon definition | | # | Process a number | | ( | Begin a comment (ends at the first whitespace) | | & | Return a pointer to a named item | | ' | Return a pointer to a string | | $ | Return the ASCII code of the first character | | @ | Fetch a stored value from a variable | | ! | Store into a variable | | \ | Create an alias to a named item | +-------+---------------------------------------------------+ Numbers In RetroForth, numbers are provided by prefixing a token with the # sigil. For negative values, add a - after the # sigil. Numbers are only supported in decimal (base 10), and any comma or periods in a number are ignored. Examples: #100 #-50.25 #1,215 Arrays : Overview Arrays are sequences of values. They are one of the central data types in RetroForth. In memory, an array consists of a count, followed by the values. For instance, an array with 67, 102, and 33 can be constructed like this: (pointer) (count) (first_value) (second_value) (last_value) here #3 comma #67 comma #102 comma #33 comma Words operating on arrays are prefixed by `a:`. Important: the size of an array is fixed at time of creation. You can not resize an array. Arrays : Creation You can create an array by passing the count and values on the stack to `a:make`. For the previous example of an array of 67, 102, and 33, this would look like: #33 #102 #67 #3 a:make This is limited by the amount of space available on the data stack. For longer arrays, create them using the manual model on the previous block or write a custom creating word. For arrays not needed long term you can use of `a:make/temp` instead. This will place the array in a rotating buffer instead of the main system memory. Arrays : Accessing Elements To read a specific value, pass a pointer to the array and the offset to `a:fetch`. &my-array #2 a:fetch To store a value into an array, pass the value, a pointer to the array and the offset to `a:store`. #76 &my-array #2 a:store Arrays : Iteration Retro provides `a:for-each` to run a function against each value in an array. For instance, to display the numeric value of each item: &my-array [ (n-) n:put sp ] a:for-each Arrays : Merging To combine two arrays into a new array, Retro provides both `a:append` and `a:prepend`. Each take two pointers to existing arrays and construct a new one. &a &b a:append (makes_new_array_from_a,_followed_by_b) &a &b a:prepend (makes_new_array_from_b,_followed_by_a) Arrays : Comparisons, Searching You can compare two arrays for equality with `a:eq?` or check for inequality with `a:-eq?`. &a &b a:eq? To see if an array contains a value, use `a:contains?`. &a #613 a:contains? Finding the indices of values in an array is done with `a:indices`. (And the first occurance can be found with `a:index`). `a:indices` returns a new array of the indexes. &a #33 a:indices Arrays : Subsets You can extract subsets of an array. Retro provides `a:left` for returning values from the beginning of the array, `a:right` to get values from the end of the array, and `a:middle` for values in the middle of the array. Variables: Overview & Creation Variables are simple containers for values. Each variable can hold a single value (number, pointer, etc). To create a variable, use `var` or `var-n`. When using `var`, the variable will have a default value set to zero. With `var-n`, the initial value is taken from the stack. Example: 'Flag var #600 'Sum var-n Variables: Accessing & Updating You can obtain a stored value by getting a pointer to a variable and using `fetch`. Or, for a more compact approach, use the `@` sigil. For updating a variable, use a pointer and `store` or the `!` sigil. Examples: &Flag fetch @Flag #600 !Flag #-14 &Flag store Variables: Additional Operations RetroForth also provides words for incrementing or decrementing a variable. These are `v:inc` and `v:dec`. Examples: &Flag v:inc &Flag v:dec Creating Words Functions (called words) begin with a : sigil and end with a `;`. Anything in between the name and the `;` form the word definition. An example: :hello #100 n:put nl ; This creates a new word ("hello") that will push 100 to the stack, display it, then display a new line. Quotations A concept used heavily in RetroForth is quotations. These are anonymous blocks of code. In Retro, these are passed to words which handle various actions like conditional execution and loops. A quote starts with a `[` and ends with `]`. An example: #100 #200 -eq? [ 'Not_equal! s:put nl ] if Quotes may be nested. Combinators Words that operate on quotations (or more generally, pointers) are called "combinators". In RetroForth there are many of these. Combinators: Conditional Execution For conditional execution, RetroForth provides three basic combinators: `choose`, `if`, and `-if`. `choose` executes one of two quotes based on a flag. E.g., #200 #100 eq? [ 'Match! ] [ 'Nope ] choose s:put `if` executes a quote if a flag passed is true. E.g., #100 #100 eq? [ 'Match! s:put ] if `-if` executes a quote if the flag passed is false. E.g., #100 #200 eq? [ 'No_Match! s:put ] -if Combinators: Loops RetroForth uses combinators for loops. The basic system provides five: `forever`, `times`, `indexed-times`, `until`, and `while`. `forever` runs a quotation repeatedly, with no end. An example: [ 'Oops! s:put nl ] forever `times` and `indexed-times` take a count and a quote. Both run the quote the specified number of times. If using the indexed version, you can access the loop index via `I`. #100 [ I n:put sp ] indexed-times #25 [ 'Loops_are_fun s:put nl ] times Combinators: Loops `until` and `while` take quotes which return a flag. With the `until` loop, it will run the quote repeatedly until this flag is true. For `while`, it does so until the flag is false. #100 [ dup n:put sp n:dec dup n:zero? ] until drop #0 [ dup n:put sp n:inc dup #100 -eq? ] while drop Reporting Bugs _ If you find a bug, please report \`*-. it to bugs@retroforth.org ) _`-. . : `. . Please include [retro/ilo] in : _ ' \ the subject line, and describe the ; *` _. `*-._ bug in as much detail as possible. `-.-' `-. ; ` `. :. . \ . \ . : .-' . ' `+.; ; ' : : ' | ; ;-. ; ' : :`-: _.`* ; [bug] .*' / .*' ; .*`- +' `*' `*-* `*-* `*-*'