Goforth
A fully compiled and forth-like language including a small virtual machine.
Install / Use
/learn @loscoala/GoforthREADME
goforth
This is a compiler and byte code interpreter for a forth-like language.
In general, Forth is, among other things, a programming language whose ideals are freedom and simplicity. Freedom requires knowledge and responsibility, and simplicity is yet more demanding. But "simple" can also mean rudimentary. This Forth attempts to be simple as possible and also fast as possible.
In this forth you can define local variables with curley brackets like in other forths and produce callable byte code subroutines. The compiler produces human readable bytecode and is amazingly fast.
Overview
goforth is the interactive compiler which produces a sequence of commands for a virtual stack machine also known as "Forth machine". The code is then interpreted and executed.
Why goforth?
- Goforth can be used as an embeddable programming language. See topic embedding.
- Goforth can be used as a compiler to produce C-code from forth. See topic Generate C-code.
- Goforth can be embedded in all sorts of different documents using the template sytax. See topic templating.
Currently there is no parallel ForthVM execution via goroutines.
Installation
If you do want to get the latest version of goforth, change to any directory that is both outside of your GOPATH and outside of a module (a temp directory is fine), and run:
go install github.com/loscoala/goforth/cmd/goforth@latest
If you do want to add the latest version to your go.mod inside your project run:
go get github.com/loscoala/goforth@latest
Build and Dependencies
-
All you need is a golang compiler 1.22
-
Build the project with build.sh or
go build -C cmd/goforth -
For C code generation you need a working C compiler.
git clone https://github.com/loscoala/goforth.git
./build.sh
Usage
Execute goforth. See "Installation"
- You can also execute forth-scripts:
goforth --file=forthscript.fs
echo ": main 5 5 + . ;" | goforth
- In the Shebang you can alternatively write the following:
#!goforth --file
# code goes here...
- Or in the command line:
goforth -script '
: myfunc ." Hello World" ;
: main myfunc ;
'
The compiler has core.fs automatically included into the binary.
Howto start
Start ./goforth and in the REPL you can look what the dictionary contains by typing:
| command | description | |---|---| | % | Shows the complete dictionary | | % name | Shows the definition of name in the dictionary | | find name | Displays all definitions that contain the string | | use filename | Loads a file | | $ | Shows the values of the stack |
Examples
Global variables
Globals are defined with the variable compiler-builtin keyword.
Lets define a variable xx. This defines an entry in the global dictionary.
forth> variable xx
Now you can set for example the value 15 to xx:
forth> 15 to xx
forth> xx . \ prints the value of variable xx
Or lets sum all values from 0 to 100:
forth> variable vv
forth> 101 0 [ vv + to vv ] for vv .
Mandelbrot
Start the interpreter:
./cmd/goforth/goforth
Then inside the REPL type the following:
forth> use examples/mandelbrot.fs \ loads the file mandelbrot.fs and parses it
forth> mb-init \ compile and run mb-init
forth> true debug \ OPTIONAL in order to show byte code and benchmark
forth> mb \ compile and run mb
As a result you see a zoom in the mandelbrot fractal.

