/* xllist - xlisp built-in list functions */

#include "xlisp.h"

/* external variables */
extern NODE *xlstack;
extern NODE *s_unbound;
extern NODE *true;

/* external routines */
extern int eq(),eql(),equal();

/* forward declarations */
FORWARD NODE *cxr();
FORWARD NODE *nth(),*assoc();
FORWARD NODE *subst(),*sublis(),*map();
FORWARD NODE *cequal();

/* xcar - return the car of a list */
NODE *xcar(args)
  NODE *args;
{
    return (cxr(args,"a"));
}

/* xcdr - return the cdr of a list */
NODE *xcdr(args)
  NODE *args;
{
    return (cxr(args,"d"));
}

/* xcaar - return the caar of a list */
NODE *xcaar(args)
  NODE *args;
{
    return (cxr(args,"aa"));
}

/* xcadr - return the cadr of a list */
NODE *xcadr(args)
  NODE *args;
{
    return (cxr(args,"da"));
}

/* xcdar - return the cdar of a list */
NODE *xcdar(args)
  NODE *args;
{
    return (cxr(args,"ad"));
}

/* xcddr - return the cddr of a list */
NODE *xcddr(args)
  NODE *args;
{
    return (cxr(args,"dd"));
}

/* cxr - common car/cdr routine */
LOCAL NODE *cxr(args,adstr)
  NODE *args; char *adstr;
{
    NODE *list;

    /* get the list */
    list = xlmatch(LIST,&args);
    xllastarg(args);

    /* perform the car/cdr operations */
    while (*adstr && consp(list))
	list = (*adstr++ == 'a' ? car(list) : cdr(list));

    /* make sure the operation succeeded */
    if (*adstr && list)
	xlfail("bad argument");

    /* return the result */
    return (list);
}

/* xcons - construct a new list cell */
NODE *xcons(args)
  NODE *args;
{
    NODE *arg1,*arg2,*val;

    /* get the two arguments */
    arg1 = xlarg(&args);
    arg2 = xlarg(&args);
    xllastarg(args);

    /* construct a new list element */
    val = newnode(LIST);
    rplaca(val,arg1);
    rplacd(val,arg2);

    /* return the list */
    return (val);
}

/* xlist - built a list of the arguments */
NODE *xlist(args)
  NODE *args;
{
    NODE *oldstk,arg,list,val,*last,*lptr;

    /* create a new stack frame */
    oldstk = xlsave(&arg,&list,&val,NULL);

    /* initialize */
    arg.n_ptr = args;

    /* evaluate and append each argument */
    for (last = NULL; arg.n_ptr != NULL; last = lptr) {

	/* evaluate the next argument */
	val.n_ptr = xlarg(&arg.n_ptr);

	/* append this argument to the end of the list */
	lptr = newnode(LIST);
	if (last == NULL)
	    list.n_ptr = lptr;
	else
	    rplacd(last,lptr);
	rplaca(lptr,val.n_ptr);
    }

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the list */
    return (list.n_ptr);
}

/* xappend - built-in function append */
NODE *xappend(args)
  NODE *args;
{
    NODE *oldstk,arg,list,last,val,*lptr;

    /* create a new stack frame */
    oldstk = xlsave(&arg,&list,&last,&val,NULL);

    /* initialize */
    arg.n_ptr = args;

    /* evaluate and append each argument */
    while (arg.n_ptr) {

	/* evaluate the next argument */
	list.n_ptr = xlmatch(LIST,&arg.n_ptr);

	/* append each element of this list to the result list */
	while (consp(list.n_ptr)) {

	    /* append this element */
	    lptr = newnode(LIST);
	    if (last.n_ptr == NULL)
		val.n_ptr = lptr;
	    else
		rplacd(last.n_ptr,lptr);
	    rplaca(lptr,car(list.n_ptr));

	    /* save the new last element */
	    last.n_ptr = lptr;

	    /* move to the next element */
	    list.n_ptr = cdr(list.n_ptr);
	}

	/* make sure the list ended in a nil */
	if (list.n_ptr != NULL)
	    xlfail("bad list");
    }

    /* restore previous stack frame */
    xlstack = oldstk;

    /* return the list */
    return (val.n_ptr);
}

