2019-04-29 18:22:50 +02:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > RETRO Handbook< / title >
< link rel = "stylesheet" href = "https://stackedit.io/style.css" / >
< / head >
< body class = "stackedit" >
< div class = "stackedit__left" >
< div class = "stackedit__toc" >
< ul >
< li > < a href = "#retro-a-modern-pragmatic-forth" > RETRO: a Modern, Pragmatic Forth< / a > < / li >
< li > < a href = "#obtaining-retro" > Obtaining RETRO< / a >
< ul >
< li > < a href = "#stable-releases" > Stable Releases< / a > < / li >
< li > < a href = "#snapshots" > Snapshots< / a > < / li >
< li > < a href = "#repository" > Repository< / a > < / li >
< / ul >
< / li >
< li > < a href = "#building-retro-on-bsd" > Building RETRO on BSD< / a >
< ul >
< li > < a href = "#requirements" > Requirements< / a > < / li >
< li > < a href = "#process" > Process< / a > < / li >
< li > < a href = "#executables" > Executables< / a > < / li >
< / ul >
< / li >
< li > < a href = "#building-retro-on-linux" > Building RETRO on Linux< / a >
< ul >
< li > < a href = "#requirements-1" > Requirements< / a > < / li >
< li > < a href = "#process-1" > Process< / a > < / li >
< li > < a href = "#executables-1" > Executables< / a > < / li >
< / ul >
< / li >
< li > < a href = "#building-retro-on-macos" > Building RETRO on macOS< / a >
< ul >
< li > < a href = "#requirements-2" > Requirements< / a > < / li >
< li > < a href = "#process-2" > Process< / a > < / li >
< li > < a href = "#executables-2" > Executables< / a > < / li >
< / ul >
< / li >
< li > < a href = "#building-retro-on-windows" > Building RETRO on Windows< / a >
< ul >
< li > < a href = "#setup-build-environment" > Setup Build Environment< / a > < / li >
< li > < a href = "#prepare-source" > Prepare Source< / a > < / li >
< li > < a href = "#build" > Build< / a > < / li >
< / ul >
< / li >
< li > < a href = "#starting-retro" > Starting RETRO< / a >
< ul >
< li > < a href = "#interactive" > Interactive< / a > < / li >
< li > < a href = "#using-in-a-pipe" > Using In a Pipe< / a > < / li >
< li > < a href = "#running-a-program-in-a-file" > Running A Program In A File< / a > < / li >
< li > < a href = "#scripting" > Scripting< / a > < / li >
< li > < a href = "#command-line-arguments" > Command Line Arguments< / a > < / li >
< / ul >
< / li >
< li > < a href = "#basic-interactions" > Basic Interactions< / a > < / li >
< li > < a href = "#syntax" > Syntax< / a >
< ul >
< li > < a href = "#tokens" > Tokens< / a > < / li >
< li > < a href = "#prefixes" > Prefixes< / a > < / li >
< li > < a href = "#word-classes" > Word Classes< / a > < / li >
< / ul >
< / li >
< li > < a href = "#a-quick-tutorial" > A Quick Tutorial< / a > < / li >
< li > < a href = "#using-the-glossary" > Using The Glossary< / a >
< ul >
< li > < a href = "#example-entry" > Example Entry< / a > < / li >
< li > < a href = "#reading-the-entry" > Reading The Entry< / a > < / li >
< li > < a href = "#access-online" > Access Online< / a > < / li >
< / ul >
< / li >
< li > < a href = "#programming-techniques" > Programming Techniques< / a > < / li >
< li > < a href = "#unu-simple-literate-source-files" > Unu: Simple, Literate Source Files< / a > < / li >
< li > < a href = "#naming-conventions" > Naming Conventions< / a >
< ul >
< li > < a href = "#general-guidelines" > General Guidelines< / a > < / li >
< li > < a href = "#typical-format" > Typical Format< / a > < / li >
< li > < a href = "#case" > Case< / a > < / li >
< li > < a href = "#namespaces" > Namespaces< / a > < / li >
< li > < a href = "#tips" > Tips< / a > < / li >
< / ul >
< / li >
< li > < a href = "#stack-diagrams" > Stack Diagrams< / a > < / li >
< li > < a href = "#word-classes-1" > Word Classes< / a >
< ul >
< li > < a href = "#how-it-works" > How It Works< / a > < / li >
< li > < a href = "#using-classes" > Using Classes< / a > < / li >
< / ul >
< / li >
< li > < a href = "#using-combinators" > Using Combinators< / a >
< ul >
< li > < a href = "#types-of-combinators" > Types of Combinators< / a > < / li >
< li > < a href = "#compositional" > Compositional< / a > < / li >
< li > < a href = "#execution-flow" > Execution Flow< / a > < / li >
< li > < a href = "#data-flow" > Data Flow< / a > < / li >
< / ul >
< / li >
< li > < a href = "#the-stacks" > The Stacks< / a >
< ul >
< li > < a href = "#data-stack" > Data Stack< / a > < / li >
< li > < a href = "#address-stack" > Address Stack< / a > < / li >
< li > < a href = "#floating-point-stack" > Floating Point Stack< / a > < / li >
< li > < a href = "#tips-2" > Tips< / a > < / li >
< li > < a href = "#notes" > Notes< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-arrays" > Working With Arrays< / a >
< ul >
< li > < a href = "#namespace" > Namespace< / a > < / li >
< li > < a href = "#creating-arrays" > Creating Arrays< / a > < / li >
< li > < a href = "#accessing-elements" > Accessing Elements< / a > < / li >
< li > < a href = "#find-the-length" > Find The Length< / a > < / li >
< li > < a href = "#duplicate" > Duplicate< / a > < / li >
< li > < a href = "#filtering" > Filtering< / a > < / li >
< li > < a href = "#mapping" > Mapping< / a > < / li >
< li > < a href = "#reduce" > Reduce< / a > < / li >
< li > < a href = "#search" > Search< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-a-buffer" > Working With a Buffer< / a >
< ul >
< li > < a href = "#namespace-1" > Namespace< / a > < / li >
< li > < a href = "#implementation" > Implementation< / a > < / li >
< li > < a href = "#limitations" > Limitations< / a > < / li >
< li > < a href = "#set-the-active-buffer" > Set The Active Buffer< / a > < / li >
< li > < a href = "#add-value" > Add Value< / a > < / li >
< li > < a href = "#fetch-last-value" > Fetch Last Value< / a > < / li >
< li > < a href = "#get-data-about-the-buffer" > Get Data About The Buffer< / a > < / li >
< li > < a href = "#reset" > Reset< / a > < / li >
< li > < a href = "#example" > Example< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-characters" > Working With Characters< / a >
< ul >
< li > < a href = "#prefix" > Prefix< / a > < / li >
< li > < a href = "#namespace-2" > Namespace< / a > < / li >
< li > < a href = "#classification" > Classification< / a > < / li >
< li > < a href = "#conversions" > Conversions< / a > < / li >
< li > < a href = "#io" > I/O< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-the-dictionary" > Working With The Dictionary< / a >
< ul >
< li > < a href = "#namespace-3" > Namespace< / a > < / li >
< li > < a href = "#variables" > Variables< / a > < / li >
< li > < a href = "#header-structure" > Header Structure< / a > < / li >
< li > < a href = "#accessing-fields" > Accessing Fields< / a > < / li >
< li > < a href = "#shortcuts-for-the-latest-header" > Shortcuts For The Latest Header< / a > < / li >
< li > < a href = "#adding-headers" > Adding Headers< / a > < / li >
< li > < a href = "#searching" > Searching< / a > < / li >
< li > < a href = "#iteration" > Iteration< / a > < / li >
< li > < a href = "#listing-words" > Listing Words< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-floating-point" > Working With Floating Point< / a >
< ul >
< li > < a href = "#prefix-1" > Prefix< / a > < / li >
< li > < a href = "#namespace-4" > Namespace< / a > < / li >
< li > < a href = "#operation" > Operation< / a > < / li >
< li > < a href = "#constants" > Constants< / a > < / li >
< li > < a href = "#comparisons" > Comparisons< / a > < / li >
< li > < a href = "#basic-math" > Basic Math< / a > < / li >
< li > < a href = "#geometry" > Geometry< / a > < / li >
< li > < a href = "#storage-and-retrieval" > Storage and Retrieval< / a > < / li >
< li > < a href = "#io-1" > I/O< / a > < / li >
< li > < a href = "#encoded-values" > Encoded Values< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-numbers" > Working With Numbers< / a >
< ul >
< li > < a href = "#token-prefix" > Token Prefix< / a > < / li >
< li > < a href = "#namespace-5" > Namespace< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-pointers" > Working With Pointers< / a >
< ul >
< li > < a href = "#prefix-2" > Prefix< / a > < / li >
< li > < a href = "#examples" > Examples< / a > < / li >
< li > < a href = "#notes-1" > Notes< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-strings" > Working With Strings< / a >
< ul >
< li > < a href = "#prefix-3" > Prefix< / a > < / li >
< li > < a href = "#namespace-6" > Namespace< / a > < / li >
< li > < a href = "#lifetime" > Lifetime< / a > < / li >
< li > < a href = "#mutability" > Mutability< / a > < / li >
< li > < a href = "#searching-1" > Searching< / a > < / li >
< li > < a href = "#comparisons-1" > Comparisons< / a > < / li >
< li > < a href = "#extraction" > Extraction< / a > < / li >
< li > < a href = "#joining" > Joining< / a > < / li >
< li > < a href = "#tokenization" > Tokenization< / a > < / li >
< li > < a href = "#conversions-1" > Conversions< / a > < / li >
< li > < a href = "#cleanup" > Cleanup< / a > < / li >
< li > < a href = "#combinators" > Combinators< / a > < / li >
< li > < a href = "#other" > Other< / a > < / li >
< li > < a href = "#controlling-the-temporary-buffers" > Controlling The Temporary Buffers< / a > < / li >
< / ul >
< / li >
< li > < a href = "#working-with-assembly-language" > Working With Assembly Language< / a >
< ul >
< li > < a href = "#assembling-a-standalone-file" > Assembling A Standalone File< / a > < / li >
< li > < a href = "#runtime-assembler" > Runtime Assembler< / a > < / li >
< / ul >
< / li >
< li > < a href = "#lexical-scope" > Lexical Scope< / a >
< ul >
< li > < a href = "#example-1" > Example< / a > < / li >
< li > < a href = "#notes-2" > Notes< / a > < / li >
< / ul >
< / li >
< li > < a href = "#internals" > Internals< / a > < / li >
< li > < a href = "#internals-nga-virtual-machine" > Internals: Nga Virtual Machine< / a >
< ul >
< li > < a href = "#overview" > Overview< / a > < / li >
< li > < a href = "#instrution-table" > Instrution Table< / a > < / li >
< li > < a href = "#encoding" > Encoding< / a > < / li >
< li > < a href = "#shifts" > Shifts< / a > < / li >
< li > < a href = "#queries-memory-stacks" > Queries: Memory, Stacks< / a > < / li >
< li > < a href = "#io-devices" > I/O Devices< / a > < / li >
< li > < a href = "#trivia" > Trivia< / a > < / li >
< / ul >
< / li >
< li > < a href = "#internals-interface-layers" > Internals: Interface Layers< / a > < / li >
< li > < a href = "#internals-the-retro-image" > Internals: The Retro Image< / a >
< ul >
< li > < a href = "#format" > Format< / a > < / li >
< li > < a href = "#header" > Header< / a > < / li >
< li > < a href = "#layout" > Layout< / a > < / li >
< / ul >
< / li >
< li > < a href = "#additional-tools" > Additional Tools< / a >
< ul >
< li > < a href = "#retro" > retro< / a > < / li >
< li > < a href = "#retro-describe" > retro-describe< / a > < / li >
< li > < a href = "#retro-embedimage" > retro-embedimage< / a > < / li >
< li > < a href = "#retro-extend" > retro-extend< / a > < / li >
< li > < a href = "#retro-muri" > retro-muri< / a > < / li >
< li > < a href = "#retro-unu" > retro-unu< / a > < / li >
< / ul >
< / li >
< li > < a href = "#advanced-builds" > Advanced Builds< / a >
< ul >
< li > < a href = "#reduced-memory" > Reduced Memory< / a > < / li >
< li > < a href = "#custom-image" > Custom Image< / a > < / li >
< / ul >
< / li >
< li > < a href = "#the-optional-retro-compiler" > The Optional Retro Compiler< / a >
< ul >
< li > < a href = "#requirements-3" > Requirements< / a > < / li >
< li > < a href = "#building" > Building< / a > < / li >
< li > < a href = "#installing" > Installing< / a > < / li >
< li > < a href = "#using" > Using< / a > < / li >
< li > < a href = "#known-limitations" > Known Limitations< / a > < / li >
< / ul >
< / li >
< li > < a href = "#errors" > Errors< / a >
< ul >
< li > < a href = "#non-fatal" > Non-Fatal< / a > < / li >
< li > < a href = "#fatal" > Fatal< / a > < / li >
< li > < a href = "#rationale" > Rationale< / a > < / li >
< / ul >
< / li >
< li > < a href = "#security-concerns" > Security Concerns< / a >
< ul >
< li > < a href = "#runtime-checks" > Runtime Checks< / a > < / li >
< li > < a href = "#isolation" > Isolation< / a > < / li >
< li > < a href = "#future-direction" > Future Direction< / a > < / li >
< li > < a href = "#rationale-1" > Rationale< / a > < / li >
< / ul >
< / li >
< li > < a href = "#technical-notes-and-reflections" > Technical Notes and Reflections< / a >
< ul >
< li > < a href = "#metacompilation-and-assembly" > Metacompilation and Assembly< / a > < / li >
< li > < a href = "#the-path-to-self-hosting" > The Path to Self Hosting< / a > < / li >
< li > < a href = "#prefixes-as-a-language-element" > Prefixes as a Language Element< / a > < / li >
< li > < a href = "#on-the-kernel-wordset" > On The Kernel Wordset< / a > < / li >
< li > < a href = "#on-the-evolution-of-ngaro-into-nga" > On The Evolution Of Ngaro Into Nga< / a > < / li >
< li > < a href = "#retro-11-2011---2019-a-look-back" > RETRO 11 (2011 - 2019): A Look Back< / a > < / li >
< / ul >
< / li >
< li > < a href = "#historical-papers-and-notes" > Historical Papers and Notes< / a >
< ul >
< li > < a href = "#on-the-naming-of-retro" > On the Naming of RETRO< / a > < / li >
< li > < a href = "#the-design-philosophy-of-retro-native-forth" > The Design Philosophy of RETRO Native Forth< / a > < / li >
< / ul >
< / li >
< / ul >
< / div >
< / div >
< div class = "stackedit__right" >
< div class = "stackedit__html" >
< h1 id = "retro-a-modern-pragmatic-forth" > RETRO: a Modern, Pragmatic Forth< / h1 >
< p > Welcome to RETRO, my personal take on the Forth language. This< br >
is a modern system primarily targetting desktop, mobile, and< br >
servers, though it can also be used on some larger (ARM, MIPS32)< br >
embedded systems.< / p >
< p > The language is Forth. It is untyped, uses a stack to pass data< br >
between functions called words, and a dictionary which tracks< br >
the word names and data structures.< / p >
< p > But it’ s not a traditional Forth. RETRO draws influences from< br >
many sources and takes a unique approach to the language.< / p >
< p > RETRO has a large vocabulary of words. Keeping a copy of the< br >
Glossary on hand is highly recommended as you learn to use RETRO.< / p >
< p > This book will hopefully help you develop a better understanding< br >
of RETRO and how it works.< / p >
< h1 id = "obtaining-retro" > Obtaining RETRO< / h1 >
< p > The RETRO source code can be obtained from < a href = "http://forthworks.com/retro" > http://forthworks.com/retro< / a > < br >
or gopher://forthworks.com/1/retro< / p >
< h2 id = "stable-releases" > Stable Releases< / h2 >
< p > I periodically make stable releases. This will typically happen< br >
two to four times per year. These are good for those needing a< br >
solid base that doesn’ t change frequently.< / p >
< h2 id = "snapshots" > Snapshots< / h2 >
< p > A lot of development happens between releases. I make snapshots< br >
of my working source tree nightly (and often more often).< / p >
< p > This is what I personally recommend for most users. It reflects< br >
my latest system and is normally reliable as it’ s used daily in< br >
production.< / p >
< p > The latest snapshot can be downloaded from the following stable< br >
URLs:< / p >
< ul >
< li > < a href = "http://forthworks.com/retro/r/latest.tar.gz" > http://forthworks.com/retro/r/latest.tar.gz< / a > < / li >
< li > gopher://forthworks.com/9/retro/r/latest.tar.gz< / li >
< / ul >
< h2 id = "repository" > Repository< / h2 >
< p > I use a Fossil repository to manage development. To obtain a< br >
copy of the repository install Fossil and:< / p >
< pre > < code > fossil clone http://forthworks.com:8000 retro.fossil
mkdir retro
cd retro
fossil open /path/to/retro.fossil
< / code > < / pre >
< p > See the Fossil documentation for details on using Fossil to< br >
keep your local copy of the repository current.< / p >
< p > This will let you stay current with my latest changes faster< br >
than the snapshots, but you may occasionally encounter bigger< br >
problems as some commits may be in a partially broken state.< / p >
< p > If you have problems, check the version of Fossil you are< br >
using. I am currently using Fossil 2.7, you may experience< br >
issues checking out or cloning if using older versions.< / p >
< h1 id = "building-retro-on-bsd" > Building RETRO on BSD< / h1 >
< p > RETRO is well supported on BSD (FreeBSD, NetBSD, OpenBSD)< br >
systems. It should build on a base install of any of these< br >
without issue.< / p >
< h2 id = "requirements" > Requirements< / h2 >
< ul >
< li > c compiler< / li >
< li > make< / li >
< / ul >
< h2 id = "process" > Process< / h2 >
< p > Run < code > make< / code > < / p >
< p > This will build the toolchain and then the main < code > retro< / code > < br >
executable.< / p >
< h2 id = "executables" > Executables< / h2 >
< p > In the < code > bin/< / code > directory:< / p >
< pre > < code > retro
retro-unu
retro-muri
retro-extend
retro-embedimage
retro-describe
< / code > < / pre >
< h1 id = "building-retro-on-linux" > Building RETRO on Linux< / h1 >
< p > Building on Linux is pretty easy. You’ ll need to make sure< br >
you have a C compiler, headers, and make.< / p >
< h2 id = "requirements-1" > Requirements< / h2 >
< ul >
< li > c compiler (tested: clang, tcc, gcc)< / li >
< li > development headers< / li >
< li > make< / li >
< / ul >
< h2 id = "process-1" > Process< / h2 >
< p > Run:< / p >
< pre > < code > make -f Makefile.linux
< / code > < / pre >
< p > This will build the toolchain and then the main < code > retro< / code > < br >
executable.< / p >
< h2 id = "executables-1" > Executables< / h2 >
< p > In the < code > bin/< / code > directory:< / p >
< pre > < code > retro
retro-unu
retro-muri
retro-extend
retro-embedimage
retro-describe
< / code > < / pre >
< h1 id = "building-retro-on-macos" > Building RETRO on macOS< / h1 >
< p > To build on macOS, you will need the command line tools from< br >
Xcode. Install these and you should be able to build and use< br >
RETRO.< / p >
< h2 id = "requirements-2" > Requirements< / h2 >
< ul >
< li > command line tools from Xcode< / li >
< / ul >
< h2 id = "process-2" > Process< / h2 >
< p > Run < code > make< / code > < / p >
< p > This will build the toolchain and then the main < code > retro< / code > < br >
executable.< / p >
< h2 id = "executables-2" > Executables< / h2 >
< p > In the < code > bin/< / code > directory:< / p >
< pre > < code > retro
retro-unu
retro-muri
retro-extend
retro-embedimage
< / code > < / pre >
< h1 id = "building-retro-on-windows" > Building RETRO on Windows< / h1 >
< p > It is possible to build RETRO on Windows, though a few of the< br >
extensions are not supported:< / p >
< ul >
< li > no < code > unix:< / code > words< / li >
< li > no < code > gopher:< / code > words< / li >
< / ul >
< p > This is currently more difficult than on a Unix host. If you have< br >
Windows 10 and WSL, it may be better to build under that (using< br >
the Linux instructions).< / p >
< h2 id = "setup-build-environment" > Setup Build Environment< / h2 >
< p > RETRO on Windows is built with TCC.< / p >
< p > Go to < a href = "http://download.savannah.gnu.org/releases/tinycc/" > http://download.savannah.gnu.org/releases/tinycc/< / a > < / p >
< p > Download the < em > winapi-full< / em > and < em > tcc-xxxx-bin< / em > packages for your< br >
system. Decompress them, copy the headers from the winapi< br >
package into the tcc directory.< / p >
< h2 id = "prepare-source" > Prepare Source< / h2 >
< p > You’ ll need to comment out (or remove) some things before RETRO< br >
will build.< / p >
< p > In < em > rre.c< / em > :< / p >
< ul >
< li > remove includes for unistd.h, sys/sockets.h, netinet/in.h,< br >
netdb.h, errno.h, sys/wait.h, signal.h< / li >
< li > remove the #define USE_TERMIOS line< / li >
< li > change the #define NUM_DEVICES to 6< / li >
< li > remove io_unix_handler and io_gopher_handler from IO_deviceHandlers< / li >
< li > remove io_unix_query and io_gopher_query from IO_queryHandlers< / li >
< / ul >
< p > In < em > image-functions.c< / em > :< / p >
< ul >
< li > remove includes for unistd.h< / li >
< / ul >
< p > In < em > image-functions.h< / em > :< / p >
< ul >
< li > remove includes for unistd.h< / li >
< / ul >
< p > In < em > io\filesystem.c< / em > :< / p >
< ul >
< li > remove includes for unistd.h< / li >
< / ul >
< p > In < em > io\floatingpoint.c< / em > :< / p >
< ul >
< li > remove includes for unistd.h< / li >
< / ul >
< h2 id = "build" > Build< / h2 >
< p > \path\to\tcc rre.c image-functions.c io\filesystem.c io\floatingpoint.c -o retro.exe< / p >
< h1 id = "starting-retro" > Starting RETRO< / h1 >
< p > RETRO can be run for scripting or interactive use.< / p >
< h2 id = "interactive" > Interactive< / h2 >
< p > To start it interactively, run: < code > retro< / code > without any command line< br >
2019-04-29 18:29:52 +02:00
arguments, or with < code > -i< / code > , < code > -s< / code > , or < code > -i,c< / code > .< / p >
2019-04-29 18:22:50 +02:00
< p > Starting the interactive system:< / p >
< pre > < code > retro
< / code > < / pre >
< p > Or:< / p >
< pre > < code > retro -i
< / code > < / pre >
< p > This should be sufficient for most uses.< / p >
< p > Starting the interactive system (without displaying the < code > Ok< / code > < br >
prompt or startup banner):< / p >
< pre > < code > retro -s
< / code > < / pre >
< p > RETRO also has a < em > character breaking< / em > mode, in which input is< br >
processed directly as entered, this is started with the < code > -c< / code > < br >
option:< / p >
2019-04-29 18:29:52 +02:00
< pre > < code > retro -i,c
2019-04-29 18:22:50 +02:00
< / code > < / pre >
< h2 id = "using-in-a-pipe" > Using In a Pipe< / h2 >
< p > If using a Unix shell and piping input between processes, you< br >
will probably want to use < code > -s< / code > to supress the startup messages< br >
and < code > Ok< / code > prompt that normally appear.< / p >
< p > E.g.,< / p >
< pre > < code > echo "'lol s:put nl" | retro -s
< / code > < / pre >
< h2 id = "running-a-program-in-a-file" > Running A Program In A File< / h2 >
< p > You can run code in a file very easily. This is simply:< / p >
< pre > < code > retro filename
< / code > < / pre >
< p > You can follow the filename with any arguments that it may need.< br >
These will be accessible to the program via the < code > sys:argc< / code > and< br >
< code > sys:argv< / code > words.< / p >
< p > Source files must be written in Unu format.< / p >
< h2 id = "scripting" > Scripting< / h2 >
< p > You can use RETRO to write scripts. Add a shebang:< / p >
< pre > < code > #!/usr/bin/env retro
< / code > < / pre >
< p > And make the file executable.< / p >
< p > Source files must be written in Unu format.< / p >
< h2 id = "command-line-arguments" > Command Line Arguments< / h2 >
< p > For a summary of the full command line arguments available:< / p >
< pre > < code > Scripting Usage:
retro filename [script arguments...]
Interactive Usage:
retro [-h] [-i] [-c] [-s] [-f filename] [-t]
-h Display this help text
-i Interactive mode (line buffered)
-c Interactive mode (character buffered)
-s Suppress the 'ok' prompt and keyboard
echo in interactive mode
-f filename Run the contents of the specified file
-t Run tests (in ``` blocks) in any loaded files
< / code > < / pre >
< h1 id = "basic-interactions" > Basic Interactions< / h1 >
< p > Start RETRO in interactive mode:< / p >
< pre > < code > retro -i
< / code > < / pre >
< p > You should see something similar to this:< / p >
< pre > < code > RETRO 12 (rx-2019.6)
8388608 MAX, TIB @ 1025, Heap @ 9374
Ok
< / code > < / pre >
< p > At this point you are at the < em > listener< / em > , which reads and< br >
processes your input. You are now set to begin exploring< br >
RETRO.< / p >
< p > To exit, run < code > bye< / code > :< / p >
< pre > < code > bye
< / code > < / pre >
< h1 id = "syntax" > Syntax< / h1 >
< p > RETRO has more syntax than a traditional Forth due to ideas< br >
borrowed from ColorForth and some design decisions. This has< br >
some useful traits, and helps to make the language more< br >
consistent.< / p >
< h2 id = "tokens" > Tokens< / h2 >
< p > Input is divided into a series of whitespace delimited tokens.< br >
Each of these is then processed individually. There are no< br >
parsing words in RETRO.< / p >
< p > Tokens may have a single character < em > prefix< / em > , which RETRO will< br >
use to decide how to process the token.< / p >
< h2 id = "prefixes" > Prefixes< / h2 >
< p > Prefixes are single characters added to the start of a token< br >
to guide the compiler. The use of these is a major way in< br >
which RETRO differs from traditional Forth.< / p >
< p > When a token is passed to < code > interpret< / code > , RETRO first takes the< br >
intitial character and looks to see if there is a word that< br >
matches this. If so, it will pass the rest of the token to< br >
that word to handle.< / p >
< p > In a traditional Forth, the interpret process is something< br >
like:< / p >
< pre > < code > get token
is token in the dictionary?
yes:
is it immediate?
yes: call the word.
no: are we interpreting?
yes: call the word
no: compile a call to the word
no:
is it a number?
yes: are we interpreting?
yes: push the number to the stack
no: compile the number as a literal
no: report an error ("not found")
< / code > < / pre >
< p > In RETRO, the interpret process is basically:< / p >
< pre > < code > get token
does the first character match a `prefix:` word?
yes: pass the token to the prefix handler
no: is token a word in the dictionary?
yes: push the XT to the stack and call the
class handler
no: report an error ("not found")
< / code > < / pre >
< p > All of the actual logic for how to deal with tokens is moved< br >
to the individual prefix handlers, and the logic for handling< br >
words is moved to word class handlers.< / p >
< p > This means that prefixes are used for a lot of things. Numbers?< br >
Handled by a < code > #< / code > prefix. Strings? Use the < code > '< / code > prefix. Comments?< br >
Use < code > (< / code > . Making a new word? Use the < code > :< / code > prefix.< / p >
< p > The major prefixes are:< / p >
< table >
< thead >
< tr >
< th > Prefix< / th >
< th > Used For< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > @< / td >
< td > Fetch from variable< / td >
< / tr >
< tr >
< td > !< / td >
< td > Store into variable< / td >
< / tr >
< tr >
< td > & < / td >
< td > Pointer to named item< / td >
< / tr >
< tr >
< td > #< / td >
< td > Numbers< / td >
< / tr >
< tr >
< td > $< / td >
< td > ASCII characters< / td >
< / tr >
< tr >
< td > ’ < / td >
< td > Strings< / td >
< / tr >
< tr >
< td > (< / td >
< td > Comments< / td >
< / tr >
< tr >
< td > :< / td >
< td > Define a word< / td >
< / tr >
< / tbody >
< / table > < p > The individual prefixes will be covered in more detail in the< br >
later chapters on working with different data types.< / p >
< h2 id = "word-classes" > Word Classes< / h2 >
< p > Word classes are words which take a pointer and do something< br >
with it. These are covered in detail in their own chapter,< br >
but essentially they decide < em > how< / em > to execute or compile specific< br >
types of words.< / p >
< h1 id = "a-quick-tutorial" > A Quick Tutorial< / h1 >
< p > Programming in RETRO is all about creating words to solve< br >
the problem at hand. Words operate on data, which can be< br >
kept in memory or on the stack.< / p >
< p > Let’ s look at this by solving a small problem: writing a< br >
word to determine if a string is a palindrome.< / p >
< p > A palindrome is a phrase which reads the same backward< br >
and forward.< / p >
< p > We first need a string to look at. Starting with something< br >
easy:< / p >
< pre > < code > 'anna
< / code > < / pre >
< p > Looking in the Glossary, there is a < code > s:reverse< / code > word for< br >
reversing a string. We can find < code > dup< / code > to copy a value, and< br >
< code > s:eq?< / code > to compare two strings. So testing:< / p >
< pre > < code > 'anna dup s:reverse s:eq?
< / code > < / pre >
< p > This yields -1 (< code > TRUE< / code > ) as expected. So we can easily< br >
name it:< / p >
< pre > < code > :palindrome dup s:reverse s:eq? ;
< / code > < / pre >
< p > Naming uses the < code > :< / code > prefix to add a new word to the dictionary.< br >
The words that make up the definition are then placed, with a< br >
final word (< code > ;< / code > ) ending the definition. We can then use this:< / p >
< pre > < code > 'anna palindrome?
< / code > < / pre >
< p > Once defined there is no difference between our new word and< br >
any of the words already provided by the RETRO system.< / p >
< h1 id = "using-the-glossary" > Using The Glossary< / h1 >
< p > The Glossary is a valuable resource. It provides information< br >
on the RETRO words.< / p >
< h2 id = "example-entry" > Example Entry< / h2 >
< pre > < code > f:+
Data: -
Addr: -
Float: FF-F
Add two floating point numbers, returning the result.
Class: class:word | Namespace: f | Interface Layer: rre
Example #1:
.3.1 .22 f:+
< / code > < / pre >
< h2 id = "reading-the-entry" > Reading The Entry< / h2 >
< p > An entry starts with the word name.< / p >
< p > This is followed by the stack effect for each stack. All RETRO< br >
systems have Data and Address stacks, some also include a< br >
floating point stack).< / p >
< p > The stack effect diagrams are followed by a short description< br >
of the word.< / p >
< p > After the description is a line providing some useful data. This< br >
includes the class handler, namespace prefix, and the interface< br >
layer that provides the word.< / p >
< p > Words in all systems will be listed as < code > all< / code > . Some words (like< br >
the < code > pb:< / code > words) are only on specific systems like iOS. These< br >
can be identified by looking at the interface layer field.< / p >
< p > At the end of the entry may be an example or two.< / p >
< h2 id = "access-online" > Access Online< / h2 >
< p > The latest Glossary can be browsed at < a href = "http://forthworks.com:9999" > http://forthworks.com:9999< / a > < br >
or gopher://forthworks.com:9999< / p >
< h1 id = "programming-techniques" > Programming Techniques< / h1 >
< p > The upcoming chapters provide helpful information on using RETRO< br >
with different types of data and hints on how to solve problems< br >
in a way consistent with the RETRO system.< / p >
< h1 id = "unu-simple-literate-source-files" > Unu: Simple, Literate Source Files< / h1 >
< p > RETRO is written in a literate style. Most of the sources< br >
are in a format called Unu. This allows easy mixing of< br >
commentary and code blocks, making it simple to document< br >
the code.< / p >
< p > As an example,< / p >
< pre > < code > # Determine The Average Word Name Length
To determine the average length of a word name two values
are needed. First, the total length of all names in the
Dictionary:
~~~
#0 [ d:name s:length + ] d:for-each
~~~
And then the number of words in the Dictionary:
~~~
#0 [ drop n:inc ] d:for-each
~~~
With these, a simple division is all that's left.
~~~
/
~~~
Finally, display the results:
~~~
'Average_name_length:_%n\n s:format s:put
~~~
< / code > < / pre >
< p > This illustrates the format. Only code in the fenced blocks< br >
(between ~~~ pairs) get extracted and run.< / p >
< p > (Note: this only applies to < em > source files< / em > ; fences are not used< br >
when entering code interactively).< / p >
< h1 id = "naming-conventions" > Naming Conventions< / h1 >
< p > Word names in RETRO generally follow the following conventions.< / p >
< h2 id = "general-guidelines" > General Guidelines< / h2 >
< ul >
< li > Readability is important< / li >
< li > Be consistent< / li >
< li > Don’ t use a prefix as the first character of a name< / li >
< li > Use short names for indices< / li >
< / ul >
< h2 id = "typical-format" > Typical Format< / h2 >
< p > The word names will generally follow a form like:< / p >
< pre > < code > [namespace:]name
< / code > < / pre >
< p > The < code > namespace:< / code > is optional, but recommended for consistency< br >
with the rest of the system and to make it easier to identify< br >
related words.< / p >
< h2 id = "case" > Case< / h2 >
< p > Word names are lowercase, with a dash (-) for compound names.< / p >
< pre > < code > hello
drop-pair
s:for-each
< / code > < / pre >
< p > Variables use TitleCase, with no dash between compound names.< / p >
< pre > < code > Base
Heap
StringBuffers
< / code > < / pre >
< p > Constants are UPPERCASE, with a dash (-) for compound names.< / p >
< pre > < code > TRUE
FALSE
f:PI
MAX-STRING-LENGTH
< / code > < / pre >
< h2 id = "namespaces" > Namespaces< / h2 >
< p > Words are grouped into broad namespaces by attaching a short< br >
prefix string to the start of a name.< / p >
< p > The common namespaces are:< / p >
< table >
< thead >
< tr >
< th > Prefix< / th >
< th > Contains< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > a:< / td >
< td > Words operating on simple arrays< / td >
< / tr >
< tr >
< td > ASCII:< / td >
< td > ASCII character constants for control characters< / td >
< / tr >
< tr >
< td > buffer:< / td >
< td > Words for operating on a simple linear LIFO buffer< / td >
< / tr >
< tr >
< td > c:< / td >
< td > Words for operating on ASCII character data< / td >
< / tr >
< tr >
< td > class:< / td >
< td > Contains class handlers for words< / td >
< / tr >
< tr >
< td > d:< / td >
< td > Words operating on the Dictionary< / td >
< / tr >
< tr >
< td > err:< / td >
< td > Words for handling errors< / td >
< / tr >
< tr >
< td > io:< / td >
< td > General I/O words< / td >
< / tr >
< tr >
< td > n:< / td >
< td > Words operating on numeric data< / td >
< / tr >
< tr >
< td > prefix:< / td >
< td > Contains prefix handlers< / td >
< / tr >
< tr >
< td > s:< / td >
< td > Words operating on string data< / td >
< / tr >
< tr >
< td > v:< / td >
< td > Words operating on variables< / td >
< / tr >
< tr >
< td > file:< / td >
< td > File I/O words< / td >
< / tr >
< tr >
< td > f:< / td >
< td > Floating Point words< / td >
< / tr >
< tr >
< td > gopher:< / td >
< td > Gopher protocol words< / td >
< / tr >
< tr >
< td > unix:< / td >
< td > Unix system call words< / td >
< / tr >
< / tbody >
< / table > < h2 id = "tips" > Tips< / h2 >
< p > Avoid using a prefix as the first character of a word name. RETRO< br >
will look for prefixes first, this will prevent direct use of< br >
the work in question.< / p >
< p > To find a list of prefix characters, do:< / p >
< pre > < code > 'prefix: d:words-with
< / code > < / pre >
< h1 id = "stack-diagrams" > Stack Diagrams< / h1 >
< p > Most words in RETRO have a stack comment. These look like:< / p >
< pre > < code > (-)
(nn-n)
< / code > < / pre >
< p > As with all comments, a stack comment begins with < code > (< / code > and< br >
should end with a < code > )< / code > . There are two parts to the comment.< br >
On the left side of the < code > -< / code > is what the word < em > consumes< / em > . On< br >
the right is what it < em > leaves< / em > .< / p >
< p > RETRO uses a short notation, with one character per value< br >
taken or left. In general, the following symbols represent< br >
certain types of values.< / p >
< table >
< thead >
< tr >
< th > Notation< / th >
< th > Represents< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > b, n, m, o, x, y, z< / td >
< td > generic numeric values< / td >
< / tr >
< tr >
< td > s< / td >
< td > string< / td >
< / tr >
< tr >
< td > v< / td >
< td > variable< / td >
< / tr >
< tr >
< td > p, a< / td >
< td > pointers< / td >
< / tr >
< tr >
< td > q< / td >
< td > quotation< / td >
< / tr >
< tr >
< td > d< / td >
< td > dictionary header< / td >
< / tr >
< tr >
< td > f< / td >
< td > < code > TRUE< / code > or < code > FALSE< / code > flag.< / td >
< / tr >
< / tbody >
< / table > < p > In the case of something like < code > (xyz-m)< / code > , RETRO expects z to be< br >
on the top of the stack, with y below it and x below the y< br >
value. And after execution, a single value (m) will be left on< br >
the stack.< / p >
< p > Words with no stack effect have a comment of (-)< / p >
< h1 id = "word-classes-1" > Word Classes< / h1 >
< p > Word classes are one of the two elements at the heart of< br >
RETRO’ s interpreter.< / p >
< p > There are different types of words in a Forth system. At a< br >
minimum there are data words, regular words, and immediate< br >
words. There are numerous approaches to dealing with this.< / p >
< p > In RETRO I define special words which receive a pointer and< br >
decide how to deal with it. These are grouped into a < code > class:< / code > < br >
namespace.< / p >
< h2 id = "how-it-works" > How It Works< / h2 >
< p > When a word is found in the dictionary, RETRO will push a< br >
pointer to the definition (the < code > d:xt< / code > field) to the stack< br >
and then call the word specified by the < code > d:class< / code > field.< / p >
< p > The word called is responsible for processing the pointer< br >
passed to it.< / p >
< p > As a simple case, let’ s look at < code > immediate< / code > words. These are< br >
words which will always be called when encountered. A common< br >
strategy is to have an immediacy bit which the interpreter< br >
will look at, but RETRO uses a class for this. The class is< br >
defined:< / p >
< pre > < code > :class:immediate (a-) call ;
< / code > < / pre >
< p > Or a normal word. These should be called at interpret time< br >
or compiled into definitions. The handler for this can look< br >
like:< / p >
< pre > < code > :class:word (a-) compiling? [ compile:call ] [ call ] choose ;
< / code > < / pre >
< h2 id = "using-classes" > Using Classes< / h2 >
< p > The ability to add new classes is useful. If I wanted to add< br >
a category of word that preserves an input value, I could do< br >
it with a class:< / p >
< pre > < code > :class:duplicating (a-)
compiling? [ & dup compile:call ] [ & dup dip ] choose
class:word ;
:duplicating & class:duplicating reclass ;
:. n:put nl ; duplicating
#100 . . .
< / code > < / pre >
< h1 id = "using-combinators" > Using Combinators< / h1 >
< p > A combinator is a function that consumes functions as input.< br >
They are used heavily by the RETRO system.< / p >
< h2 id = "types-of-combinators" > Types of Combinators< / h2 >
< p > Combinators are divided into three primary types: compositional,< br >
execution flow, and data flow.< / p >
< h2 id = "compositional" > Compositional< / h2 >
< p > A compositional combinator takes elements from the stack and< br >
returns a new quote.< / p >
< p > < code > curry< / code > takes a value and a quote and returns a new quote< br >
applying the specified quote to the specified value. As an< br >
example,< / p >
< pre > < code > :acc (n-) here swap , [ dup v:inc fetch ] curry ;
< / code > < / pre >
< p > This would create an accumulator function, which takes an< br >
initial value and returns a quote that will increase the< br >
accumulator by 1 each time it is invoked. It will also return< br >
the latest value. So:< / p >
< pre > < code > #10 acc
dup call n:put
dup call n:put
dup call n:put
< / code > < / pre >
< h2 id = "execution-flow" > Execution Flow< / h2 >
< p > Combinators of this type execute other functions.< / p >
< h3 id = "fundamental" > Fundamental< / h3 >
< p > < code > call< / code > takes a quote and executes it immediately.< / p >
< pre > < code > [ #1 n:put ] call
& words call
< / code > < / pre >
< h3 id = "conditionals" > Conditionals< / h3 >
< p > RETRO provides three primary combinators for use with< br >
conditional execution of quotes. These are < code > choose< / code > , < code > if< / code > ,< br >
and < code > -if< / code > .< / p >
< p > < code > choose< / code > takes a flag and two quotes from the stack. If the< br >
flag is true, the first quote is executed. If false, the< br >
second quote is executed.< / p >
< pre > < code > #-1 [ 'true s:put ] [ 'false s:put ] choose
#0 [ 'true s:put ] [ 'false s:put ] choose
< / code > < / pre >
< p > < code > if< / code > takes a flag and one quote from the stack. If the flag is< br >
true, the quote is executed. If false, the quote is discarded.< / p >
< pre > < code > #-1 [ 'true s:put ] if
#0 [ 'true s:put ] if
< / code > < / pre >
< p > < code > -if< / code > takes a flag and one quote from the stack. If the flag is< br >
false, the quote is executed. If true, the quote is discarded.< / p >
< pre > < code > #-1 [ 'false s:put ] -if
#0 [ 'false s:put ] -if
< / code > < / pre >
< p > RETRO also provides < code > case< / code > and < code > s:case< / code > for use when you have< br >
multiple values to check against. This is similar to a < code > switch< / code > < br >
in C.< / p >
< p > < code > case< / code > takes two numbers and a quote. The initial value is< br >
compared to the second one. If they match, the quote is< br >
executed. If false, the quote is discarded and the initial< br >
value is left on the stack.< / p >
< p > Additionally, if the first value was matched, < code > case< / code > will exit< br >
the calling function, but if false, it returns to the calling< br >
function.< / p >
< p > < code > s:case< / code > works the same way, but for strings instead of simple< br >
values.< / p >
< pre > < code > :test (n-)
#1 [ 'Yes s:put ] case
#2 [ 'No s:put ] case
drop 'No idea s:put ;
< / code > < / pre >
< h3 id = "looping" > Looping< / h3 >
< p > Several combinators are available for handling various looping< br >
constructs.< / p >
< p > < code > while< / code > takes a quote from the stack and executes it repeatedly< br >
as long as the quote returns a true flag on the stack. This flag< br >
must be well formed and equal -1 or 0.< / p >
< pre > < code > #10 [ dup n:put sp n:dec dup 0 -eq? ] while
< / code > < / pre >
< p > < code > times< / code > takes a count and quote from the stack. The quote will< br >
be executed the number of times specified. No indexes are pushed< br >
to the stack.< / p >
< pre > < code > #1 #10 [ dup n:put sp n:inc ] times drop
< / code > < / pre >
< p > There is also a < code > times< with-index> < / code > variation that provides< br >
access to the loop index (via < code > I< / code > ) and parent loop indexes< br >
(via < code > J< / code > and < code > K< / code > ).< / p >
< pre > < code > #10 [ I n:put sp ] times< with-index>
< / code > < / pre >
< h2 id = "data-flow" > Data Flow< / h2 >
< p > These combinators exist to simplify stack usage in various< br >
circumstances.< / p >
< h3 id = "preserving" > Preserving< / h3 >
< p > Preserving combinators execute code while preserving portions< br >
of the data stack.< / p >
< p > < code > dip< / code > takes a value and a quote, moves the value off the main< br >
stack temporarily, executes the quote, and then restores the< br >
value.< / p >
< pre > < code > #10 #20 [ n:inc ] dip
< / code > < / pre >
< p > Would yield the following on the stack:< / p >
< pre > < code > 11 20
< / code > < / pre >
< p > < code > sip< / code > is similar to < code > dip< / code > , but leaves a copy of the original< br >
value on the stack during execution of the quote. So:< / p >
< pre > < code > #10 [ n:inc ] sip
< / code > < / pre >
< p > Leaves us with:< / p >
< pre > < code > 11 10
< / code > < / pre >
< h3 id = "cleave" > Cleave< / h3 >
< p > Cleave combinators apply multiple quotations to a single value< br >
or set of values.< / p >
< p > < code > bi< / code > takes a value and two quotes, it then applies each quote to< br >
a copy of the value.< / p >
< pre > < code > #100 [ n:inc ] [ n:dec ] bi
< / code > < / pre >
< p > < code > tri< / code > takes a value and three quotes. It then applies each quote< br >
to a copy of the value.< / p >
< pre > < code > #100 [ n:inc ] [ n:dec ] [ dup * ] tri
< / code > < / pre >
< h3 id = "spread" > Spread< / h3 >
< p > Spread combinators apply multiple quotations to multiple values.< br >
The asterisk suffixed to these function names signifies that< br >
they are spread combinators.< / p >
< p > < code > bi*< / code > takes two values and two quotes. It applies the first< br >
quote to the first value and the second quote to the second< br >
value.< / p >
< pre > < code > #1 #2 [ n:inc ] [ #2 * ] bi*
< / code > < / pre >
< p > < code > tri*< / code > takes three values and three quotes, applying the< br >
first quote to the first value, the second quote to the< br >
second value, and the third quote to the third value.< / p >
< pre > < code > #1 #2 #3 [ n:inc ] [ #2 * ] [ n:dec ] tri*
< / code > < / pre >
< h3 id = "apply" > Apply< / h3 >
< p > Apply combinators apply a single quotation to multiple values.< br >
The @ sign suffixed to these function names signifies that they< br >
are apply combinators.< / p >
< p > < code > bi@< / code > takes two values and a quote. It then applies the quote to< br >
each value.< / p >
< pre > < code > #1 #2 [ n:inc ] bi@
< / code > < / pre >
< p > < code > tri@< / code > takes three values and a quote. It then applies the quote< br >
to each value.< / p >
< pre > < code > #1 #2 #3 [ n:inc ] tri@
< / code > < / pre >
< p > RETRO also provides < code > for-each< / code > combinators for various data< br >
structures. The exact usage of these varies; consult the< br >
Glossary and relevant chapters for more details on these.< / p >
< h1 id = "the-stacks" > The Stacks< / h1 >
< p > The stacks are a defining feature of Forth. They are are used< br >
to pass data between words and to track return addresses for< br >
function calls.< / p >
< p > RETRO always has two stacks, and optionally (if built with< br >
floating point support) a third.< / p >
< h2 id = "data-stack" > Data Stack< / h2 >
< p > This is the primary stack. Values are placed here, passed to< br >
words which consume them and then return results. When I< br >
refer to “the stack”, this is the one I mean. Learning to use< br >
the stack is a crucial part to making effective use of RETRO.< / p >
< h3 id = "placing-values-on-the-stack" > Placing Values On The Stack< / h3 >
< p > Values can be placed on the stack directly.< / p >
< table >
< thead >
< tr >
< th > Example< / th >
< th > Action< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > < code > #300123< / code > < / td >
< td > Push the number < code > 300123< / code > to the stack< / td >
< / tr >
< tr >
< td > < code > $h< / code > < / td >
< td > Push the ASCII code for < code > h< / code > to the stack< / td >
< / tr >
< tr >
< td > < code > 'hello_world< / code > < / td >
< td > Push a pointer to a string to the stack< / td >
< / tr >
< tr >
< td > < code > & fetch< / code > < / td >
< td > Push the address of < code > fetch< / code > to the stack< / td >
< / tr >
< / tbody >
< / table > < h3 id = "reordering-the-stack" > Reordering The Stack< / h3 >
< p > RETRO provides a number of < em > shufflers< / em > for reordering items< br >
on the stack.< / p >
< p > Some of the most common ones are:< / p >
< table >
< thead >
< tr >
< th > Word< / th >
< th > Before< / th >
< th > After< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > dup< / td >
< td > #1< / td >
< td > #1 #1< / td >
< / tr >
< tr >
< td > drop< / td >
< td > #1 #2< / td >
< td > #1< / td >
< / tr >
< tr >
< td > swap< / td >
< td > #1 #2< / td >
< td > #2 #1< / td >
< / tr >
< tr >
< td > over< / td >
< td > #1 #2< / td >
< td > #1 #2 #1< / td >
< / tr >
< tr >
< td > tuck< / td >
< td > #1 #2< / td >
< td > #2 #1 #2< / td >
< / tr >
< tr >
< td > nip< / td >
< td > #1 #2< / td >
< td > #2< / td >
< / tr >
< tr >
< td > rot< / td >
< td > #1 #2 #3< / td >
< td > #3 #1 #2< / td >
< / tr >
< / tbody >
< / table > < p > You can use < code > push< / code > and < code > pop< / code > to move values to and from the< br >
address stack. Make sure you < code > pop< / code > them back before the word< br >
ends or RETRO will crash. These two words can not be used< br >
at the interpreter.< / p >
< p > There is also a special one, < code > reorder< / code > , which allows for big< br >
stack restructuring. This is slow but can be very useful.< / p >
< p > As an example, let’ s say we have four values:< / p >
< pre > < code > #1 #2 #3 #4
< / code > < / pre >
< p > And we want them to become:< / p >
< pre > < code > #4 #3 #2 #1
< / code > < / pre >
< p > Doing this with the basic shufflers is difficult. You could end< br >
up with something similar to:< / p >
< pre > < code > swap rot push rot pop swap
< / code > < / pre >
< p > But with < code > reorder< / code > , you can just express the before and after< br >
states:< / p >
< pre > < code > 'abcd 'dcba reorder
< / code > < / pre >
< h3 id = "resetting-the-stack" > Resetting The Stack< / h3 >
< p > If you need to quickly empty the stack, use < code > reset< / code > .< / p >
< h3 id = "get-the-stack-depth" > Get The Stack Depth< / h3 >
< p > To find out how many items are on the stack, use < code > depth< / code > .< / p >
< h3 id = "displaying-the-stack" > Displaying The Stack< / h3 >
< p > You can display the stack by running < code > dump-stack< / code > .< / p >
< h3 id = "data-flow-combinators" > Data Flow Combinators< / h3 >
< p > RETRO provides < em > combinators< / em > for working with data order on< br >
the stack. These are covered in a later chapter and are worth< br >
learning to use as they can help provide a cleaner, more< br >
structured means of working.< / p >
< h3 id = "tips-1" > Tips< / h3 >
< p > The stack is < em > not< / em > an array in addressable memory. Don’ t try< br >
to treat it like one.< / p >
< h2 id = "address-stack" > Address Stack< / h2 >
< p > This stack primarily holds return addresses for function calls.< br >
You normally won’ t need to directly interact with this stack,< br >
but you can use < code > push< / code > and < code > pop< / code > to move values between the< br >
data stack and this.< / p >
< h2 id = "floating-point-stack" > Floating Point Stack< / h2 >
< p > If you are using a build with floating point support a third< br >
stack will be present. Floating point values are kept and< br >
passed between words using this.< / p >
< p > See the Floating Point chapter for more details on this.< / p >
< h2 id = "tips-2" > Tips< / h2 >
< p > I recommend keeping the data stack shallow. Don’ t try to juggle< br >
too much; it’ s better to factor definitions into shorter ones< br >
that deal with simpler parts of the stack values than to have< br >
a big definition with a lot of complex shuffling.< / p >
< h2 id = "notes" > Notes< / h2 >
< p > The standard system is configured with a very deep data stack< br >
(around 2,000 items) and an address stack that is 3x deeper.< br >
In actual use, your programs are unlikely to ever need this,< br >
but if you do, keep the limits in mind.< / p >
< h1 id = "working-with-arrays" > Working With Arrays< / h1 >
< p > RETRO offers a number of words for operating on statically sized< br >
arrays.< / p >
< h2 id = "namespace" > Namespace< / h2 >
< p > The words operating on arrays are kept in an < code > a:< / code > namespace.< / p >
< h2 id = "creating-arrays" > Creating Arrays< / h2 >
< p > The easiest way to create an array is to wrap the values in a< br >
< code > {< / code > and < code > }< / code > pair:< / p >
< pre > < code > { #1 #2 #3 #4 }
{ 'this 'is 'an 'array 'of 'strings }
{ 'this 'is 'a 'mixed 'array #1 #2 #3 }
< / code > < / pre >
< p > You can also make an array from a quotation which returns< br >
values and the number of values to store in the a:< / p >
< pre > < code > [ #1 #2 #3 #3 ] a:counted-results
[ #1 #2 #3 #3 ] a:make
< / code > < / pre >
< h2 id = "accessing-elements" > Accessing Elements< / h2 >
< p > You can access a specific value with < code > a:nth< / code > and < code > fetch< / code > or< br >
< code > store< / code > :< / p >
< pre > < code > { #1 #2 #3 #4 } #3 a:nth fetch
< / code > < / pre >
< h2 id = "find-the-length" > Find The Length< / h2 >
< p > Use < code > a:length< / code > to find the size of the array.< / p >
< pre > < code > { #1 #2 #3 #4 } a:length
< / code > < / pre >
< h2 id = "duplicate" > Duplicate< / h2 >
< p > Use < code > a:dup< / code > to make a copy of an a:< / p >
< pre > < code > { #1 #2 #3 #4 } a:dup
< / code > < / pre >
< h2 id = "filtering" > Filtering< / h2 >
< p > RETRO provides < code > a:filter< / code > which extracts matching values< br >
from an array. This is used like:< / p >
< pre > < code > { #1 #2 #3 #4 #5 #6 #7 #8 } [ n:even? ] a:filter
< / code > < / pre >
< p > The quote will be passed each value in the array and should< br >
return TRUE or FALSE. Values that lead to TRUE will be collected< br >
into a new array.< / p >
< h2 id = "mapping" > Mapping< / h2 >
< p > < code > a:map< / code > applies a quotation to each item in an array and< br >
constructs a new array from the returned values.< / p >
< p > Example:< / p >
< pre > < code > { #1 #2 #3 } [ #10 * ] a:map
< / code > < / pre >
< h2 id = "reduce" > Reduce< / h2 >
< p > < code > a:reduce< / code > takes an array, a starting value, and a quote. It< br >
executes the quote once for each item in the array, passing the< br >
item and the value to the quote. The quote should consume both< br >
and return a new value.< / p >
< pre > < code > { #1 #2 #3 } #0 [ + ] a:reduce
< / code > < / pre >
< h2 id = "search" > Search< / h2 >
< p > RETRO provides < code > a:contains?< / code > and < code > a:contains-string?< / code > < br >
to search an array for a value (either a number or string) and< br >
return either TRUE or FALSE.< / p >
< pre > < code > #100 { #1 #2 #3 } a:contains?
'test { 'abc 'def 'test 'ghi } a:contains-string?
< / code > < / pre >
< h1 id = "working-with-a-buffer" > Working With a Buffer< / h1 >
< p > RETRO provides words for operating on a linear memory area.< br >
This can be useful in building strings or custom data< br >
structures.< / p >
< h2 id = "namespace-1" > Namespace< / h2 >
< p > Words operating on the buffer are kept in the < code > buffer:< / code > < br >
namespace.< / p >
< h2 id = "implementation" > Implementation< / h2 >
< p > A buffer is a linear sequence of memory. The buffer words< br >
provide a means of incrementally storing and retrieving< br >
values from it.< / p >
< p > The buffer words keep track of the start and end of the< br >
buffer. They also ensure that an < code > ASCII:NULL< / code > is written< br >
after the last value, which make using them for string< br >
data easy.< / p >
< h2 id = "limitations" > Limitations< / h2 >
< p > Only one buffer can be active at a time. RETRO provides a< br >
< code > buffer:preserve< / code > combinator to allow using a second one< br >
before returning to the prior one.< / p >
< h2 id = "set-the-active-buffer" > Set The Active Buffer< / h2 >
< p > To set a buffer as the active one use < code > buffer:set< / code > . This takes< br >
an address.< / p >
< p > The buffer will be assumed to be empty. The inital value will< br >
be set to ASCII:NULL.< / p >
< h2 id = "add-value" > Add Value< / h2 >
< p > Use < code > buffer:add< / code > to append a value to the buffer. This takes< br >
a single value and will also add an ASCII:NULL after the end< br >
of the buffer.< / p >
< h2 id = "fetch-last-value" > Fetch Last Value< / h2 >
< p > To return the last value in the buffer you can use < code > buffer:get< / code > .< br >
This removes the value and sets an ASCII:NULL in the memory< br >
location the returned value occupied.< / p >
< h2 id = "get-data-about-the-buffer" > Get Data About The Buffer< / h2 >
< p > RETRO provides < code > buffer:start< / code > to get the initial address in< br >
the buffer, < code > buffer:end< / code > to get the last address (ignoring the< br >
ASCII:NULL), and < code > buffer:size< / code > to return the number of values< br >
in the buffer.< / p >
< h2 id = "reset" > Reset< / h2 >
< p > You can reset a buffer to the empty state using < code > buffer:empty< / code > .< / p >
< h2 id = "example" > Example< / h2 >
< p > To begin, create a memory region to use as a buffer.< / p >
< pre > < code > 'Test d:create #1025 allot
< / code > < / pre >
< p > Then you can set this as the current buffer:< / p >
< pre > < code > & Test buffer:set
< / code > < / pre >
< p > When a buffer is set, the vocabulary sets an internal< br >
index to the first address in it. This will be< br >
incremented when you add data and decremented when you< br >
remove data.< / p >
< p > Let’ s add some stuff using < code > buffer:add< / code > :< / p >
< pre > < code > #100 buffer:add
#200 buffer:add
#300 buffer:add
< / code > < / pre >
< p > And then retreive the values:< / p >
< pre > < code > buffer:get n:put nl
buffer:get n:put nl
buffer:get n:put nl
< / code > < / pre >
< p > You can remove all values using < code > buffer:empty< / code > :< / p >
< pre > < code > #100 buffer:add
#200 buffer:add
#300 buffer:add
buffer:empty
< / code > < / pre >
< p > And ask the buffer how many items it contains:< / p >
< pre > < code > buffer:size n:put nl
#100 buffer:add
#200 buffer:add
#300 buffer:add
buffer:size n:put nl
buffer:empty
< / code > < / pre >
< p > The other functions are < code > buffer:start< / code > , which returns< br >
the address of the buffer, < code > buffer:end< / code > , which returns< br >
the address of the last value, and < code > buffer:preserve< / code > .< br >
The first is easy to demo:< / p >
< pre > < code > buffer:start Test eq? n:put nl
< / code > < / pre >
< p > The last one is useful. Only one buffer is ever active< br >
at a given time. The < code > buffer:preserve< / code > combinator lets< br >
you execute a word, saving and restoring the current< br >
buffer indexes. So the word could assign and use a new< br >
buffer and this will reset the previous one after< br >
control returns.< / p >
< p > There are a few notes that need to be considered. The< br >
preserve combinator saves the start and current index< br >
but < em > not< / em > the contents. If the word you call uses the< br >
same buffer, the contents will remain altered.< / p >
< p > Finally, the buffer words have one interesting trait:< br >
they store an ASCII NULL after adding each item to the< br >
buffer. This lets one use them to build strings easily.< / p >
< pre > < code > Test buffer:set
$h buffer:add
$e buffer:add
$l buffer:add
$l buffer:add
$o buffer:add
$, buffer:add
#32 buffer:add
$w buffer:add
$o buffer:add
$r buffer:add
$l buffer:add
$d buffer:add
buffer:start s:put nl
< / code > < / pre >
< h1 id = "working-with-characters" > Working With Characters< / h1 >
< p > RETRO provides words for working with ASCII characters.< / p >
< h2 id = "prefix" > Prefix< / h2 >
< p > Character constants are returned using the < code > $< / code > prefix.< / p >
< h2 id = "namespace-2" > Namespace< / h2 >
< p > Words operating on characters are in the < code > c:< / code > namespace.< / p >
< h2 id = "classification" > Classification< / h2 >
< p > RETRO provides a number of words to determine if a character< br >
fits into predefined groups.< / p >
< p > The primary words for this are:< / p >
< ul >
< li > < code > c:consonant?< / code > < / li >
< li > < code > c:digit?< / code > < / li >
< li > < code > c:letter?< / code > < / li >
< li > < code > c:lowercase?< / code > < / li >
< li > < code > c:uppercase?< / code > < / li >
< li > < code > c:visible?< / code > < / li >
< li > < code > c:vowel?< / code > < / li >
< li > < code > c:whitespace?< / code > < / li >
< / ul >
< p > There are also corresponding “not” forms:< / p >
< ul >
< li > < code > c:-consonant?< / code > < / li >
< li > < code > c:-digit?< / code > < / li >
< li > < code > c:-lowercase?< / code > < / li >
< li > < code > c:-uppercase?< / code > < / li >
< li > < code > c:-visible?< / code > < / li >
< li > < code > c:-vowel?< / code > < / li >
< li > < code > c:-whitespace?< / code > < / li >
< / ul >
< p > All of these take a character and return either a < code > TRUE< / code > or< br >
< code > FALSE< / code > flag.< / p >
< h2 id = "conversions" > Conversions< / h2 >
< p > A few words are provided to convert case. Each takes a character< br >
and returns the modified character.< / p >
< ul >
< li > < code > c:to-lower< / code > < / li >
< li > < code > c:to-number< / code > < / li >
< li > < code > c:to-upper< / code > < / li >
< li > < code > c:toggle-case< / code > < / li >
< / ul >
< p > RETRO also has < code > c:to-string< / code > , which takes a character and< br >
creates a new temporary string with the character.< / p >
< h2 id = "io" > I/O< / h2 >
< p > Characters can be displayed using < code > c:put< / code > .< / p >
< pre > < code > $a c:put
< / code > < / pre >
< p > With the default system on BSD, Linux, and macOS (and other< br >
Unix style hosts), < code > c:get< / code > is provided to read input. This< br >
may be buffered, depending on the host.< / p >
< h1 id = "working-with-the-dictionary" > Working With The Dictionary< / h1 >
< p > The Dictionary is a linked list containing the dictionary< br >
headers.< / p >
< h2 id = "namespace-3" > Namespace< / h2 >
< p > Words operating on the dictionary are in the < code > d:< / code > namespace.< / p >
< h2 id = "variables" > Variables< / h2 >
< p > < code > Dictionary< / code > is a variable holding a pointer to the most recent< br >
header.< / p >
< h2 id = "header-structure" > Header Structure< / h2 >
< p > Each entry follows the following structure:< / p >
< pre > < code > Offset Contains
------ ---------------------------
0000 Link to Prior Header
0001 Link to XT
0002 Link to Class Handler
0003+ Word name (null terminated)
< / code > < / pre >
< p > RETRO provides words for accessing the fields in a portable< br >
manner. It’ s recommended to use these to allow for future< br >
revision of the header structure.< / p >
< h2 id = "accessing-fields" > Accessing Fields< / h2 >
< p > Given a pointer to a header, you can use < code > d:xt< / code > , < code > d:class< / code > ,< br >
and < code > d:name< / code > to access the address of each specific field.< br >
There is no < code > d:link< / code > , as the link will always be the first< br >
field.< / p >
< h2 id = "shortcuts-for-the-latest-header" > Shortcuts For The Latest Header< / h2 >
< p > RETRO provides several words for operating on the most recent< br >
header.< / p >
< p > < code > d:last< / code > returns a pointer to the latest header. < code > d:last< xt> < / code > < br >
will give the contents of the < code > d:xt< / code > field for the latest< br >
header. There are also < code > d:last< class> < / code > and < code > d:last< name> < / code > .< / p >
< h2 id = "adding-headers" > Adding Headers< / h2 >
< p > Two words exist for making new headers. The easy one is< br >
< code > d:create< / code > . This takes a string for the name and makes a< br >
new header with the class set to < code > class:data< / code > and the XT< br >
field pointing to < code > here< / code > .< / p >
< p > Example:< / p >
< pre > < code > 'Base d:create
< / code > < / pre >
< p > The other is < code > d:add-header< / code > . This takes a string, a pointer< br >
to the class handler, and a pointer for the XT field and< br >
builds a new header using these.< / p >
< p > Example:< / p >
< pre > < code > 'Base & class:data #10000 d:add-header
< / code > < / pre >
< h2 id = "searching" > Searching< / h2 >
< p > RETRO provides two words for searching the dictionary.< / p >
< p > < code > d:lookup< / code > takes a string and tries to find it in the< br >
dictionary. It will return a pointer to the dictionary header< br >
or a value of zero if the word was not found.< / p >
< p > < code > d:lookup-xt< / code > takes a pointer and will return the dictionary< br >
header that has this as the < code > d:xt< / code > field, or zero if no match< br >
is found.< / p >
< h2 id = "iteration" > Iteration< / h2 >
< p > You can use the < code > d:for-each< / code > combinator to iterate over all< br >
entries in the dictionary. For instance, to display the names< br >
of all words:< / p >
< pre > < code > [ d:name s:put sp ] d:for-each
< / code > < / pre >
< p > For each entry, this combinator will push a pointer to the< br >
entry to the stack and call the quotation.< / p >
< h2 id = "listing-words" > Listing Words< / h2 >
< p > Most Forth systems provide WORDS for listing the names of all< br >
words in the dictionary. RETRO does as well, but this is named< br >
< code > d:words< / code > .< / p >
< p > This isn’ t super useful as looking through several hundred< br >
names is annoying. RETRO also provides < code > d:words-with< / code > to help< br >
in filtering the results.< / p >
< p > Example:< / p >
< pre > < code > 'class: d:words-with
< / code > < / pre >
< h1 id = "working-with-floating-point" > Working With Floating Point< / h1 >
< p > Some RETRO systems include support for floating point numbers.< br >
When present, this is built over the system < code > libm< / code > using the< br >
C < code > double< / code > type.< / p >
< p > Floating point values are typically 64 bit IEEE 754 double< br >
precision (1 bit for the sign, 11 bits for the exponent, and< br >
the remaining 52 bits for the value), i.e. 15 decimal digits< br >
of precision.< / p >
< h2 id = "prefix-1" > Prefix< / h2 >
< p > Floating point numbers start with a < code > .< / code > < / p >
< p > Examples:< / p >
< p > Token Value< br >
.1 1.0< br >
.0.5 0.5< br >
.-.4 -0.4< br >
.1.3 1.3< / p >
< h2 id = "namespace-4" > Namespace< / h2 >
< p > Floating point words are in the < code > f:< / code > namespace. There is also< br >
a related < code > e:< / code > namespace for < em > encoded values< / em > , which allows< br >
storing of floats in standard memory.< / p >
< h2 id = "operation" > Operation< / h2 >
< p > Floating point values exist on a separate stack, and are bigger< br >
than the standard memory cells, so can not be directly stored< br >
and fetched from memory.< / p >
< p > The floating point system also provides an alternate stack that< br >
can be used to temporarily store values.< / p >
< p > The following words exist for arranging values on the floating< br >
point stack. These are direct analogs to the non-prefiexd words< br >
for dealing with the data stack.< / p >
< ul >
< li > < code > f:nip< / code > < / li >
< li > < code > f:over< / code > < / li >
< li > < code > f:depth< / code > < / li >
< li > < code > f:drop< / code > < / li >
< li > < code > f:drop-pair< / code > < / li >
< li > < code > f:dup< / code > < / li >
< li > < code > f:dup-pair< / code > < / li >
< li > < code > f:dump-stack< / code > < / li >
< li > < code > f:tuck< / code > < / li >
< li > < code > f:swap< / code > < / li >
< li > < code > f:rot< / code > < / li >
< / ul >
< p > For the secondary floating point stack, the following words are< br >
provided:< / p >
< ul >
< li > < code > f:push< / code > < / li >
< li > < code > f:pop< / code > < / li >
< li > < code > f:adepth< / code > < / li >
< li > < code > f:dump-astack< / code > < / li >
< / ul >
< h2 id = "constants" > Constants< / h2 >
< pre > < code > | Name | Returns |
| -------- | ----------------- |
| `f:E` | Euler's number |
| `f:-INF` | Negative infinity |
| `f:INF` | Positive infinity |
| `f:NAN` | Not a Number |
| `f:PI` | PI |
< / code > < / pre >
< h2 id = "comparisons" > Comparisons< / h2 >
< p > The basic set of comparators are the same as those for< br >
operating on integers. These are:< / p >
< ul >
< li > < code > f:-eq?< / code > < / li >
< li > < code > f:between?< / code > < / li >
< li > < code > f:eq?< / code > < / li >
< li > < code > f:gt?< / code > < / li >
< li > < code > f:lt?< / code > < / li >
< li > < code > f:negative?< / code > < / li >
< li > < code > f:positive?< / code > < / li >
< li > < code > f:case< / code > < / li >
< / ul >
< p > There are also a few additions for comparing to special values< br >
like infinity and NaN.< / p >
< ul >
< li > < code > f:-inf?< / code > < / li >
< li > < code > f:inf?< / code > < / li >
< li > < code > f:nan?< / code > < / li >
< / ul >
< h2 id = "basic-math" > Basic Math< / h2 >
< ul >
< li > < code > f:*< / code > < / li >
< li > < code > f:+< / code > < / li >
< li > < code > f:-< / code > < / li >
< li > < code > f:/< / code > < / li >
< li > < code > f:abs< / code > < / li >
< li > < code > f:floor< / code > < / li >
< li > < code > f:inc< / code > < / li >
< li > < code > f:limit< / code > < / li >
< li > < code > f:max< / code > < / li >
< li > < code > f:min< / code > < / li >
< li > < code > f:negate< / code > < / li >
< li > < code > f:power< / code > < / li >
< li > < code > f:ceiling< / code > < / li >
< li > < code > f:dec< / code > < / li >
< li > < code > f:log< / code > < / li >
< li > < code > f:sqrt< / code > < / li >
< li > < code > f:square< / code > < / li >
< li > < code > f:round< / code > < / li >
< li > < code > f:sign< / code > < / li >
< li > < code > f:signed-sqrt< / code > < / li >
< li > < code > f:signed-square< / code > < / li >
< / ul >
< h2 id = "geometry" > Geometry< / h2 >
< p > RETRO provides a small number of words for doing geometric< br >
related calculations.< / p >
< table >
< thead >
< tr >
< th > Word< / th >
< th > Returns< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > < code > f:acos< / code > < / td >
< td > arc cosine< / td >
< / tr >
< tr >
< td > < code > f:asin< / code > < / td >
< td > arc sine< / td >
< / tr >
< tr >
< td > < code > f:atan< / code > < / td >
< td > arc tangent< / td >
< / tr >
< tr >
< td > < code > f:cos< / code > < / td >
< td > cosine< / td >
< / tr >
< tr >
< td > < code > f:sin< / code > < / td >
< td > sine< / td >
< / tr >
< tr >
< td > < code > f:tan< / code > < / td >
< td > tangent< / td >
< / tr >
< / tbody >
< / table > < h2 id = "storage-and-retrieval" > Storage and Retrieval< / h2 >
< p > By leveraging the encoded value functions, RETRO is able to< br >
allow storage of floating point values in memory. This does< br >
have a tradeoff in accuracy as the memory cells are considerably< br >
smaller than a full floating point size.< / p >
< p > You can use < code > f:fetch< / code > to fetch a floating point value and< br >
< code > f:store< / code > to store one.< / p >
< p > If you need more precision, try Kiyoshi Yoneda’ s FloatVar< br >
example (< code > example/FloatVar.forth< / code > ), which includes words to< br >
store and retrieve values using multiple cells.< / p >
< ul >
< li > < code > f:to-number< / code > < / li >
< li > < code > f:to-string< / code > < / li >
< / ul >
< h2 id = "io-1" > I/O< / h2 >
< p > The floating point vocabulary has a single I/O word, < code > f:put< / code > ,< br >
for the display of floating point numbers.< / p >
< h2 id = "encoded-values" > Encoded Values< / h2 >
< p > RETRO provides a means of encoding and decoding floating point< br >
values into standard integer cells. This is based on the paper< br >
“Encoding floating point values to shorter integers” by Kiyoshi< br >
Yoneda and Charles Childers.< / p >
< ul >
< li > < code > f:E1< / code > < / li >
< li > < code > f:to-e< / code > < / li >
< li > < code > e:-INF< / code > < / li >
< li > < code > e:-inf?< / code > < / li >
< li > < code > e:INF< / code > < / li >
< li > < code > e:MAX< / code > < / li >
< li > < code > e:MIN< / code > < / li >
< li > < code > e:NAN< / code > < / li >
< li > < code > e:clip< / code > < / li >
< li > < code > e:inf?< / code > < / li >
< li > < code > e:max?< / code > < / li >
< li > < code > e:min?< / code > < / li >
< li > < code > e:n?< / code > < / li >
< li > < code > e:nan?< / code > < / li >
< li > < code > e:put< / code > < / li >
< li > < code > e:to-f< / code > < / li >
< li > < code > e:zero?< / code > < / li >
< / ul >
< h1 id = "working-with-numbers" > Working With Numbers< / h1 >
< p > Numbers in RETRO are signed, 32 bit integers with a range of< br >
-2,147,483,648 to 2,147,483,647.< / p >
< h2 id = "token-prefix" > Token Prefix< / h2 >
< p > All numbers start with a < code > #< / code > prefix.< / p >
< h2 id = "namespace-5" > Namespace< / h2 >
< p > Most words operating on numbers are in the < code > n:< / code > namespace.< / p >
< h1 id = "working-with-pointers" > Working With Pointers< / h1 >
< h2 id = "prefix-2" > Prefix< / h2 >
< p > Pointers are returned by the < code > & < / code > prefix.< / p >
< h2 id = "examples" > Examples< / h2 >
< pre > < code > 'Base var
& Base fetch
#10 & Base store
#10 & n:inc call
< / code > < / pre >
< h2 id = "notes-1" > Notes< / h2 >
< p > The use of < code > & < / code > to get a pointer to a data structure (with a< br >
word class of < code > class:data< / code > ) is not required. I like to use it< br >
anyway as it makes my intent a little clearer.< / p >
< p > Pointers are useful with combinators. Consider:< / p >
< pre > < code > :abs dup n:negative? [ n:negate ] if ;
< / code > < / pre >
< p > Since the target quote body is a single word, it is more< br >
efficient to use a pointer instead:< / p >
< pre > < code > :abs dup n:negative? & n:negate if ;
< / code > < / pre >
< p > The advantages are speed (saves a level of call/return by< br >
avoiding the quotation) and size (for the same reason).< br >
This may be less readable though, so consider the balance< br >
of performance to readability when using this approach.< / p >
< h1 id = "working-with-strings" > Working With Strings< / h1 >
< p > Strings in RETRO are NULL terminated sequences of values< br >
representing characters. Being NULL terminated, they can’ t< br >
contain a NULL (ASCII 0).< / p >
< p > The character words in RETRO are built around ASCII, but< br >
strings can contain UTF8 encoded data if the host platform< br >
allows. Words like < code > s:length< / code > will return the number of bytes,< br >
not the number of logical characters in this case.< / p >
< h2 id = "prefix-3" > Prefix< / h2 >
< p > Strings begin with a single < code > '< / code > .< / p >
< pre > < code > 'Hello
'This_is_a_string
'This_is_a_much_longer_string_12345_67890_!!!
< / code > < / pre >
< p > RETRO will replace spaces with underscores. If you need both< br >
spaces and underscores in a string, escape the underscores and< br >
use < code > s:format< / code > :< / p >
< pre > < code > 'This_has_spaces_and_under\_scored_words. s:format
< / code > < / pre >
< h2 id = "namespace-6" > Namespace< / h2 >
< p > Words operating on strings are in the < code > s:< / code > namespace.< / p >
< h2 id = "lifetime" > Lifetime< / h2 >
< p > At the interpreter, strings get allocated in a rotating buffer.< br >
This is used by the words operating on strings, so if you need< br >
to keep them around, use < code > s:keep< / code > or < code > s:copy< / code > to move them to< br >
more permanent storage.< / p >
< p > In a definition, the string is compiled inline and so is in< br >
permanent memory.< / p >
< p > You can manually manage the string lifetime by using < code > s:keep< / code > < br >
to place it into permanent memory or < code > s:temp< / code > to copy it to< br >
the rotating buffer.< / p >
< h2 id = "mutability" > Mutability< / h2 >
< p > Strings are mutable. If you need to ensure that a string is< br >
not altered, make a copy before operating on it or see the< br >
individual glossary entries for notes on words that may do< br >
this automatically.< / p >
< h2 id = "searching-1" > Searching< / h2 >
< p > RETRO provides four words for searching within a string.< / p >
< p > < code > s:contains-char?< / code > < br >
< code > s:contains-string?< / code > < br >
< code > s:index-of< / code > < br >
< code > s:index-of-string< / code > < / p >
< h2 id = "comparisons-1" > Comparisons< / h2 >
< p > < code > s:eq?< / code > < br >
< code > s:case< / code > < / p >
< h2 id = "extraction" > Extraction< / h2 >
< p > To obtain a new string containing the first < code > n< / code > characters from< br >
a source string, use < code > s:left< / code > :< / p >
< pre > < code > 'Hello_World #5 s:left
< / code > < / pre >
< p > To obtain a new string containing the last < code > n< / code > characters from< br >
a source string, use < code > s:right< / code > :< / p >
< pre > < code > 'Hello_World #5 s:right
< / code > < / pre >
< p > If you need to extract data from the middle of the string, use< br >
< code > s:substr< / code > . This takes a string, the offset of the first< br >
character, and the number of characters to extract.< / p >
< pre > < code > 'Hello_World #3 #5 s:substr
< / code > < / pre >
< h2 id = "joining" > Joining< / h2 >
< p > You can use < code > s:append< / code > or < code > s:prepend< / code > to merge two strings.< / p >
< pre > < code > 'First 'Second s:append
'Second 'First s:prepend
< / code > < / pre >
< h2 id = "tokenization" > Tokenization< / h2 >
< p > < code > s:tokenize< / code > < br >
< code > s:tokenize-on-string< / code > < br >
< code > s:split< / code > < br >
< code > s:split-on-string< / code > < / p >
< h2 id = "conversions-1" > Conversions< / h2 >
< p > To convert the case of a string, RETRO provides < code > s:to-lower< / code > < br >
and < code > s:to-upper< / code > .< / p >
< p > < code > s:to-number< / code > is provided to convert a string to an integer< br >
value. This has a few limitations:< / p >
< ul >
< li > only supports decimal< / li >
< li > non-numeric characters will result in incorrect values< / li >
< / ul >
< h2 id = "cleanup" > Cleanup< / h2 >
< p > RETRO provides a handful of words for cleaning up strings.< / p >
< p > < code > s:chop< / code > will remove the last character from a string. This< br >
is done by replacing it with an ASCII:NULL.< / p >
< p > < code > s:trim< / code > removes leading and trailing whitespace from a string.< br >
For more control, there is also < code > s:trim-left< / code > and < code > s:trim-right< / code > < br >
which let you trim just the leading or trailing end as desired.< / p >
< h2 id = "combinators" > Combinators< / h2 >
< p > < code > s:for-each< / code > < br >
< code > s:filter< / code > < br >
< code > s:map< / code > < / p >
< h2 id = "other" > Other< / h2 >
< p > < code > s:evaluate< / code > < br >
< code > s:copy< / code > < br >
< code > s:reverse< / code > < br >
< code > s:hash< / code > < br >
< code > s:length< / code > < br >
< code > s:replace< / code > < br >
< code > s:format< / code > < br >
< code > s:empty< / code > < / p >
< h2 id = "controlling-the-temporary-buffers" > Controlling The Temporary Buffers< / h2 >
< p > As dicussed in the Lifetime subsection, temporary strings are< br >
allocated in a rotating buffer. The details of this can be< br >
altered by updating two variables.< / p >
< table >
< thead >
< tr >
< th > Variable< / th >
< th > Holds< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > TempStrings< / td >
< td > The number of temporary strings< / td >
< / tr >
< tr >
< td > TempStringMax< / td >
< td > The maximum length of a temporary string< / td >
< / tr >
< / tbody >
< / table > < p > For example, to increase the number of temporary strings to< br >
48:< / p >
< pre > < code > #48 !TempStrings
< / code > < / pre >
< p > The defaults are:< / p >
< table >
< thead >
< tr >
< th > Variable< / th >
< th > Default< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > TempStrings< / td >
< td > 32< / td >
< / tr >
< tr >
< td > TempStringMax< / td >
< td > 512< / td >
< / tr >
< / tbody >
< / table > < p > It’ s also important to note that altering these will affect< br >
the memory map for all temporary buffers. Do not use anything< br >
already in the buffers after updating these or you will risk< br >
data corruption and possible crashes.< / p >
< h1 id = "working-with-assembly-language" > Working With Assembly Language< / h1 >
< p > RETRO runs on a virtual machine called Nga. It provides a< br >
standard assembler for this called < em > Muri< / em > .< / p >
< p > Muri is a simple, multipass model that’ s not fancy, but< br >
suffices for RETRO’ s needs.< / p >
< h2 id = "assembling-a-standalone-file" > Assembling A Standalone File< / h2 >
< p > A small example (< em > test.muri< / em > )< / p >
< pre > < code > ~~~
i liju....
r main
: c:put
i liiire..
i 0
: main
i lilica..
d 97
i liju....
r main
~~~
< / code > < / pre >
< p > Assembling it:< / p >
< pre > < code > retro-muri test.muri
< / code > < / pre >
< p > So breaking down: Muri extracts the assembly code blocks to< br >
assemble, then proceeds to do the assembly. Each source line< br >
starts with a directive, followed by a space, and then ending< br >
with a value.< / p >
< dl >
< dt > The directives are:< / dt >
< dd > value is a label< br >
i value is an instruction bundle< br >
d value is a numeric value< br >
r value is a reference< br >
s value is a string to inline< / dd >
< / dl >
< p > Instructions for Nga are provided as bundles. Each memory< br >
location can store up to four instructions. And each instruction< br >
gets a two character identifier.< / p >
< p > From the list of instructions:< / p >
< pre > < code > 0 nop 5 push 10 ret 15 fetch 20 div 25 zret
1 lit 6 pop 11 eq 16 store 21 and 26 end
2 dup 7 jump 12 neq 17 add 22 or 27 ienum
3 drop 8 call 13 lt 18 sub 23 xor 28 iquery
4 swap 9 ccall 14 gt 19 mul 24 shift 29 iinvoke
< / code > < / pre >
< p > This reduces to:< / p >
< pre > < code > 0 .. 5 pu 10 re 15 fe 20 di 25 zr
1 li 6 po 11 eq 16 st 21 an 26 en
2 du 7 ju 12 ne 17 ad 22 or 27 ie
3 dr 8 ca 13 lt 18 su 23 xo 28 iq
4 sw 9 cc 14 gt 19 mu 24 sh 29 ii
< / code > < / pre >
< p > Most are just the first two letters of the instruction name. I< br >
use < code > ..< / code > instead of < code > no< / code > for < code > NOP< / code > , and the first letter of< br >
each I/O instruction name. So a bundle may look like:< / p >
< pre > < code > dumure..
< / code > < / pre >
< p > (This would correspond to < code > dup multiply return nop< / code > ).< / p >
< h2 id = "runtime-assembler" > Runtime Assembler< / h2 >
< p > RETRO also has a runtime variation of Muri that can be used< br >
when you need to generate more optimal code. So one can write:< / p >
< pre > < code > :n:square dup * ;
< / code > < / pre >
< p > Or:< / p >
< pre > < code > :n:square as{ 'dumure.. i }as ;
< / code > < / pre >
< p > The second one will be faster, as the entire definition is one< br >
bundle, which reduces memory reads and decoding by 2/3.< / p >
< p > Doing this is less readable, so I only recommend doing so after< br >
you have finalized working RETRO level code and determined the< br >
best places to optimize.< / p >
< p > The runtime assembler has the following directives:< / p >
< pre > < code > i value is an instruction bundle
d value is a numeric value
r value is a reference
< / code > < / pre >
< p > Additionally, in the runtime assembler, these are reversed:< / p >
< pre > < code > 'dudumu.. i
< / code > < / pre >
< p > Instead of:< / p >
< pre > < code > i dudumu..
< / code > < / pre >
< h1 id = "lexical-scope" > Lexical Scope< / h1 >
< p > RETRO has a single dictionary, but does provide a means of using< br >
lexical scope to keep this dictionary clean.< / p >
< h2 id = "example-1" > Example< / h2 >
< pre > < code > {{
'A var
:++A & A v:inc ;
---reveal---
:B ++A ++A @A n:put nl ;
}}
< / code > < / pre >
< p > In this example, the lexical namespace is created with < code > {{< / code > . A< br >
variable (< code > A< / code > ) and word (< code > ++A< / code > ) are defined. Then a marker is< br >
set with < code > ---reveal---< / code > . Another word (< code > B< / code > ) is defined, and the< br >
lexical area is closed with < code > }}< / code > .< / p >
< p > The headers between < code > {{< / code > and < code > ---reveal---< / code > are then hidden from< br >
the dictionary, leaving only the headers between < code > ---reveal---< / code > < br >
and < code > }}< / code > exposed.< / p >
< h2 id = "notes-2" > Notes< / h2 >
< p > This only affects word visibility within the scoped area. As an< br >
example:< / p >
< pre > < code > :a #1 ;
{{
:a #2 ;
---reveal---
:b 'a s:evaluate n:put ;
}}
< / code > < / pre >
< p > In this, after < code > }}< / code > closes the area, the < code > :a #2 ;< / code > is hidden and< br >
the < code > s:evaluate< / code > will find the < code > :a #1 ;< / code > when < code > b< / code > is run.< / p >
< h1 id = "internals" > Internals< / h1 >
< p > The next few chapters dive into RETRO’ s architecture. If you< br >
seek to implement a port to a new platform or to extend the< br >
I/O functionality you’ ll find helpful information here.< / p >
< h1 id = "internals-nga-virtual-machine" > Internals: Nga Virtual Machine< / h1 >
< h2 id = "overview" > Overview< / h2 >
< p > At the heart of RETRO is a simple MISC (minimal instruction< br >
set computer) processor for a dual stack architecture.< / p >
< p > This is a very simple and straightforward system. There are< br >
30 instructions. The memory is a linear array of signed 32< br >
bit values. And there are two stacks: one for data and one< br >
for return addresses.< / p >
< h2 id = "instrution-table" > Instrution Table< / h2 >
< table >
< thead >
< tr >
< th > Opcode< / th >
< th > Muri< / th >
< th > Full Name< / th >
< th > Data Stack< / th >
< th > Address Stack< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > 0< / td >
< td > …< / td >
< td > nop< / td >
< td > -< / td >
< td > -< / td >
< / tr >
< tr >
< td > 1< / td >
< td > li< / td >
< td > lit< / td >
< td > -n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 2< / td >
< td > du< / td >
< td > dup< / td >
< td > n-nn< / td >
< td > -< / td >
< / tr >
< tr >
< td > 3< / td >
< td > dr< / td >
< td > drop< / td >
< td > n-< / td >
< td > -< / td >
< / tr >
< tr >
< td > 4< / td >
< td > sw< / td >
< td > swap< / td >
< td > xy-yx< / td >
< td > -< / td >
< / tr >
< tr >
< td > 5< / td >
< td > pu< / td >
< td > push< / td >
< td > n-< / td >
< td > -n< / td >
< / tr >
< tr >
< td > 6< / td >
< td > po< / td >
< td > pop< / td >
< td > -n< / td >
< td > n-< / td >
< / tr >
< tr >
< td > 7< / td >
< td > ju< / td >
< td > jump< / td >
< td > a-< / td >
< td > -< / td >
< / tr >
< tr >
< td > 8< / td >
< td > ca< / td >
< td > call< / td >
< td > a-< / td >
< td > -A< / td >
< / tr >
< tr >
< td > 9< / td >
< td > cc< / td >
< td > conditional call< / td >
< td > af-< / td >
< td > -A< / td >
< / tr >
< tr >
< td > 10< / td >
< td > re< / td >
< td > return< / td >
< td > -< / td >
< td > A-< / td >
< / tr >
< tr >
< td > 11< / td >
< td > eq< / td >
< td > equality< / td >
< td > xy-f< / td >
< td > -< / td >
< / tr >
< tr >
< td > 12< / td >
< td > ne< / td >
< td > inequality< / td >
< td > xy-f< / td >
< td > -< / td >
< / tr >
< tr >
< td > 13< / td >
< td > lt< / td >
< td > less than< / td >
< td > xy-f< / td >
< td > -< / td >
< / tr >
< tr >
< td > 14< / td >
< td > gt< / td >
< td > greater than< / td >
< td > xy-f< / td >
< td > -< / td >
< / tr >
< tr >
< td > 15< / td >
< td > fe< / td >
< td > fetch< / td >
< td > a-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 16< / td >
< td > st< / td >
< td > store< / td >
< td > na-< / td >
< td > -< / td >
< / tr >
< tr >
< td > 17< / td >
< td > ad< / td >
< td > addition< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 18< / td >
< td > su< / td >
< td > subtraction< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 19< / td >
< td > mu< / td >
< td > multiplication< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 20< / td >
< td > di< / td >
< td > divide & remainder< / td >
< td > xy-rq< / td >
< td > -< / td >
< / tr >
< tr >
< td > 21< / td >
< td > an< / td >
< td > bitwise and< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 22< / td >
< td > or< / td >
< td > bitwise or< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 23< / td >
< td > xo< / td >
< td > bitwise xor< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 24< / td >
< td > sh< / td >
< td > shift< / td >
< td > xy-n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 25< / td >
< td > zr< / td >
< td > zero return< / td >
< td > n-?< / td >
< td > -< / td >
< / tr >
< tr >
< td > 26< / td >
< td > en< / td >
< td > end< / td >
< td > -< / td >
< td > -< / td >
< / tr >
< tr >
< td > 27< / td >
< td > ie< / td >
< td > i/o enumerate< / td >
< td > -n< / td >
< td > -< / td >
< / tr >
< tr >
< td > 28< / td >
< td > iq< / td >
< td > i/o query< / td >
< td > n-xy< / td >
< td > -< / td >
< / tr >
< tr >
< td > 29< / td >
< td > ii< / td >
< td > i/o invoke< / td >
< td > …n-< / td >
< td > -< / td >
< / tr >
< / tbody >
< / table > < h2 id = "encoding" > Encoding< / h2 >
< p > Up to four instructions can be packed into each memory cell.< / p >
< p > As an example,< / p >
< pre > < code > Opcode 1 Opcode 2 Opcode 3 Opcode 4
00000000:00000000:00000000:00000000
< / code > < / pre >
< p > If we have a bundle of < code > lidumu..< / code > , it would look like:< / p >
< pre > < code > li du mu ..
00000001:00000010:00010011:00000000
< / code > < / pre >
< p > Each < code > li< / code > should have a value in the following cell(s). These< br >
values will be pushed to the stack. E.g., < code > lili....< / code > and< br >
1, 2:< / p >
< pre > < code > 00000001:00000001:00000000:00000000
00000000 00000000 00000000 00000001 (1)
00000000 00000000 00000000 00000010 (2)
< / code > < / pre >
< h2 id = "shifts" > Shifts< / h2 >
< p > < code > sh< / code > performs a bitwise arithmetic shift operation.< / p >
< p > This takes two values:< / p >
< pre > < code > xy
< / code > < / pre >
< p > And returns a single one:< / p >
< pre > < code > z
< / code > < / pre >
< p > If y is positive, this shifts < code > x< / code > right by < code > y< / code > bits. If negative,< br >
it shifts left.< / p >
< h2 id = "queries-memory-stacks" > Queries: Memory, Stacks< / h2 >
< p > The < code > fe< / code > instruction allows queries of some data related to< br >
the Nga VM state. These are returned by reading from negative< br >
addresses:< / p >
< table >
< thead >
< tr >
< th > Address< / th >
< th > Returns< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > -1< / td >
< td > Data stack depth< / td >
< / tr >
< tr >
< td > -2< / td >
< td > Address stack depth< / td >
< / tr >
< tr >
< td > -3< / td >
< td > Maximum Image Size< / td >
< / tr >
< / tbody >
< / table > < h2 id = "io-devices" > I/O Devices< / h2 >
< p > Nga provides three instructions for interacting with I/O devices.< br >
These are:< / p >
< pre > < code > ie i/o enumerate returns the number of attached devices
iq i/o query returns information about a device
ii i/o interact invokes an interaction with a device
< / code > < / pre >
< p > As an example, with an implementation providing an output source,< br >
a block storage system, and keyboard:< / p >
< pre > < code > ie will return `3` since there are three i/o devices
0 iq will return 0 0, since the first device is a screen (0)
with a version of 0
1 iq will return 1 3, since the second device is a block
storage (3), with a version of 1
2 iq will return 0 1, since the third device is a keyboard
(1), with a version of 0
< / code > < / pre >
< dl >
< dt > In this case, some interactions can be defined:< / dt >
< dd >
< p > c:put< br >
i liiire…< br >
d 0< / p >
< / dd >
< dd >
< p > c:get< br >
i liiire…< br >
d 2< / p >
< / dd >
< / dl >
< p > Setup the stack, push the device ID to the stack, and then use< br >
< code > ii< / code > to invoke the interaction.< / p >
< p > A RETRO system requires one I/O device (a generic output for a< br >
single character). This must be the first device, and must have< br >
a device ID of 0.< / p >
< p > All other devices are optional and can be specified in any order.< / p >
< p > The currently supported and reserved device identifiers are:< / p >
< table >
< thead >
< tr >
< th > ID< / th >
< th > Device Type< / th >
< th > Notes< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > 0000< / td >
< td > Generic Output< / td >
< td > Always present as device 0< / td >
< / tr >
< tr >
< td > 0001< / td >
< td > Keyboard< / td >
< td > < / td >
< / tr >
< tr >
< td > 0002< / td >
< td > Floating Point< / td >
< td > < / td >
< / tr >
< tr >
< td > 0003< / td >
< td > Block Storage< / td >
< td > Raw, 1024 cell blocks< / td >
< / tr >
< tr >
< td > 0004< / td >
< td > Filesystem< / td >
< td > Unix-style Files< / td >
< / tr >
< tr >
< td > 0005< / td >
< td > Network: Gopher< / td >
< td > Make gopher requests< / td >
< / tr >
< tr >
< td > 0006< / td >
< td > Network: HTTP< / td >
< td > Make HTTP requests< / td >
< / tr >
< tr >
< td > 0007< / td >
< td > Network: Sockets< / td >
< td > < / td >
< / tr >
< tr >
< td > 0008< / td >
< td > Syscalls: Unix< / td >
< td > < / td >
< / tr >
< tr >
< td > 0009< / td >
< td > Scripting Hooks< / td >
< td > < / td >
< / tr >
< tr >
< td > 0010< / td >
< td > Random Number< / td >
< td > < / td >
< / tr >
< / tbody >
< / table > < p > This list may be revised in the future. The only guaranteed< br >
stable indentifier is 0000 for generic output.< / p >
< h2 id = "trivia" > Trivia< / h2 >
< p > There are 810,000 possible combinations of instructions. Only< br >
73 are used in the implementation of RETRO.< / p >
< h1 id = "internals-interface-layers" > Internals: Interface Layers< / h1 >
< p > Nga provides a virtual processor and an extensible way of adding< br >
I/O devices, but does not provide any I/O itself. Adding I/O is< br >
the responsability of the < em > interface layer< / em > .< / p >
< p > An interface layer will wrap Nga, providing at least one I/O< br >
device (a generic output target), and a means of interacting< br >
with the < em > retro image< / em > .< / p >
< p > It’ s expected that this layer will be host specific, adding any< br >
system interactions that are needed via the I/O instructions.< br >
The image will typically be extended with words to use these.< / p >
< h1 id = "internals-the-retro-image" > Internals: The Retro Image< / h1 >
< p > The actual RETRO language is stored as a memory image for Nga.< / p >
< h2 id = "format" > Format< / h2 >
< p > The image file is a flat, linear sequence of signed 32-bit< br >
values. Each value is stored in little endian format. The< br >
size is not fixed. An interface should check when loading to< br >
ensure that the physical image is not larger than the emulated< br >
memory.< / p >
< h2 id = "header" > Header< / h2 >
< p > The image will start with two cells. The first is a liju…< br >
instruction, the second is the target address for the jump.< br >
This serves to skip over the rest of the data and reach the< br >
actual entry point.< / p >
< p > This is followed by a pointer to the most recent dictionary< br >
header, a pointer to the next free address in memory, and< br >
then the RETRO version number.< / p >
< pre > < code > | Offset | Contains |
| ------ | --------------------------- |
| 0 | lit call nop nop |
| 1 | Pointer to main entry point |
| 2 | Dictionary |
| 3 | Heap |
| 4 | RETRO version |
< / code > < / pre >
< p > The actual code starts after this header.< / p >
< p > The version number is the year and month. As an example,< br >
the 12.2019.6 release will have a version number of< br >
< code > 201906< / code > .< / p >
< h2 id = "layout" > Layout< / h2 >
< p > Assuming an Nga built with 524287 cells of memory:< / p >
< pre > < code > | RANGE | CONTAINS |
| --------------- | ---------------------------- |
| 0 - 1024 | rx kernel |
| 1025 - 1535 | token input buffer |
| 1536 + | start of heap space |
| ............... | free memory for your use |
| 506879 | buffer for string evaluate |
| 507904 | temporary strings (32 * 512) |
| 524287 | end of memory |
< / code > < / pre >
< p > The buffers at the end of memory will resize when specific< br >
variables related to them are altered.< / p >
< h1 id = "additional-tools" > Additional Tools< / h1 >
< p > In addition to the core < code > retro< / code > binary, the < code > bin< / code > directory< br >
will contain a few other tools.< / p >
< h2 id = "retro" > retro< / h2 >
< p > This is the main RETRO binary.< / p >
< h2 id = "retro-describe" > retro-describe< / h2 >
< p > This is a program that looks up entries in the Glossary.< / p >
< p > At the command line, you can use it like:< / p >
< pre > < code > retro-describe s:for-each
< / code > < / pre >
< h2 id = "retro-embedimage" > retro-embedimage< / h2 >
< p > This is a program which generates a C file with the ngaImage< br >
contents. It’ s used when building < code > retro< / code > .< / p >
< pre > < code > retro-embedimage ngaImage
< / code > < / pre >
< p > The output is written to stdout; redirect it as needed.< / p >
< h2 id = "retro-extend" > retro-extend< / h2 >
< p > This is a program which compiles code into the ngaImage.< br >
It’ s used when building < code > retro< / code > and when you want to make a< br >
standalone image with custom additions.< / p >
< p > Example command line:< / p >
< pre > < code > retro-extend ngaImage example/rot13.forth
< / code > < / pre >
< p > Pass the image name as the first argument, and then file names< br >
as susequent ones. Do < em > not< / em > use this for things relying on I/O< br >
apart from the basic console output as it doesn’ t emulate other< br >
devices. If you need to load in things that rely on using the< br >
optional I/O devices, see the Advanced Builds chapter.< / p >
< h2 id = "retro-muri" > retro-muri< / h2 >
< p > This is the assembler for Nga. It’ s used to build the initial< br >
RETRO kernel and can be used by other tools as well.< / p >
< h2 id = "retro-unu" > retro-unu< / h2 >
< p > This is the literate source extraction tool for RETRO. It< br >
is used in building < code > retro< / code > .< / p >
< p > Example usage:< / p >
< pre > < code > retro-unu literate/RetroForth.md
< / code > < / pre >
< p > Output is written to stdout; redirect as neeeded.< / p >
< h1 id = "advanced-builds" > Advanced Builds< / h1 >
< h2 id = "reduced-memory" > Reduced Memory< / h2 >
< p > RETRO can be built for reduced memory targets. This is primarily< br >
intendeded for use with embedded targets, but can be useful on< br >
large machines as well.< / p >
< p > To do this, < code > make< / code > with < code > CFLAGS< / code > set to < code > -DMEM1024K< / code > , < code > -DMEM512K< / code > ,< br >
< code > -DMEM256K< / code > , < code > -DMEM192K< / code > , < code > -DMEM128K< / code > ’ or < code > -DMEM96K< / code > . These will< br >
target memory sizes as specified per the following table.< / p >
< table >
< thead >
< tr >
< th > < / th >
< th > 1024kB< / th >
< th > 512kB< / th >
< th > 256kB< / th >
< th > 192kB< / th >
< th > 128kB< / th >
< th > 96kB< / th >
< / tr >
< / thead >
< tbody >
< tr >
< td > Total Bytes< / td >
< td > 1,048,579< / td >
< td > 524,288< / td >
< td > 262,144< / td >
< td > 196,608< / td >
< td > 131,072< / td >
< td > 98,304< / td >
< / tr >
< tr >
< td > Image< / td >
< td > 968,000< / td >
< td > 450,000< / td >
< td > 192,000< / td >
< td > 126,000< / td >
< td > 96,000< / td >
< td > 72,000< / td >
< / tr >
< tr >
< td > Data Stack< / td >
< td > 512< / td >
< td > 512< / td >
< td > 512< / td >
< td > 512< / td >
< td > 512< / td >
< td > 512< / td >
< / tr >
< tr >
< td > Address Stack< / td >
< td > 1,024< / td >
< td > 1,024< / td >
< td > 1,024< / td >
< td > 1,024< / td >
< td > 1,024< / td >
< td > 1,024< / td >
< / tr >
< tr >
< td > Remaining< / td >
< td > 79,040< / td >
< td > 72,752< / td >
< td > 68,608< / td >
< td > 69,072< / td >
< td > 33,536< / td >
< td > 24,768< / td >
< / tr >
< / tbody >
< / table > < p > The remaining memory is available for use by the VM.< / p >
< h2 id = "custom-image" > Custom Image< / h2 >
< p > For users of BSD, Linux, macOS, you can customize the image at< br >
build time.< / p >
< p > In the top level directory is a < code > package< / code > directory containing< br >
a file named < code > list< / code > . You can add files to compile into your< br >
system by adding them to the < code > list< / code > and rebuilding.< / p >
< p > Example:< / p >
< p > If you have wanted to include the NumbersWithoutPrefixes.forth< br >
example, add:< / p >
< pre > < code > ~~~
'example/NumbersWithoutPrefixes.forth include
~~~
< / code > < / pre >
< p > To the start of the < code > list< / code > file and then run < code > make< / code > again. The< br >
newly built < code > bin/retro< / code > will now include your additions.< / p >
< h1 id = "the-optional-retro-compiler" > The Optional Retro Compiler< / h1 >
< p > In addition to the base system, users of RETRO on Unix hosts< br >
with ELF executables can build and use the < code > retro-compiler< / code > < br >
to generate turnkey executables.< / p >
< h2 id = "requirements-3" > Requirements< / h2 >
< ul >
< li > Unix host< / li >
< li > ELF executable support< / li >
< li > objcpy in the $PATH< / li >
< / ul >
< h2 id = "building" > Building< / h2 >
< p > BSD users:< / p >
< p > make bin/retro-compiler< / p >
< p > Linux users:< / p >
< p > make -f Makefile.linux bin/retro-compiler< / p >
< h2 id = "installing" > Installing< / h2 >
< p > Copy < code > bin/retro-compiler< / code > to somewhere in your $PATH.< / p >
< h2 id = "using" > Using< / h2 >
< p > < code > retro-compiler< / code > takes two arguments: the source file to< br >
compile and the name of the word to use as the main entry< br >
point.< / p >
< p > Example:< / p >
< p > Given a < code > hello.forth< / code > :< / p >
< pre > < code > :hello 'Hello_World! s:put nl ;
< / code > < / pre >
< p > Use:< / p >
< pre > < code > retro-compiler hello.forth hello
< / code > < / pre >
< p > The compiler will generate an < code > a.out< / code > file which you can< br >
then rename.< / p >
< h2 id = "known-limitations" > Known Limitations< / h2 >
< p > This does not provide the scripting support for command line< br >
arguments that the standard < code > retro< / code > interface offers.< / p >
< p > A copy of < code > objcopy< / code > needs to be in the path for compilation< br >
to work.< / p >
< p > The current working directory must be writable.< / p >
< p > This only supports hosts using ELF executables.< / p >
< p > The output file name is fixed to < code > a.out< / code > .< / p >
< h1 id = "errors" > Errors< / h1 >
< p > RETRO does only minimal error checking.< / p >
< h2 id = "non-fatal" > Non-Fatal< / h2 >
< p > A non-fatal error will be reported on < em > word not found< / em > during< br >
interactive or compile time. Note that this only applies to< br >
calls: if you try to get a pointer to an undefined word, the< br >
returned pointer will be zero.< / p >
< h2 id = "fatal" > Fatal< / h2 >
< p > A number of conditions are known to cause fatal errors. The< br >
main ones are stack overflow, stack underflow, and division< br >
by zero.< / p >
< p > On these, RETRO will generally exit. For stack depth issues,< br >
the VM will attempt to display an error prior to exiting.< / p >
< p > In some cases, the VM may get stuck in an endless loop. If this< br >
occurs, try using CTRL+C to kill the process, or kill it using< br >
whatever means your host system provides.< / p >
< h2 id = "rationale" > Rationale< / h2 >
< p > Error checks are useful, but slow - especially on a minimal< br >
system like RETRO. The overhead of doing depth or other checks< br >
adds up quickly.< / p >
< p > As an example, adding a depth check to < code > drop< / code > increases the< br >
time to use it 250,000 times in a loop from 0.16 seconds to< br >
1.69 seconds.< / p >
< h1 id = "security-concerns" > Security Concerns< / h1 >
< p > The standard RETRO is not a good choice for applications< br >
needing to be highly secure.< / p >
< h2 id = "runtime-checks" > Runtime Checks< / h2 >
< p > The RETRO system performs only minimal checks. It will not< br >
load an image larger than the max set at build time. And< br >
stack over/underflow are checked for as code executes.< / p >
< p > The system does not attempt to validate anything else, it’ s< br >
quite easy to crash.< / p >
< h2 id = "isolation" > Isolation< / h2 >
< p > The VM itself and the core code is self contained. Nga does< br >
not make use of malloc/free, and uses only standard system< br >
libraries. It’ s possible for buffer overruns within the image< br >
(overwriting Nga code), but the RETRO image shouldn’ t leak< br >
into the C portions.< / p >
< p > I/O presents a bigger issue. Anything involving I/O, especially< br >
with the < code > unix:< / code > words, may be a vector for attacks.< / p >
< h2 id = "future-direction" > Future Direction< / h2 >
< p > I’ m not planning to add anything to the < em > image< / em > side as, for me,< br >
the performance hit due to added checks is bigger than the< br >
benefits.< / p >
< p > The story is different on the VM side. I’ ve already begun taking< br >
steps to address some of the issues, using functions that check< br >
for overruns with strings and doing some minor testing for these< br >
conditions. I will be gradually addressing the various I/O< br >
related extensions, though it’ s unlikely to ever be fully guarded< br >
against attacks.< / p >
< h2 id = "rationale-1" > Rationale< / h2 >
< p > RETRO is, primarily, a personal system. I’ m running code I wrote< br >
to solve problems I face. On the occasions where I run code sent< br >
to me by others, I read it carefully first and then run inside a< br >
sandboxed environment if I’ m worried about anything in it.< / p >
< h1 id = "technical-notes-and-reflections" > Technical Notes and Reflections< / h1 >
< p > This is a collection of short papers providing some additional< br >
background and reflections on design decisions.< / p >
< h2 id = "metacompilation-and-assembly" > Metacompilation and Assembly< / h2 >
< p > RETRO 10 and 11 were written in themselves using a metacompiler.< br >
I had been fascinated by this idea for a long time and was able< br >
to explore it heavily. While I still find it to be a good idea,< br >
the way I ended up doing it was problematic.< / p >
< p > The biggest issue I faced was that I wanted to do this in one< br >
step, where loading the RETRO source would create a new image< br >
in place of the old one, switch to the new one, and then load< br >
the higher level parts of the language over this. In retrospect,< br >
this was a really bad idea.< / p >
< p > My earlier design for RETRO was very flexible. I allowed almost< br >
everything to be swapped out or extended at any time. This made< br >
it extremely easy to customize the language and environment, but< br >
made it crucial to keep track of what was in memory and what had< br >
been patched so that the metacompiler wouldn’ t refer to anything< br >
in the old image during the relocation and control change. It< br >
was far too easy to make a mistake, discover that elements of< br >
the new image were broken, and then have to go and revert many< br >
changes to try to figure out what went wrong.< / p >
< p > This was also complicated by the fact that I built new images< br >
as I worked, and, while a new image could be built from the last< br >
built one, it wasn’ t always possible to build a new image from< br >
the prior release version. (Actually, it was often worse - I< br >
failed to check in every change as I went, so often even the< br >
prior commits couldn’ t rebuild the latest images).< / p >
< p > For RETRO 12 I wanted to avoid this problem, so I decided to go< br >
back to writing the kernel (“Rx”) in assembly. I actually wrote< br >
a Machine Forth dialect to generate the initial assembly, before< br >
eventually hand tuning the final results to its current state.< / p >
< p > I could (and likely will eventually) write the assembler in< br >
RETRO, but the current one is in C, and is built as part of the< br >
standard toolchain.< / p >
< p > My VM actually has two assemblers. The older one is Naje. This< br >
was intended to be fairly friendly to work with, and handles< br >
many of the details of packing instructions for the user. Here< br >
is an example of a small program in it:< / p >
< pre > < code > :square
dup
mul
ret
:main
lit 35
lit & square
call
lit & square
call
end
< / code > < / pre >
< p > The other assembler is Muri. This is a far more minimalistic< br >
assembler, but I’ ve actually grown to prefer it. The above< br >
example in Muri would become:< / p >
< pre > < code > i liju....
r main
: square
i dumure..
: main
i lilica..
d 35
r square
i en......
< / code > < / pre >
< p > In Muri, each instruction is reduced to two characters, and the< br >
bundlings are listed as part of an instruction bundle (lines< br >
starting with < code > i< / code > ). This is less readable if you aren’ t very< br >
familiar with Nga’ s assembly and packing rules, but allows a< br >
very quick, efficient way of writing assembly for those who are.< / p >
< p > I eventually rewrote the kernel in the Muri style as it’ s what< br >
I prefer, and since there’ s not much need to make changes in it.< / p >
< h2 id = "the-path-to-self-hosting" > The Path to Self Hosting< / h2 >
< p > RETRO is an image based Forth system running on a lightweight< br >
virtual machine. This is the story of how that image is made.< / p >
< p > The first RETRO to use an image based approach was RETRO 10.< br >
The earliest images were built using a compiler written in< br >
Toka, an earlier experimental stack language I had written.< br >
It didn’ t take long to want to drop the dependency on Toka,< br >
so I rewrote the image compiler in RETRO and then began< br >
development at a faster pace.< / p >
< p > RETRO 11 was built using the last RETRO 10 image and an< br >
evolved version of the metacompiler. This worked well, but< br >
I eventually found it to be problematic.< / p >
< p > One of the issues I faced was the inability to make a new< br >
image from the prior stable release. Since I develop and< br >
test changes incrementally, I reached a point where the< br >
current metacompiler and image required each other. This< br >
wasn’ t a fatal flaw, but it was annoying.< / p >
< p > Perhaps more critical was the fragility of the system. In< br >
R11 small mistakes could result in a corrupt image. The test< br >
suite helped identify some of these, but there were a few< br >
times I was forced to dig back through the version control< br >
history to recover a working image.< / p >
< p > The fragile nature was amplified by some design decisions.< br >
In R11, after the initial kernel was built, it would be< br >
moved to memory address 0, then control would jump into the< br >
new kernel to finish building the higher level parts.< / p >
< p > Handling this was a tricky task. In R11 almost everything< br >
could be revectored, so the metacompiler had to ensure that< br >
it didn’ t rely on anything in the old image during the move.< br >
This caused a large number of issues over R11’ s life.< / p >
< p > So on to RETRO 12. I decided that this would be different.< br >
First, the kernel would be assembly, with an external tool< br >
to generate the core image. The kernel is in < code > Rx.md< / code > and the< br >
assembler is < code > Muri< / code > . To load the standard library, I wrote a< br >
second tool, < code > retro-extend< / code > . This separation has allowed me< br >
many fewer headaches as I can make changes more easily and< br >
rebuild from scratch when necessary.< / p >
< p > But I miss self-hosting. So last fall I decided to resolve< br >
this. And today I’ m pleased to say that it is now done.< / p >
< p > There are a few parts to this.< / p >
< p > < strong > Unu< / strong > . I use a Markdown variation with fenced code blocks.< br >
The tool I wrote in C to extract these is called < code > unu< / code > . For< br >
a self hosting RETRO, I rewrote this as a combinator that< br >
reads in a file and runs another word against each line in the< br >
file. So I could display the code block contents by doing:< / p >
< pre > < code > 'filename [ s:put nl ] unu
< / code > < / pre >
< p > This made it easier to implement the other tools.< / p >
< p > < strong > Muri< / strong > . This is my assembler. It’ s minimalistic, fast, and< br >
works really well for my purposes. RETRO includes a runtime< br >
version of this (using < code > as{< / code > , < code > }as< / code > , < code > i< / code > , < code > d< / code > , and < code > r< / code > ), so< br >
all I needed for this was to write a few words to parse the< br >
lines and run the corresponding runtime words. As with the C< br >
version, this is a two pass assembler.< / p >
< p > Muri generates a new < code > ngaImage< / code > with the kernel. To create a< br >
full image I needed a way to load in the standard library and< br >
I/O extensions.< / p >
< p > This is handled by < strong > retro-extend< / strong > . This is where it gets< br >
more complex. I implemented the Nga virtual machine in RETRO< br >
to allow this to run the new image in isolation from the< br >
host image. The new ngaImage is loaded, the interpreter is< br >
located, and each token is passed to the interpreter. Once< br >
done, the new image is written to disk.< / p >
< p > So at this point I’ m pleased to say that I can now develop< br >
RETRO using only an existing copy of RETRO (VM+image) and< br >
tools (unu, muri, retro-extend, and a line oriented text< br >
editor) written in RETRO.< / p >
< p > This project has delivered some additional side benefits.< br >
During the testing I was able to use it to identify a few< br >
bugs in the I/O extensions, and the Nga-in-RETRO will replace< br >
the older attempt at this in the debugger, allowing a safer< br >
testing environment.< / p >
< p > What issues remain?< / p >
< p > The extend process is < em > slow< / em > . On my main development server< br >
(Linode 1024, OpenBSD 6.4, 64-bit) it takes a bit over five< br >
minutes to complete loading the standard library, and a few< br >
additional depending on the I/O drivers selected.< / p >
< p > Most of the performance issues come from running Nga-in-RETRO< br >
to isolate the new image from the host one. It’ d be possible< br >
to do something a bit more clever (e.g., running a RETRO< br >
instance using the new image via a subprocess and piping in< br >
the source, or doing relocations of the data), but this is< br >
less error prone and will work on all systems that I plan to< br >
support (including, with a few minor adjustments, the native< br >
hardware versions [assuming the existance of mass storage]).< / p >
< p > Sources:< / p >
< p > < strong > Unu< / strong > < / p >
< ul >
< li > < a href = "http://forth.works/c8820f85e0c52d32c7f9f64c28f435c0" > http://forth.works/c8820f85e0c52d32c7f9f64c28f435c0< / a > < / li >
< li > gopher://forth.works/0/c8820f85e0c52d32c7f9f64c28f435c0< / li >
< / ul >
< p > < strong > Muri< / strong > < / p >
< ul >
< li > < a href = "http://forth.works/09d6c4f3f8ab484a31107dca780058e3" > http://forth.works/09d6c4f3f8ab484a31107dca780058e3< / a > < / li >
< li > gopher://forth.works/0/09d6c4f3f8ab484a31107dca780058e3< / li >
< / ul >
< p > < strong > retro-extend< / strong > < / p >
< ul >
< li > < a href = "http://forth.works/c812416f397af11db58e97388a3238f2" > http://forth.works/c812416f397af11db58e97388a3238f2< / a > < / li >
< li > gopher://forth.works/0/c812416f397af11db58e97388a3238f2< / li >
< / ul >
< h2 id = "prefixes-as-a-language-element" > Prefixes as a Language Element< / h2 >
< p > A big change in RETRO 12 was the elimination of the traditional< br >
parser from the language. This was a sacrifice due to the lack< br >
of an I/O model. RETRO has no way to know < em > how< / em > input is given< br >
to the < code > interpret< / code > word, or whether anything else will ever be< br >
passed into it.< / p >
< p > And so < code > interpret< / code > operates only on the current token. The core< br >
language does not track what came before or attempt to guess at< br >
what might come in the future.< / p >
< p > This leads into the prefixes. RETRO 11 had a complicated system< br >
for prefixes, with different types of prefixes for words that< br >
parsed ahead (e.g., strings) and words that operated on the< br >
current token (e.g., < code > @< / code > ). RETRO 12 eliminates all of these in< br >
favor of just having a single prefix model.< / p >
< p > The first thing < code > interpret< / code > does is look to see if the first< br >
character in a token matches a < code > prefix:< / code > word. If it does, it< br >
passes the rest of the token as a string pointer to the prefix< br >
specific handler to deal with. If there is no valid prefix< br >
found, it tries to find it in the dictionary. Assuming that it< br >
finds the words, it passes the < code > d:xt< / code > field to the handler that< br >
< code > d:class< / code > points to. Otherwise it calls < code > err:notfound< / code > .< / p >
< p > This has an important implication: < em > words can not reliably< br >
have names that start with a prefix character.< / em > < / p >
< p > It also simplifies things. Anything that would normally parse< br >
becomes a prefix handler. So creating a new word? Use the < code > :< / code > < br >
prefix. Strings? Use < code > '< / code > . Pointers? Try < code > & < / code > . And so on. E.g.,< / p >
< pre > < code > In ANS | In RETRO
: foo ... ; | :foo ... ;
' foo | & foo
: bar ... ['] foo ; | :bar ... & foo ;
s" hello world!" | 'hello_world!
< / code > < / pre >
< p > If you are familiar with ColorForth, prefixes are a similar< br >
idea to colors, but can be defined by the user as normal words.< / p >
< p > After doing this for quite a while I rather like it. I can see< br >
why Chuck Moore eventually went towards ColorForth as using< br >
color (or prefixes in my case) does simplify the implementation< br >
in many ways.< / p >
< h2 id = "on-the-kernel-wordset" > On The Kernel Wordset< / h2 >
< p > In implementing the RETRO 12 kernel (called Rx) I had to decide< br >
on what functionality would be needed. It was important to me< br >
that this be kept clean and minimalistic, as I didn’ t want to< br >
spend a lot of time changing it as time progressed. It’ s far< br >
nicer to code at the higher level, where the RETRO language is< br >
functional, as opposed to writing more assembly code.< / p >
< p > So what made it in?< / p >
< p > Primitives< / p >
< p > These are words that map directly to Nga instructions.< / p >
< pre > < code > dup drop swap call eq? -eq? lt? gt?
fetch store + - * /mod and or
xor shift push pop 0;
< / code > < / pre >
< p > Memory< / p >
< pre > < code > fetch-next store-next , s,
< / code > < / pre >
< p > Strings< / p >
< pre > < code > s:to-number s:eq? s:length
< / code > < / pre >
< p > Flow Control< / p >
< pre > < code > choose if -if repeat again
< / code > < / pre >
< p > Compiler & Interpreter< / p >
< pre > < code > Compiler Heap ; [ ] Dictionary
d:link d:class d:xt d:name d:add-header
class:word class:primitive class:data class:macro
prefix:: prefix:# prefix:& prefix:$
interpret d:lookup err:notfound
< / code > < / pre >
< p > I < em > could< / em > slightly reduce this. The $ prefix could be defined in< br >
higher level code, and I don’ t strictly < em > need< / em > to expose the< br >
< code > fetch-next< / code > and < code > store-next< / code > here. But since the are already< br >
implemented as dependencies of the words in the kernel, it would< br >
be a bit wasteful to redefine them later in higher level code.< / p >
< p > With these words the rest of the language can be built up. Note< br >
that the Rx kernel does not provide any I/O words. It’ s assumed< br >
that the RETRO interfaces will add these as best suited for the< br >
systems they run on.< / p >
< p > There is another small bit. All images start with a few key< br >
pointers in fixed offsets of memory. These are:< / p >
< pre > < code > | Offset | Contains |
| ------ | --------------------------- |
| 0 | lit call nop nop |
| 1 | Pointer to main entry point |
| 2 | Dictionary |
| 3 | Heap |
| 4 | RETRO version identifier |
< / code > < / pre >
< p > An interface can use the dictionary pointer and knowledge of the< br >
dictionary format for a specific RETRO version to identify the< br >
location of essential words like < code > interpret< / code > and < code > err:notfound< / code > < br >
when implementing the user facing interface.< / p >
< h2 id = "on-the-evolution-of-ngaro-into-nga" > On The Evolution Of Ngaro Into Nga< / h2 >
< p > When I decided to begin work on what became RETRO 12, I knew< br >
the process would involve updating Ngaro, the virtual machine< br >
that RETRO 10 and 11 ran on.< / p >
< p > Ngaro rose out of an earlier experimental virtual machine I had< br >
written back in 2005-2006. This earlier VM, called Maunga, was< br >
very close to what Ngaro ended up being, though it had a very< br >
different approach to I/O. (All I/O in Maunga was intended to be< br >
memory mapped; Ngaro adopted a port based I/O system).< / p >
< p > Ngaro itself evolved along with RETRO, gaining features like< br >
automated skipping of NOPs and a LOOP opcode to help improve< br >
performance. But the I/O model proved to be a problem. When I< br >
created Ngaro, I had the idea that I would always be able to< br >
assume a console/terminal style environment. The assumption was< br >
that all code would be entered via the keyboard (or maybe a< br >
block editor), and that proved to be the fundamental flaw as< br >
time went on.< / p >
< p > As RETRO grew it was evident that the model had some serious< br >
problems. Need to load code from a file? The VM and language had< br >
functionality to pretend it was being typed in. Want to run on< br >
something like a browser, Android, or iOS? The VM would need to< br >
be implemented in a way that simulates input being typed into< br >
the VM via a simulated keyboard. And RETRO was built around this.< br >
I couldn’ t change it because of a promise to maintain, as much< br >
as possible, source compatibility for a period of at least five< br >
years.< / p >
< p > When the time came to fix this, I decided at the start to keep< br >
the I/O model separate from the core VM. I also decided that the< br >
core RETRO language would provide some means of interpreting< br >
code without requiring an assumption that a traditional terminal< br >
was being used.< / p >
< p > So Nga began. I took the opportunity to simplify the instruction< br >
set to just 26 essential instructions, add support for packing< br >
multiple instructions per memory location (allowing a long due< br >
reduction in memory footprint), and to generally just make a far< br >
simpler design.< / p >
< p > I’ ve been pleased with Nga. On its own it really isn’ t useful< br >
though. So with RETRO I embed it into a larger framework that< br >
adds some basic I/O functionality. The < em > interfaces< / em > handle the< br >
details of passing tokens into the language and capturing any< br >
output. They are free to do this in whatever model makes most< br >
sense on a given platform.< / p >
< p > So far I’ ve implemented:< / p >
< pre > < code > - a scripting interface, reading input from a file and
offering file i/o, gopher, and reading from stdin, and
sending output to stdout.
- an interactive interface, built around ncurses, reading
input from stdin, and displaying output to a scrolling
buffer.
- an iOS interface, built around a text editor, directing
output to a separate interface pane.
- an interactive block editor, using a gopher-based block
data store. Output is displayed to stdout, and input is
done via the blocks being evaluated or by reading from
stdin.
< / code > < / pre >
< p > In all cases, the only common I/O word that has to map to an< br >
exposed instruction is < code > putc< / code > , to display a single character to< br >
some output device. There is no requirement for a traditional< br >
keyboard input model.< / p >
< p > By doing this I was able to solve the biggest portability issue< br >
with the RETRO 10/11 model, and make a much simpler, cleaner< br >
language in the end.< / p >
< h2 id = "retro-11-2011---2019-a-look-back" > RETRO 11 (2011 - 2019): A Look Back< / h2 >
< p > So it’ s now been about five years since the last release of RETRO< br >
11. While I still see some people obtaining and using it, I’ ve< br >
moved on to the twelth generation of RETRO. It’ s time for me to< br >
finally retire RETRO 11.< / p >
< p > As I prepare to do so, I thought I’ d take a brief look back.< / p >
< p > RETRO 11 began life in 2011. It grew out of RETRO 10, which was< br >
the first version of RETRO to not be written in x86 assembly< br >
language. For R10 and R11, I wrote a portable virtual machine< br >
(with numerous implementations) and the Forth dialect was kept< br >
in an image file which ran on the VM.< / p >
< p > RETRO 10 worked, but was always a bit too sloppy and changed< br >
drastically between releases. The major goal of RETRO 11 was to< br >
provide a stable base for a five year period. In retrospect,< br >
this was mostly achieved. Code from earlier releases normally< br >
needed only minor adjustments to run on later releases, though< br >
newer releases added significantly to the language.< / p >
< p > There were seven releases.< / p >
< ul >
< li > Release 11.0: 2011, July< / li >
< li > Release 11.1: 2011, November< / li >
< li > Release 11.2: 2012, January< / li >
< li > Release 11.3: 2012, March< / li >
< li > Release 11.4: 2012, July< / li >
< li > Release 11.5: 2013, March< / li >
< li > Release 11.6: 2014, August< / li >
< / ul >
< p > Development was fast until 11.4. This was the point at which I< br >
had to slow down due to RSI problems. It was also the point< br >
which I started experiencing some problems with the metacompiler< br >
(as discussed previously).< / p >
< p > RETRO 11 was flexible. All colon definitions were setup as hooks,< br >
allowing new functionality to be layered in easily. This allowed< br >
the later releases to add things like vocabularies, search order,< br >
tab completion, and keyboard remapping. This all came at a cost< br >
though: later things could use the hooks to alter behavior of< br >
existing words, so it was necessary to use a lot of caution to< br >
ensure that the layers didn’ t break the earlier code.< / p >
< p > The biggest issue was the I/O model. RETRO 11 and the Ngaro VM< br >
assumed the existence of a console environment. All input was< br >
required to be input at the keyboard, and all output was to be< br >
shown on screen. This caused some problems. Including code from< br >
a file required some tricks, temporarily rewriting the keyboard< br >
input function to read from the file. It also became a major< br >
issue when I wrote the iOS version. The need to simulate the< br >
keyboard and console complicated everything and I had to spend< br >
a considerable amount of effort to deal with battery performance< br >
resulting from the I/O polling and wait states.< / p >
< p > But on the whole it worked well. I used RETRO 11.6 until I started< br >
work on RETRO 12 in late 2016, and continued running some tools< br >
written in R11 until the first quarter of last year.< / p >
< p > The final image file was 23,137 cells (92,548 bytes). This was< br >
bloated by keeping some documentation (stack comments and short< br >
descriptions) in the image, which started in 11.4. This contained< br >
269 words.< / p >
< p > I used RETRO 11 for a wide variety of tasks. A small selection of< br >
things that were written includes:< / p >
< ul >
< li > a pastebin< / li >
< li > front end to ii (irc client)< / li >
< li > small explorations of interactive fiction< / li >
< li > irc log viewer< / li >
< li > tool to create html from templates< / li >
< li > tool to automate creation of an SVCD from a set of photos< / li >
< li > tools to generate reports from data sets for my employer< / li >
< / ul >
< p > In the end, I’ m happy with how RETRO 11 turned out. I made some< br >
mistakes in embracing too much complexity, but despite this it< br >
was a successful system for many years.< / p >
< h1 id = "historical-papers-and-notes" > Historical Papers and Notes< / h1 >
< h2 id = "on-the-naming-of-retro" > On the Naming of RETRO< / h2 >
< p > Taken from < a href = "http://lists.tunes.org/archives/tunes-lll/1999-July/000121.html" > http://lists.tunes.org/archives/tunes-lll/1999-July/000121.html< / a > < / p >
< p > On Fri, Jul 30, 1999 at 07:43:54PM -0400, Paul Dufresne wrote:< / p >
< blockquote >
< p > My brother did found it funny that Retro is called like that.< br >
For him retro means going back (generally in time) so this< br >
does not looks like a name of a OS to come. So he’ d like to< br >
know from where the name came.< / p >
< / blockquote >
< p > Heheh, here’ s the story: When I started playing with OS stuff< br >
last year (not seriously), I was reading about some old things< br >
like FORTH and ITS, dating back to the 1960’ s and 70’ s. The< br >
past few years in America, there’ s been a revival of disco< br >
music (along with bell bottoms, platform shoes, and all that< br >
crap) and they call it “retro”. Now, my OS was named by< br >
musicians… I was telling a fellow musician about my ideas,< br >
how it would be cool to have a small OS that isn’ t bloated and< br >
unmanageable like Windows… go back to the 70’ s and resurrect< br >
a line of software that died out. He goes “hmm… sounds kinda< br >
retro…”< / p >
< p > I think it sounds kinda rebellious, which is a Good Thing now< br >
that everybody hates the M$ empire. :) It seems like other< br >
people are as sick of the future as I am. Look at TUNES, the< br >
idea there isn’ t to make some great new invention, just take< br >
some decades-old ideas and combine them in one OS. The first< br >
time I saw Knuth’ s “Art of Computer Programming” in the library< br >
I thought “god that looks old… 1973!!! nevermind…” Now it’ s< br >
my programming bible. Find me something better published in< br >
the 90’ s… if such a thing exists, it’ ll be like a needle in a< br >
haystack. “Newer” doesn’ t necessarily mean “better”.< / p >
< pre > < code > New cars = flimsier
New farming methods = more devastating
New version of Netscape = more bloat, more bullshit
< / code > < / pre >
< p > One thing is better now: computer hardware. Give me 70’ s< br >
software on 90’ s and 00’ s hardware :)< / p >
< ul >
< li > Tom Novelli < a href = "mailto:tcn@tunes.org" > tcn@tunes.org< / a > < / li >
< / ul >
< h2 id = "the-design-philosophy-of-retro-native-forth" > The Design Philosophy of RETRO Native Forth< / h2 >
< p > Computer software is a technology in its infancy, a mere fifty years< br >
old. The last 25 years in particular have seen an explosion in the< br >
software business. However, software has seen little innovation while< br >
hardware technology has improved phenomenally (notwithstanding the advent< br >
of lousy slave-made parts). Proven software techniques of forty years ago< br >
have yet to reach widespread use, in deference to the “latest and< br >
greatest” proprietary solutions of dubious value. Thanks to agressive< br >
marketing, we make huge investments in these dead-end technologies< br >
(through our businesses and governments, if not personally) and we end up< br >
with a reliance on a heap of complicated, error-prone, poorly understood< br >
junk software.< / p >
< p > Complexity will dominate the software industry for the foreseeable< br >
future. The Retro philosophy is a simple alternative for those willing to< br >
make a clean break with legacy software. A Retro system can communicate< br >
with other systems, but it won’ t run much legacy software, especially< br >
proprietary software without source code. An emulation layer could be< br >
added, but doing so would defeat the purpose of a simple operating system.< br >
I think TCP/IP support is all the compatibility that’ s needed.< / p >
< p > At first Retro will appeal to computer hobbyists and electronic< br >
engineers. Once the rough edges are smoothed out, it could catch on with< br >
ordinary folks who don’ t like waiting five minutes just to check their< br >
email (not to mention the long hours of setup and maintenance). Game< br >
programmers who take their craft seriously may also be interested.< br >
Businesses might even see a use for it, if the managers decide it’ s more< br >
cost-effective to carefully design software for specific needs, rather< br >
than buying off-the-shelf crap and spending countless manhours working< br >
around the bugs. Since it’ s not practical for businesses to make a clean< br >
break, my advice is to run Retro (and its ilk) on separate machines< br >
connected by a network. Retro is efficient enough to run on older< br >
machines that would otherwise sit idle, being too slow for the latest< br >
Microsoft bloatware (or Linux, for that matter).< / p >
< p > I strive to avoid the extraneous. That applies even to proven< br >
technologies, if I don’ t need them. If my computer isn’ t set up for< br >
people to log in over the network, I don’ t want security features; they< br >
just get in the way. If I’ m only running programs I wrote, I should be< br >
able to run them with full access to the hardware; I don’ t need protection< br >
from viruses. If I download something I don’ t trust, then I can run it in< br >
an isolated process, which is customary with Unix and kin. But that’ s not< br >
core functionality. All that’ s needed is the flexibility to add things< br >
like security, graphical interfaces, and distributed processing - if the< br >
need ever arises.< / p >
< p > In programming languagues, I was misled. It’ s the Tower of Babel all< br >
over again. The thousands of languages in existence all fall into a< br >
handful of archetypes: Assembler, LISP, FORTRAN and FORTH represent the< br >
earliest descendants of nearly all languages. I hesitate to name a< br >
definitive “object-oriented” language, and here’ s why: Object-Oriented< br >
programming is just a technique, and any language will suffice, even< br >
Assembler. The complexites of fancy languages like Ada and C++ are a< br >
departure from reality – the reality of the actual physical machine.< br >
When it all boils down, even LISP, FORTRAN and FORTH are only extensions< br >
of the machine.< / p >
< p > I chose FORTH as the “native tongue” of Retro. LISP, FORTRAN, and< br >
other languages can be efficiently implemented as extensions of FORTH, but< br >
the reverse isn’ t so efficient. Theoretically all languages are< br >
equivalent, but when design time, compilation time, and complexity are< br >
accounted for, FORTH is most efficient. FORTH also translates most< br >
directly to the hardware. (In fact, FORTH has been implemented in< br >
hardware; these “stack machines” are extremely efficient.) FORTH is also< br >
the easiest language to implement from scratch - a major concern when< br >
you’ re trying to make a clean break. So with simplicity in mind, FORTH< br >
was the obvious choice.< / p >
< p > I’ m perfectly happy working with text only, and I go to great lengths< br >
to avoid using the standard graphical environments, which have major< br >
problems: windows, pulldown menus, and mice. Windows can’ t share the< br >
screen nicely; that idea is hopeless. Pulldowns are tedious. Mice get in< br >
the way of typing without reducing the need for it; all they give me is< br >
tendonitis. Their main use is for drawing.< / p >
< p > Some of my favorite interfaces: Telix, Telegard BBS, Pine, Pico, Lynx,< br >
and ScreamTracker. All “hotkey” interfaces where you press a key or two< br >
to perform an action. Usually the important commands are listed at the< br >
bottom of the screen, or at least on a help screen. The same principles< br >
apply to graphical interfaces: use the full screen, except for a status< br >
and menu area on one edge. Resist the temptation to clutter up the< br >
screen.< / p >
< p > As for switching between programs, the Windows methods suck; the only< br >
thing worse is Unix job control (jobs, fg, and such). The Linux method is< br >
tolerable: Alt-Arrows, Alt-F1, Alt-F2, etc. Still, things could be< br >
better: F11 and F12 cycle back and forth through all open programs; Alt-F1< br >
assigns the currently selected program to F1, and likewise for the other< br >
function keys. Programs just won’ t use function keys - Control and Alt< br >
combinations are less awkward and easier to remember, besides. I’ ll also< br >
want a “last channel” key and a “task list” key; maybe I’ ll borrow those< br >
stupid Win95 keys. The Pause key will do like it says - pause the current< br >
program - and Ctrl-Pause (Break) will kill it.< / p >
< p > One more thing: consistency. I like programs to look different so I< br >
can tell them apart, but the keys should be the same as much as possible.< br >
Keys should be configured in one place, for all programs. Finally,< br >
remember the most consistent interface, one of the few constants< br >
throughout the history of computing - the text screen and keyboard, and< br >
the teletypewriter before that. Don’ t overlook it.< / p >
< p > More to come, maybe… :)< / p >
< p > “If it’ s on line, it’ s a work in progress.”< / p >
< p > Tom Novelli, 3/4/2000< / p >
< / div >
< / div >
< / body >
< / html >