xenocara/app/xedit/lisp/compile.c
2015-05-10 10:07:47 +00:00

2225 lines
53 KiB
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;
const 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->key && !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->key;
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->key->value);
}
/* 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)->value : 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->key->value,
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->key->value);
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);
}