/* xreverse - built-in function reverse */
NODE *xreverse(args)
  NODE *args;
{
    NODE *oldstk,list,val,*lptr;

    /* create a new stack frame */
    oldstk = xlsave(&list,&val,NULL);

    /* get the list to reverse */
    list.n_ptr = xlmatch(LIST,&args);
    xllastarg(args);

    /* append each element of this list to the result list */
    while (consp(list.n_ptr)) {

	/* append this element */
	lptr = newnode(LIST);
	rplaca(lptr,car(list.n_ptr));
	rplacd(lptr,val.n_ptr);
	val.n_ptr = lptr;

	/* move to the next element */
	list.n_ptr = cdr(list.n_ptr);
    }

    /* make sure the list ended in a nil */
    if (list.n_ptr != NULL)
	xlfail("bad list");

    /* restore previous stack frame */
    xlstack = oldstk;

    /* return the list */
    return (val.n_ptr);
}

/* xlast - return the last cons of a list */
NODE *xlast(args)
  NODE *args;
{
    NODE *list;

    /* get the list */
    list = xlmatch(LIST,&args);
    xllastarg(args);

    /* find the last cons */
    while (consp(list) && cdr(list))
	list = cdr(list);

    /* return the last element */
    return (list);
}

/* xmember - built-in function 'member' */
NODE *xmember(args)
  NODE *args;
{
    NODE *oldstk,x,list,fcn,*val;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&x,&list,&fcn,NULL);

    /* get the expression to look for and the list */
    x.n_ptr = xlarg(&args);
    list.n_ptr = xlmatch(LIST,&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* look for the expression */
    for (val = NULL; consp(list.n_ptr); list.n_ptr = cdr(list.n_ptr))
	if (dotest(x.n_ptr,car(list.n_ptr),fcn.n_ptr) == tresult) {
	    val = list.n_ptr;
	    break;
	}

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the result */
    return (val);
}

/* xassoc - built-in function 'assoc' */
NODE *xassoc(args)
  NODE *args;
{
    NODE *oldstk,x,alist,fcn,*pair,*val;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&x,&alist,&fcn,NULL);

    /* get the expression to look for and the association list */
    x.n_ptr = xlarg(&args);
    alist.n_ptr = xlmatch(LIST,&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* look for the expression */
    for (val = NULL; consp(alist.n_ptr); alist.n_ptr = cdr(alist.n_ptr))
	if ((pair = car(alist.n_ptr)) && consp(pair))
	    if (dotest(x.n_ptr,car(pair),fcn.n_ptr) == tresult) {
		val = pair;
		break;
	    }

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the result */
    return (val);
}

/* xsubst - substitute one expression for another */
NODE *xsubst(args)
  NODE *args;
{
    NODE *oldstk,to,from,expr,fcn,*val;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&to,&from,&expr,&fcn,NULL);

    /* get the to value, the from value and the expression */
    to.n_ptr = xlarg(&args);
    from.n_ptr = xlarg(&args);
    expr.n_ptr = xlarg(&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* do the substitution */
    val = subst(to.n_ptr,from.n_ptr,expr.n_ptr,fcn.n_ptr,tresult);

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the result */
    return (val);
}

/* subst - substitute one expression for another */
LOCAL NODE *subst(to,from,expr,fcn,tresult)
  NODE *to,*from,*expr,*fcn; int tresult;
{
    NODE *oldstk,carval,cdrval,*val;

    if (dotest(expr,from,fcn) == tresult)
	val = to;
    else if (consp(expr)) {
	oldstk = xlsave(&carval,&cdrval,NULL);
	carval.n_ptr = subst(to,from,car(expr),fcn,tresult);
	cdrval.n_ptr = subst(to,from,cdr(expr),fcn,tresult);
	val = newnode(LIST);
	rplaca(val,carval.n_ptr);
	rplacd(val,cdrval.n_ptr);
	xlstack = oldstk;
    }
    else
	val = expr;
    return (val);
}

