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.
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.
- 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.
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
-
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
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.
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 |
use filename | Loads a file |
$ | Shows the values of the stack |
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 .
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.
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
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>
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
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.
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)
}
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 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
:
forth> template test html.tfs
forth> variable title
forth> a" Example title" to title
forth> test
filename | description |
---|---|
main.go | The main function |
compiler.go | The forth compiler |
vm.go | A stack machine with additional functionality for forth |
stack.go | A stack of strings implementation |
label.go | A label generator for the bytecode |
show.go | Visual representation and the REPL |
config.go | Configuration during runtime |
The stack machine consists of a single LIFO stack and memory. Memory is adressed by an unsigned number. The stack and memory consists only of numbers.
command | description |
---|---|
RDI | Reads a value from the input. The input is implementation dependant. |
PRI | Prints a value from the stack as a number. |
PRA | Prints a value from the stack as an character. |
DUP | Duplicates the top stack value. |
OVR | Copies the second value from the top on the top. |
TVR | Copies the second pair onto the top pair. |
TWP | Swaps the top pair with the pair under it. |
QDP | ?dup implemented as: if the top value is zero - duplicate it - otherwise do nothing. |
ROT | Rotates the top three elements by pushing to stack one element "down" an taking the last element on the top. |
TDP | Duplicates the top pair on the stack. |
DRP | Drops the top element on the stack. |
SWP | Swaps the top pair on the stack. |
#id NOP | Does nothing. Used for labeling. |
JMP #id | Unconditional jump to label with id |
JIN #id | Conditional jump to label with id if the top value on the stack is zero. |
ADI | Simple addition of the top pair. |
SBI | Simple subtraction of the top pair. |
DVI | Simple division of the top pair. |
LSI | 1 on the stack if the top element is less than the second element. 0 otherwise. |
GRI | 1 on the stack if the top element id greater than the second element. 0 otherwise. |
MLI | Multiplies the first two elements on the stack. |
ADF | Simple float addition of the top pair. |
SBF | Simple float subtraction of the top pair. |
MLF | Multiplies the first two float elements on the stack. |
DVF | Simple float division of the top pair. |
PRF | Prints a float value from the stack as a number. |
LSF | 1 on the stack if the top float element is less than the second element. 0 otherwise. |
GRF | 1 on the stack if the top float element is greater than the second element. 0 otherwise. |
OR | 1 if one of the first two elements on the stack unequal to 0 else 0. |
AND | 1 if the first two elements on the stack unequal to 0 else 0. |
NOT | 1 if the first element on the stack is 0 else 0. |
EQI | 1 if the first two element on the stack are equal else 0. |
LV | Loads a value from the given address. |
L number | Loads a value on the stack. |
LF float | Loads a float on the stack. |
STR | Stores the second element from the stack into the memory address which is the first element from the stack. |
SYS | Make a syscall. The top element from the stack is used to make different syscalls. |
STP | Quits the execution. |
SUB name | Declares a subroutine. Used only for code generation. Not used by the vm |
END | Ends the subroutine. Used only for code generation. Not used by the vm |
MAIN | Declares the beginning of the main. Used only for code generation. Not used by the vm |
GDEF | Creates a new global variable initialized with zero |
GSET | Assigns the top value of the stack to a global variable |
GBL | Pushes the global value on top of the stack |
LCTX | Creates a new context of local variables |
LDEF | Pops the top value of the stack and copies it to the local definitions |
LSET | Assigns the top value of the stack to a local variable |
LCL | Pushes the local value on top of the stack |
LCLR | Clears the local definitions |
CALL name | Call a SUB routine. |
REF name | Pushes the address of a SUB on top of the stack |
EXC | Pops the top value from the stack and calls a SUB routine. |
PCK | dup = 0 pick |
NRT | -rot = rot rot |
TR | to r |
FR | from r |
RF | r fetch |
TTR | 2 to r |
TFR | 2 from r |
TRF | 2 r fetch |
INC | Increments the top value of the stack by 1 |
DEC | Decrements the top value of the stack by 1 |