/* xscom.c - a simple scheme bytecode compiler */
/*	Copyright (c) 1988, by David Michael Betz
	All Rights Reserved
	Permission is granted for unrestricted non-commercial use	*/

#include "xscheme.h"
#include "xsbcode.h"

/* size of code buffer */
#define CMAX	4000

/* continuation types */
#define C_RETURN	-1
#define C_NEXT		-2

/* macro to check for a lambda list keyword */
#define lambdakey(x)	((x) == lk_optional || (x) == lk_rest)

/* external variables */
extern LVAL lk_optional,lk_rest,true;

/* local variables */
static LVAL info;		/* compiler info */

/* code buffer */
static unsigned char cbuff[CMAX];	/* base of code buffer */
static int cbase;			/* base for current function */
static int cptr;			/* code buffer pointer */

/* forward declarations */
int do_define(),do_set(),do_quote(),do_lambda(),do_consstream(),do_delay();
int do_let(),do_letrec(),do_letstar(),do_cond(),do_and(),do_or();
int do_if(),do_begin(),do_while(),do_access();
LVAL make_code_object();

/* integrable function table */
typedef struct { char *nt_name; int nt_code,nt_args; } NTDEF;
static NTDEF *nptr,ntab[] = {
	"ATOM",			OP_ATOM,	1,
	"EQ?",			OP_EQ,		2,
	"NULL?",		OP_NULL,	1,
	"NOT",			OP_NULL,	1,
	"CONS",			OP_CONS,	2,
	"CAR",			OP_CAR,		1,
	"CDR",			OP_CDR,		1,
	"SET-CAR!",		OP_SETCAR,	2,
	"SET-CDR!",		OP_SETCDR,	2,
	"+",			OP_ADD,		-2,
	"-",			OP_SUB,		-2,
	"*",			OP_MUL,		-2,
	"QUOTIENT",		OP_QUO,		-2,
	"<",			OP_LSS,		-2,
	"=",			OP_EQL,		-2,
	">",			OP_GTR,		-2,
	0
};

/* special form table */
typedef struct { char *ft_name; int (*ft_fcn)(); } FTDEF;
static FTDEF *fptr,ftab[] = {
	"QUOTE",	do_quote,
	"LAMBDA",	do_lambda,
	"DELAY",	do_delay,
	"LET",		do_let,
	"LET*",		do_letstar,
	"LETREC",	do_letrec,
	"DEFINE",	do_define,
	"SET!",		do_set,
	"IF",		do_if,
	"COND",		do_cond,
	"BEGIN",	do_begin,
	"SEQUENCE",	do_begin,
	"AND",		do_and,
	"OR",		do_or,
	"WHILE",	do_while,
	"ACCESS",	do_access,
	0
};

/* xlcompile - compile an expression */
LVAL xlcompile(expr,ctenv)
  LVAL expr,ctenv;
{
    /* initialize the compile time environment */
    info = cons(NIL,NIL); cpush(info);
    rplaca(info,newframe(ctenv,1));
    rplacd(info,cons(NIL,NIL));

    /* setup the base of the code for this function */
    cbase = cptr = 0;

    /* setup the entry code */
    putcbyte(OP_FRAME);
    putcbyte(1);

    /* compile the expression */
    do_expr(expr,C_RETURN);

    /* build the code object */
    settop(make_code_object(NIL));
    return (pop());
}

/* xlfunction - compile a function */
LVAL xlfunction(fun,fargs,body,ctenv)
  LVAL fun,fargs,body,ctenv;
{
    /* initialize the compile time environment */
    info = cons(NIL,NIL); cpush(info);
    rplaca(info,newframe(ctenv,1));
    rplacd(info,cons(NIL,NIL));

    /* setup the base of the code for this function */
    cbase = cptr = 0;

    /* compile the lambda list and the function body */
    parse_lambda_list(fargs,body);
    do_begin(body,C_RETURN);

    /* build the code object */
    settop(make_code_object(fun));
    return (pop());
}

/* do_expr - compile an expression */
LOCAL do_expr(expr,cont)
  LVAL expr; int cont;
{
    LVAL fun;
    if (consp(expr)) {
	fun = car(expr);
 	if (!symbolp(fun) || (!in_ntab(expr,cont) && !in_ftab(expr,cont)))
	    do_call(expr,cont);
    }
    else if (symbolp(expr))
	do_identifier(expr,cont);
    else
	do_literal(expr,cont);
}

/* in_ntab - check for a function in ntab */
LOCAL int in_ntab(expr,cont)
  LVAL expr; int cont;
{
    unsigned char *pname;
    pname = getstring(getpname(car(expr)));
    for (nptr = ntab; nptr->nt_name; ++nptr)
	if (strcmp(pname,nptr->nt_name) == 0) {
	    do_nary(nptr->nt_code,nptr->nt_args,expr,cont);
	    return (TRUE);
	}
    return (FALSE);
}