/* xsublis - substitute using an association list */
NODE *xsublis(args)
  NODE *args;
{
    NODE *oldstk,alist,expr,fcn,*val;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&alist,&expr,&fcn,NULL);

    /* get the assocation list and the expression */
    alist.n_ptr = xlmatch(LIST,&args);
    expr.n_ptr = xlarg(&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* do the substitution */
    val = sublis(alist.n_ptr,expr.n_ptr,fcn.n_ptr,tresult);

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the result */
    return (val);
}

/* sublis - substitute using an association list */
LOCAL NODE *sublis(alist,expr,fcn,tresult)
  NODE *alist,*expr,*fcn; int tresult;
{
    NODE *oldstk,carval,cdrval,*val;

    if (val = assoc(expr,alist,fcn,tresult))
	val = cdr(val);
    else if (consp(expr)) {
	oldstk = xlsave(&carval,&cdrval,NULL);
	carval.n_ptr = sublis(alist,car(expr),fcn,tresult);
	cdrval.n_ptr = sublis(alist,cdr(expr),fcn,tresult);
	val = newnode(LIST);
	rplaca(val,carval.n_ptr);
	rplacd(val,cdrval.n_ptr);
	xlstack = oldstk;
    }
    else
	val = expr;
    return (val);
}

/* assoc - find a pair in an association list */
LOCAL NODE *assoc(expr,alist,fcn,tresult)
  NODE *expr,*alist,*fcn; int tresult;
{
    NODE *pair;

    for (; consp(alist); alist = cdr(alist))
	if ((pair = car(alist)) && consp(pair))
	    if (dotest(expr,car(pair),fcn) == tresult)
		return (pair);
    return (NULL);
}

/* xremove - built-in function 'remove' */
NODE *xremove(args)
  NODE *args;
{
    NODE *oldstk,x,list,fcn,val,*p,*last;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&x,&list,&fcn,&val,NULL);

    /* get the expression to remove and the list */
    x.n_ptr = xlarg(&args);
    list.n_ptr = xlmatch(LIST,&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* remove matches */
    while (consp(list.n_ptr)) {

	/* check to see if this element should be deleted */
	if (dotest(x.n_ptr,car(list.n_ptr),fcn.n_ptr) != tresult) {
	    p = newnode(LIST);
	    rplaca(p,car(list.n_ptr));
	    if (val.n_ptr) rplacd(last,p);
	    else val.n_ptr = p;
	    last = p;
	}

	/* move to the next element */
	list.n_ptr = cdr(list.n_ptr);
    }

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the updated list */
    return (val.n_ptr);
}

/* dotest - call a test function */
int dotest(arg1,arg2,fcn)
  NODE *arg1,*arg2,*fcn;
{
    NODE *oldstk,args,*val;

    /* create a new stack frame */
    oldstk = xlsave(&args,NULL);

    /* build an argument list */
    args.n_ptr = newnode(LIST);
    rplaca(args.n_ptr,arg1);
    rplacd(args.n_ptr,newnode(LIST));
    rplaca(cdr(args.n_ptr),arg2);

    /* apply the test function */
    val = xlapply(fcn,args.n_ptr);

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the result of the test */
    return (val != NULL);
}

/* xnth - return the nth element of a list */
NODE *xnth(args)
  NODE *args;
{
    return (nth(args,FALSE));
}

/* xnthcdr - return the nth cdr of a list */
NODE *xnthcdr(args)
  NODE *args;
{
    return (nth(args,TRUE));
}

/* nth - internal nth function */
LOCAL NODE *nth(args,cdrflag)
  NODE *args; int cdrflag;
{
    NODE *list;
    int n;

    /* get n and the list */
    if ((n = xlmatch(INT,&args)->n_int) < 0)
	xlfail("bad argument");
    if ((list = xlmatch(LIST,&args)) == NULL)
	xlfail("bad argument");
    xllastarg(args);

    /* find the nth element */
    for (; n > 0; n--) {
	list = cdr(list);
	if (!consp(list))
	    xlfail("bad argument");
    }

    /* return the list beginning at the nth element */
    return (cdrflag ? list : car(list));
}

