Firth is an experimental programming language heavily inspired by Forth. Its goals are to be small, fast and efficient, portable, and embeddable.
The latest release build executable version of Firth is just 37 kilobytes in size. The debug build is only 73 kilobytes. That is small for a compiler and an interpreter with over 192 built-in library functions!
There are ZERO heap memory allocations in Firth (compilation or execution) after initializing the run-time. All memory, including the stacks, dictionary, etc. is acquired through a single memory allocation once at startup.
The Firth codebase builds and runs in both 32-bit and 64-bit environments. It is built and tested regularly using Continuous Integration on Windows, Mac OSX and Linux.
I believe that embedding a scripting language should be easy. LUA does this well and was the inspiration for the API design. Embedding Firth is as easy as including just a few .C files and calling some initialization functions. You can see some examples here. Firth can access native variables and call into native C functions. The host app can read/write Firth constants and variables and execute code written in Firth.
This is the C version of Firth. There is also an earlier C++ version available here. The main differences between the two versions are:
- This version is written in C to be more portable
- The C version uses a traditional linked list dictionary implementation
- The C version uses subroutine threading vs. bytecode interpreter
If you are new to Forth, you can learn a lot of the basics at Easy Forth. I also highly recommend Starting Forth by Leo Brodie of Forth, Inc.
I created Firth primarily as an exercise in learning Forth. And also to have my own environment for experimentation. The original plan was to create a simple Forth system, written in C++. However, along the way I realized that there were some things about Forth that I probably wanted to change. Mainly small things to modernize the syntax to make it a little bit easier for beginners. That said, Firth remains largely Forth compatible.
If you are already familiar with Forth you may find the idea of creating a new Forth-like language to be strange. After all, a key feature of Forth is the ability to completely redefine existing behavior.
Rather than create a version of Forth that might not be compatible with existing Forth code, it seemed a better idea to make a language heavily influenced by, and largely compatible with Forth. While still retaining the option to break basic Forth compatibility when and where that made sense.
In Firth, as in Forth, there are really only two key concepts. There are Numbers
and there are Words
.
Everything that is not a Word
is a Number
. Numbers largely live on the stack.
When you type a number Firth pushes it onto the stack. Parameters (or arguments)
to words are usually passed on the stack. The result of a word is usually placed
on the stack.
Arithmetic in Firth, as in Forth, uses Reverse Polish Notation (RPN). To add two numbers together you would write:
Firth> 1 2 +
ok
Firth> .S
Top -> [ 3 ]
ok
This code pushes the number 1 and then the number 2 onto the stack. And then it
adds those two numbers together, replacing them with the result on the
stack. The built-in Word
called .S
prints out, without modifying, the
contents of the stack.
Words
are simply another name for functions. In Firth it is very easy to create new
Words
. Let's create a Word
for addition. To do so looks like this:
Firth> func add + ;
ok
Firth> 1 2 add
ok
Firth> print
3 ok
This code creates a new Word
named add
that calls +
to add the top two stack
entries and put the result on the stack. The built-in Word
called print
prints the top of stack.
Below are a few examples of Firth in action. First, the canonical hello world program.
Firth> func hello ." Hello World! " ;
ok
Firth> hello
Hello World!
ok
Next, we define a new Word
called fibonacci
that computes the Fibonacci sequence
for the number currently on the stack.
Firth> func fibonacci DUP
0<> IF DUP 1
<> IF
0 1 ROT 1- 0 FOR DUP ROT + LOOP NIP
ENDIF
ENDIF
;
ok
Firth> 9 fibonacci PRINT
34 ok
You may be asking, where does Firth diverge from Forth? In nearly all cases
I've added "syntactic sugar" to modernize the feel of the code. These are
implemented as Word
synonyms. You can stick with Forth syntax if you prefer.
Or you can migrate to the more modern Firth syntax when and as you wish.
Here are the word synonyms that Firth offers:
Firth | Forth | Comments |
---|---|---|
VAR | VARIABLE | The keyword var is pretty common in modern languages |
CONST | CONSTANT | The use of const is also pretty common |
FUNC | : | Colon seems obscure for a modern function declaration |
ENDIF | THEN | I prefer IF-ELSE-ENDIF to the Forth IF-ELSE-THEN construct |
FOR | DO | The limit and index make this is a FOR loop by modern standards |
ALLOC | ALLOT | Alloc seems more modern for allocating space for memory cells |
At the moment I prefer func
as a colon synonym. It is short yet descriptive,
which seems to be in the spirit of Forth word naming. So that is what I've
documented here.
That said, I am still actively debating whether to use the Rust fn
,
Swift's func
, Javascript's function
or Python's def
as a replacement
for :
. For now they all exist to see which feels better. If you have strong
opinions please let me know. You can configure the defaults in
firth_config.h.
Firth is designed to be very easy to embed into other apps. Doing so requires integration of one .c and one .h file (firth.c and firth.h) and just a few Firth API calls. Adding in optional floating point support words involves one additional API call and one more .c file (firth_float.c).
The file main.c demonstrates how to initialize Firth and add custom
Words
for a constant, a variable, and a native function. The important excerpts
of main.c are shown below.
#include "firth.h"
// custom word functions
static int isEven(Firth *pFirth)
{
FirthNumber n = fth_pop(pFirth);
fth_push(pFirth, (n % 2) == 0 ? FTH_TRUE : FTH_FALSE);
return FTH_TRUE;
}
static int isOdd(Firth *pFirth)
{
FirthNumber n = fth_pop(pFirth);
fth_push(pFirth, (n % 2) ? FTH_TRUE : FTH_FALSE);
return FTH_TRUE;
}
// register our collection of custom words
static const FirthWordSet myWords[] =
{
{ "even?", isEven },
{ "odd?", isOdd },
{ NULL, NULL }
};
// examples of calling Firth from native code
void callFirth(Firth *pFirth)
{
// fth_exec_word is a set of convenience methods to push
// 1, 2, or 3 parameters on stack and execute a word
fth_exec_word2(pFirth, "+", 1, 2);
// execute any defined word, no passed parameters
fth_exec_word(pFirth, ".");
// parse and execute a line of text
fth_parse_string(pFirth, "CP @ .");
}
int main()
{
// create a new Firth state object
pFirth = fth_create_state();
// use our output function
fth_set_output_function(pFirth, myPrint);
banner(pFirth);
// REPL loop
while (!pFirth->halted)
{
fth_update(pFirth);
}
// we're done, cleanup and quit
fth_delete_state(pFirth);
return 0;
}
I have created a small test harness to allow testing words in Firth. This framework is called test.fth and is located in the test sub-folder. You load the framework as follows:
Firth> include test\test.fth
ok
I have also created a set of unit tests for the core Firth Words
. The unit
tests are also found in the test sub-folder in a file called
core-tests.fth. Once again you load the tests by including the file.
I found that having unit tests was critical to the development of this project. It allowed me to ensure that newly added words functioned correctly. But even more importantly, having a rich set of unit tests allowed me to re-factor and optimize code with confidence.
The core-tests.fth file both defines and runs the unit tests. If all goes well a message will be displayed saying that All tests passed!. At this time there are more than 142 individual test cases.
Firth> include test\core-tests.fth
Test DROP
1 √
Test SWAP
2 √
Test DUP
3 √
Test MAX
4 √
5 √
6 √
Test MIN
7 √
8 √
9 √
Test NEGATE
10 √
11 √
12 √
Test ABS
13 √
14 √
15 √
Test NIP
16 √
Test NOT
17 √
18 √
Test OR
19 √
20 √
21 √
22 √
23 √
Test XOR
24 √
25 √
26 √
27 √
Test 2DUP
28 √
Test 2DROP
29 √
Test ?DUP
30 √
31 √
Test */
32 √
33 √
34 √
Test <
35 √
36 √
37 √
Test >
38 √
39 √
40 √
Test =
41 √
42 √
Test <>
43 √
44 √
Test 0=
45 √
46 √
Test 0<
47 √
48 √
49 √
Test 0>
50 √
51 √
52 √
Test 0<>
53 √
54 √
Test AND
55 √
56 √
57 √
58 √
59 √
Test OVER
60 √
Test POW
61 √
62 √
Test ROT
63 √
Test TUCK
64 √
Test +
65 √
66 √
67 √
Test -
68 √
69 √
70 √
Test *
71 √
72 √
73 √
Test /
74 √
75 √
76 √
Test MOD
77 √
78 √
79 √
Test /MOD
80 √
81 √
82 √
Test SQR
83 √
84 √
85 √
86 √
Test 1+
87 √
88 √
89 √
Test 1-
90 √
91 √
92 √
Test LSHIFT
93 √
94 √
95 √
Test RSHIFT
96 √
97 √
98 √
99 √
Test DO LOOP
100 √
101 √
Test FOR LOOP
102 √
Test FOR +LOOP
103 √
Test DEPTH
104 √
105 √
106 √
Test IF ELSE THEN
107 √
108 √
109 √
110 √
111 √
112 √
Test IF ELSE ENDIF
113 √
114 √
115 √
116 √
117 √
118 √
Test >R R>
119 √
120 √
Test 2>R 2R>
121 √
122 √
Test CHARS
123 √
124 √
Test CELLS
125 √
126 √
Test RECURSE
127 √
128 √
Test UNLOOP and EXIT
129 √
Test BEGIN and UNTIL
130 √
131 √
132 √
Test WHILE and REPEAT
133 √
134 √
135 √
136 √
Test AGAIN
137 √
138 √
All tests passed!
There are a (growing) number of basic Words
that have already been defined in Firth.
They are:
Word | Description | Stack effects |
---|---|---|
ABS | take absolute value of TOS | ( n -- |n| ) |
AGAIN | loop back to BEGIN | ( -- ) |
ALLOT | reserve n extra cells for array | ( n -- ) |
ALLOC | synonym for ALLOT | |
AND | bitwise AND | ( n1 n2 -- n3 ) |
BEGIN | start an indefinite loop | ( -- ) |
BEL | emits a BEL char | ( -- ) |
BL | put a space on the stack | ( -- sp ) |
CELLS | calculate cell count for array size | ( n -- n ) |
CHAR | put the ascii value of the next character on the stack | ( -- n ) |
CHARS | calculate space needed for n chars | ( n -- count ) |
CONST | define a new constant | ( n -- ) |
CR | print a carriage return | ( -- ) |
DEPTH | put current depth of stack on data stack | ( -- n ) |
DO | start a definite loop | ( limit index -- ) |
DROP | discard TOS | ( n -- ) |
DUP | duplicate TOS | ( n -- n n ) |
ELSE | start of else clause | ( -- ) |
EMIT | print TOS as ASCII | ( n -- ) |
ENDIF | synonym for THEN | |
ERASE | set count chars to zero at addr | ( addr count -- ) |
EXIT | exit from current loop | ( -- ) |
FALSE | constant representing logical false | ( -- f ) |
FILL | fill count chars with char at addr | ( addr count char -- ) |
FOR | synonym for DO | ( limit index -- ) |
FUNC | begin definition of new word | ( -- ) |
I | put current loop index on the stack | ( -- n ) |
INCLUDE | load and parse the given Firth file | ( -- ) |
IF | start a conditional | ( f -- ) |
LF | print a line feed | ( -- ) |
LOOP | inc index by 1, end of definite loop | ( -- ) |
+LOOP | inc index by TOS, end of definite loop | ( n -- ) |
LSHIFT | logical shift left n, u places | ( n1 u -- n2 ) |
MAX | leave greater of top two stack entries | ( n1 n2 -- n1 |
MAX-INT | puts largest representable int value on stack | ( -- n ) |
MIN | leave lesser of top two stack entries | ( n1 n2 -- n1 |
MIN-INT | puts smallest representable int value on stack | ( -- n ) |
MOD | compute remainder | ( n1 n2 -- n3 ) |
NEGATE | change sign of TOS | ( n -- -n ) |
NIP | discard the second entry on stack | ( n1 n2 -- n2 ) |
NOT | bitwise NOT | ( n1 n2 -- n3 ) |
OR | bitwise OR | ( n1 n2 -- n3 ) |
OVER | dupe the second stack entry to the top | ( n1 n2 -- n1 n2 n1 ) |
POW | raise x to power of y | ( x y -- x^y ) |
RECURSE | Cause current word to call itself | ( -- ) |
REPEAT | loop back to BEGIN | ( -- ) |
ROT | rotate the top 3 stack entries | ( n1 n2 n3 -- n2 n3 n1 ) |
RSHIFT | logical shift right n, u places | ( n1 u -- n2 ) |
SWAP | swap top two stack entries | ( n1 n2 -- n2 n1 ) |
TAB | prints a tab char | ( -- ) |
THEN | end of IF conditional | ( -- ) |
TRUE | constant representing logical true | ( -- f ) |
TUCK | copy the top stack item below the second stack item | ( n1 n2 -- n2 n1 n2) |
UNLOOP | Discard the loop-control parameters for the current loop | (r: limit index -- ) |
UNTIL | end of indefinite loop | ( -- ) |
VAR | define a new variable | ( -- ) |
WHILE | test whether loop condition is true | ( -- ) |
WORDS | list all words in the dictionary | ( -- ) |
XOR | bitwise XOR | ( n1 n2 -- n3 ) |
2DUP | duplicate top two stack entries | ( n1 n2 -- n1 n2 n1 n2 ) |
?DUP | duplicate TOS if it is non-zero | ( n1 -- n1 | n1 n1 ) |
; | end definition of new word | ( -- ) |
+ | addition | ( n1 n2 -- n3 ) |
- | subtraction | ( n1 n2 -- n3 ) |
* | multiplcation | ( n1 n2 -- n3 ) |
/ | division | ( n1 n2 -- n3 ) |
*/ | multiply then divide | ( n1 n2 n3 -- n4 ) |
/MOD | remainder and quotient | ( n1 n2 -- n3 n4 ) |
< | less than comparison | ( n1 n2 -- f ) |
> | greater than comparison | ( n1 n2 -- f ) |
= | equivalence comparison | ( n1 n2 -- f ) |
<> | not equivalence comparison | ( n1 n2 -- f ) |
0= | true if TOS is zero | ( n -- f ) |
0< | true if TOS is less than zero | ( n -- f ) |
0> | true if TOS is greater than zero | ( n -- f ) |
0<> | true if TOS is not equal zero | ( n -- f ) |
. | print TOS | ( n -- ) |
.S | non-destructively print the stack contents | ( -- ) |
." | print the following " delimited string | ( -- ) |
['] | store the execution token of the next word as a literal | ( -- ) |
- Note: These are not yet implemented
Firth is designed to be somewhat configurable. Configuration parameters are adjusted by editing the file firth_config.h and rebuilding Firth. The parameters are:
Name | Description |
---|---|
FTH_UNDEFINED | The value placed in memory for uninitialized variables |
FTH_CASE_SENSITIVE | Controls compiler case sensitivity |
FTH_INCLUDE_FLOAT | Controls whether floating point is compiled into Firth |
FTH_MAX_WORD_NAME | Sets the limit for the length of a word name |
FTH_DEFAULT_DATA_SEGMENT_SIZE | Sets the default size reserved for variables |
FTH_MAX_PRINTF_SIZE | Sets the limit for the length of a firth_printf result |
FTH_EPSILON | Defines epsilon for floating point precision |
Forth traditionally does not include support for floating point. Firth by default does include primitive floating point support, adding a floating point number stack, float variables and load/store operations.
If memory space is at a premium and you do not need or want floating point support you can edit the file firth_config.h to remove it.
By also including the files firth_float.c and firth_float.h you can install a set of floating point support words.
#include "firth.h"
#include "firth_float.h"
...
FirthFloat fSimTime;
// create a float const and var
fth_define_word_fconst(pFirth, "PI", 3.1415926);
fth_define_word_fvar(pFirth, "SimTime", &fSimTime);
Below is a list of the additional words that the floating point library includes. In the listing below TOFS represents the top of float stack.
Word | Description | Stack effects |
---|---|---|
F+ | float addition | ( f: n1 n2 -- n1+n2 ) |
F- | float subtraction | ( f: n1 n2 -- n1-n2 ) |
F* | float multiplication | ( f: n1 n2 -- n1*n2 ) |
F/ | float division | ( f: n1 n2 -- n1/n2 ) |
F. | print TOFS | ( f: n -- ) |
F@ | fetch a float variable | ( s: addr -- f: n ) |
F! | store a float variable | ( f: n s: addr -- ) |
F< | less than comparison | ( f: n1 n2 -- s: f ) |
F> | greater than comparison | ( f: n1 n2 -- s: f ) |
F= | equivalence comparison | ( f: n1 n2 -- s: f ) |
F<> | not equivalence comparison | ( f: n1 n2 -- s: f ) |
FABS | absolute value of TOFS | ( f: n -- abs(n) ) |
FCONST | define a new float constant | ( f: n -- ) |
COS | cos of TOFS | ( f: n -- cos(n) ) |
FDEPTH | put depth of float stack on data stack | ( s: -- n ) |
FDROP | drop the TOFS | ( f: n -- ) |
FDUP | duplicate the TOFS | ( f: n -- n n ) |
FEXP | exp of TOFS | ( f: n -- exp(n) ) |
FLN | natural log of TOFS | ( f: n -- log(n) ) |
FNIP | discard the second entry on stack | ( f: n1 n2 -- n2 ) |
FOVER | copy the second stack entry to the top | ( f: n1 n2 -- n1 n2 n1 ) |
FROT | rotate the top 3 stack entries | ( f: n1 n2 n3 -- n2 n3 n1 ) |
SIN | sin of TOFS | ( f: n -- sin(n) ) |
TAN | tan of TOFS | ( f: n -- tan(n) ) |
FTUCK | copy the top stack item below the second stack item | ( f: n1 n2 -- n2 n1 n2) |
SQRT | square root of TOFS | ( f: n -- sqrt(n) ) |
FSWAP | swap the top two float stack entries | ( f: n1 n2 -- n2 n1 ) |
FVAR | define a new float variable | ( f: -- ) |
.F | non-destructively print the float stack contents | ( f: -- ) |