/* in_ftab - check for a function in ftab */
LOCAL int in_ftab(expr,cont)
  LVAL expr; int cont;
{
    unsigned char *pname;
    pname = getstring(getpname(car(expr)));
    for (fptr = ftab; fptr->ft_name; ++fptr)
	if (strcmp(pname,fptr->ft_name) == 0) {
	    (*fptr->ft_fcn)(cdr(expr),cont);
	    return (TRUE);
	}
    return (FALSE);
}

/* do_define - handle the (DEFINE ... ) expression */
LOCAL do_define(form,cont)
  LVAL form; int cont;
{
    if (atom(form))
	xlerror("expecting symbol or function template",form);
    define1(car(form),cdr(form),cont);
}

/* define1 - helper routine for do_define */
LOCAL define1(list,body,cont)
  LVAL list,body; int cont;
{
    LVAL fargs;
    int off;

    /* handle nested definitions */
    if (consp(list)) {
	cpush(cons(xlenter("LAMBDA"),NIL));	/* (LAMBDA) */
	rplacd(top(),cons(cdr(list),NIL));	/* (LAMBDA args) */
	rplacd(cdr(top()),body);		/* (LAMBDA args body) */
	settop(cons(top(),NIL));		/* ((LAMBDA args body)) */
	define1(car(list),top(),cont);
	drop(1);
    }
    
    /* compile procedure definitions */
    else {

	/* make sure it's a symbol */
	if (!symbolp(list))
	    xlerror("expecting a symbol",list);

	/* check for a procedure definition */
	if (consp(body)
        &&  consp(car(body))
        &&  car(car(body)) == xlenter("LAMBDA")) {
	    fargs = car(cdr(car(body)));
	    body = cdr(cdr(car(body)));
	    cd_fundefinition(list,fargs,body);
	}

	/* compile the value expression or procedure body */
	else
	    do_begin(body,C_NEXT);
    
	/* define the variable value */
	if (findcvariable(list,&off))
	    cd_evariable(OP_ESET,0,off);
	else
	    cd_variable(OP_GSET,list);
	do_literal(list,cont);
    }
}

/* do_set - compile the (SET! ... ) expression */
LOCAL do_set(form,cont)
  LVAL form; int cont;
{
    if (atom(form))
	xlerror("expecting symbol or ACCESS form",form);
    else if (symbolp(car(form)))
	do_setvar(form,cont);
    else if (consp(car(form)))
	do_setaccess(form,cont);
    else
	xlerror("expecting symbol or ACCESS form",form);
}

/* do_setvar - compile the (SET! var value) expression */
LOCAL do_setvar(form,cont)
  LVAL form; int cont;
{
    int lev,off;
    LVAL sym;

    /* get the variable name */
    sym = car(form);

    /* compile the value expression */
    form = cdr(form);
    if (atom(form))
	xlerror("expecting value expression",form);
    do_expr(car(form),C_NEXT);

    /* set the variable value */
    if (findvariable(sym,&lev,&off))
	cd_evariable(OP_ESET,lev,off);
    else
	cd_variable(OP_GSET,sym);
    do_continuation(cont);
}

/* do_quote - compile the (QUOTE ... ) expression */
LOCAL do_quote(form,cont)
  LVAL form; int cont;
{
    if (atom(form))
	xlerror("expecting quoted expression",form);
    do_literal(car(form),cont);
}

/* do_lambda - compile the (LAMBDA ... ) expression */
LOCAL do_lambda(form,cont)
  LVAL form; int cont;
{
    if (atom(form))
	xlerror("expecting argument list",form);
    cd_fundefinition(NIL,car(form),cdr(form));
    do_continuation(cont);
}

/* cd_fundefinition - compile the function */
LOCAL cd_fundefinition(fun,fargs,body)
  LVAL fun,fargs,body;
{
    int oldcbase;

    /* establish a new environment frame */
    oldcbase = add_level();

    /* compile the lambda list and the function body */
    parse_lambda_list(fargs,body);
    do_begin(body,C_RETURN);

    /* build the code object */
    cpush(make_code_object(fun));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_CLOSE);
}