/* xlength - return the length of a list */
NODE *xlength(args)
  NODE *args;
{
    NODE *list,*val;
    int n;

    /* get the list */
    list = xlmatch(LIST,&args);
    xllastarg(args);

    /* find the length */
    for (n = 0; consp(list); n++)
	list = cdr(list);

    /* create the value node */
    val = newnode(INT);
    val->n_int = n;

    /* return the length */
    return (val);
}

/* xmapc - built-in function 'mapc' */
NODE *xmapc(args)
  NODE *args;
{
    return (map(args,TRUE,FALSE));
}

/* xmapcar - built-in function 'mapcar' */
NODE *xmapcar(args)
  NODE *args;
{
    return (map(args,TRUE,TRUE));
}

/* xmapl - built-in function 'mapl' */
NODE *xmapl(args)
  NODE *args;
{
    return (map(args,FALSE,FALSE));
}

/* xmaplist - built-in function 'maplist' */
NODE *xmaplist(args)
  NODE *args;
{
    return (map(args,FALSE,TRUE));
}

/* map - internal mapping function */
LOCAL NODE *map(args,carflag,valflag)
  NODE *args; int carflag,valflag;
{
    NODE *oldstk,fcn,lists,arglist,val,*last,*p,*x,*y;

    /* create a new stack frame */
    oldstk = xlsave(&fcn,&lists,&arglist,&val,NULL);

    /* get the function to apply and the first list */
    fcn.n_ptr = xlarg(&args);
    lists.n_ptr = xlmatch(LIST,&args);

    /* save the first list if not saving function values */
    if (!valflag)
	val.n_ptr = lists.n_ptr;

    /* set up the list of argument lists */
    p = newnode(LIST);
    rplaca(p,lists.n_ptr);
    lists.n_ptr = p;

    /* get the remaining argument lists */
    while (args) {
	p = newnode(LIST);
	rplacd(p,lists.n_ptr);
	lists.n_ptr = p;
	rplaca(p,xlmatch(LIST,&args));
    }

    /* if the function is a symbol, get its value */
    if (symbolp(fcn.n_ptr))
	fcn.n_ptr = xleval(fcn.n_ptr);

    /* loop through each of the argument lists */
    for (;;) {

	/* build an argument list from the sublists */
	arglist.n_ptr = NULL;
	for (x = lists.n_ptr; x && (y = car(x)) && consp(y); x = cdr(x)) {
	    p = newnode(LIST);
	    rplacd(p,arglist.n_ptr);
	    arglist.n_ptr = p;
	    rplaca(p,carflag ? car(y) : y);
	    rplaca(x,cdr(y));
	}

	/* quit if any of the lists were empty */
	if (x) break;

	/* apply the function to the arguments */
	if (valflag) {
	    p = newnode(LIST);
	    if (val.n_ptr) rplacd(last,p);
	    else val.n_ptr = p;
	    rplaca(p,xlapply(fcn.n_ptr,arglist.n_ptr));
	    last = p;
	}
	else
	    xlapply(fcn.n_ptr,arglist.n_ptr);
    }

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the last test expression value */
    return (val.n_ptr);
}

/* xrplca - replace the car of a list node */
NODE *xrplca(args)
  NODE *args;
{
    NODE *list,*newcar;

    /* get the list and the new car */
    if ((list = xlmatch(LIST,&args)) == NULL)
	xlfail("bad argument");
    newcar = xlarg(&args);
    xllastarg(args);

    /* replace the car */
    rplaca(list,newcar);

    /* return the list node that was modified */
    return (list);
}

/* xrplcd - replace the cdr of a list node */
NODE *xrplcd(args)
  NODE *args;
{
    NODE *list,*newcdr;

    /* get the list and the new cdr */
    if ((list = xlmatch(LIST,&args)) == NULL)
	xlfail("bad argument");
    newcdr = xlarg(&args);
    xllastarg(args);

    /* replace the cdr */
    rplacd(list,newcdr);

    /* return the list node that was modified */
    return (list);
}

