Clem is a minimal stack-based programming language featuring first-class functions. Your objective is to write an interpreter for the Clem language. It should properly execute all examples included in the reference implementation, which is available here.
- As usual, standard loopholes apply.
- Smallest entry by byte count wins.
The Clem language
Clem is a stack based programming language with first-class functions. The best
way to learn Clem is to run the clem
interpreter with no arguments. It will
start in interactive mode, allowing you to play with the available commands. To
run the example programs, type clem example.clm
where example is the name of
the program. This brief tutorial should be enough to get you started.
There are two main classes of functions. Atomic functions and compound functions. Compound functions are lists composed of other compound functions and atomic functions. Note that a compound function cannot contain itself.
Atomic Functions
The first type of atomic function is the constant. A constant is simply an
integer value. For example, -10. When the interpreter encounters a constant,
it pushes it to the stack. Run clem
now. Type -10
at the prompt. You should
see
> -10
001: (-10)
>
The value 001
describes the position of the function in the stack and (-10)
is the constant you just entered. Now enter +11
at the prompt. You should see
> +11
002: (-10)
001: (11)
>
Notice that (-10)
has moved to the second position in the stack and (11)
now
occupies the first. This is the nature of a stack! You will notice that -
is also the decrement command. Whenever -
or +
precede a number, they denote the sign of that number and not the corresponding command. All other atomic functions are commands. There are 14 in total:
@ Rotate the top three functions on the stack
# Pop the function on top of the stack and push it twice
$ Swap the top two functions on top of the stack
% Pop the function on top of the stack and throw it away
/ Pop a compound function. Split off the first function, push what's left,
then push the first function.
. Pop two functions, concatenate them and push the result
+ Pop a function. If its a constant then increment it. Push it
- Pop a function. If its a constant then decrement it. Push it
< Get a character from STDIN and push it to the stack. Pushes -1 on EOF.
> Pop a function and print its ASCII character if its a constant
c Pop a function and print its value if its a constant
w Pop a function from the stack. Peek at the top of the stack. While it is
a non-zero constant, execute the function.
Typing a command at the prompt will execute the command. Type #
at the prompt
(the duplicate command). You should see
> #
003: (-10)
002: (11)
001: (11)
>
Notice that the (11) has been duplicated. Now type %
at the prompt (the drop
command). You should see
> %
002: (-10)
001: (11)
>
To push a command to the stack, simply enclose it in parenthesis. Type (-)
at
the prompt. This will push the decrement operator to the stack. You should see
> (-)
003: (-10)
002: (11)
001: (-)
>
Compound functions
You may also enclose multiple atomic functions in parenthesis to form a compound
function. When you enter a compound function at the prompt, it is pushed to the
stack. Type ($+$)
at the prompt. You should see
> ($+$)
004: (-10)
003: (11)
002: (-)
001: ($ + $)
>
Technically, everything on the stack is a compound function. However, some of
the compound functions on the stack consist of a single atomic function (in
which case, we will consider them to be atomic functions for the sake of
convenience). When manipulating compound functions on the stack, the .
command
(concatenation) is frequently useful. Type .
now. You should see
> .
003: (-10)
002: (11)
001: (- $ + $)
>
Notice that the first and second functions on the stack were concatenated, and
that the second function on the stack comes first in the resulting list. To
execute a function that is on the stack (whether it is atomic or compound), we
must issue the w
command (while). The w
command will pop the first function
on the stack and execute it repeatedly so long as the second function on the
stack is a non-zero constant. Try to predict what will happen if we type w
.
Now, type w
. You should see
> w
002: (1)
001: (0)
>
Is that what you expected? The two numbers sitting on top of the stack were
added and their sum remains. Let's try it again. First we'll drop the zero and
push a 10 by typing %10
. You should see
> %10
002: (1)
001: (10)
>
Now we'll type the entire function in one shot, but we'll add an extra %
at
the end to get rid of the zero. Type (-$+$)w%
at the prompt. You should see
> (-$+$)w%
001: (11)
>
(Note this algorithm only works if the first constant on the stack is positive).
Strings
Strings are also present. They are mostly syntactic sugar, but can be quite useful. When the interpreter encounters a string, it pushes each character from last to first onto the stack. Type %
to drop the 11 from the previous example. Now, type 0 10 "Hi!"
on the prompt. The 0
will insert a NULL terminator and the 10
will insert a new-line character. You should see
> 0 10 "Hi!"
005: (0)
004: (10)
003: (33)
002: (105)
001: (72)
>
Type (>)w
to print characters from the stack until we encounter the NULL terminator. You should see
> (>)w
Hi!
001: (0)
>
Conclusions
Hopefully this should be enough to get you started with the interpreter. The language design should be relatively straight-forward. Let me know if anything is terribly unclear :) A few things have been left intentionally vague: values must be signed and at least 16 bits, the stack must be large enough to run all reference programs, etc. Many details haven't been carved out here because a full blown language specification would be prohibitively large to post (and I haven't written one yet :P). When in doubt, mimic the reference implementation.
q
andh
commands need not be implemented. I'll remove them from the post. \$\endgroup\$