/* parse_lambda_list - parse the formal argument list */
LOCAL parse_lambda_list(fargs,body)
  LVAL fargs,body;
{
    LVAL arg,restarg,new,last;
    int frame,slotn;
    
    /* setup the entry code */
    putcbyte(OP_FRAME);
    frame = putcbyte(0);

    /* initialize the argument name list and slot number */
    restarg = last = NIL;
    slotn = 1;
    
    /* handle each required argument */
    while (consp(fargs) && (arg = car(fargs)) && !lambdakey(arg)) {

	/* make sure the argument is a symbol */
	if (!symbolp(arg))
	    xlerror("variable must be a symbol",arg);

	/* add the argument name to the name list */
	new = cons(arg,NIL);
	if (last) rplacd(last,new);
	else setelement(car(car(info)),0,new);
	last = new;

	/* generate an instruction to move the argument into the frame */
	putcbyte(OP_MVARG);
	putcbyte(slotn++);
	
	/* move the formal argument list pointer ahead */
	fargs = cdr(fargs);
    }

    /* check for the '#!optional' argument */
    if (consp(fargs) && car(fargs) == lk_optional) {
	fargs = cdr(fargs);

	/* handle each optional argument */
	while (consp(fargs) && (arg = car(fargs)) && !lambdakey(arg)) {

	    /* make sure the argument is a symbol */
	    if (!symbolp(arg))
		xlerror("#!optional variable must be a symbol",arg);

	    /* add the argument name to the name list */
	    new = cons(arg,NIL);
	    if (last) rplacd(last,new);
	    else setelement(car(car(info)),0,new);
	    last = new;

	    /* move the argument into the frame */
	    putcbyte(OP_MVOARG);
	    putcbyte(slotn++);
	
	    /* move the formal argument list pointer ahead */
	    fargs = cdr(fargs);
	}
    }

    /* check for the '#!rest' argument */
    if (consp(fargs) && car(fargs) == lk_rest) {
	fargs = cdr(fargs);

	/* handle the rest argument */
	if (consp(fargs) && (restarg = car(fargs)) && !lambdakey(restarg)) {

	    /* make sure the argument is a symbol */
	    if (!symbolp(restarg))
		xlerror("#!rest variable must be a symbol",restarg);

	    /* add the argument name to the name list */
	    new = cons(restarg,NIL);
	    if (last) rplacd(last,new);
	    else setelement(car(car(info)),0,new);
	    last = new;

	    /* make the #!rest argument list */
	    putcbyte(OP_MVRARG);
	    putcbyte(slotn++);

	    /* move the formal argument list pointer ahead */
	    fargs = cdr(fargs);
	}
	else
	    xlerror("expecting the #!rest variable");
    }

    /* check for the a dotted tail */
    if (restarg == NIL && symbolp(fargs)) {
	restarg = fargs;

	/* add the argument name to the name list */
	new = cons(restarg,NIL);
	if (last) rplacd(last,new);
	else setelement(car(car(info)),0,new);
	last = new;

	/* make the #!rest argument list */
	putcbyte(OP_MVRARG);
	putcbyte(slotn++);
	fargs = NIL;
    }

    /* check for the end of the argument list */
    if (fargs != NIL)
	xlerror("bad argument list tail",fargs);

    /* make sure the user didn't supply too many arguments */
    if (restarg == NIL)
	putcbyte(OP_ALAST);
	
    /* scan the body for internal definitions */
    slotn += find_internal_definitions(body,last);
	
    /* fixup the frame instruction */
    cbuff[cbase+frame] = slotn;
}

/* find_internal_definitions - find internal definitions */
LOCAL int find_internal_definitions(body,last)
  LVAL body,last;
{
    LVAL define,sym,new;
    int n=0;

    /* look for all (define...) forms */
    for (define = xlenter("DEFINE"); consp(body); body = cdr(body))
	if (consp(car(body)) && car(car(body)) == define) {
	    sym = cdr(car(body)); /* the rest of the (define...) form */
	    if (consp(sym)) {     /* make sure there is a second subform */
		sym = car(sym);   /* get the second subform */
		while (consp(sym))/* check for a procedure definition */
		    sym = car(sym);
		if (symbolp(sym)) {
		    new = cons(sym,NIL);
		    if (last) rplacd(last,new);
		    else setelement(car(car(info)),0,new);
		    last = new;
		    ++n;
		}
	    }
	}
    return (n);
}

/* do_delay - compile the (DELAY ... ) expression */
LOCAL do_delay(form,cont)
  LVAL form; int cont;
{
    int oldcbase;

    /* check argument list */
    if (atom(form))
	xlerror("expecting delay expression",form);

    /* establish a new environment frame */
    oldcbase = add_level();

    /* setup the entry code */
    putcbyte(OP_FRAME);
    putcbyte(1);

    /* compile the expression */
    do_expr(car(form),C_RETURN);

    /* build the code object */
    cpush(make_code_object(NIL));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_DELAY);
    do_continuation(cont);
}

/* do_let - compile the (LET ... ) expression */
LOCAL do_let(form,cont)
  LVAL form; int cont;
{
    /* handle named let */
    if (consp(form) && symbolp(car(form)))
	do_named_let(form,cont);
    
    /* handle unnamed let */
    else
        cd_let(NIL,form,cont);
}

/* do_named_let - compile the (LET name ... ) expression */
LOCAL do_named_let(form,cont)
  LVAL form; int cont;
{
    int oldcbase,nxt;

    /* save a continuation */
    if (cont != C_RETURN) {
	putcbyte(OP_SAVE);
	nxt = putcword(0);
    }
    
    /* establish a new environment frame */
    oldcbase = add_level();
    setelement(car(car(info)),0,cons(car(form),NIL));

    /* setup the entry code */
    putcbyte(OP_FRAME);
    putcbyte(2);
    
    /* compile the let expression */
    cd_let(car(form),cdr(form),C_RETURN);

    /* build the code object */
    cpush(make_code_object(NIL));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_CLOSE);

    /* apply the function */
    putcbyte(OP_CALL);
    putcbyte(1);

    /* target for the continuation */
    if (cont != C_RETURN)
	fixup(nxt);
}

