Another tiny interpreter for a stack-based language… this time in OCaml

So a few days ago I was wondering if simple stack functions could be implemented concisely in a statically typed language like OCaml… similar to the way it can be done in Scheme and Python, as discussed here. This prompted an interesting comment by Juri Pakaste.

However, one thing that does come to mind is that there’s a limited number of stack effects you need to support – maybe create higher order functions like one_two (for dup), two_two (for swap), etc, that would take a function as a parameter and return a function that takes in the env and ns parameters, pop the correct number of items, give them as args to the function received as a parameter, then push the return values?

So, using this idea, I wrote another proof-of-concept “interpreter” in OCaml. I’ve deliberately kept it as simple as possible; the stack only takes integers, and there’s only a few words, and no real parser; rather, you pass a list of words (as strings) to the process_words function. 

(Again, my OCaml is clunky, but then again I haven’t read ML for the Working Programmer yet.)

First of all, let’s define a simple stack:

type stack = { mutable items: int list };;

let push stk x =
    stk.items <- x :: stk.items;;

let pop stk =
    match stk.items with
      [] -> failwith "stack is empty"
    | hd::tl ->
      stk.items <- tl;
      hd;;

let stack_repr stk =
    let contents = List.map string_of_int (List.rev stk.items) in
    let s = String.concat " " contents in
    "[" ^ s ^ "]";;

let make_stack () =
    { items=[] };;

Next, let’s write a few common “stack transformers”.

let one_two f =
    let g stk =
        let n1 = pop stk in
        let (r1, r2) = f n1 in
        push stk r1; push stk r2
    in g;;

let two_one f =
    let g stk =
        let n2 = pop stk in
        let n1 = pop stk in
        let r1 = f n1 n2 in
        push stk r1
    in g;;

let two_two f =
    let g stk =
        let n2 = pop stk in
        let n1 = pop stk in
        let (r1, r2) = f n1 n2 in
        push stk r1; push stk r2
    in g;;

let one_zero f =
    let g stk =
        let n1 = pop stk in
        let _ = f n1 in
        ()
    in g;;  

The idea is to take functions with different signatures; e.g. one_two wants a function that takes one argument and returns a tuple of two items, two_one takes two arguments and returns one, etc. They are then “transformed” to (or rather, encapsulated in) functions that take one argument (a stack) and apply the wrapped function to it, popping and pushing the right number of items.

Using these transformers, we can now define our built-in words.

let words = [
    ("dup", one_two (fun x -> (x, x)));
    ("swap", two_two (fun a b -> (b, a)));
    ("drop", one_zero (fun x -> ()));
    ("+", two_one (fun x y -> x + y));
    ("-", two_one (fun x y -> x - y))
];;

It looks a bit odd, but the actual functions have different signatures, and OCaml won’t let us store them in the same list, unless we wrap them. (Python’s decorators come to mind…)

Next up is the “meat” of the interpreter… processing words, determining whether they are numbers, looking them up in our list of builtins.

let lookup_word word =
    let rec lookup_word_aux words_left =
        match words_left with
          [] -> failwith ("unknown word: " ^ word)
        | (name, f) :: tl ->
          if name = word
          then f
          else lookup_word_aux tl
    in
    lookup_word_aux words;;

let is_integer word =
    try
        let _ = int_of_string word in
        true
    with Failure e ->
        false;;

let process word stk =
    if is_integer word
    then push stk (int_of_string word)
    else
      let f = lookup_word word in
      let _ = f stk in
      ();;

let process_words words stk =
    let g word =
        print_string ("> " ^ word ^ "\n");
        process word stk;
        print_string ("stack: " ^ (stack_repr stk) ^ "\n")
    in
    List.iter g words;;

Test test…

process_words ["1"; "2"; "3"; "+"; "dup"; "drop"; "swap"; "-"]
    (make_stack ());;

…produces:

> 1
stack: [1]
> 2
stack: [1 2]
> 3
stack: [1 2 3]
> +
stack: [1 5]
> dup
stack: [1 5 5]
> drop
stack: [1 5]
> swap
stack: [5 1]
> -
stack: [4]

Fun! Now, let’s try this in Java… ;-)

7 Comments

  1. Rich said,

    December 24, 2008 @ 4:40 am

    Quick tip: You don’t need all those double-semicolons.

    When ;; appears before let, type, open [and a few others] you can drop it.

  2. Mark Lee Smith said,

    December 24, 2008 @ 6:11 am

    In Java:

    5 – 1

    ;).

    Seriously, nice work.

  3. Hans Nowak said,

    December 24, 2008 @ 7:02 am

    But I kinda like them… :-}

  4. JJ said,

    December 24, 2008 @ 10:04 am

    Why not have the functions return lists? Then they would all have the same signature.

  5. Hans Nowak said,

    December 24, 2008 @ 10:11 am

    @JJ: That might work, but they would still take a different number of arguments…

  6. Josh said,

    December 24, 2008 @ 10:38 am

    I thought it made more sense to have the top item be the first operand in case you want to subtract a lot of numbers from a single number, so my subtraction is reversed compared to yours.

    import java.io.*;
    import java.util.ArrayList;

    class Stack {
    private ArrayList stack = new ArrayList ();
    public void push(Integer i) { stack.add(i); }
    public Integer pop() { return stack.remove(stack.size() – 1); }
    public void swap() { push(stack.remove(stack.size() – 2)); }
    public Integer peek() { return stack.get(stack.size() – 1); }
    public String toString() { return stack.toString(); }
    public void execute(String cmd) {
    if (cmd.equals(“swap”)) { swap(); }
    else if (cmd.equals(“dup”)) { push(peek()); }
    else if (cmd.equals(“drop”)) { pop(); }
    else if (cmd.equals(“+”)) { push(pop() + pop()); }
    else if (cmd.equals(“-”)) { push(pop() – pop()); }
    else if (cmd.equals(“*”)) { push(pop() * pop()); }
    else if (cmd.equals(“/”)) { push(pop() / pop()); }
    else { push(Integer.parseInt(cmd)); }
    System.out.println(stack);
    }
    public static void main(String[] args) {
    try {
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    String line = “”;
    Stack stack = new Stack();
    while ((line = in.readLine()) != null) {
    stack.execute(line);
    }
    } catch (Exception e) {
    System.out.println(e);
    }
    }
    }

  7. lasts said,

    December 24, 2008 @ 9:12 pm

    Hi,

    I’m not sure why it’s useful to explicitly type the primitives : why not use the stack directly ? (btw, adding state to lists is a nice exercise, but I wouldn’t use it in general : lists are very handy for recursion, stacks aren’t ^_^)

    So, something like this might be simpler :

    let dup (a::stack) = a::a::stack
    let add (a::b::stack) = (a + b)::stack

    let words = [ "dup", dup; "add", add ]

    type input = Num of int | Word of string

    let eval stack = function
    | Num a -> a::stack
    | Word w -> List.assoc w words stack

    let process = List.fold_left eval []

    Typechecking the expression seems to be more funny :) .

RSS feed for comments on this post