Patrick the purple dragon

dragoncoder047’s site

projects, ideas, and everything else

Adding Macros to uLisp

The Lisp form defmacro allows for more powerful syntactic constructs. Note that this does not include backquote support – there is a separate page for that.

EDIT: In response to all of the confusion I seem to have caused by posting this error-ridden guide, I have done my best to correct all of the errors. Hopefully, now it should be possible to to directly follow this guide from a vanilla uLisp to be able to add macros. I apologize for any confusion, compiler problems, and crashes caused by my sloppiness.

Part 1 - the Functions

  1. Add these functions and their table entries:

    bool is_macro_call (object* form, object* env) {
        if (form == nil) return false;
        CHECK:
        if (symbolp(car(form))) {
            object* pair = findpair(car(form), env);
            if (pair == NULL) return false;
            form = cons(cdr(pair), cdr(form));
            goto CHECK;
        }
        if (!consp(form)) return false;
        object* lambda = first(form);
        if (!consp(lambda)) return false;
        return isbuiltin(first(lambda), MACRO);
    }
    
    object* macroexpand1 (object* form, object* env, bool* done) {
        if (!is_macro_call(form, env)) {
            *done = true;
            return form;
        }
        while (symbolp(car(form))) form = cons(cdr(findvalue(car(form), env)), cdr(form));
        push(form, GCStack);
        form = closure(0, sym(NIL), car(form), cdr(form), &env);
        object* result = eval(form, env);
        pop(GCStack);
        return result;
    }
    
    object* fn_macroexpand1 (object* args, object* env) {
        bool dummy;
        return macroexpand1(first(args), env, &dummy);
    }
    
    object* macroexpand (object* form, object* env) {
        bool done = false;
        push(form, GCStack);
        while (!done) {
            form = macroexpand1(form, env, &done);
            car(GCStack) = form;
        }
        pop(GCStack);
        return form;
    }
    
    object* fn_macroexpand (object* args, object* env) {
        return macroexpand(first(args), env);
    }
    const char stringbackquote[] PROGMEM = "backquote";
    const char stringunquote[] PROGMEM = "unquote";
    const char stringuqsplicing[] PROGMEM = "unquote-splicing";
    const char docmacro[] PROGMEM = "(macro (parameter*) form*)\n"
    "Creates an unnamed lambda-macro with parameters. The body is evaluated with the parameters as local variables\n"
    "whose initial values are defined by the values of the forms after the macro form;\n"
    "the resultant Lisp code returned is then evaluated again, this time in the scope of where the macro was called.";
    const char docdefmacro[] PROGMEM = "(defmacro name (parameters) form*)\n"
    "Defines a syntactic macro.";
    { stringmacro, NULL, 0017, docmacro },
    { stringdefmacro, sp_defmacro, 0327, docdefmacro },
    { stringmacroexpand1, fn_macroexpand1, 0111, docmacroexpand1 },
    { stringmacroexpand, fn_macroexpand, 0111, docmacroexpand },
  2. Wire up the evaluator to expand the macros:

    // in object* eval (object* form, object* env)
    if (symbolp(form)) {
        // snip
    }
    // Expand macros
    form = macroexpand(form, env);

Part 2 - Stack overflow checking’

The macro evaluator is recursive and I couldn’t find a way to do it with tail-call elimination, so stack overflow checks have to be added. The nice part is that they also prevent normal functions from exploding the stack (like a naive (let ((foo (lambda (f) (f f) (f f)))) (foo foo)) would).

  1. Add a global variable:

    #define MAX_STACK 10000
    void* StackBottom;
  2. In setup() add two variables to init the stack bottom:

    int foo = 0;
    StackBottom = &foo;
    initworkspace();
    initenv();
    initsleep();
    initgfx();
  3. Add a check in eval() to prevent stack overflow:

    // Escape
    if (tstflag(ESCAPE)) { clrflag(ESCAPE); error2(PSTR("escape!"));}
    if (!tstflag(NOESC)) testescape();
    // Stack overflow check
    if (abs(static_cast<int*>(StackBottom) - &TC) > MAX_STACK) error(PSTR("C stack overflow"), form);

Part 3 (Optional) - setf macro support

  1. Forward-declare is_macro_call at the top:

    bool is_macro_call (object*, object*);
  2. Add a last case to place() like so:

    object** place (object* args, object* env, int* bit) {
        PLACE:
        *bit = -1;
        if (atom(args)) return &cdr(findvalue(args, env));
        object* function = first(args);
        if (symbolp(function)) {
            // snip
        }
        else if (is_macro_call(args, env)) {
            function = eval(function, env);
            goto PLACE;
        }
        error2(PSTR("illegal place"));
        return nil;
    }