/* cd_let - code a let expression */
LOCAL cd_let(name,form,cont)
  LVAL name,form; int cont;
{
    int oldcbase,nxt,lev,off,n;

    /* make sure there is a binding list */
    if (atom(form) || !listp(car(form)))
	xlerror("expecting binding list",form);

    /* save a continuation */
    if (cont != C_RETURN) {
	putcbyte(OP_SAVE);
	nxt = putcword(0);
    }
    
    /* push the initialization expressions */
    n = push_init_expressions(car(form));

    /* establish a new environment frame */
    oldcbase = add_level();

    /* compile the binding list */
    parse_let_variables(car(form),cdr(form));

    /* compile the body of the let/letrec */
    do_begin(cdr(form),C_RETURN);

    /* build the code object */
    cpush(make_code_object(NIL));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_CLOSE);

    /* store the procedure */
    if (name && findvariable(name,&lev,&off))
	cd_evariable(OP_ESET,lev,off);

    /* apply the function */
    putcbyte(OP_CALL);
    putcbyte(n);

    /* target for the continuation */
    if (cont != C_RETURN)
	fixup(nxt);
}

/* do_letrec - compile the (LETREC ... ) expression */
LOCAL do_letrec(form,cont)
  LVAL form; int cont;
{
    int oldcbase,nxt,n;

    /* make sure there is a binding list */
    if (atom(form) || !listp(car(form)))
	xlerror("expecting binding list",form);

    /* save a continuation */
    if (cont != C_RETURN) {
	putcbyte(OP_SAVE);
	nxt = putcword(0);
    }
    
    /* push the initialization expressions */
    n = push_dummy_values(car(form));

    /* establish a new environment frame */
    oldcbase = add_level();

    /* compile the binding list */
    parse_let_variables(car(form),cdr(form));

    /* compile instructions to set the bound variables */
    set_bound_variables(car(form));
    
    /* compile the body of the let/letrec */
    do_begin(cdr(form),C_RETURN);

    /* build the code object */
    cpush(make_code_object(NIL));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_CLOSE);

    /* apply the function */
    putcbyte(OP_CALL);
    putcbyte(n);

    /* target for the continuation */
    if (cont != C_RETURN)
	fixup(nxt);
}

/* do_letstar - compile the (LET* ... ) expression */
LOCAL do_letstar(form,cont)
  LVAL form; int cont;
{
    int nxt;
    
    /* make sure there is a binding list */
    if (atom(form) || !listp(car(form)))
	xlerror("expecting binding list",form);

    /* handle the case where there are bindings */
    if (consp(car(form))) {
    
	/* save a continuation */
	if (cont != C_RETURN) {
	    putcbyte(OP_SAVE);
	    nxt = putcword(0);
	}
    
	/* build the nested lambda expressions */
	letstar1(car(form),cdr(form));
    
	/* target for the continuation */
	if (cont != C_RETURN)
	    fixup(nxt);
    }
    
    /* handle the case where there are no bindings */
    else
	do_begin(cdr(form),cont);
}

/* letstar1 - helper routine for let* */
LOCAL letstar1(blist,body)
  LVAL blist,body;
{
    int oldcbase,n;

    /* push the next initialization expressions */
    cpush(cons(car(blist),NIL));
    n = push_init_expressions(top());

    /* establish a new environment frame */
    oldcbase = add_level();

    /* handle the case where there are more bindings */
    if (consp(cdr(blist))) {
	parse_let_variables(top(),NIL);
	letstar1(cdr(blist),body);
    }
    
    /* handle the last binding */
    else {
	parse_let_variables(top(),body);
	do_begin(body,C_RETURN);
    }
	
    /* build the code object */
    settop(make_code_object(NIL));
    
    /* restore the previous environment */
    remove_level(oldcbase);

    /* compile code to create a closure */
    do_literal(pop(),C_NEXT);
    putcbyte(OP_CLOSE);

    /* apply the function */
    putcbyte(OP_CALL);
    putcbyte(n);
}

/* push_dummy_values - push dummy values for a 'letrec' expression */
LOCAL int push_dummy_values(blist)
  LVAL blist;
{
    int n=0;
    if (consp(blist)) {
	putcbyte(OP_NIL);
	for (; consp(blist); blist = cdr(blist), ++n)
	    putcbyte(OP_PUSH);
    }
    return (n);
}

/* push_init_expressions - push init expressions for a 'let' expression */
LOCAL int push_init_expressions(blist)
  LVAL blist;
{
    int n;
    if (consp(blist)) {
	n = push_init_expressions(cdr(blist));
	if (consp(car(blist)) && consp(cdr(car(blist))))
	    do_expr(car(cdr(car(blist))),C_NEXT);
	else
	    putcbyte(OP_NIL);
	putcbyte(OP_PUSH);
	return (n+1);
    }
    return (0);
}

