Thursday, April 04, 2013

Building a Lisp Interpreter from Scratch -- Part 2: Core Forms

(This is Part 2 of a series of posts on pLisp)

Before looking at the object model of pLisp, we turn our attention to the core forms in our interpreter. Core forms are the primitive expressions that need to be handled by the interpreter; they cannot be palmed off to the libraries, where they could be implemented in pLisp itself.

Here's a list of the core forms. Quite a few of them are there because of necessity (see above), while some of them are more for convenience, i.e, life as we know it would not end if they were absent. Also, if you want to do anything practical, say arithmetic, strings, arrays, and so on, and not end up with an interpreter that just manipulates symbols, you need things like +, -, ARRAY-GET, and ARRAY-SET (not to mention PRINT for output).

'Core' core forms QUOTE, LAMBDA, IF, SET, CALL-CC, DEFINE, CONS, EQ, ATOM, CAR, CDR
Basic real-world stuff+, -, *, /, GT, PRINT, ERROR
Intermediate real-world stuffSTRING, MAKE-ARRAY, ARRAY-GET, ARRAY-SET, SUB-ARRAY, ARRAY-LENGTH, PRINT-STRING
Advanced real-world stuffCREATE-PACKAGE, IN-PACKAGE, CREATE-IMAGE, LOAD-FOREIGN-LIBRARY, CALL-FOREIGN-FUNCTION
ConveniencePROGN, SETCAR, SETCDR, LISTP, SYMBOL-VALUE
MacrosCOMMA, COMMA-AT, BACKQUOTE, MACRO, GENSYM
DebuggingENV, BREAK, RESUME, BACKTRACE, EXPAND-MACRO
Up to elevenEVAL

A few things:

1. EQ tests for equality of content.

2. QUOTE, BACKQUOTE, COMMA and COMMA-AT are not the operators' real symbols; they are indicated so to avoid confusion with punctuation (ditto for GT, but this has more to do with my laziness in working with HTML tags).

3. PROGN can actually be implemented as a macro using a series of LAMBDAs; I just went with a native implementation. Macros involving LAMBDAs are used for implementing WHILE and LET, however (the LET macro uses the MAP function in turn):

(defmacro while (condition &rest body)
  `(((lambda (f) (set f (lambda ()
                          (if ,condition
                              (progn ,@body (f)))))) '())))

(defun map (f lst)
  (if (null lst)
      nil
    (cons (f (car lst)) (map f (cdr lst)))))

(defmacro let (specs &rest body)
  `((lambda ,(map car specs) ,@body) ,@(map cadr specs)))

4. The other comparison operators are implemented using macros. 

5. LISTP can be implemented as a macro as well, through ATOM.

6. Strings are implemented as arrays, the corresponding GET and SET are again taken care of by macros.

7. DEFUN and DEFMACRO are macros that build on LAMBDA and MACRO respectively.

8. Did I mention that macros are awesome?

Astute readers may be wondering by now what kind of a bastard Lisp they're looking at. I initially started out with Common Lisp in mind (though building pLisp as a Lisp-1), then made a detour into Scheme -- mainly for the continuations -- before I decided to persist with the veneer of CL (maybe not just the veneer; the macro system is more or less full CL; I didn't venture into Scheme syntax rules territory at all). Anyway, to a large extent, one dialect of Lisp can be made to look like another by liberal and injudicious usage of macros, so it doesn't matter that much for our purposes, I guess.