Forth is an old stack-based language, primarily used in embedded / resource-constrained environments. I watched this Forth intro talk.
- JonesForth - writing Forth compiler in Forth (with some assembly!)
- GForth source code
- Starting Forth - classic web book
- Easy Forth - web book
- A Beginner's Guide to Forth - online notes
- more online notes
- Learn Forth in Y Minutes
- Forth Style Conventions
- Swift Forth is a proprietary IDE for Forth - it can be used free of cost for non-commercial use.
- Calling it an IDE might be an over-statement - I just tried it, and it doesn't even have syntax hightlighting in its REPL! I am definitely not impressed - it is very little improvement over just using
gforth
in the terminal!
- Install the REPL -
sudo dnf install -y gforth
. (Obviously, replacednf
with the package manager of your distro. For example, useapt
for Debian based distros.) gforth
by itself is quite bare bones - the usage experience can be vastly improved by using Jupyter Notebook. The Forth kernel for Jupyter can be installed like this:
pip3 install jupyter forth_kernel
...or if you want to use the development version of forth_kernel
:
git clone https://sohang3112/iforth.git
cd iforth
pip install jupyter .
Now, jupyter kernelspec list
should include IForth
kernel.
Note: All the below code was tested to work in gforth 0.7.3
- it may or may not work exactly like this in any other REPL.
- Run multiple Forth scripts with
gforth file1.fs file2.fs
.
OR
- Start
gforth
, then type the commands" file-location.fs included
(wherefile-location.fs
should be replaced by your Forth script's location - this will run the script and then return back to REPL, where all the state (stack, words) of script is available.) Issue: Didn't work for me in GForth in WSL 2
- Numbers: (example: 5, -42)
- Booleans: are represented by
-1
(TRUE) and0
(FALSE) - Strings:
- Print a string like this:
." Hello World!"
(it prints Hello World!). Note that there is a space after starting ." - that is because."
is a word that prints until it reaches ". - Store a string using word
s"
like this:
- Print a string like this:
s" 123" ok \ a string - note space after s"
.s <2> 93888287649920 3 ok \ string's address, number of characters were pushed on the stack
type 123 ok \ print the string whose address, no. of characters are on top of the stack
Individual characters in string can be accessed using word C@
(char-fetch). This Reddit comment shows an example of iterating through all characters in a string.
Forth Words are case insensitive. They are equivalent to functions / procedures in other languages.
Unless otherwise mentioned, Forth words always consume (remove from stack) their inputs, and place their output (if any) on top of stack.
.s
prints whole stack with length (example:<3> 1 2 3
where<3>
indicates length of stack)
Note: Only the top 9 items of the stack are shown. However, the length is always shown accurately.clearstack
clears stackswap
swaps top 2 elements of stackdup
copies top of stack?dup
copies top of stack if it is non-zeroover
copies second top of stackrot
rotates top 3 entries of stack to the right (example:4 5 6
becomes6 5 4
).-rot
rotates in the opposite direction (example:6 5 4
becomes4 5 6
).drop
removes top of stack.nip
removes second of stack.tuck ( a b -- b a b )
copies top of stack below the second top of the stack.- Similar operations for 2 elements:
2dup
,2drop
, etc. roll
movesn
th element of stack to top of stack.pick
copiesn
th element of stack to top of stack.depth
shows depth of stack
Notes:
- The default stack can only hold integers.
- The operations mentioned so far are all on the Parameter Stack. But the Return Stack can also be manipulated using the words
R>
,>R
,R@
. See more about the Return Stack here.
- Normal Math Operators:
+
,-
,*
,/
- For Convinience:
1+
,1-
,2+
,2-
,2*
,2/
max
,min
: work using top 2 numbers of stack
<
,<=
,<>
(not equal),=
,>
,>=
,0<
,0<=
,0<>
,0=
,0>
,0>=
,within
- Versions of these operations also exist for: doubles
d
, unsignedu
, floatsf
.
- These boolean operations output
0
for FALSE, and-1
for TRUE. (not sure why -1 !) - Higher level operations (like on complex numbers or matrices) can be done using Forth Scientific Library.
.
prints top of stack (and removes it from stack).page
clears the screen.emit
prints character corresponding to ASCII code (example:65 emit
printsA
).char
converts next character in input stream to ASCII code (example:char A .
prints65
).spaces
prints number of specified spaces (example:15 spaces
prints 15 spaces).cr
prints a newline (AKA carriage return).key
waits for user to press a character and immediately returns. It doesn't print the character, and also doesn't wait until Enter is pressed.ms
sleeps for specified number of milliseconds - eg.5000 ms
sleeps for 5 seconds.
- Wait for a single character, print it, then return:
: read-char key dup emit ;
\
comments until end of line.(
comments until closing parenthesis)
.
Commenting Convention: ( x -- description of x )
Note: Whitespace is necessary around \
, (
and )
since they are normal verbs.
- Point Free Style (arguments not explicitly named):
: NEW-WORD ( ... everything until ; ) ;
creates a wordNEW-WORD
. - Explicitly named arguments:
: NEW-WORD { a b } ... ;
defines wordNEW-WORD
which uses argumentsa
,b
from the stack. An example of more advanced usage (including local variables in word) is in this Stack Overflow question.
-
: square ( x -- x^2 )
defines asquare
word, that can be used like this:5 square .
(which prints25
). -
: dot ( x1 y1 x2 y2 -- z ) rot * rot * + ;
defines dot product of 2d vectors. Use it like this:1 2 3 4 dot .
i.e.,$(\hat{i} + 2\hat{j}) \cdot (3\hat{i} + 4\hat{j})$ - TODO: 2d vector cross product
hex
switches to hexadecimal mode, i.e., memory locations will now print in hexadecimal.
Similarly,decimal
,binary
switch to decimal and binary modes, respectively.
3.14 constant PI \ define constant
PI . \ print constant
Define (using VARIABLE
) & set (using !
) a variable memory-location
:
hex \ print all memory locations in hexadecimal
VARIABLE memory-location \ define variable - this basically defines a word that pushes its own address on the stack
200 memory-location ! \ set variable to 200 (& remove from stack)
memory-location . \ prints address of memory-location
memory-location @ . \ fetch and print value at address `memory-location`
Note: Instead of fetch-and-print (@ .
, as in last statement of the above code), the word ?
can be used to directly print the variable's value. So, the last line of the above code can also be written as memory-location ?
.
END START do INSTRUCTIONS-HERE loop
is equivalent to:
for _ in range(START, END):
# INSTRUCTIONS-HERE
The loop index can be accessed using the word I
. For example:
: DECADE 10 0 DO I . LOOP ;
DECADE
Custom step in the loop can be used by using the word +loop
instead of loop
. +loop
expects the step on the top of the stack. The following Forth and Python codes are equivalent:
: RANGE-STEP 10 0 DO I . 2 +LOOP ;
RANGE-STEP
for i in range(0, 10, step=2):
print(i)
- Indefinite Loop (example copied from learnxinyminutes.com):
\ Indefinite loops with `begin` <stuff to do> <flag> `until`:
: death ( -- ) begin ." Are we there yet?" 0 until ; \ ok
Note: Loop words cannot be interpreted directly - that is why we have defined a word DECADE
that uses do .. loop
and then called the word.
TODO: Words while
, again
, repeat
, leave
, unloop
Words can be passed to other words like this:
- Quoting a word gives the address of the word (example:
' negate
places the address of wordnegate
on the stack). - A word can be run from its address using its address using
execute
. So we can pass an address of a word to another word, which can then run it usingexecute
. (example:5 ' negate execute
is same as5 negate
). - Annonymous words can be created using
:noname
(example::noname dup * ; 5 execute .
prints 25).
Forth has multiple built-in stacks - the one discussed now was actually the most commonly used, Parameter Stack.
Other stack types also exist:
- Return Stack: Forth internally uses the Return Stack to store return addresses while calling words. Read about it here in the section titled The Return Stack.
- Floating Point Stack: It stores floating point numbers (default stack can only store integers).
see WORD
shows source code ofWORD
.WORDS
word prints all the words from the top (pre-defined) vocabulary.VOCS
word prints all defined vocabularies.
Note: This Stack Overflow answer details other ways of inspecting words and vocabularies.
- Marker mark a spot in the stack. Later, any words after the marker can be deleted by executing the marker. Didn't understand syntax
<ADDR> <SIZE> dump
- never tried!- Words
CREATE
,DOES>
- don't understand what they do! - String Indexing
- Arrays - Interesting explanation in Stack Overflow
- How to read input from STDIN?? Tried to understand by reading this, but it's too complicated!
- C FFI (Foreign Function Interface) - how to call C functions from Forth code?
- Number Formatting (as string):
- How to create words like
char
, which wait for input after the word, not before??
- Find value of quadratic
$ax^2 + bx + c$ for given$x$ (see source):
: quadratic ( a b c x -- n ) >r swap rot r@ * + r> * + ;
clearstack 4 -7 3 10 quadratic .s \ example usage
This prints <1> 333 ok
- Area of a circle
$\pi r^2$ (source here):
: circle ( r -- pi*r^2, decimal truncated ) dup * 355 113 */ ;
2 circle . / example usage - area of a circle of radius 2
This prints 12
, whereas the exact answer is 12.566
- so only the fraction part has been truncated.
Note: Here, we have utilized the fact that 355/113
is a very good approximation of pi
(accurate upto 6 decimal places).
$x \pm y$
: +- ( x y -- x+y x-y ) 2dup + -rot - ;
clearstack 6 5 +- .s \ 11 1 -- (6+5) (6-5)
- Print
n
Fibonacci Numbers:
: fibonacci ( x y n -- x,y <- starting numbers, n <- how many to print ) 0 do over . tuck + loop ;
0 1 10 fibonacci \ example usage
This prints 0 1 1 2 3 5 8 13 21 34 ok
- Sum of first
n
natural numbers using formula$n(n+1)/2$ :
: sum-nat ( n -- sum of first n natural numbers ) dup 1+ 2 */ ;
clearstack 10 sum-nat .s \ example usage
This prints <1> 55 ok
- 55
is sum of first 10 natural numbers.
- Determinant of Quadratic Equation
a*x^2 + b*x + c = 0
:
: determinant ( a b c -- b^2 - 4ac ) rot -4 * * over dup * + nip ;
clearstack 4 -7 3 determinant .s \ example usage - for equation 4x^2 - 7x + 3
This prints <1> 1 ok
- the inputs a
, b
, c
have been removed from the stack, and the output 1
is now on top of stack.
- Factorial:
: factorial ( n -- n! = product of 1 to n ) 1 over 1+ 2 do i * loop nip ;
clearstack 6 factorial .s \ example usage - 6! = factorial of 6
This prints: <1> 720 ok
- Puts digits of a number on the stack:
: print-digits ( num ) begin 10 /mod over 0= until 2drop ;
12345 print-digits .s
This prints <5> 5 4 3 2 1 ok
- Multiplication Table:
: multiples ( lim n -- lim n ; prints n, 2*n, 3*n .. upto limit*n ) over 1+ 1 do dup i * . loop ;
: mul-table ( lim -- lim ; prints multiplication table from 2 to lim ) dup 1+ 2 do cr i multiples drop loop ;
clearstack 5 mul-table .s \ example usage
This prints:
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25 <1> 5 ok
- Swap second and third items on the stack:
: swap-skip1 ( a b c -- b a c ) -rot swap rot ;
clearstack 2 3 1 swap-skip1 .s \ example usage
This prints <3> 3 2 1 ok
- Fizz Buzz
: fizz-buzz ( n -- ; Prints Fizz | Buzz | FizzBuzz )
dup 15 mod 0 = if
." FizzBuzz" drop
else
dup 3 mod 0 = if
." Fizz" drop
else
dup 5 mod 0 = if
." Buzz" drop
else .
then
then
then ;
: fizz-buzz-upto 1+ 1 do cr i fizz-buzz loop ;
100 fizz-buzz-upto
TODO: Simplify fizz-buzz
- Is it a leap year?
: true -1 ;
: false 0 ;
: leap-year? ( y -- is y a leap year? )
dup 400 mod 0 = if \ if y mod 400 == 0
true \ ans = true
else
dup 100 mod 0 = if \ else if y mod 100 == 0
false \ ans = false
else
dup 4 mod 0 = \ ans = y mod 4 == 0
then
then nip ; \ remove y from stack
2016 leap-year? \ example usage - outputs -1 (TRUE) or 0 (FALSE)
Note: Here, dup
has been used before all conditionals (eg. dup 400 mod 0 =
) to preserve a copy of y
for subsequent operations (since only 400 mod 0=
would consume y
on the stack). In the end, y
is removed from the stack using nip
.
- Print Hello in an infinite loop, until user presses q to quit.
: inf-loop ( c -- c ; keep printing Hello, until given character is pressed )
begin cr ." Hello" dup key = until ;
char q inf-loop \ start infinite loop, with 'q' as the exit character
- Print sign of a number:
: sign ( n -- ; prints sign )
dup 0> if ." POSITIVE"
else dup 0< if ." NEGATIVE"
else ." ZERO" then then ;
- GCD (Greatest Common Divisor), LCM (Least Common Multiple):
: gcd ( m n -- ans )
2dup < if swap then
begin tuck mod dup 0= until
drop ;
clearstack 30 40 gcd .s \ <1> 10 ok
: lcm ( m n -- ans ) 2dup gcd */ ;
clearstack 66 121 lcm .s \ <1> 726 ok