/* parse_let_variables - parse the binding list */
LOCAL parse_let_variables(blist,body)
  LVAL blist,body;
{
    LVAL arg,new,last;
    int frame,slotn;
    
    /* setup the entry code */
    putcbyte(OP_FRAME);
    frame = putcbyte(0);

    /* initialize the argument name list and slot number */
    last = NIL;
    slotn = 1;
    
    /* handle each required argument */
    while (consp(blist) && (arg = car(blist))) {

	/* make sure the argument is a symbol */
	if (symbolp(arg))
	    new = cons(arg,NIL);
	else if (consp(arg) && symbolp(car(arg)))
	    new = cons(car(arg),NIL);
	else
	    xlerror("invalid binding",arg);

	/* add the argument name to the name list */
	if (last) rplacd(last,new);
	else setelement(car(car(info)),0,new);
	last = new;

	/* generate an instruction to move the argument into the frame */
	putcbyte(OP_MVARG);
	putcbyte(slotn++);
	
	/* move the formal argument list pointer ahead */
	blist = cdr(blist);
    }
    putcbyte(OP_ALAST);

    /* scan the body for internal definitions */
    slotn += find_internal_definitions(body,last);
	
    /* fixup the frame instruction */
    cbuff[cbase+frame] = slotn;
}

/* set_bound_variables - set bound variables in a 'letrec' expression */
LOCAL set_bound_variables(blist)
  LVAL blist;
{
    int lev,off;
    for (; consp(blist); blist = cdr(blist)) {
	if (consp(car(blist)) && consp(cdr(car(blist)))) {
	    do_expr(car(cdr(car(blist))),C_NEXT);
	    if (findvariable(car(car(blist)),&lev,&off))
		cd_evariable(OP_ESET,lev,off);
	    else
		xlerror("compiler error -- can't find",car(car(blist)));
	}
    }
}

/* make_code_object - build a code object */
LOCAL LVAL make_code_object(fun)
  LVAL fun;
{
    unsigned char *cp;
    LVAL code,p;
    int i;

    /* create a code object */
    code = newcode(FIRSTLIT + length(car(cdr(info)))); cpush(code);
    setbcode(code,newstring(cptr - cbase));
    setcname(code,fun);			       	 /* function name */
    setvnames(code,getelement(car(car(info)),0));/* lambda list variables */

    /* copy the literals into the code object */
    for (i = FIRSTLIT, p = car(cdr(info)); consp(p); p = cdr(p), ++i)
	setelement(code,i,car(p));

    /* copy the byte codes */
    for (i = cbase, cp = getstring(getbcode(code)); i < cptr; )
	*cp++ = cbuff[i++];

    /* return the new code object */
    return (pop());
}

/* do_cond - compile the (COND ... ) expression */
LOCAL do_cond(form,cont)
  LVAL form; int cont;
{
    int nxt,end;
    if (consp(form)) {
	for (end = 0; consp(form); form = cdr(form)) {
	    if (atom(car(form)))
		xlerror("expecting a cond clause",form);
	    do_expr(car(car(form)),C_NEXT);
	    putcbyte(OP_BRF);
	    nxt = putcword(0);
	    if (cdr(car(form)))
		do_begin(cdr(car(form)),cont);
	    else
		do_continuation(cont);
	    if (cont == C_NEXT) {
		putcbyte(OP_BR);
		end = putcword(end);
	    }
	    fixup(nxt);
	}
	fixup(end);
    }
    else
	putcbyte(OP_NIL);
    do_continuation(cont);
}

/* do_and - compile the (AND ... ) expression */
LOCAL do_and(form,cont)
  LVAL form; int cont;
{
    int end;
    if (consp(form)) {
	for (end = 0; consp(form); form = cdr(form)) {
	    if (cdr(form)) {
		do_expr(car(form),C_NEXT);
		putcbyte(OP_BRF);
		end = putcword(end);
	    }
	    else
		do_expr(car(form),cont);
	}
	fixup(end);
    }
    else
	putcbyte(OP_NIL);
    do_continuation(cont);
}

/* do_or - compile the (OR ... ) expression */
LOCAL do_or(form,cont)
  LVAL form; int cont;
{
    int end;
    if (consp(form)) {
	for (end = 0; consp(form); form = cdr(form)) {
	    if (cdr(form)) {
		do_expr(car(form),C_NEXT);
		putcbyte(OP_BRT);
		end = putcword(end);
	    }
	    else
		do_expr(car(form),cont);
	}
	fixup(end);
    }
    else
	putcbyte(OP_T);
    do_continuation(cont);
}

/* do_if - compile the (IF ... ) expression */
LOCAL do_if(form,cont)
  LVAL form; int cont;
{
    int nxt,end;

    /* compile the test expression */
    if (atom(form))
	xlerror("expecting test expression",form);
    do_expr(car(form),C_NEXT);

    /* skip around the 'then' clause if the expression is false */
    putcbyte(OP_BRF);
    nxt = putcword(0);

    /* skip to the 'then' clause */
    form = cdr(form);
    if (atom(form))
	xlerror("expecting then clause",form);

    /* compile the 'then' and 'else' clauses */
    if (consp(cdr(form))) {
	if (cont == C_NEXT) {
	    do_expr(car(form),C_NEXT);
	    putcbyte(OP_BR);
	    end = putcword(0);
	}
	else {
	    do_expr(car(form),cont);
	    end = -1;
	}
	fixup(nxt);
	do_expr(car(cdr(form)),cont);
	nxt = end;
    }

    /* compile just a 'then' clause */
    else
	do_expr(car(form),cont);

    /* handle the end of the statement */
    if (nxt >= 0) {
	fixup(nxt);
	do_continuation(cont);
    }
}

