Edit

IABSD.fr/xenocara/app/xedit/lisp/compile.c

Branch :

  • Show log

    Commit

  • Author : matthieu
    Date : 2006-11-25 20:07:29
    Hash : 616b6f15
    Message : Importing from X.Org 7.2RC2

  • app/xedit/lisp/compile.c
  • /*
     * Copyright (c) 2002 by The XFree86 Project, Inc.
     *
     * Permission is hereby granted, free of charge, to any person obtaining a
     * copy of this software and associated documentation files (the "Software"),
     * to deal in the Software without restriction, including without limitation
     * the rights to use, copy, modify, merge, publish, distribute, sublicense,
     * and/or sell copies of the Software, and to permit persons to whom the
     * Software is furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     * THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
     * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
     * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     *
     * Except as contained in this notice, the name of the XFree86 Project shall
     * not be used in advertising or otherwise to promote the sale, use or other
     * dealings in this Software without prior written authorization from the
     * XFree86 Project.
     *
     * Author: Paulo César Pereira de Andrade
     */
    
    /* $XFree86: xc/programs/xedit/lisp/compile.c,v 1.15tsi Exp $ */
    
    #define VARIABLE_USED		0x0001
    #define VARIABLE_ARGUMENT	0x0002
    
    /*
     * Prototypes
     */
    static void ComPredicate(LispCom*, LispBuiltin*, LispBytePredicate);
    static void ComReturnFrom(LispCom*, LispBuiltin*, int);
    
    static int ComConstantp(LispCom*, LispObj*);
    static void ComAddVariable(LispCom*, LispObj*, LispObj*);
    static int ComGetVariable(LispCom*, LispObj*);
    static void ComVariableSetFlag(LispCom*, LispAtom*, int);
    #define COM_VARIABLE_USED(atom)				\
        ComVariableSetFlag(com, atom, VARIABLE_USED)
    #define COM_VARIABLE_ARGUMENT(atom)			\
    	ComVariableSetFlag(com, atom, VARIABLE_ARGUMENT)
    
    static int FindIndex(void*, void**, int);
    static int compare(const void*, const void*);
    static int BuildTablePointer(void*, void***, int*);
    
    static void ComLabel(LispCom*, LispObj*);
    static void ComPush(LispCom*, LispObj*, LispObj*, int, int, int);
    static int ComCall(LispCom*, LispArgList*, LispObj*, LispObj*, int, int, int);
    static void ComFuncall(LispCom*, LispObj*, LispObj*, int);
    static void ComProgn(LispCom*, LispObj*);
    static void ComEval(LispCom*, LispObj*);
    
    static void ComRecursiveCall(LispCom*, LispArgList*, LispObj*, LispObj*);
    static void ComInlineCall(LispCom*, LispArgList*, LispObj*, LispObj*, LispObj*);
    
    static void ComMacroBackquote(LispCom*, LispObj*);
    static void ComMacroCall(LispCom*, LispArgList*, LispObj*, LispObj*, LispObj*);
    static LispObj *ComMacroExpandBackquote(LispCom*, LispObj*);
    static LispObj *ComMacroExpand(LispCom*, LispObj*);
    static LispObj *ComMacroExpandFuncall(LispCom*, LispObj*, LispObj*);
    static LispObj *ComMacroExpandEval(LispCom*, LispObj*);
    
    /*
     * Implementation
     */
    void
    Com_And(LispCom *com, LispBuiltin *builtin)
    /*
     and &rest args
     */
    {
        LispObj *args;
    
        args = ARGUMENT(0);
    
        if (CONSP(args)) {
    	/* Evaluate first argument */
    	ComEval(com, CAR(args));
    	args = CDR(args);
    
    	/* If more than one argument, create jump list */
    	if (CONSP(args)) {
    	    CodeTree *tree = NULL, *group;
    
    	    group = NEW_TREE(CodeTreeJumpIf);
    	    group->code = XBC_JUMPNIL;
    
    	    for (; CONSP(args); args = CDR(args)) {
    		ComEval(com, CAR(args));
    		tree = NEW_TREE(CodeTreeJumpIf);
    		tree->code = XBC_JUMPNIL;
    		group->group = tree;
    		group = tree;
    	    }
    	    /*  Finish form the last CodeTree code is changed to sign the
    	     * end of the AND list */
    	    group->code = XBC_NOOP;
    	    if (group)
    		group->group = tree;
    	}
        }
        else
    	/* Identity of AND is T */
    	com_Bytecode(com, XBC_T);
    }
    
    void
    Com_Block(LispCom *com, LispBuiltin *builtin)
    /*
     block name &rest body
     */
    {
    
        LispObj *name, *body;
    
        body = ARGUMENT(1);
        name = ARGUMENT(0);
    
        if (name != NIL && name != T && !SYMBOLP(name))
    	LispDestroy("%s: %s cannot name a block",
    		    STRFUN(builtin), STROBJ(name));
        if (CONSP(body)) {
    	CompileIniBlock(com, LispBlockTag, name);
    	ComProgn(com, body);
    	CompileFiniBlock(com);
        }
        else
    	/* Just load NIL without starting an empty block */
    	com_Bytecode(com, XBC_NIL);
    }
    
    void
    Com_C_r(LispCom *com, LispBuiltin *builtin)
    /*
     c[ad]{1,4}r list
     */
    {
        LispObj *list;
        char *desc;
    
        list = ARGUMENT(0);
    
        desc = STRFUN(builtin);
        if (*desc == 'F')		/* FIRST */
    	desc = "CAR";
        else if (*desc == 'R')	/* REST */
    	desc = "CDR";
    
        /* Check if it is a list of constants */
        while (desc[1] != 'R')
    	desc++;
        ComEval(com, list);
        while (*desc != 'C') {
    	com_Bytecode(com, *desc == 'A' ? XBC_CAR : XBC_CDR);
    	--desc;
        }
    }
    
    void
    Com_Cond(LispCom *com, LispBuiltin *builtin)
    /*
     cond &rest body
     */
    {
        int count;
        LispObj *code, *body;
        CodeTree *group, *tree;
    
        body = ARGUMENT(0);
    
        count = 0;
        group = NULL;
        if (CONSP(body)) {
    	for (; CONSP(body); body = CDR(body)) {
    	    code = CAR(body);
    	    CHECK_CONS(code);
    	    ++count;
    	    ComEval(com, CAR(code));
    	    tree = NEW_TREE(CodeTreeCond);
    	    if (group)
    		group->group = tree;
    	    tree->code = XBC_JUMPNIL;
    	    group = tree;
    	    /* The code to execute if the test is true */
    	    ComProgn(com, CDR(code));
    	    /* Add a node signaling the end of the PROGN code */
    	    tree = NEW_TREE(CodeTreeCond);
    	    tree->code = XBC_JUMPT;
    	    if (group)
    		group->group = tree;
    	    group = tree;
    	}
        }
        if (!count)
    	com_Bytecode(com, XBC_NIL);
        else
    	/* Where to jump after T progn */
    	group->code = XBC_NOOP;
    }
    
    void
    Com_Cons(LispCom *com, LispBuiltin *builtin)
    /*
     cons car cdr
     */
    {
        LispObj *car, *cdr;
    
        cdr = ARGUMENT(1);
        car = ARGUMENT(0);
    
        if (ComConstantp(com, car) && ComConstantp(com, cdr))
    	com_BytecodeCons(com, XBC_CCONS, car, cdr);
        else {
    	++com->stack.cpstack;
    	if (com->stack.pstack < com->stack.cpstack)
    	    com->stack.pstack = com->stack.cpstack;
    	ComEval(com, car);
    	com_Bytecode(com, XBC_CSTAR);
    	ComEval(com, cdr);
    	com_Bytecode(com, XBC_CFINI);
    	--com->stack.cpstack;
        }
    }
    
    void
    Com_Consp(LispCom *com, LispBuiltin *builtin)
    /*
     consp object
     */
    {
        ComPredicate(com, builtin, XBP_CONSP);
    }
    
    void
    Com_Dolist(LispCom *com, LispBuiltin *builtin)
    /*
     dolist init &rest body
     */
    {
        int unbound, item;
        LispObj *symbol, *list, *result;
        LispObj *init, *body;
        CodeTree *group, *tree;
    
        body = ARGUMENT(1);
        init = ARGUMENT(0);
    
        CHECK_CONS(init);
        symbol = CAR(init);
        CHECK_SYMBOL(symbol);
        CHECK_CONSTANT(symbol);
        init = CDR(init);
        if (CONSP(init)) {
    	list = CAR(init);
    	init = CDR(init);
        }
        else
    	list = NIL;
        if (CONSP(init)) {
    	result = CAR(init);
    	if (CONSP(CDR(init)))
    	    LispDestroy("%s: too many arguments %s",
    			STRFUN(builtin), STROBJ(CDR(init)));
        }
        else
    	result = NIL;
    
        /*	Generate code for the body of the form.
         *	The generated code uses two objects unavailable to user code,
         * in the format:
         *	(block NIL
         *	    (let ((? list) (item NIL))
         *		(tagbody
         *		    .			    ; the DOT object as a label
         *		    (when (consp list)
         *			(setq item (car ?))
         *			@body		    ; code to be executed
         *			(setq ? (cdr ?))
         *			(go .)
         *		    )
         *		)
         *		(setq item nil)
         *		result
         *	    )
         *	)
         */
    
        /* XXX All of the logic below should be simplified at some time
         * by adding more opcodes for compound operations ... */
    
        /* Relative offsets the locally added variables will have at run time */
        unbound = lisp__data.env.length - lisp__data.env.lex;
        item = unbound + 1;
    
        /* Start BLOCK NIL */
        FORM_ENTER();
        CompileIniBlock(com, LispBlockTag, NIL);
    
        /* Add the <?> variable */
        ComPush(com, UNBOUND, list, 1, 0, 0);
        /* Add the <item> variable */
        ComPush(com, symbol, NIL, 0, 0, 0);
        /* Stack length is increased */
        CompileStackEnter(com, 2, 0);
        /* Bind variables */
        com_Bind(com, 2);
        com->block->bind += 2;
        lisp__data.env.head += 2;
    
        /* Remember that iteration variable is used even if it not referenced */
        COM_VARIABLE_USED(symbol->data.atom);
    
        /* Initialize the TAGBODY */
        FORM_ENTER();
        CompileIniBlock(com, LispBlockBody, NIL);
    
        /* Create the <.> label */
        ComLabel(com, DOT);
    
        /* Load <?> variable */
        com_BytecodeShort(com, XBC_LOAD, unbound);
        /* Check if <?> is a list */
        com_BytecodeChar(com, XBC_PRED, XBP_CONSP);
    
        /* Start WHEN block */
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPNIL;
        /* Load <?> again */
        com_BytecodeShort(com, XBC_LOAD, unbound);
        /* Get CAR of <?> */
        com_Bytecode(com, XBC_CAR);
        /* Store it in <item> */
        com_BytecodeShort(com, XBC_SET, item);
        /* Execute @BODY */
        ComProgn(com, body);
    
        /* Load <?> again */
        com_BytecodeShort(com, XBC_LOAD, unbound);
        /* Get CDR of <?> */
        com_Bytecode(com, XBC_CDR);
        /* Change value of <?> */
        com_BytecodeShort(com, XBC_SET, unbound);
    
        /* GO back to <.> */
        tree = NEW_TREE(CodeTreeGo);
        tree->data.object = DOT;
    
        /* Finish WHEN block */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    
        /* Finish the TAGBODY */
        CompileFiniBlock(com);
        FORM_LEAVE();
    
        /* Set <item> to NIL, in case result references it...
         * Loaded value is NIL as the CONSP predicate */
        com_BytecodeShort(com, XBC_SET, item);
    
        /* Evaluate <result> */
        ComEval(com, result);
    
        /* Unbind variables */
        lisp__data.env.head -= 2;
        lisp__data.env.length -= 2;
        com->block->bind -= 2;
        com_Unbind(com, 2);
        /* Stack length is reduced. */
        CompileStackLeave(com, 2, 0);
    
        /* Finish BLOCK NIL */
        CompileFiniBlock(com);
        FORM_LEAVE();
    }
    
    void
    Com_Eq(LispCom *com, LispBuiltin *builtin)
    /*
     eq left right
     eql left right
     equal left right
     equalp left right
     */
    {
        LispObj *left, *right;
        LispByteOpcode code;
        char *name;
    
        right = ARGUMENT(1);
        left = ARGUMENT(0);
    
        CompileStackEnter(com, 1, 1);
        /* Just like preparing to call a builtin function */
        ComEval(com, left);
        com_Bytecode(com, XBC_PUSH);
        /* The second argument is now loaded */
        ComEval(com, right);
    
        /* Compare arguments and restore builtin stack */
        name = STRFUN(builtin);
        switch (name[3]) {
    	case 'L':
    	    code = XBC_EQL;
    	    break;
    	case 'U':
    	    code = name[5] == 'P' ? XBC_EQUALP : XBC_EQUAL;
    	    break;
    	default:
    	    code = XBC_EQ;
    	    break;
        }
        com_Bytecode(com, code);
    
        CompileStackLeave(com, 1, 1);
    }
    
    void
    Com_Go(LispCom *com, LispBuiltin *builtin)
    /*
     go tag
     */
    {
        int bind;
        LispObj *tag;
        CodeTree *tree;
        CodeBlock *block;
    
        tag = ARGUMENT(0);
    
        block = com->block;
        bind = block->bind;
    
        while (block) {
    	if (block->type == LispBlockClosure || block->type == LispBlockBody)
    	    break;
    	block = block->prev;
    	if (block)
    	    bind += block->bind;
        }
    
        if (!block || block->type != LispBlockBody)
    	LispDestroy("%s called not within a block", STRFUN(builtin));
    
        /* Unbind any local variables */
        com_Unbind(com, bind);
        tree = NEW_TREE(CodeTreeGo);
        tree->data.object = tag;
    }
    
    void
    Com_If(LispCom *com, LispBuiltin *builtin)
    /*
     if test then &optional else
     */
    {
        CodeTree *group, *tree;
        LispObj *test, *then, *oelse;
    
        oelse = ARGUMENT(2);
        then = ARGUMENT(1);
        test = ARGUMENT(0);
    
        /* Build code to execute test */
        ComEval(com, test);
    
        /* Add jump node to use if test is NIL */
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPNIL;
    
        /* Build T code */
        ComEval(com, then);
    
        if (oelse != UNSPEC) {
    	/* Remember start of NIL code */
    	tree = NEW_TREE(CodeTreeJump);
    	tree->code = XBC_JUMP;
    	group->group = tree;
    	group = tree;
    	/* Build NIL code */
    	ComEval(com, oelse);
        }
    
        /* Remember jump of T code */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    }
    
    void
    Com_Last(LispCom *com, LispBuiltin *builtin)
    /*
     last list &optional count
     */
    {
        LispObj *list, *count;
    
        count = ARGUMENT(1);
        list = ARGUMENT(0);
    
        ComEval(com, list);
        CompileStackEnter(com, 1, 1);
        com_Bytecode(com, XBC_PUSH);
        if (count == UNSPEC)
    	count = FIXNUM(1);
        ComEval(com, count);
        CompileStackLeave(com, 1, 1);
        com_Bytecode(com, XBC_LAST);
    }
    
    void
    Com_Length(LispCom *com, LispBuiltin *builtin)
    /*
     length sequence
     */
    {
        LispObj *sequence;
    
        sequence = ARGUMENT(0);
    
        ComEval(com, sequence);
        com_Bytecode(com, XBC_LENGTH);
    }
    
    void
    Com_Let(LispCom *com, LispBuiltin *builtin)
    /*
     let init &rest body
     */
    {
        int count;
        LispObj *symbol, *value, *pair;
    
        LispObj *init, *body;
    
        body = ARGUMENT(1);
        init = ARGUMENT(0);
    
        if (init == NIL) {
    	/* If no local variables */
    	ComProgn(com, body);
    	return;
        }
        CHECK_CONS(init);
    
        /* Could optimize if the body is empty and the
         * init form is known to have no side effects */
    
        for (count = 0; CONSP(init); init = CDR(init), count++) {
    	pair = CAR(init);
    	if (CONSP(pair)) {
    	    symbol = CAR(pair);
    	    pair = CDR(pair);
    	    if (CONSP(pair)) {
    		value = CAR(pair);
    		if (CDR(pair) != NIL)
    		    LispDestroy("%s: too much arguments to initialize %s",
    				STRFUN(builtin), STROBJ(symbol));
    	    }
    	    else
    		value = NIL;
    	}
    	else {
    	    symbol = pair;
    	    value = NIL;
    	}
    	CHECK_SYMBOL(symbol);
    	CHECK_CONSTANT(symbol);
    
    	/* Add the variable */
    	ComPush(com, symbol, value, 1, 0, 0);
        }
    
        /* Stack length is increased */
        CompileStackEnter(com, count, 0);
        /* Bind the added variables */
        com_Bind(com, count);
        com->block->bind += count;
        lisp__data.env.head += count;
        /* Generate code for the body of the form */
        ComProgn(com, body);
        /* Unbind the added variables */
        lisp__data.env.head -= count;
        lisp__data.env.length -= count;
        com->block->bind -= count;
        com_Unbind(com, count);
        /* Stack length is reduced. */
        CompileStackLeave(com, count, 0);
    }
    
    void
    Com_Letx(LispCom *com, LispBuiltin *builtin)
    /*
     let* init &rest body
     */
    {
        int count;
        LispObj *symbol, *value, *pair;
    
        LispObj *init, *body;
    
        body = ARGUMENT(1);
        init = ARGUMENT(0);
    
        if (init == NIL) {
    	/* If no local variables */
    	ComProgn(com, body);
    	return;
        }
        CHECK_CONS(body);
    
        /* Could optimize if the body is empty and the
         * init form is known to have no side effects */
    
        for (count = 0; CONSP(init); init = CDR(init), count++) {
    	pair = CAR(init);
    	if (CONSP(pair)) {
    	    symbol = CAR(pair);
    	    pair = CDR(pair);
    	    if (CONSP(pair)) {
    		value = CAR(pair);
    		if (CDR(pair) != NIL)
    		    LispDestroy("%s: too much arguments to initialize %s",
    				STRFUN(builtin), STROBJ(symbol));
    	    }
    	    else
    		value = NIL;
    	}
    	else {
    	    symbol = pair;
    	    value = NIL;
    	}
    	CHECK_SYMBOL(symbol);
    	CHECK_CONSTANT(symbol);
    
    	/* LET* is identical to &AUX arguments, just bind the symbol */
    	ComPush(com, symbol, value, 1, 0, 0);
    	/* Every added variable is binded */
    	com_Bind(com, 1);
    	/* Must be binded at compile time also */
    	++lisp__data.env.head;
    	++com->block->bind;
        }
    
        /* Generate code for the body of the form */
        CompileStackEnter(com, count, 0);
        ComProgn(com, body);
        com_Unbind(com, count);
        com->block->bind -= count;
        lisp__data.env.head -= count;
        lisp__data.env.length -= count;
        CompileStackLeave(com, count, 0);
    }
    
    void
    Com_Listp(LispCom *com, LispBuiltin *builtin)
    /*
     listp object
     */
    {
        ComPredicate(com, builtin, XBP_LISTP);
    }
    
    void
    Com_Loop(LispCom *com, LispBuiltin *builtin)
    /*
     loop &rest body
     */
    {
        CodeTree *tree, *group;
        LispObj *body;
    
        body = ARGUMENT(0);
    
        /* Start NIL block */
        CompileIniBlock(com, LispBlockTag, NIL);
    
        /* Insert node to mark LOOP start */
        tree = NEW_TREE(CodeTreeJump);
        tree->code = XBC_NOOP;
    
        /* Execute @BODY */
        if (CONSP(body))
    	ComProgn(com, body);
        else
    	/* XXX bytecode.c code require that blocks have at least one opcode */
    	com_Bytecode(com, XBC_NIL);
    
        /* Insert node to jump of start of LOOP */
        group = NEW_TREE(CodeTreeJump);
        group->code = XBC_JUMP;
        group->group = tree;
    
        /* Finish NIL block */
        CompileFiniBlock(com);
    }
    
    void
    Com_Nthcdr(LispCom *com, LispBuiltin *builtin)
    /*
     nthcdr index list
     */
    {
        LispObj *oindex, *list;
    
        list = ARGUMENT(1);
        oindex = ARGUMENT(0);
    
        ComEval(com, oindex);
        CompileStackEnter(com, 1, 1);
        com_Bytecode(com, XBC_PUSH);
        ComEval(com, list);
        CompileStackLeave(com, 1, 1);
        com_Bytecode(com, XBC_NTHCDR);
    }
    
    void
    Com_Null(LispCom *com, LispBuiltin *builtin)
    /*
     null list
     */
    {
        LispObj *list;
    
        list = ARGUMENT(0);
    
        if (list == NIL)
    	com_Bytecode(com, XBC_T);
        else if (ComConstantp(com, list))
    	com_Bytecode(com, XBC_NIL);
        else {
    	ComEval(com, list);
    	com_Bytecode(com, XBC_INV);
        }
    }
    
    void
    Com_Numberp(LispCom *com, LispBuiltin *builtin)
    /*
     numberp object
     */
    {
        ComPredicate(com, builtin, XBP_NUMBERP);
    }
    
    void
    Com_Or(LispCom *com, LispBuiltin *builtin)
    /*
     or &rest args
     */
    {
        LispObj *args;
    
        args = ARGUMENT(0);
    
        if (CONSP(args)) {
    	/* Evaluate first argument */
    	ComEval(com, CAR(args));
    	args = CDR(args);
    
    	/* If more than one argument, create jump list */
    	if (CONSP(args)) {
    	    CodeTree *tree = NULL, *group;
    
    	    group = NEW_TREE(CodeTreeJumpIf);
    	    group->code = XBC_JUMPT;
    
    	    for (; CONSP(args); args = CDR(args)) {
    		ComEval(com, CAR(args));
    		tree = NEW_TREE(CodeTreeJumpIf);
    		tree->code = XBC_JUMPT;
    		group->group = tree;
    		group = tree;
    	    }
    	    /*  Finish form the last CodeTree code is changed to sign the
    	     * end of the AND list */
    	    group->code = XBC_NOOP;
    	    group->group = tree;
    	}
        }
        else
    	/* Identity of OR is NIL */
    	com_Bytecode(com, XBC_NIL);
    }
    
    void
    Com_Progn(LispCom *com, LispBuiltin *builtin)
    /*
     progn &rest body
     */
    {
        LispObj *body;
    
        body = ARGUMENT(0);
    
        ComProgn(com, body);
    }
    
    void
    Com_Return(LispCom *com, LispBuiltin *builtin)
    /*
     return &optional result
     */
    {
        ComReturnFrom(com, builtin, 0);
    }
    
    void
    Com_ReturnFrom(LispCom *com, LispBuiltin *builtin)
    /*
     return-from name &optional result
     */
    {
        ComReturnFrom(com, builtin, 1);
    }
    
    void
    Com_Rplac_(LispCom *com, LispBuiltin *builtin)
    /*
     rplac[ad] place value
     */
    {
        LispObj *place, *value;
    
        value = ARGUMENT(1);
        place = ARGUMENT(0);
    
        CompileStackEnter(com, 1, 1);
        ComEval(com, place);
        com_Bytecode(com, XBC_PUSH);
        ComEval(com, value);
        com_Bytecode(com, STRFUN(builtin)[5] == 'A' ? XBC_RPLACA : XBC_RPLACD);
        CompileStackLeave(com, 1, 1);
    }
    
    void
    Com_Setq(LispCom *com, LispBuiltin *builtin)
    /*
     setq &rest form
     */
    {
        int offset;
        LispObj *form, *symbol, *value;
    
        form = ARGUMENT(0);
    
        for (; CONSP(form); form = CDR(form)) {
    	symbol = CAR(form);
    	CHECK_SYMBOL(symbol);
    	CHECK_CONSTANT(symbol);
    	form = CDR(form);
    	if (!CONSP(form))
    	    LispDestroy("%s: odd number of arguments", STRFUN(builtin));
    	value = CAR(form);
    	/* Generate code to load value */
    	ComEval(com, value);
    	offset = ComGetVariable(com, symbol);
    	if (offset >= 0)
    	    com_Set(com, offset);
    	else
    	    com_SetSym(com, symbol->data.atom);
        }
    }
    
    void
    Com_Tagbody(LispCom *com, LispBuiltin *builtin)
    /*
     tagbody &rest body
     */
    {
        LispObj *body;
    
        body = ARGUMENT(0);
    
        if (CONSP(body)) {
    	CompileIniBlock(com, LispBlockBody, NIL);
    	ComProgn(com, body);
    	/* Tagbody returns NIL */
    	com_Bytecode(com, XBC_NIL);
    	CompileFiniBlock(com);
        }
        else
    	/* Tagbody always returns NIL */
    	com_Bytecode(com, XBC_NIL);
    }
    
    void
    Com_Unless(LispCom *com, LispBuiltin *builtin)
    /*
     unless test &rest body
     */
    {
        CodeTree *group, *tree;
        LispObj *test, *body;
    
        body = ARGUMENT(1);
        test = ARGUMENT(0);
    
        /* Generate code to evaluate test */
        ComEval(com, test);
        /* Add node after test */
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPT;
        /* Generate NIL code */
        ComProgn(com, body);
        /* Insert node to know where to jump if test is T */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    }
    
    void
    Com_Until(LispCom *com, LispBuiltin *builtin)
    /*
     until test &rest body
     */
    {
        CodeTree *tree, *group, *ltree, *lgroup;
        LispObj *test, *body;
    
        body = ARGUMENT(1);
        test = ARGUMENT(0);
    
        /* Insert node to mark LOOP start */
        ltree = NEW_TREE(CodeTreeJump);
        ltree->code = XBC_NOOP;
    
        /* Build code for test */
        ComEval(com, test);
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPT;
    
        /* Execute @BODY */
        ComProgn(com, body);
    
        /* Insert node to jump to test again */
        lgroup = NEW_TREE(CodeTreeJump);
        lgroup->code = XBC_JUMP;
        lgroup->group = ltree;
    
        /* Insert node to know where to jump if test is T */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    }
    
    void
    Com_When(LispCom *com, LispBuiltin *builtin)
    /*
     when test &rest body
     */
    {
        CodeTree *group, *tree;
        LispObj *test, *body;
    
        body = ARGUMENT(1);
        test = ARGUMENT(0);
    
        /* Generate code to evaluate test */
        ComEval(com, test);
        /* Add node after test */
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPNIL;
        /* Generate T code */
        ComProgn(com, body);
        /* Insert node to know where to jump if test is NIL */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    }
    
    void
    Com_While(LispCom *com, LispBuiltin *builtin)
    /*
     while test &rest body
     */
    {
        CodeTree *tree, *group, *ltree, *lgroup;
        LispObj *test, *body;
    
        body = ARGUMENT(1);
        test = ARGUMENT(0);
    
        /* Insert node to mark LOOP start */
        ltree = NEW_TREE(CodeTreeJump);
        ltree->code = XBC_NOOP;
    
        /* Build code for test */
        ComEval(com, test);
        group = NEW_TREE(CodeTreeJumpIf);
        group->code = XBC_JUMPNIL;
    
        /* Execute @BODY */
        ComProgn(com, body);
    
        /* Insert node to jump to test again */
        lgroup = NEW_TREE(CodeTreeJump);
        lgroup->code = XBC_JUMP;
        lgroup->group = ltree;
    
        /* Insert node to know where to jump if test is NIL */
        tree = NEW_TREE(CodeTreeJumpIf);
        tree->code = XBC_NOOP;
        group->group = tree;
    }
    
    
    /***********************************************************************
     * Com_XXX helper functions
     ***********************************************************************/
    static void
    ComPredicate(LispCom *com, LispBuiltin *builtin, LispBytePredicate predicate)
    {
        LispObj *object;
    
        object = ARGUMENT(0);
    
        if (ComConstantp(com, object)) {
    	switch (predicate) {
    	    case XBP_CONSP:
    		com_Bytecode(com, CONSP(object) ? XBC_T : XBC_NIL);
    		break;
    	    case XBP_LISTP:
    		com_Bytecode(com, CONSP(object) || object == NIL ?
    			     XBC_T : XBC_NIL);
    		break;
    	    case XBP_NUMBERP:
    		com_Bytecode(com, NUMBERP(object) ? XBC_T : XBC_NIL);
    		break;
    	}
        }
        else {
    	ComEval(com, object);
    	com_BytecodeChar(com, XBC_PRED, predicate);
        }
    }
    
    /* XXX Could receive an argument telling if is the last statement in the
     * block(s), i.e. if a jump opcode should be generated or just the
     * evaluation of the returned value. Probably this is better done in
     * an optimization step. */
    static void
    ComReturnFrom(LispCom *com, LispBuiltin *builtin, int from)
    {
        int bind;
        CodeTree *tree;
        LispObj *name, *result;
        CodeBlock *block = com->block;
    
        if (from) {
    	result = ARGUMENT(1);
    	name = ARGUMENT(0);
        }
        else {
    	result = ARGUMENT(0);
    	name = NIL;
        }
        if (result == UNSPEC)
    	result = NIL;
    
        bind = block->bind;
        while (block) {
    	if (block->type == LispBlockClosure)
    	    /* A function call */
    	    break;
    	else if (block->type == LispBlockTag && block->tag == name)
    	    break;
    	block = block->prev;
    	if (block)
    	    bind += block->bind;
        }
    
        if (!block || block->tag != name)
    	LispDestroy("%s: no visible %s block", STRFUN(builtin), STROBJ(name));
    
        /* Generate code to load result */
        ComEval(com, result);
    
        /* Check for added variables that the jump is skiping the unbind opcode */
        com_Unbind(com, bind);
    
        tree = NEW_TREE(CodeTreeReturn);
        tree->data.block = block;
    }
    
    /***********************************************************************
     * Helper functions
     ***********************************************************************/
    static int
    ComConstantp(LispCom *com, LispObj *object)
    {
        switch (OBJECT_TYPE(object)) {
    	case LispAtom_t:
    	    /* Keywords are guaranteed to evaluate to itself */
    	    if (object->data.atom->package == lisp__data.keyword)
    		break;
    	    return (0);
    
    	    /* Function call */
    	case LispCons_t:
    
    	    /* Need macro expansion, these are special abstract objects */
    	case LispQuote_t:
    	case LispBackquote_t:
    	case LispComma_t:
    	case LispFunctionQuote_t:
    	    return (0);
    
    	    /* Anything else is a literal constant */
    	default:
    	    break;
        }
    
        return (1);
    }
    
    static int
    FindIndex(void *item, void **table, int length)
    {
        long cmp;
        int left, right, i;
    
        left = 0;
        right = length - 1;
        while (left <= right) {
    	i = (left + right) >> 1;
    	cmp = (char*)item - (char*)table[i];
    	if (cmp == 0)
    	    return (i);
    	else if (cmp < 0)
    	    right = i - 1;
    	else
    	    left = i + 1;
        }
    
        return (-1);
    }
    
    static int
    compare(const void *left, const void *right)
    {
        long cmp = *(char**)left - *(char**)right;
    
        return (cmp < 0 ? -1 : 1);
    }
    
    static int
    BuildTablePointer(void *pointer, void ***pointers, int *num_pointers)
    {
        int i;
    
        if ((i = FindIndex(pointer, *pointers, *num_pointers)) < 0) {
    	*pointers = LispRealloc(*pointers,
    				sizeof(void*) * (*num_pointers + 1));
    	(*pointers)[*num_pointers] = pointer;
    	if (++*num_pointers > 1)
    	    qsort(*pointers, *num_pointers, sizeof(void*), compare);
    	i = FindIndex(pointer, *pointers, *num_pointers);
        }
    
        return (i);
    }
    
    static void
    ComAddVariable(LispCom *com, LispObj *symbol, LispObj *value)
    {
        LispAtom *atom = symbol->data.atom;
    
        if (atom && atom->string && !com->macro) {
    	int i, length = com->block->variables.length;
    
    	i = BuildTablePointer(atom, (void***)&com->block->variables.symbols,
    			      &com->block->variables.length);
    
    	if (com->block->variables.length != length) {
    	    com->block->variables.flags =
    		LispRealloc(com->block->variables.flags,
    			    com->block->variables.length * sizeof(int));
    
    	    /* Variable was inserted in the middle of the list */
    	    if (i < length)
    		memmove(com->block->variables.flags + i + 1,
    			com->block->variables.flags + i,
    			(length - i) * sizeof(int));
    
    	    com->block->variables.flags[i] = 0;
    	}
        }
    
        LispAddVar(symbol, value);
    }
    
    static int
    ComGetVariable(LispCom *com, LispObj *symbol)
    {
        LispAtom *name;
        int i, base, offset;
        Atom_id id;
    
        name = symbol->data.atom;
        if (name->constant) {
    	if (name->package == lisp__data.keyword)
    	    /*	Just load <symbol> from the byte stream, keywords are
    	     * guaranteed to evaluate to itself. */
    	    return (SYMBOL_KEYWORD);
    	return (SYMBOL_CONSTANT);
        }
    
        offset = name->offset;
        id = name->string;
        base = lisp__data.env.lex;
        i = lisp__data.env.head - 1;
    
        /* If variable is local */
        if (offset <= i && offset >= com->lex && lisp__data.env.names[offset] == id) {
    	COM_VARIABLE_USED(name);
    	/* Relative offset */
    	return (offset - base);
        }
    
        /* name->offset may have been changed in a macro expansion */
        for (; i >= com->lex; i--)
    	if (lisp__data.env.names[i] == id) {
    	    name->offset = i;
    	    COM_VARIABLE_USED(name);
    	    return (i - base);
    	}
    
        if (!name->a_object) {
    	++com->warnings;
    	LispWarning("variable %s is neither declared nor bound",
    		    name->string);
        }
    
        /* Not found, resolve <symbol> at run time */
        return (SYMBOL_UNBOUND);
    }
    
    static void
    ComVariableSetFlag(LispCom *com, LispAtom *atom, int flag)
    {
        int i;
        CodeBlock *block = com->block;
    
        while (block) {
    	i = FindIndex(atom, (void**)block->variables.symbols,
    		      block->variables.length);
    	if (i >= 0) {
    	    block->variables.flags[i] |= flag;
    	    /*  Descend block list if an argument to function being called
    	     * has the same name as a bound variable in the current function.
    	     */
    	    if ((flag & VARIABLE_ARGUMENT) ||
    		!(block->variables.flags[i] & VARIABLE_ARGUMENT))
    		break;
    	}
    	block = block->prev;
        }
    }
    
    /***********************************************************************
     * Bytecode compiler functions
     ***********************************************************************/
    static void
    ComLabel(LispCom *com, LispObj *label)
    {
        int i;
        CodeTree *tree;
    
        for (i = 0; i < com->block->tagbody.length; i++)
    	if (label == com->block->tagbody.labels[i])
    	    LispDestroy("TAGBODY: tag %s specified more than once",
    			STROBJ(label));
    
        if (com->block->tagbody.length >= com->block->tagbody.space) {
    	com->block->tagbody.labels =
    	    LispRealloc(com->block->tagbody.labels,
    			sizeof(LispObj*) * (com->block->tagbody.space + 8));
    	/*  Reserve space, will be used at link time when
    	 * resolving GO jumps. */
    	com->block->tagbody.codes =
    	    LispRealloc(com->block->tagbody.codes,
    			sizeof(CodeTree*) * (com->block->tagbody.space + 8));
    	com->block->tagbody.space += 8;
        }
    
        com->block->tagbody.labels[com->block->tagbody.length++] = label;
        tree = NEW_TREE(CodeTreeLabel);
        tree->data.object = label;
    }
    
    static void
    ComPush(LispCom *com, LispObj *symbol, LispObj *value,
    	int eval, int builtin, int compile)
    {
        /*  If <compile> is set, it is pushing an argument to one of
         * Com_XXX functions. */
        if (compile) {
    	if (builtin)
    	    lisp__data.stack.values[lisp__data.stack.length++] = value;
    	else
    	    ComAddVariable(com, symbol, value);
    	return;
        }
    
        /*  If <com->macro> is set, it is expanding a macro, just add the local
         * variable <symbol> bounded to <value>, so that it will be available
         * when calling the interpreter to expand the macro. */
        else if (com->macro) {
    	ComAddVariable(com, symbol, value);
    	return;
        }
    
        /*  If <eval> is set, it must generate the opcodes to evaluate <value>.
         * If <value> is a constant, just generate the opcodes to load it. */
        else if (eval && !ComConstantp(com, value)) {
    	switch (OBJECT_TYPE(value)) {
    	    case LispAtom_t: {
    		int offset = ComGetVariable(com, value);
    
    		if (offset >= 0) {
    		    /* Load <value> from user stack at the relative offset */
    		    if (builtin)
    			com_LoadPush(com, offset);
    		    else
    			com_LoadLet(com, offset, symbol->data.atom);
    		}
    		/* ComConstantp() does not return true for this, as the
    		 * current value must be computed. */
    		else if (offset == SYMBOL_CONSTANT) {
    		    value = value->data.atom->property->value;
    		    if (builtin)
    			com_LoadConPush(com, value);
    		    else
    			com_LoadConLet(com, value, symbol->data.atom);
    		}
    		else {
    		    /* Load value bound to <value> at run time */
    		    if (builtin)
    			com_LoadSymPush(com, value->data.atom);
    		    else
    			com_LoadSymLet(com, value->data.atom,
    				       symbol->data.atom);
    		}
    	    }	break;
    
    	    default:
    		/* Generate code to evaluate <value> */
    		ComEval(com, value);
    		if (builtin)
    		    com_Bytecode(com, XBC_PUSH);
    		else
    		    com_Let(com, symbol->data.atom);
    		break;
    	}
    
    	/*  Remember <symbol> will be bound, <value> only matters for
    	 * the Com_XXX  functions */
    	if (builtin)
    	    lisp__data.stack.values[lisp__data.stack.length++] = value;
    	else
    	    ComAddVariable(com, symbol, value);
    	return;
        }
    
        if (builtin) {
    	/* Load <value> as a constant in builtin stack */
    	com_LoadConPush(com, value);
    	lisp__data.stack.values[lisp__data.stack.length++] = value;
        }
        else {
    	/* Load <value> as a constant in stack */
    	com_LoadConLet(com, value, symbol->data.atom);
    	/* Remember <symbol> will be bound */
    	ComAddVariable(com, symbol, value);
        }
    }
    
    /*  This function does almost the same job as LispMakeEnvironment, but
     * it is not optimized for speed, as it is not building argument lists
     * to user code, but to Com_XXX functions, or helping in generating the
     * opcodes to load arguments at bytecode run time. */
    static int
    ComCall(LispCom *com, LispArgList *alist,
    	LispObj *name, LispObj *values,
    	int eval, int builtin, int compile)
    {
        char *desc;
        int i, count, base;
        LispObj **symbols, **defaults, **sforms;
    
        if (builtin) {
    	base = lisp__data.stack.length;
    	/* This should never be executed, but make the check for safety */
    	if (base + alist->num_arguments > lisp__data.stack.space) {
    	    do
    		LispMoreStack();
    	    while (base + alist->num_arguments > lisp__data.stack.space);
    	}
        }
        else
    	base = lisp__data.env.length;
    
        desc = alist->description;
        switch (*desc++) {
    	case '.':
    	    goto normal_label;
    	case 'o':
    	    goto optional_label;
    	case 'k':
    	    goto key_label;
    	case 'r':
    	    goto rest_label;
    	case 'a':
    	    goto aux_label;
    	default:
    	    goto done_label;
        }
    
    
        /* Normal arguments */
    normal_label:
        i = 0;
        symbols = alist->normals.symbols;
        count = alist->normals.num_symbols;
        for (; i < count && CONSP(values); i++, values = CDR(values)) {
    	ComPush(com, symbols[i], CAR(values), eval, builtin, compile);
    	if (!builtin && !com->macro)
    	    COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
        }
        if (i < count)
    	LispDestroy("%s: too few arguments", STROBJ(name));
    
        switch (*desc++) {
    	case 'o':
    	    goto optional_label;
    	case 'k':
    	    goto key_label;
    	case 'r':
    	    goto rest_label;
    	case 'a':
    	    goto aux_label;
    	default:
    	    goto done_label;
        }
    
    
        /* &OPTIONAL */
    optional_label:
        i = 0;
        count = alist->optionals.num_symbols;
        symbols = alist->optionals.symbols;
        defaults = alist->optionals.defaults;
        sforms = alist->optionals.sforms;
        for (; i < count && CONSP(values); i++, values = CDR(values)) {
    	ComPush(com, symbols[i], CAR(values), eval, builtin, compile);
    	if (!builtin && !com->macro)
    	    COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
    	if (sforms[i]) {
    	    ComPush(com, sforms[i], T, 0, builtin, compile);
    	    if (!builtin && !com->macro)
    		COM_VARIABLE_ARGUMENT(sforms[i]->data.atom);
    	}
        }
        for (; i < count; i++) {
    	if (!builtin) {
    	    int lex = com->lex;
    	    int head = lisp__data.env.head;
    
    	    com->lex = base;
    	    lisp__data.env.head = lisp__data.env.length;
    	    /* default arguments are evaluated for macros */
    	    ComPush(com, symbols[i], defaults[i], 1, 0, compile);
    	    if (!com->macro)
    		COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
    	    lisp__data.env.head = head;
    	    com->lex = lex;
    	}
    	else
    	    ComPush(com, symbols[i], defaults[i], eval, 1, compile);
    	if (sforms[i]) {
    	    ComPush(com, sforms[i], NIL, 0, builtin, compile);
    	    if (!builtin && !com->macro)
    		COM_VARIABLE_ARGUMENT(sforms[i]->data.atom);
    	}
        }
    
        switch (*desc++) {
    	case 'k':
    	    goto key_label;
    	case 'r':
    	    goto rest_label;
    	case 'a':
    	    goto aux_label;
    	default:
    	    goto done_label;
        }
    
    
        /* &KEY */
    key_label:
        {
    	int varset;
    	LispObj *val, *karg, **keys;
    
    	count = alist->keys.num_symbols;
    	symbols = alist->keys.symbols;
    	defaults = alist->keys.defaults;
    	sforms = alist->keys.sforms;
    	keys = alist->keys.keys;
    
    	/* Check if arguments are correctly specified */
    	for (karg = values; CONSP(karg); karg = CDR(karg)) {
    	    val = CAR(karg);
    	    if (KEYWORDP(val)) {
    		for (i = 0; i < alist->keys.num_symbols; i++)
    		    if (!keys[i] && symbols[i] == val)
    			break;
    	    }
    
    	    else if (!builtin &&
    		     QUOTEP(val) && SYMBOLP(val->data.quote)) {
    		for (i = 0; i < alist->keys.num_symbols; i++)
    		    if (keys[i] && ATOMID(keys[i]) == ATOMID(val->data.quote))
    			break;
    	    }
    
    	    else
    		/* Just make the error test true */
    		i = alist->keys.num_symbols;
    
    	    if (i == alist->keys.num_symbols) {
    		/* If not in argument specification list... */
    		char function_name[36];
    
    		strcpy(function_name, STROBJ(name));
    		LispDestroy("%s: invalid keyword %s",
    			    function_name, STROBJ(val));
    	    }
    
    	    karg = CDR(karg);
    	    if (!CONSP(karg))
    		LispDestroy("%s: &KEY needs arguments as pairs",
    			    STROBJ(name));
    	}
    
    	/* Add variables */
    	for (i = 0; i < alist->keys.num_symbols; i++) {
    	    val = defaults[i];
    	    varset = 0;
    	    if (!builtin && keys[i]) {
    		Atom_id atom = ATOMID(keys[i]);
    
    		/* Special keyword specification, need to compare ATOMID
    		 * and keyword specification must be a quoted object */
    		for (karg = values; CONSP(karg); karg = CDR(karg)) {
    		    val = CAR(karg);
    		    if (QUOTEP(val) && atom == ATOMID(val->data.quote)) {
    			val = CADR(karg);
    			varset = 1;
    			break;
    		    }
    		    karg = CDR(karg);
    		}
    	    }
    
    	    else {
    		/* Normal keyword specification, can compare object pointers,
    		 * as they point to the same object in the keyword package */
    		for (karg = values; CONSP(karg); karg = CDR(karg)) {
    		    /* Don't check if argument is a valid keyword or
    		     * special quoted keyword */
    		    if (symbols[i] == CAR(karg)) {
    			val = CADR(karg);
    			varset = 1;
    			break;
    		    }
    		    karg = CDR(karg);
    		}
    	    }
    
    	    /* Add the variable to environment */
    	    if (varset) {
    		ComPush(com, symbols[i], val, eval, builtin, compile);
    		if (sforms[i])
    		    ComPush(com, sforms[i], T, 0, builtin, compile);
    	    }
    	    else {
    		/* default arguments are evaluated for macros */
    		if (!builtin) {
    		    int lex = com->lex;
    		    int head = lisp__data.env.head;
    
    		    com->lex = base;
    		    lisp__data.env.head = lisp__data.env.length;
    		    ComPush(com, symbols[i], val, eval, 0, compile);
    		    lisp__data.env.head = head;
    		    com->lex = lex;
    		}
    		else
    		    ComPush(com, symbols[i], val, eval, builtin, compile);
    		if (sforms[i])
    		    ComPush(com, sforms[i], NIL, 0, builtin, compile);
    	    }
    	    if (!builtin && !com->macro) {
    		COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
    		if (sforms[i])
    		    COM_VARIABLE_ARGUMENT(sforms[i]->data.atom);
    	    }
    	}
        }
    
        if (*desc == 'a') {
    	/* &KEY uses all remaining arguments */
    	values = NIL;
    	goto aux_label;
        }
        goto finished_label;
    
    
        /* &REST */
    rest_label:
        if (!eval || !CONSP(values) || (compile && !builtin))
    	ComPush(com, alist->rest, values, eval, builtin, compile);
        else {
    	char *string;
    	LispObj *list, *car = NIL;
    	int count, constantp;
    
    	/* Count number of arguments and check if it is a list of constants */
    	for (count = 0, constantp = 1, list = values;
    	     CONSP(list);
    	     list = CDR(list), count++) {
    	    car = CAR(list);
    	    if (!ComConstantp(com, car))
    		constantp = 0;
    	}
    
    	string = builtin ? ATOMID(name) : NULL;
    	/* XXX FIXME should have a flag indicating if function call
    	 * change the &REST arguments even if it is a constant list
    	 * (or if the returned value may be changed). */
    	if (string && (count < MAX_BCONS || constantp) &&
    	    strcmp(string, "LIST") &&
    	    strcmp(string, "APPLY") &&	/* XXX depends on function argument */
    	    strcmp(string, "VECTOR") &&
    	    /* Append does not copy the last/single list */
    	    (strcmp(string, "APPEND") || !CONSP(car))) {
    	    if (constantp) {
    		/* If the builtin function changes the &REST parameters, must
    		 * define a Com_XXX function for it. */
    		ComPush(com, alist->rest, values, 0, builtin, compile);
    	    }
    	    else {
    		CompileStackEnter(com, count - 1, 1);
    		for (; CONSP(CDR(values)); values = CDR(values)) {
    		    /* Evaluate this argument */
    		    ComEval(com, CAR(values));
    		    /* Save result in builtin stack */
    		    com_Bytecode(com, XBC_PUSH);
    		}
    		CompileStackLeave(com, count - 1, 1);
    		/* The last argument is not saved in the stack */
    		ComEval(com, CAR(values));
    		values = NIL;
    		com_Bytecode(com, (LispByteOpcode)(XBC_BCONS + (count - 1)));
    	    }
    	}
    	else {
    	    /* Allocate a fresh list of cons */
    
    	    /* Generate code to load object */
    	    ComEval(com, CAR(values));
    
    	    com->stack.cpstack += 2;
    	    if (com->stack.pstack < com->stack.cpstack)
    		com->stack.pstack = com->stack.cpstack;
    	    /* Start building a gc protected list, with the loaded value */
    	    com_Bytecode(com, XBC_LSTAR);
    
    	    for (values = CDR(values); CONSP(values); values = CDR(values)) {
    		/* Generate code to load object */
    		ComEval(com, CAR(values));
    
    		/* Add loaded value to gc protected list */
    		com_Bytecode(com, XBC_LCONS);
    	    }
    
    	    /* Finish gc protected list */
    	    com_Bytecode(com, XBC_LFINI);
    
    	    /* Push loaded value */
    	    if (builtin)
    		com_Bytecode(com, XBC_PUSH);
    	    else {
    		com_Let(com, alist->rest->data.atom);
    
    		/* Remember this symbol will be bound */
    		ComAddVariable(com, alist->rest, values);
    	    }
    	    com->stack.cpstack -= 2;
    	}
        }
        if (!builtin && !com->macro)
    	COM_VARIABLE_ARGUMENT(alist->rest->data.atom);
        if (*desc != 'a')
    	goto finished_label;
    
    
        /* &AUX */
    aux_label:
        i = 0;
        count = alist->auxs.num_symbols;
        symbols = alist->auxs.symbols;
        defaults = alist->auxs.initials;
        if (!builtin && !compile) {
    	int lex = com->lex;
    
    	com->lex = base;
    	lisp__data.env.head = lisp__data.env.length;
    	for (; i < count; i++) {
    	    ComPush(com, symbols[i], defaults[i], 1, 0, 0);
    	    if (!com->macro)
    		COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
    	    ++lisp__data.env.head;
    	}
    	com->lex = lex;
        }
        else {
    	for (; i < count; i++) {
    	    ComPush(com, symbols[i], defaults[i], eval, builtin, compile);
    	    if (!builtin && !com->macro)
    		COM_VARIABLE_ARGUMENT(symbols[i]->data.atom);
    	}
        }
    
    done_label:
        if (CONSP(values))
    	LispDestroy("%s: too many arguments", STROBJ(name));
    
    finished_label:
        if (builtin)
    	lisp__data.stack.base = base;
        else
    	lisp__data.env.head = lisp__data.env.length;
    
        return (base);
    }
    
    static void
    ComFuncall(LispCom *com, LispObj *function, LispObj *arguments, int eval)
    {
        int base, compile;
        LispAtom *atom;
        LispArgList *alist;
        LispBuiltin *builtin;
        LispObj *lambda;
    
        switch (OBJECT_TYPE(function)) {
    	case LispFunction_t:
    	    function = function->data.atom->object;
    	case LispAtom_t:
    	    atom = function->data.atom;
    	    alist = atom->property->alist;
    
    	    if (atom->a_builtin) {
    		builtin = atom->property->fun.builtin;
    		compile = builtin->compile != NULL;
    
    		/*  If one of:
    		 * 	o expanding a macro
    		 *	o calling a builtin special form
    		 *	o builtin function is a macro
    		 * don't evaluate arguments. */
    		if (com->macro || compile || builtin->type == LispMacro)
    		    eval = 0;
    
    		if (!com->macro && builtin->type == LispMacro) {
    		    /* Set flag of variable used, in case variable is only
    		     * used as a builtin macro argument. */
    		    LispObj *obj;
    
    		    for (obj = arguments; CONSP(obj); obj = CDR(obj)) {
    			if (SYMBOLP(CAR(obj)))
    			    COM_VARIABLE_USED(CAR(obj)->data.atom);
    		    }
    		}
    
    		FORM_ENTER();
    		if (!compile && !com->macro)
    		    CompileStackEnter(com, alist->num_arguments, 1);
    
    		/* Build argument list in the interpreter stacks */
    		base = ComCall(com, alist, function, arguments,
    			       eval, 1, compile);
    
    		/* If <compile> is set, it is a special form */
    		if (compile)
    		    builtin->compile(com, builtin);
    
    		/* Else, generate opcodes to call builtin function */
    		else {
    		    com_Call(com, alist->num_arguments, builtin);
    		    CompileStackLeave(com, alist->num_arguments, 1);
    		}
    		lisp__data.stack.base = lisp__data.stack.length = base;
    		FORM_LEAVE();
    	    }
    	    else if (atom->a_function) {
    		int macro;
    
    		lambda = atom->property->fun.function;
    		macro = lambda->funtype == LispMacro;
    
    		/* If <macro> is set, expand macro */
    		if (macro)
    		    ComMacroCall(com, alist, function, lambda, arguments);
    
    		else {
    		    if (com->toplevel->type == LispBlockClosure &&
    			com->toplevel->tag == function)
    			ComRecursiveCall(com, alist, function, arguments);
    		    else {
    #if 0
    			ComInlineCall(com, alist, function, arguments,
    				      lambda->data.lambda.code);
    #else
    			com_Funcall(com, function, arguments);
    #endif
    		    }
    		}
    	    }
    	    else if (atom->a_defstruct &&
    		     atom->property->structure.function != STRUCT_NAME &&
    		     atom->property->structure.function != STRUCT_CONSTRUCTOR) {
    		LispObj *definition = atom->property->structure.definition;
    
    		if (!CONSP(arguments) || CONSP(CDR(arguments)))
    		    LispDestroy("%s: too %s arguments", atom->string,
    				CONSP(arguments) ? "many" : "few");
    
    		ComEval(com, CAR(arguments));
    		if (atom->property->structure.function == STRUCT_CHECK)
    		    com_Structp(com, definition);
    		else
    		    com_Struct(com,
    			       atom->property->structure.function, definition);
    	    }
    	    else if (atom->a_compiled) {
    		FORM_ENTER();
    		CompileStackEnter(com, alist->num_arguments, 0);
    
    		/* Build argument list in the interpreter stacks */
    		base = ComCall(com, alist, function, arguments, 1, 0, 0);
    		com_Bytecall(com, alist->num_arguments,
    			     atom->property->fun.function);
    		CompileStackLeave(com, alist->num_arguments, 0);
    		lisp__data.env.head = lisp__data.env.length = base;
    		FORM_LEAVE();
    	    }
    	    else {
    		/* Not yet defined function/macro. */
    		++com->warnings;
    		LispWarning("call to undefined function %s", atom->string);
    		com_Funcall(com, function, arguments);
    	    }
    	    break;
    
    	case LispLambda_t:
    	    lambda = function->data.lambda.code;
    	    alist = (LispArgList*)function->data.lambda.name->data.opaque.data;
    	    ComInlineCall(com, alist, NIL, arguments, lambda->data.lambda.code);
    	    break;
    
    	case LispCons_t:
    	    if (CAR(function) == Olambda) {
    		function = EVAL(function);
    		if (LAMBDAP(function)) {
    		    GC_ENTER();
    
    		    GC_PROTECT(function);
    		    lambda = function->data.lambda.code;
    		    alist = (LispArgList*)function->data.lambda.name->data.opaque.data;
    		    ComInlineCall(com, alist, NIL, arguments, lambda->data.lambda.code);
    		    GC_LEAVE();
    		    break;
    		}
    	    }
    
    	default:
    	    /*  XXX If bytecode objects are made available, should
    	     * handle it here. */
    	    LispDestroy("EVAL: %s is invalid as a function",
    			STROBJ(function));
    	    /*NOTREACHED*/
    	    break;
        }
    }
    
    /* Generate opcodes for an implicit PROGN */
    static void
    ComProgn(LispCom *com, LispObj *code)
    {
        if (CONSP(code)) {
    	for (; CONSP(code); code = CDR(code))
    	    ComEval(com, CAR(code));
        }
        else
    	/* If no code to execute, empty PROGN returns NIL */
    	com_Bytecode(com, XBC_NIL);
    }
    
    /* Generate opcodes to evaluate <object>. */
    static void
    ComEval(LispCom *com, LispObj *object)
    {
        int offset;
        LispObj *form;
    
        switch (OBJECT_TYPE(object)) {
    	case LispAtom_t:
    	    if (IN_TAGBODY())
    		ComLabel(com, object);
    	    else {
    		offset = ComGetVariable(com, object);
    		if (offset >= 0)
    		    /* Load from user stack at relative offset */
    		    com_Load(com, offset);
    		else if (offset == SYMBOL_KEYWORD)
    		    com_LoadCon(com, object);
    		else if (offset == SYMBOL_CONSTANT)
    		    /* Symbol defined as constant, just load it's value */
    		    com_LoadCon(com, LispGetVar(object));
    		else
    		    /* Load value bound to symbol at run time */
    		    com_LoadSym(com, object->data.atom);
    	    }
    	    break;
    
    	case LispCons_t: {
    	    /* Macro expansion may be done in the object form */
    	    form = com->form;
    	    com->form = object;
    	    ComFuncall(com, CAR(object), CDR(object), 1);
    	    com->form = form;
    	}   break;
    
    	case LispQuote_t:
    	    com_LoadCon(com, object->data.quote);
    	    break;
    
    	case LispBackquote_t:
    	    /* Macro expansion is stored in the current value of com->form */
    	    ComMacroBackquote(com, object);
    	    break;
    
    	case LispComma_t:
    	    LispDestroy("EVAL: comma outside of backquote");
    	    break;
    
    	case LispFunctionQuote_t:
    	    object = object->data.quote;
    	    if (SYMBOLP(object))
    		object = LispSymbolFunction(object);
    	    else if (CONSP(object) && CAR(object) == Olambda) {
    		/* object will only be associated with bytecode later,
    		 * so, make sure it is protected until compilation finishes */
    		object = EVAL(object);
    		RPLACD(com->plist, CONS(CAR(com->plist), CDR(com->plist)));
    		RPLACA(com->plist, object);
    	    }
    	    else
    		LispDestroy("FUNCTION: %s is not a function", STROBJ(object));
    	    com_LoadCon(com, object);
    	    break;
    
    	case LispFixnum_t:
    	    if (IN_TAGBODY()) {
    		ComLabel(com, object);
    		break;
    	    }
    	    /*FALLTROUGH*/
    
    	default:
    	    /* Constant object */
    	    com_LoadCon(com, object);
    	    break;
        }
    }
    
    /***********************************************************************
     * Lambda expansion helper functions
     ***********************************************************************/
    static void
    ComRecursiveCall(LispCom *com, LispArgList *alist,
    		 LispObj *name, LispObj *arguments)
    {
        int base, lex;
    
        /* Save state */
        lex = lisp__data.env.lex;
    
        FORM_ENTER();
    
        /* Generate code to push function arguments in the stack */
        base = ComCall(com, alist, name, arguments, 1, 0, 0);
    
        /* Stack will grow this amount */
        CompileStackEnter(com, alist->num_arguments, 0);
    
    #if 0
        /* Make the variables available at run time */
        com_Bind(com, alist->num_arguments);
        com->block->bind += alist->num_arguments;
    #endif
    
        com_BytecodeChar(com, XBC_LETREC, alist->num_arguments);
    
    #if 0
        /* The variables are now unbound */
        com_Unbind(com, alist->num_arguments);
        com->block->bind -= alist->num_arguments;
    #endif
    
        /* Stack length is reduced */
        CompileStackLeave(com, alist->num_arguments, 0);
        FORM_LEAVE();
    
        /* Restore state */
        lisp__data.env.lex = lex;
        lisp__data.env.head = lisp__data.env.length = base;
    }
    
    static void
    ComInlineCall(LispCom *com, LispArgList *alist,
    	      LispObj *name, LispObj *arguments, LispObj *lambda)
    {
        int base, lex;
    
        /* Save state */
        lex = lisp__data.env.lex;
    
        FORM_ENTER();
        /* Start the inline function block */
        CompileIniBlock(com, LispBlockClosure, name);
    
        /* Generate code to push function arguments in the stack */
        base = ComCall(com, alist, name, arguments, 1, 0, 0);
    
        /* Stack will grow this amount */
        CompileStackEnter(com, alist->num_arguments, 0);
    
        /* Make the variables available at run time */
        com_Bind(com, alist->num_arguments);
        com->block->bind += alist->num_arguments;
    
        /* Expand the lambda list */
        ComProgn(com, lambda);
    
        /* The variables are now unbound */
        com_Unbind(com, alist->num_arguments);
        com->block->bind -= alist->num_arguments;
    
        /* Stack length is reduced */
        CompileStackLeave(com, alist->num_arguments, 0);
    
        /* Finish the inline function block */
        CompileFiniBlock(com);
        FORM_LEAVE();
    
        /* Restore state */
        lisp__data.env.lex = lex;
        lisp__data.env.head = lisp__data.env.length = base;
    }
    
    /***********************************************************************
     * Macro expansion helper functions.
     ***********************************************************************/
    static LispObj *
    ComMacroExpandBackquote(LispCom *com, LispObj *object)
    {
        return (LispEvalBackquote(object->data.quote, 1));
    }
    
    static LispObj *
    ComMacroExpandFuncall(LispCom *com, LispObj *function, LispObj *arguments)
    {
        return (LispFuncall(function, arguments, 1));
    }
    
    static LispObj *
    ComMacroExpandEval(LispCom *com, LispObj *object)
    {
        LispObj *result;
    
        switch (OBJECT_TYPE(object)) {
    	case LispAtom_t:
    	    result = LispGetVar(object);
    
    	    /* Macro expansion requires bounded symbols */
    	    if (result == NULL)
    		LispDestroy("EVAL: the variable %s is unbound",
    			    STROBJ(object));
    	    break;
    
    	case LispCons_t:
    	    result = ComMacroExpandFuncall(com, CAR(object), CDR(object));
    	    break;
    
    	case LispQuote_t:
    	    result = object->data.quote;
    	    break;
    
    	case LispBackquote_t:
    	    result = ComMacroExpandBackquote(com, object);
    	    break;
    
    	case LispComma_t:
    	    LispDestroy("EVAL: comma outside of backquote");
    
    	case LispFunctionQuote_t:
    	    result = EVAL(object);
    	    break;
    
    	default:
    	    result = object;
    	    break;
        }
    
        return (result);
    }
    
    static LispObj *
    ComMacroExpand(LispCom *com, LispObj *lambda)
    {
        LispObj *result, **presult = &result;
        int jumped, *pjumped = &jumped, backquote, *pbackquote = &backquote;
        LispBlock *block;
    
        int interpreter_lex, interpreter_head, interpreter_base;
    
        /* Save interpreter state */
        interpreter_base = lisp__data.stack.length;
        interpreter_head = lisp__data.env.length;
        interpreter_lex = lisp__data.env.lex;
    
        /* Use the variables */
        *presult = NIL;
        *pjumped = 1;
        *pbackquote = !CONSP(lambda);
    
        block = LispBeginBlock(NIL, LispBlockProtect);
        if (setjmp(block->jmp) == 0) {
    	if (!backquote) {
    	    for (; CONSP(lambda); lambda = CDR(lambda))
    		result = ComMacroExpandEval(com, CAR(lambda));
    	}
    	else
    	    result = ComMacroExpandBackquote(com, lambda);
    
    	*pjumped = 0;
        }
        LispEndBlock(block);
    
        /* If tried to jump out of the macro expansion block */
        if (!lisp__data.destroyed && jumped)
    	LispDestroy("*** EVAL: bad jump in macro expansion");
    
        /* Macro expansion did something wrong */
        if (lisp__data.destroyed) {
    	LispMessage("*** EVAL: aborting macro expansion");
    	LispDestroy(".");
        }
    
        /* Restore interpreter state */
        lisp__data.env.lex = interpreter_lex;
        lisp__data.stack.length = interpreter_base;
        lisp__data.env.head = lisp__data.env.length = interpreter_head;
    
        return (result);
    }
    
    static void
    ComMacroCall(LispCom *com, LispArgList *alist,
    	     LispObj *name, LispObj *lambda, LispObj *arguments)
    {
        int base;
        LispObj *body;
    
        ++com->macro;
        base = ComCall(com, alist, name, arguments, 0, 0, 0);
        body = lambda->data.lambda.code;
        body = ComMacroExpand(com, body);
        --com->macro;
        lisp__data.env.head = lisp__data.env.length = base;
    
        /* Macro is expanded, store the result */
        CAR(com->form) = body;
        ComEval(com, body);
    }
    
    static void
    ComMacroBackquote(LispCom *com, LispObj *lambda)
    {
        LispObj *body;
    
        ++com->macro;
        body = ComMacroExpand(com, lambda);
        --com->macro;
    
        /* Macro is expanded, store the result */
        CAR(com->form) = body;
    
        com_LoadCon(com, body);
    }