Interactive development
You can also define new words:
forth> : myadd 2 + ;
Or words with a local context:
: fakulty
1 { x }
1+ 1 ?do
x i * to x
loop
x
;
then run it:
forth> 5 fakulty .
which prints:
120
OOP with classes
Goforth supports OOP. A class is defnied by placing the word "class" after the semicolon. Each property has a number of cells as a prefix to the name of the property.
For example:
: class A
1 aa
5 bb
1 cc
;
While the compiler is parsing the class definition, several methods are generated:
TO BE CONTINUED...
Macros / Inlines
GoForth refers to macros as "inline." An inline is defined by placing the word "inline" after the semicolon. Then the name has to be given. After that you can have up to four arguments. These arguments are stored in registers. The number of arguments can be obmitted, then no word is removed before the macro is called. If you provide the number of words like: @1@ for 1 or @4@ for 4 then the number of words before the inline call are removed.
: inline test @4@
word #1#
word #2#
word #3#
word #4#
;
: myword
d c b a test
;
\ myword is now:
: myword
word a
word b
word c
word d
;
Generate C-Code
To translate one or more words into C code and generate a native binary file there is a compile statement.
This is how the html.fs example written in Forth can be easily translated into C and executed immediately:
forth> use examples/html.fs
forth> compile test
The word test was defined in the sample file as follows:
: test
document
[
[
[ ." charset=\"utf-8\"" ] meta
[ ." Example Page" ] title
] head
[
[ ." Example Page" ] h1
[ ." Hello " [ ." World!" ] b ] p
] body
] html
;
After the compile test statement, a C file main.c was generated in the lib directory and compiled with the C compiler and executed.
The result is also shown as follows:
<!doctype html>
<html lang="de-DE"><head><meta charset="utf-8"><title>Example Page</title></head><body><h1>Example Page</h1><p>Hello <b>World!</b></p></body></html>
Call external programs
The following example shows the usage of the shell word to list all the files and directories in the current directory under linux machine.
: ls [ a" ls -l" shell ] alloc ;
Optional: Now you can compile the word to a native binary.
forth> compile ls
Debugging
By calling true debug you can enable the benchmark mode.
forth> true debug
Now the byte code is displayed and the execution time of the program.
forth> true debug
forth> 5 3 min .
SUB min;TDP;LSI;JIN #0;DRP;JMP #1;#0 NOP;SWP;DRP;#1 NOP;END;
MAIN;L 5;L 3;CALL min;PRI;STP;
3
execution time: 15.947µs
Number of Cmds: 13
Speed: 0.000815 cmd/ns
The actual debugger can be run like this:
forth> debug 34 21 min .
Which gives the following result:
SUB min;TDP;LSI;JIN #0;DRP;JMP #1;#0 NOP;SWP;DRP;#1 NOP;END;
MAIN;L 34;L 21;CALL min;PRI;STP;
11 MAIN | | |
12 L 34 | 34 | |
13 L 21 | 34 21 | |
14 CALL min | 34 21 | 14 |
1 TDP | 34 21 34 21 | 14 |
2 LSI | 34 21 0 | 14 |
3 JIN #0 | 34 21 | 14 |
6 NOP #0 | 34 21 | 14 |
7 SWP | 21 34 | 14 |
8 DRP | 21 | 14 |
9 NOP #1 | 21 | 14 |
10 END | 21 | |
15 PRI | | | 21
16 STP
As you can see on the top there is the ByteCode and below you see the program pointer, the command, the stack, the return stack and the output.
Embedding
First, you have to add goforth to go.mod:
go get github.com/loscoala/goforth@latest
Then in golang you can import goforth:
import "github.com/loscoala/goforth"
Now all you need is a ForthCompiler:
fc := goforth.NewForthCompiler()
fc.Fvm.Sysfunc = func(fvm *goforth.ForthVM, syscall int64) {
switch syscall {
case 999:
value := fvm.Pop()
fvm.Push(value + value)
fmt.Println("This is a custom sys call in Forth")
default:
fmt.Println("Not implemented")
}
}
// Parse the Core lib:
if err := fc.Parse(goforth.Core); err != nil {
goforth.PrintError(err)
}
// Run some code:
if err := fc.Run(": main .\" Hello World!\" ;"); err != nil {
goforth.PrintError(err)
}
// Call custom syscall (calculated 10+10 and prints it):
if err := fc.Run(": customcall 999 sys ; : main 10 customcall . ;"); err != nil {
goforth.PrintError(err)
}
Templates
Goforth has a meta command called template <name> <filename>. When goforth parses a template file, it looks for opening and closing tags, which are <?fs and ?> which
tell goforth to start and stop embedding the code between them. Parsing in this manner allows goforth to be embedded in all sorts of different documents, as everything
outside of a pair of opening and closing tags is ignored by the goforth parser.
Example #1 Goforth opening and closing tags
Example file html.tfs:
<html>
<head>
<title><?fs title .s ?></title>
</head>
<body>
<?fs 10 0 do ?>
<p><?fs i . ?></p>
<?fs loop ?>
</body>
</html>
Now you can load the template named test:
f