/* do_begin - compile the (BEGIN ... ) expression */
LOCAL do_begin(form,cont)
  LVAL form; int cont;
{
    if (consp(form))
	for (; consp(form); form = cdr(form))
	    if (consp(cdr(form)))
		do_expr(car(form),C_NEXT);
	    else
		do_expr(car(form),cont);
    else {
	putcbyte(OP_NIL);
	do_continuation(cont);
    }
}

/* do_while - compile the (WHILE ... ) expression */
LOCAL do_while(form,cont)
  LVAL form; int cont;
{
    int loop,nxt;

    /* make sure there is a test expression */
    if (atom(form))
	xlerror("expecting test expression",form);

    /* skip around the 'body' to the test expression */
    putcbyte(OP_BR);
    nxt = putcword(0);

    /* compile the loop body */
    loop = cptr - cbase;
    do_begin(cdr(form),C_NEXT);

    /* label for the first iteration */
    fixup(nxt);

    /* compile the test expression */
    nxt = cptr - cbase;
    do_expr(car(form),C_NEXT);

    /* skip around the 'body' if the expression is false */
    putcbyte(OP_BRT);
    putcword(loop);

    /* compile the continuation */
    do_continuation(cont);
}

/* do_access - compile the (ACCESS var env) expression */
LOCAL do_access(form,cont)
  LVAL form; int cont;
{
    LVAL sym;

    /* get the variable name */
    if (atom(form) || !symbolp(car(form)))
	xlerror("expecting symbol",form);
    sym = car(form);

    /* compile the environment expression */
    form = cdr(form);
    if (atom(form))
	xlerror("expecting environment expression",form);
    do_expr(car(form),C_NEXT);

    /* get the variable value */
    cd_variable(OP_AREF,sym);
    do_continuation(cont);
}

/* do_setaccess - compile the (SET! (ACCESS var env) value) expression */
LOCAL do_setaccess(form,cont)
  LVAL form; int cont;
{
    LVAL aform,sym;

    /* make sure this is an access form */
    aform = car(form);
    if (atom(aform) || car(aform) != xlenter("ACCESS"))
	xlerror("expecting an ACCESS form",aform);

    /* get the variable name */
    aform = cdr(aform);
    if (atom(aform) || !symbolp(car(aform)))
	xlerror("expecting symbol",aform);
    sym = car(aform);

    /* compile the environment expression */
    aform = cdr(aform);
    if (atom(aform))
	xlerror("expecting environment expression",aform);
    do_expr(car(aform),C_NEXT);
    putcbyte(OP_PUSH);

    /* compile the value expression */
    form = cdr(form);
    if (atom(form))
	xlerror("expecting value expression",form);
    do_expr(car(form),C_NEXT);

    /* set the variable value */
    cd_variable(OP_ASET,sym);
    do_continuation(cont);
}

/* do_call - compile a function call */
LOCAL do_call(form,cont)
  LVAL form; int cont;
{
    int nxt,n;
    
    /* save a continuation */
    if (cont != C_RETURN) {
	putcbyte(OP_SAVE);
	nxt = putcword(0);
    }
    
    /* compile each argument expression */
    n = push_args(cdr(form));

    /* compile the function itself */
    do_expr(car(form),C_NEXT);

    /* apply the function */
    putcbyte(OP_CALL);
    putcbyte(n);

    /* target for the continuation */
    if (cont != C_RETURN)
	fixup(nxt);
}

/* push_args - compile the arguments for a function call */
LOCAL int push_args(form)
  LVAL form;
{
    int n;
    if (consp(form)) {
	n = push_args(cdr(form));
	do_expr(car(form),C_NEXT);
	putcbyte(OP_PUSH);
	return (n+1);
    }
    return (0);
}

/* do_nary - compile nary operator expressions */
LOCAL do_nary(op,n,form,cont)
  int op,n; LVAL form; int cont;
{
    if (n < 0 && (n = (-n)) != length(cdr(form)))
	do_call(form,cont);
    else {
	push_nargs(cdr(form),n);
	putcbyte(op);
	do_continuation(cont);
    }
}

/* push_nargs - compile the arguments for an inline function call */
LOCAL int push_nargs(form,n)
  LVAL form; int n;
{
    if (consp(form)) {
	if (n == 0)
	    xlerror("too many arguments",form);
	if (push_nargs(cdr(form),n-1))
	    putcbyte(OP_PUSH);
	do_expr(car(form),C_NEXT);
	return (TRUE);
    }
    if (n)
	xlerror("too few arguments",form);
    return (FALSE);
}