/* xnconc - destructively append lists */
NODE *xnconc(args)
  NODE *args;
{
    NODE *list,*last,*val;

    /* concatenate each argument */
    for (val = NULL; args; ) {

	/* concatenate this list */
	if (list = xlmatch(LIST,&args)) {

	    /* check for this being the first non-empty list */
	    if (val)
		rplacd(last,list);
	    else
		val = list;

	    /* find the end of the list */
	    while (consp(list) && cdr(list))
		list = cdr(list);

	    /* make sure the list ended correctly */
	    if (!consp(list))
		xlfail("bad list");

	    /* save the new last element */
	    last = list;
	}
    }

    /* return the list */
    return (val);
}

/* xdelete - built-in function 'delete' */
NODE *xdelete(args)
  NODE *args;
{
    NODE *oldstk,x,list,fcn,*last,*val;
    int tresult;

    /* create a new stack frame */
    oldstk = xlsave(&x,&list,&fcn,NULL);

    /* get the expression to delete and the list */
    x.n_ptr = xlarg(&args);
    list.n_ptr = xlmatch(LIST,&args);
    xltest(&fcn.n_ptr,&tresult,&args);
    xllastarg(args);

    /* delete leading matches */
    while (consp(list.n_ptr)) {
	if (dotest(x.n_ptr,car(list.n_ptr),fcn.n_ptr) != tresult)
	    break;
	list.n_ptr = cdr(list.n_ptr);
    }
    val = last = list.n_ptr;

    /* delete embedded matches */
    if (consp(list.n_ptr)) {

	/* skip the first non-matching element */
	list.n_ptr = cdr(list.n_ptr);

	/* look for embedded matches */
	while (consp(list.n_ptr)) {

	    /* check to see if this element should be deleted */
	    if (dotest(x.n_ptr,car(list.n_ptr),fcn.n_ptr) == tresult)
		rplacd(last,cdr(list.n_ptr));
	    else
		last = list.n_ptr;

	    /* move to the next element */
	    list.n_ptr = cdr(list.n_ptr);
 	}
    }

    /* make sure the list ended in a nil */
    if (list.n_ptr != NULL)
	xlfail("bad list");

    /* restore the previous stack frame */
    xlstack = oldstk;

    /* return the updated list */
    return (val);
}

/* xatom - is this an atom? */
NODE *xatom(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (atom(arg) ? true : NULL);
}

/* xsymbolp - is this an symbol? */
NODE *xsymbolp(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (arg == NULL || symbolp(arg) ? true : NULL);
}

/* xnumberp - is this an number? */
NODE *xnumberp(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (fixp(arg) ? true : NULL);
}

/* xboundp - is this a value bound to this symbol? */
NODE *xboundp(args)
  NODE *args;
{
    NODE *sym;
    sym = xlmatch(SYM,&args);
    xllastarg(args);
    return (sym->n_symvalue == s_unbound ? NULL : true);
}

/* xnull - is this null? */
NODE *xnull(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (null(arg) ? true : NULL);
}

/* xlistp - is this a list? */
NODE *xlistp(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (listp(arg) ? true : NULL);
}

/* xconsp - is this a cons? */
NODE *xconsp(args)
  NODE *args;
{
    NODE *arg;
    arg = xlarg(&args);
    xllastarg(args);
    return (consp(arg) ? true : NULL);
}

/* xeq - are these equal? */
NODE *xeq(args)
  NODE *args;
{
    return (cequal(args,eq));
}

/* xeql - are these equal? */
NODE *xeql(args)
  NODE *args;
{
    return (cequal(args,eql));
}

/* xequal - are these equal? */
NODE *xequal(args)
  NODE *args;
{
    return (cequal(args,equal));
}

/* cequal - common eq/eql/equal function */
LOCAL NODE *cequal(args,fcn)
  NODE *args; int (*fcn)();
{
    NODE *arg1,*arg2;

    /* get the two arguments */
    arg1 = xlarg(&args);
    arg2 = xlarg(&args);
    xllastarg(args);

    /* compare the arguments */
    return ((*fcn)(arg1,arg2) ? true : NULL);
}
