tags:

views:

86

answers:

3

I've implemented my own Lisp on top of node.js, I can run s-expressions like this:

(assert (= 3 (+ 1 2)))

(def even? (fn [n] (= 0 (bit-and n 1))))

(assert (even? 4))
(assert (= false (even? 5)))

Now I would like to add macros - the defmacro function - but this is where I'm stuck. I'm wondering how macro systems are implemented in other Lisps but I couldn't find many pointers (apart from this and this).

I've looked at the Clojure macro system - the Lisp I'm most familiar with - but that seemed too complicated and I couldn't find additional clues that I can readily apply (Clojure macros ultimately compile to byte code which doesn't apply to javascript, also I couldn't make sense of the macroexpand1 function.)

So my question is: given a Lisp implementation without macros but with an AST, how does one add a macro system like Clojure's macro system? Can this macro system be implemented in Lisp, or does it require extra features in the implementation in the host language?

One additional remark: I haven't implemented quote (') yet because I couldn't figure out what kind of values should be in the returned list. Should it contain AST elements or objects like Symbol and Keyword (the latter being the case for Clojure)?

+1  A: 

All a macro does is take unevaluated forms as parameters and perform replacement on its body. The trick to implementation a macro system is to tell your compiler to be lazy.

Put in another way, when the compiler encounters a function, it first evaluates its formal parameter list, yields the results and passes them to the function. When the compiler finds a macro, it passes the arguments unevaluated to the body, then performs any computation the body requests, and finally replaces itself with the result of those.

For instance, lets say you have a function:

(defun print-3-f (x) (progn (princ x) (princ x) (princ x)))

and a macro:

(defmacro print-3-m (x) `(progn (princ ,x) (princ ,x) (princ ,x)))

Then you can see the difference straight away:

CL-USER> (print-3-f (rand))
* 234
* 234
* 234

CL-USER> (print-3-m (rand))
* 24
* 642
* 85

To understand why this is, you need to, in a manner of speaking, run the compiler in your head.

When Lisp comes across the function, it builds a tree in which (rand) is first evaluated and the result passed to the function, which prints said result three times.

On the other hand, when Lisp comes across the macro, it passes the form (rand) untouched to the body, which returns a quoted list where x is replaced by (rand), yielding:

(progn (princ (rand)) (princ (rand)) (princ (rand)))

and replacing the macro call for this new form.

Here you will find vast amounts of documentation regarding macros in a variety of languages, including Lisp.

dsm
Thanks for the reply, I can summarize your answer as follows:For `defun`: 1) evaluate AST (returns javascript `function` objects) 2) execute the javascripts functions 3) pass the resulting values as arguments to the lisp function. This is what I'm already doing.For `defmacro`: 1) idem 2) skip 3) pass the javascript functions as arguments to the macro. The result that is returned by ` should then be AST elements that should be evaluated and executed.This leaves one question unanswered: what should `quote` return? Should it be a list of AST elements or javascript functions and other objs?
Steven Devijver
(quote ...) returns a list of "stuff", where "stuff" is in a form that may be later evaluated. The beauty of lisp is that its list representation is the same as the AST representation, so returning a list or an AST are equivalent.
dsm
That a macro does not evaluate its arguments seems to be a side effect of its nature, rather than its main defining property, to me.
Svante
+1  A: 

You need to have a macro-expansion phase in your evaluation chain:

text-input -> read -> macroexpand -> compile -> load

Note that the macro expansion should be recursive (macroexpand until nothing macroexpandable is left).

Your environments need the ability to "hold" macro-expansion functions that can be looked up by name in this phase. Note that defmacro is a macro itself in Common Lisp, which sets up the right calls to associate the name with the macro expansion function in that environment.

Svante
Thanks for your reply. If I understand correctly the macroexpand function converts a macro and its arguments into macro-free lisp code. Correct?
Steven Devijver
Your description is somewhat imprecise, but I think that you have the right idea. :)
Svante
Ok, I've made the mental click. I'm refactoring my implementation as follows: 1/ read user code, convert to lists containing AstSymbol, AstKeyword, AstString, AstInteger, ... and of course other lists; 2/ convert library macros (`defmacro`) to AST; 3/ traverse user AST looking for macros (`defmacro`); 4/ the actual macroexpand phase: replace AST elements that call macros (detect macro calls here) with the AST elements of the macro (replace macro arguments with relevant user AST elements); 5/ AST should now be macro-free, interpret. I found [this](http://bit.ly/CpcCU) very helpful too.
Steven Devijver
A: 

Take a look at this example. It's a toy implementation of an Arc-like compiler, with a decent macro support.

SK-logic