/* do_literal - compile a literal */
LOCAL do_literal(lit,cont)
  LVAL lit; int cont;
{
    cd_literal(lit);
    do_continuation(cont);
}

/* do_identifier - compile an identifier */
LOCAL do_identifier(sym,cont)
  LVAL sym; int cont;
{
    int lev,off;
    if (sym == true)
	putcbyte(OP_T);
    else if (findvariable(sym,&lev,&off))
	cd_evariable(OP_EREF,lev,off);
    else
	cd_variable(OP_GREF,sym);
    do_continuation(cont);
}

/* do_continuation - compile a continuation */
LOCAL do_continuation(cont)
  int cont;
{
    switch (cont) {
    case C_RETURN:
	putcbyte(OP_RETURN);
	break;
    case C_NEXT:
	break;
    }
}

/* add_level - add a nesting level */
LOCAL int add_level()
{
    int oldcbase;
    
    /* establish a new environment frame */
    rplaca(info,newframe(car(info),1));
    rplacd(info,cons(NIL,cdr(info)));

    /* setup the base of the code for this function */
    oldcbase = cbase;
    cbase = cptr;

    /* return the old code base */
    return (oldcbase);
}

/* remove_level - remove a nesting level */
LOCAL remove_level(oldcbase)
  int oldcbase;
{
    /* restore the previous environment */
    rplaca(info,cdr(car(info)));
    rplacd(info,cdr(cdr(info)));

    /* restore the base and code pointer */
    cptr = cbase;
    cbase = oldcbase;
}

/* findvariable - find an environment variable */
LOCAL int findvariable(sym,plev,poff)
  LVAL sym; int *plev,*poff;
{
    int lev,off;
    LVAL e,a;
    for (e = car(info), lev = 0; envp(e); e = cdr(e), ++lev)
	for (a = getelement(car(e),0), off = 1; consp(a); a = cdr(a), ++off)
	    if (sym == car(a)) {
		*plev = lev;
		*poff = off;
		return (TRUE);
	    }
    return (FALSE);
}

/* findcvariable - find an environment variable in the current frame */
LOCAL int findcvariable(sym,poff)
  LVAL sym; int *poff;
{
    int off;
    LVAL a;
    a = getelement(car(car(info)),0);
    for (off = 1; consp(a); a = cdr(a), ++off)
	if (sym == car(a)) {
	    *poff = off;
	    return (TRUE);
	}
    return (FALSE);
}

/* findliteral - find a literal in the literal frame */
LOCAL int findliteral(lit)
  LVAL lit;
{
    int o = FIRSTLIT;
    LVAL t,p;
    if (t = car(cdr(info))) {
	for (p = NIL; consp(t); p = t, t = cdr(t), ++o)
	    if (equal(lit,car(t)))
		return (o);
	rplacd(p,cons(lit,NIL));
    }
    else
	rplaca(cdr(info),cons(lit,NIL));
    return (o);
}

/* cd_variable - compile a variable reference */
LOCAL cd_variable(op,sym)
  int op; LVAL sym;
{
    putcbyte(op);
    putcbyte(findliteral(sym));
}

/* cd_evariable - compile an environment variable reference */
LOCAL cd_evariable(op,lev,off)
  int op,lev,off;      
{
    putcbyte(op);
    putcbyte(lev);
    putcbyte(off);
}

/* cd_literal - compile a literal reference */
LOCAL cd_literal(lit)
  LVAL lit;
{
    if (lit == NIL)
	putcbyte(OP_NIL);
    else if (lit == true)
	putcbyte(OP_T);
    else {
	putcbyte(OP_LIT);
	putcbyte(findliteral(lit));
    }
}

/* putcbyte - put a code byte into data space */
LOCAL int putcbyte(b)
  int b;
{
    int adr;
    if (cptr >= CMAX)
	xlabort("insufficient code space");
    adr = (cptr - cbase);
    cbuff[cptr++] = b;
    return (adr);
}

/* putcword - put a code word into data space */
LOCAL int putcword(w)
  int w;
{
    int adr;
    adr = putcbyte(w >> 8);
    putcbyte(w);
    return (adr);
}

/* fixup - fixup a reference chain */
LOCAL fixup(chn)
  int chn;
{
    int val,hval,nxt;

    /* store the value into each location in the chain */
    val = cptr - cbase; hval = val >> 8;
    for (; chn; chn = nxt) {
	nxt = (cbuff[cbase+chn] << 8) | (cbuff[cbase+chn+1]);
	cbuff[cbase+chn] = hval;
	cbuff[cbase+chn+1] = val;
    }
}

/* length - find the length of a list */
int length(list)
  LVAL list;
{
    int len;
    for (len = 0; consp(list); list = cdr(list))
	++len;
    return (len);
}

/* instruction output formats */
#define FMT_NONE	0
#define FMT_BYTE	1
#define FMT_LOFF	2
#define FMT_WORD	3
#define FMT_EOFF	4

