by Kristopher Johnson
SuwaneeForth is an implementation of a Forth interpreter, written in Swift for OS X. With a little more work, it could probably run on iOS as well.
SuwaneeForth is a translation/port of the system described in "A sometimes minimal FORTH compiler and tutorial for Linux/i386 systems" (a.k.a. "JONESFORTH") by Richard W.M. Jones. Refer to the JONESFORTH source code for details of the memory layouts and execution mechanisms used by SuwaneeForth.
Like JONESFORTH, SuwaneeForth is not compliant with the ANS Forth standard.
Like JONESFORTH, SuwaneeForth's source code is released into the public domain.
SuwaneeForth is only intended to be useful as an educational toy. If you want to use an open-source Forth for serious software development, it is recommended that you start with pForth, GForth, or another mature Forth implementation.
Suwanee is the name of the city where the author lives.
This Forth implementation is in no way related to SwiftForth, SwiftX, SwiftOS or any other FORTH, Inc. product. (So if you landed here after doing a web search for "swift forth", take a minute to make sure you are looking at the right Forth.)
The SuwaneeForth repository contains the JONESFORTH source code as a git submodule, so the best way to get everything at once is to do this:
git clone --recursive https://github.com/kristopherjohnson/suwaneeforth
Alternatively, you can run these commands:
git clone https://github.com/kristopherjohnson/suwaneeforth
cd suwaneeforth
git submodule init
git submodule update
Note that the JONESFORTH source code is not required for building and running SuwaneeForth, so you could ignore it, but it is very useful to have that code as a documentation reference and it is also useful to have the JONESFORTH unit test data to test SuwaneeForth.
README.md
- what you are reading nowjonesforth/
- the original JONESFORTH source code. You should readjonesforth.S
andjonesforth.f
to understand what's going on.forth.xcodeproj
- Xcode projectforth.xcworkspace
- Xcode workspaceforth/
- the code that implements the SuwaneeForth kernel and systemforthTests/
- unit tests forforth
suwaneeforth/
- source for thesuwaneeforth
command-line tool executable
An additional subdirectory, build/
, will be created when the suwaneeforth
executable is built.
Building SuwaneeForth requires the Xcode command-line tools.
To build the application from the command line, execute this command in the top-level directory:
xcodebuild -target suwaneeforth
If the build succeeds, the suwaneeforth
executable and system.forth
will be copied to the build
subdirectory.
To build the application from within Xcode, do the following:
- Open Xcode
- Open
forth.xcworkspace
- Select the
suwaneeforth
scheme if it is not already selected - Choose the menu item Product > Build For > Running
- If all goes well, the
suwaneeforth
executable and the filesystem.forth
will be copied to thebuild
directory in your workspace directory.
Note that the Xcode scheme builds the Debug configuration, which is very slow in comparison to the Release configuration.
Like JONESFORTH and many other Forths, the implementation is divided into a small "kernel" written in a low-level language (Swift, in this case) and then the rest of the system is written in Forth itself. You launch the kernel and load the standard system words before loading your application code or using the interpreter interatively.
The kernel is the suwaneeforth
executable and the additional Forth-defined words are in system.forth
. Running the kernel without system.forth
is generally not useful, unless you are providing alternative definitions for its contents, so you will usually want to pipe system.forth
into the kernel before doing anything else.
To run a Forth program, do something like this to load multiple source files into the kernel:
cat system.forth mylibrary.forth myprogram.forth | ./suwaneeforth
To run the interpreter interactively, do something like this to pipe the system words and then your input into the kernel:
cat system.forth - | ./suwaneeforth
The kernel code, in forth/kernel.swift
, is in an OS X framework named forth
. This framework is currently only used by the unit tests, but could be useful to create an application with an embedded Forth interpreter.
Note: The suwaneeforth
executable currently does not use the framework, because Xcode currently does not provide the tooling needed to build a command-line tool written in Swift that uses a framework written in Swift. So the suwaneeforth
target simply compiles and links kernel.swift
directly.
To build and test the framework, do the following:
- Open Xcode
- Open
forth.xcworkspace
- Select the
forth
scheme if it is not already selected - Choose the menu item Product > Test
Set the isTraceEnabled
property in ForthMachine
to true to enable debug trace message output during execution.
The ForthMachine
has several safety checks that try to prevent bad Forth code from reading/writing memory outside of the virtual machine's reserved address space. Run with assertions enabled to enable all these checks, and run in the debugger to break when they are triggered. Setting a breakpoint in the abortWithMessage()
method may be helpful too.
SuwaneeForth does not support these words that are included in JONESFORTH:
- Environment words:
ARGC
,ARGV
,ENVIRON
- File-access words:
OPEN-FILE
,CREATE-FILE
,CLOSE-FILE
,READ-FILE
,PERROR
- System call words:
GET-BRK
,BRK
,MORECORE
,SYSCALL0
,SYSCALL1
,SYSCALL2
,SYSCALL3
, and the related constants - Assembler words
FORTH is one of those alien languages which most working programmers regard in the same way as Haskell, LISP, and so on. Something so strange that they'd rather any thoughts of it just go away so they can get on with writing this paying code. But that's wrong and if you care at all about programming then you should at least understand all these languages, even if you will never use them.
That's the first paragraph of jonesforth.S
, and I heartily agree with it.
Implementing a low-level language in a high-level language is a strange thing to do. It's not a very productive use of time, but it can be interesting and educational.
I've used Forth before, especially Quartus Forth (now defunct) for Palm OS (also defunct), but have never implemented Forth before. I can't take credit for this implementation, as Mr. Jones did all the hard work in JONESFORTH, but I have a much better understanding of how the internals of a Forth implementation work.
The implementation turned out to be more complicated than I expected. There are more lines of code in my kernel than in the JONESFORTH kernel, which seems strange for a high-level language implementation. Some of this expansion may be due to my tendency toward over-abstraction, but a lot of it is due to the need to implement a virtual machine rather than just writing x86 code to be executed directly by the CPU.
It's disappointing that the suwaneeforth
executable is over 4 megabytes in size. In contrast, jonesforth
is around 13 kilobytes. But a "Hello, world!" application in Swift is 3.8 megabytes, so I don't think there is much I can do to reduce the size of the executable.
SuwaneeForth currently uses C standard library calls like getchar
, putchar
, exit
, and abort
to handle I/O and process termination. To make the ForthMachine
useful as an embedded interpreter within an application, there should be some sort of delegate to handle the interface to the host process. Maybe something like this:
protocol ForthMachineHost {
// Get next input character, or return EOF on end of input
func readChar(forthMachine: ForthMachine) -> FChar
// Send character to output
func writeChar(forthMachine: ForthMachine, char: FChar)
// Called on a normal termination condition (e.g., EOF or BYE)
func onExit(forthMachine: ForthMachine)
// Called on an abnormal termination condition
func onAbort(forthMachine: ForthMachine, errorMessage: String)
}
class ForthMachine {
// ...
weak var host: ForthMachineHost
// ...
}
If I wanted to implement Forth or a Forth-like language in Swift from scratch, I would try to take advantage of Swift's abstraction capabilities and rely less on assembly-style byte-by-byte bit twiddling. For example, a dictionary entry could be defined something like this:
class FDictionaryEntry {
var link: FDictionaryEntry
var name: String
var isHidden: Bool
var isImmediate: Bool
var codeword: FCell
var data: [FChar]
// methods ...
}
and then the dictionary would be a linked list of dictionary entries, rather than a big array of bytes. This might make it impossible to make it compliant with ANS Forth, and may prohibit some common Forth idioms, but it would be easier to understand and there would be less code to write.
It would be nice to be able to define primitives with a syntax like this (instead of using the enum
and switch
and a lot of methods):
defcode("SWAP") {
let (x1, x2) = pop2()
push(x2, x1)
}
I considered doing this in SuwaneeForth, but making each primitive a full-fledged Swift method made debugging and testing easier.
To make Forth useful for OS X and iOS development, one would want some kind of generic bridge to Objective-C. I have no idea how to do that, nor any interest in implementing it.