🙋 The Little Man Stack Machine
Firth: A High Level Stack Oriented Language
A design goal of the LMSM is to provide just enough infrastructure such that a small, high-level programming language can be built for it relatively easily. Because of the stack instructions that the LMSM adds to the LMC, a stack-oriented programming language makes the most sense.
Stack languages are not popular today, but in the past there was a lot of interest in them, and they provide an interesting, alternative way to think about programming.
The programming language for the LSMS is called "Firth", which is, to an extent, based on the Forth programming language. Similar to Forth, Firth mainly uses a Reverse Polish notation style of syntax, and the concept of an implicit stack.
It is recommended that a Firth compiler is provided with implementations of the LMSM, to show how a high-level programming language can compile down to LMSM assembly.
An Example Firth Program: Squaring A Number
Before we delve into the details of Firth, let's look at an example program, based on the SQUARE
assembly program we looked at on the LMSM Assembly page. The Firth program
that would compile down to this assembly looks like this:
get square() . def square() dup * end
This program probably looks a little strange to you, so let's go through it line by line.
The first line, get
, gets an integer from the user and pushes it onto the top of the value stack.
The second line, square()
, invokes the function square
. Note that the argument to
the square
function is passed implicitly, on the value stack. A little crazy but
this is how Forth and many stack-based programming languages work.
The third line, .
, prints the top of the stack out.
Next comes the definition of the function square()
. Its body consists of a single line: dup *
.
These two "words" are the Firth way of saying "Duplicate the top of the value stack and then replace the top two
values of the value stack with the result of multiplying them together."
This is the "Reverse Polish notation" nature of Firth. In a more traditional programming language, this calculation would look something like this:
def square(x) x * x end
In this code the multiplication operator uses Infix notation. Firth, instead, uses the RPN style, where you "push" values onto a stack and then apply operations to them.
Note that there is no explicit return
in the definition of the square()
function. There is actually support for the return
keyword in Firth, but it isn't necessary here:
the square value is computed by the body of the function and left on the value stack, so when the function returns
the return value is where it should be.
Kind of a hack, but it works, and this program will do what we want: ask the user for a number and then print the square of it.
So that's a quick introduction to the Firth programming language. Now let's look at all the elements of the language in more detail.
Elements Of The Firth Programming Langauge
The Firth programming language consists of a set of elements. Calling them "expressions" or "statements" in the traditional sense of those terms is a little misleading since values are stored on a stack, so we will stick with the term "element".
Basic Elements
Firth supports the following "basic" elements:
Element | Example | Assembly |
---|---|---|
Numbers |
3
|
LDI 3 SPUSH
|
Plus |
2 3 +
|
LDI 2 SPUSH LDI 3 SPUSH SADD
|
Minus |
2 3 -
|
LDI 2 SPUSH LDI 3 SPUSH SSUB
|
Multiplication |
2 3 *
|
LDI 2 SPUSH LDI 3 SPUSH SMUL
|
Division |
2 3 /
|
LDI 2 SPUSH LDI 3 SPUSH SDIV
|
Maximum |
2 3 max
|
LDI 2 SPUSH LDI 3 SPUSH SMAX
|
Minimum |
2 3 min
|
LDI 2 SPUSH LDI 3 SPUSH SMIN
|
Stack Elements
Firth supports the following elements to operate on the value stack:
Element | Example | Assembly |
---|---|---|
Pop The Top Of The Stack |
pop
|
SPOP
|
Duplicate Top Of Stack |
dup
|
SDUP
|
Swap The Top Two Elements On The Stack |
swap
|
SSWAP
|
Drop The Top Element Of The Stack |
drop
|
SDROP
|
The difference between pop
and drop
is that pop
leaves the top value
of the stack in the accumulator, whereas drop
does not.
I/O
Firth supports the following elements for IO:
Element | Example | Assembly |
---|---|---|
Get |
get
|
INP SPUSH
|
.
|
SDUP POP OUT
|
Conditional Elements
Firth supports two conditional statements: zero?
and positive?
. These elements
consume the top of the stack. If the condition is true of the top of the stack, they execute the elements in
their body, which is terminated by an end
keyword.
If the condition is not true, the elements will execute an optional else
block, if it is included,
otherwise they will do nothing.
Here is a simple "inverter" example Firth program that asks the user for some input and then prints
1
if the user enter0
and 1
otherwise.
get zero? 1 else 0 end .
The Loop Element
Firth supports a simple loop that starts with the do
keyword and ends with the loop
keyword. A loop can be exited with the stop
keyword.
Here is an example of a loop that runs until the user enters a zero value:
do get zero? stop end loop
Global Variables
Firth supports global variables. They can be declared using the var
keyword:
var x
Variables can be set by appending an exclamation point to their name. This will consume the top of the stack
and set the variable to the value. The following program declares a variable x
, saves user input
into it, multiplies the variable by itself and then prints the result:
var x get x! x x * .
Variables must be declared first in Firth programs, and are global. They cannot be declared within functions, loops or conditionals.
Functions
Functions can be declared using the def
keyword, followed by a function name. All function names
must end in the characters ()
. Note that you cannot declare parameters for a function: they
are passed implicitly on the stack.
The implementation of the function consists of a series of elements, followed by the keyword end
:
def square() dup * end
Functions can return to a caller by using the return
element. The return
element
is optional: a function will implicitly return when its body completes.
Note that functions in Firth can call themselves recursively.
Inline Assembly
LMSM assembly can be included in a Firth program by using the asm
keyword, followed by the
end
keyword.
Here is some Firth code that uses raw assembly to print out the value stored in the accumulator:
get pop asm OUT end
Fibbonacci.firth
Below is a recursive implementation of the fibonacci algorithm in Firth:
get fib() . def fib() dup zero? return end dup 1 - zero? return end dup 2 - fib() swap 1 - fib() + end