Building my own Forth language

2024-07-21

For the past month or two I've been spending my free time working on my own implementation of a Forth-based language.

I'm currently calling it 3th since it's a toy so far. My end goal is to create a Processing-like graphics programming API that binds to Love2d graphics commands but written in a Forth-like language. Just typing that sentence out made me realize this is going to sound particularly niche, but here's my thinking:

The following currently works: the stack, math, creating new words, reverse polish notation, commenting, REPL, piped input, evaluating input from the command line. Not yet implemented: loops, conditionals, string arrays. Halfway working: variables, file input.

I already had my REPL working in my language (3th), and the ability to evaluate string input to the 3th program. So I added a simple pipe command and now the 3th language allows input from standard input. But I was hung up on implementing string arrays and conditionals. After several hours/days of working on this, I decided I jsut couldn't solve it working on the ipad alone, so I'd have to use a bigger monitor and my other software tools when I returned home and found the time. Then even when I arrived home I found I was bogged down and needed a break, something to 'raise the spirit' like when you start a campfire when camping. So I pivoted toward getting some minimal graphics compilation working.

In the paragraphs ahead, the word "word" itself references a particular meaning in Forth-based languages. You can think of it similar to a function in other languages. Words when compiled expand out to represent other words. You build up a program by writing words that compile to other series of words, and eventually all of these words are expanded into the minimal primitive commands that the language is built upon.

My goal had always been to write words that compile to graphics commands in the Love2d library, which I've written about previously. I decided to see if I could get a rectangle and ellipse drawing commands in 3th, background, the use of width and height as built-in variables, randomness for number generation, and ideally some text drawing on screen. I got all of these implemented, though in very minimal ways.

Examples:

1 0 0 color

compiles currently to:

love.graphics.setColor(1,0,0,1)

This is red, no green, no blue, and full opacity. I made opacity default to 1 for complete opacity, so it's not necessary to type it each time, since that's how Processing and p5.js work. In Processing and p5.js rather than 0 to 1 for colors we use 0 - 255 but I didn't implement that yet. But perhaps I should have as it would have been trivial. Finally, I had to think a lot about how I would use numbers from the stack. Since Forth languages are Last In First Out (LIFO) let's consider how I would translate a Processing-like command such as:

processing
rect( x , y , w , h );

In the reverse polish notation system of a Forth-based language should I have a user write: h w y x rect

I thought that was confusing and made this choice:

forth
x y w h rect

This means if your stack looks like this...

forth
100 200 300 400 
rect
...and when you call rect that you are drawing a rectangle at 100,200 with a width 300 and height 400.

I implemented the following commands as primitives, built in Lua. The words in parenthesis are called stack descriptions and are written as comments. They indicate what input from the stack is necessary.

Here are all of the words I added for drawing currently:

forth
rect ( x y w h -- )   draws a rectangle
ellipse ( x y w h -- ) draws an ellipse
color ( r g b -- ) RGB values set
width ( -- n ) adds width of screen to stack
height ( -- n ) adds height of screen to stack
print ( x y -- ) prints text at x y
random ( n -- n ) replaces top of stack with random number between 0 and previous top of stack
setvar ( n -- n ) Places top of stack in a special var
getvar ( -- n ) adds var to top of stack
fontsize ( n -- ) Changes fontsize
background ( -- ) Fill screen with current color

I wrote most of the above as primitives in Lua but a couple words I wrote in 3th itself. For example, background's definition:

forth
: background 0 0 width height rect ;

I had fun creating random circles on the screen for example. Here's how I did that:

forth
: randomcirc width random height 100 random 50 + setvar getvar getvar ellipse ;
randomcirc

That translates to something like: "Pick a random number between 0 and the width of the screen for the X. Pick a random number between 0 and the height of the screen for Y. Pick a random number between 0 and 100 and add 50 and set the var equal to that. Then use that for both the with and height and use these 4 values for the ellipse input, then call it.

One thing I had to figure out: both Processing, p5.js and Love2d allow variable amount of arguments for some commands. I don't have a great way to do that in my language without changing the syntax, and I didn't want to do that, so it means I'm making specific choices of the number of stack elements required for each command.

So in Processing and p5.js you can write random() or random(100) or random(0,100). Since I don't have a simple way of specifying number of arguments without using parenthesis I decided to only have random with a single argument. But it's not hard to make a range. For example 100 random 50 + means get a random number between 0 and 100, then add 50 to it, so the range is 50 to 150. If you wanted to create a new word randomrange that could be written as:

: randomrange ( min max -- n **random output within range** ) 2dup swap - nip random + ; 

I ended up making a minimal "zine" with some text and graphics created by my language. I decided to call it p3th as a tribute to p5.js. I did the text as comments since I didn't fully implement the print command to write arbitrary string input yet. Currently the print command pulls a random word from the dictionary and writes it to the screen at the specified x y location.

I created a few images and wrote some text in comments, screenshotted and compiled into a pdf. You can check it out below.

p3th zine (pdf)

3th language in progress (https)

Back to index

---

Send a comment by emailing lettuce at the ctrl-c.club domain. Please list the post and your public handle.