HOME

primlisp

Primitive Lisp in Rust

This is day 3.

Today I want to implement a primitive Lisp interpreter in Rust.

How did it go?

Well, it turns out that my initial plan was too ambitious. What a surprise. The plan was to implement an interpreter for a very simple lisp. Unfortunately, I had stuff to do, so I only got as far as the parser.

The parser is implemented using rust-peg, a Rust library for parsing expression grammers. As such, the entire parsing is as simple as

read -> super::AST
    = cons / integer / nil / symbol

cons -> super::AST
    = "(" space car:read space "." space cdr:read space ")"
    { super::AST::Cons(Box::new(car),
      Box::new(cdr)) }

nil -> super::AST
    = "()" { super::AST::Nil }

integer -> super::AST
    = sign:"-"? n:num
    { super::AST::Integer(if sign.is_some() { - n } else { n }) }

symbol -> super::AST
    = [a-zA-Z][a-zA-Z0-9\-]* { super::AST::Symbol(match_str.to_string()) }

num -> isize
    = [0-9]+ { match_str.parse().unwrap() }

space -> ()
    = [ \n\t]*

which I think is quite elegant. Of course, primlisp can only handle integers, cons-cells, nil and symbols, so that explains the extremely simple grammar, but still.

I was going to make primlisp a Lisp-1 for simplicity, but I've run into a roadblock when trying to define a symbol-table. With lispski it was easy: Lisp is untyped, so I could just stuff closures into the table, but what is the type of a symbol-table?

Right now, I'm going for a vector of (String, Something), where the String is the symbol name, and the Something is… something else. What are possible values for Something? Symbols should be bindable to integers, nil, conses, functions (both primitive and compound). So an enum like

enum Something {
    Integer(AST::Integer),
    Nil,
    Cons(AST::Cons),
    PrimFun(SomeType),
    CompoundFun(SomeType2),
}

So far so good, but what is SomeType? Primitive functions include cons and car, but they have different types: cons: Fn(AST, AST) -> AST and car: Fn(AST) -> AST. I could curry them, like in lispski, but then cons would just be cons: Fn(AST) -> Fn(AST) -> AST, and that's no good either.

I'm rambling, and now I'll go to bed. Perhaps tomorrow I'll think of something.