typedef struct { int ot_code; char *ot_name; int ot_fmt; } OTDEF;
OTDEF otab[] = {
{	OP_BRT,		"BRT",		FMT_WORD	},
{	OP_BRF,		"BRF",		FMT_WORD	},
{	OP_BR,		"BR",		FMT_WORD	},
{	OP_LIT,		"LIT",		FMT_LOFF	},
{	OP_GREF,	"GREF",		FMT_LOFF	},
{	OP_GSET,	"GSET",		FMT_LOFF	},
{	OP_EREF,	"EREF",		FMT_EOFF	},
{	OP_ESET,	"ESET",		FMT_EOFF	},
{	OP_SAVE,	"SAVE",		FMT_WORD	},
{	OP_CALL,	"CALL",		FMT_BYTE	},
{	OP_RETURN,	"RETURN",	FMT_NONE	},
{	OP_T,		"T",		FMT_NONE	},
{	OP_NIL,		"NIL",		FMT_NONE	},
{	OP_PUSH,	"PUSH",		FMT_NONE	},
{	OP_CLOSE,	"CLOSE",	FMT_NONE	},
{	OP_DELAY,	"DELAY",	FMT_NONE	},

{	OP_FRAME,	"FRAME",	FMT_BYTE	},
{	OP_MVARG,	"MVARG",	FMT_BYTE	},
{	OP_MVOARG,	"MVOARG",	FMT_BYTE	},
{	OP_MVRARG,	"MVRARG",	FMT_BYTE	},
{	OP_ADROP,	"ADROP",	FMT_NONE	},
{	OP_ALAST,	"ALAST",	FMT_NONE	},

{	OP_AREF,	"AREF",		FMT_LOFF	},
{	OP_ASET,	"ASET",		FMT_LOFF	},

{0,0,0}
};

/* decode_procedure - decode the instructions in a code object */
decode_procedure(fptr,fun)
  LVAL fptr,fun;
{
    int len,lc,n;
    LVAL code,env;
    code = getcode(fun);
    env = getenv(fun);
    len = getslength(getbcode(code));
    for (lc = 0; lc < len; lc += n)
	n = decode_instruction(fptr,code,lc,env);
}

/* decode_instruction - decode a single bytecode instruction */
int decode_instruction(fptr,code,lc,env)
  LVAL fptr,code; int lc; LVAL env;
{
    unsigned char *cp;
    char buf[100];
    OTDEF *op;
    NTDEF *np;
    int i,n=1;
    LVAL tmp;

    /* get a pointer to the bytecodes for this instruction */
    cp = getstring(getbcode(code)) + lc;

    /* show the address and opcode */
    if (tmp = getcname(code))
	sprintf(buf,"%s:%04x %02x ",getstring(getpname(tmp)),lc,*cp);
    else {
	sprintf(buf,AFMT,code); xlputstr(fptr,buf);
    	sprintf(buf,":%04x %02x ",lc,*cp);
    }
    xlputstr(fptr,buf);

    /* display the operands */
    for (op = otab; op->ot_name; ++op)
	if (*cp == op->ot_code) {
	    switch (op->ot_fmt) {
	    case FMT_NONE:
		sprintf(buf,"      %s\n",op->ot_name);
		xlputstr(fptr,buf);
		break;
	    case FMT_BYTE:
		sprintf(buf,"%02x    %s %02x\n",cp[1],op->ot_name,cp[1]);
		xlputstr(fptr,buf);
		n += 1;
		break;
	    case FMT_LOFF:
		sprintf(buf,"%02x    %s %02x ; ",cp[1],op->ot_name,cp[1]);
		xlputstr(fptr,buf);
		xlprin1(getelement(code,cp[1]),fptr);
		xlterpri(fptr);
		n += 1;
		break;
	    case FMT_WORD:
		sprintf(buf,"%02x %02x %s %02x%02x\n",cp[1],cp[2],
			op->ot_name,cp[1],cp[2]);
		xlputstr(fptr,buf);
		n += 2;
		break;
	    case FMT_EOFF:
		if ((i = cp[1]) == 0)
		    tmp = getvnames(code);
		else {
		    for (tmp = env; i > 1; --i) tmp = cdr(tmp);
		    tmp = getelement(car(tmp),0);
		}
		for (i = cp[2]; i > 1; --i) tmp = cdr(tmp);
		sprintf(buf,"%02x %02x %s %02x %02x ; ",cp[1],cp[2],
			op->ot_name,cp[1],cp[2]);
		xlputstr(fptr,buf);
		xlprin1(car(tmp),fptr);
		xlterpri(fptr);
		n += 2;
		break;
	    }
	    return (n);
	}
    
    /* check for an integrable function */
    for (np = ntab; np->nt_name; ++np)
	if (*cp == np->nt_code) {
	    sprintf(buf,"      %s\n",np->nt_name);
	    xlputstr(fptr,buf);
	    return (n);
	}

    /* unknown opcode */
    sprintf(buf,"      <UNKNOWN>\n");
    xlputstr(fptr,buf);
    return (n);
}
