File r38/lisp/csl/cslbase/eval1.c artifact 30040a574c part of check-in b5833487d7


/*  eval1.c                          Copyright (C) 1989-2007 Codemist Ltd */

/*
 * Interpreter (part 1).
 */

/*
 * This code may be used and modified, and redistributed in binary
 * or source form, subject to the "CCL Public License", which should
 * accompany it. This license is a variant on the BSD license, and thus
 * permits use of code derived from this in either open and commercial
 * projects: but it does require that updates to this code be made
 * available back to the originators of the package.
 * Before merging other code in with this or linking this code
 * with other packages or libraries please check that the license terms
 * of the other material are compatible with those of this.
 */


/* Signature: 6f8a0427 01-Jan-2008 */

#include "headers.h"



Lisp_Object nreverse(Lisp_Object a)
{
    Lisp_Object nil = C_nil;
    Lisp_Object b = nil;
    while (consp(a))
    {   Lisp_Object c = a;
        a = qcdr(a);
        qcdr(c) = b;
        b = c;
    }
    return b;
}

/*
 *  Environments are represented as association lists, and have to cope
 * with several sorts of things.  The items in an environment can be
 * in one of the following forms:
 *
 *  (a)       (symbol  .  value)      normal lexical variable binding
 *  (b)       (symbol  .  ~magic~)    given symbol is (locally) special
 *  (c)       (0  . tag)              (block tag ...) marker
 *  (d)       (1  . (tag ...))        (tagbody ... tag ...) marker
 *  (e)       (2  . <anything>)       case (c) or (d) but now invalidated
 *  (f)       (def . symbol)          (flet ...) or (macrolet ...) binding,
 *                                    where the def is non-atomic.
 *
 * Format for def in case (f)
 *
 *  (1)       (funarg env bvl ...)    flet and labels
 *  (2)       (bvl ...)               macrolet
 *                                    Note that 'funarg is not valid as a bvl
 *                                    and indeed in this case bvl is a list
 */

/*
 * In CSL mode flet, macrolet and local declarations are not supported.
 */

Lisp_Object Ceval(Lisp_Object u, Lisp_Object env)
{
    Lisp_Object nil = C_nil;
#ifdef COMMON
    int t;
#ifdef CHECK_STACK
    if (check_stack(__FILE__,__LINE__)) return aerror("deep stack in eval");
#endif
restart:
    t = (int)u & TAG_BITS;
/*
 * The first case considered is of symbols - lexical and special bindings
 * have to be sorted out.
 */
    if (t == TAG_SYMBOL)
    {
        Header h = qheader(u);
        if (h & SYM_SPECIAL_VAR)
        {   Lisp_Object v = qvalue(u);
            if (v == unset_var) return error(1, err_unset_var, u);
            else return onevalue(v);
        }
        else
        {
            while (env != nil)
            {   Lisp_Object p = qcar(env);
                if (qcar(p) == u)
                {   Lisp_Object v =qcdr(p);
/*
 * If a variable is lexically bound to the value work_symbol that means
 * that the symbol has been (lexically) declared to be special, so its
 * value cell should be inspected.
 */
                    if (v == work_symbol)
                    {   v = qvalue(u);
                        if (v == unset_var) return error(1, err_unset_var, u);
                    }
                    return onevalue(v);
                }
                env = qcdr(env);
            }
            {   Lisp_Object v = qvalue(u);
                if (v == unset_var) return error(1, err_unset_var, u);
                else return onevalue(v);
            }
        }
    }
/*
 * Things that are neither symbols nor lists evaluate to themselves,
 * e.g. numbers and vectors.
 */
    else if (t != TAG_CONS) return onevalue(u);
    else
#endif /* COMMON */
    {
/*
 * The final case is that of a list (fn ...), and one case that has to
 * be checked is if fn is lexically bound.
 */
        Lisp_Object fn, args;
#ifdef COMMON
/*
 * The test for nil here is because although nil is a symbol the tagging
 * structure tested here marks it as a list.
 */
        if (u == nil) return onevalue(nil);
#endif
        stackcheck2(0, u, env);
        fn = qcar(u);
        args = qcdr(u);
#ifdef COMMON
/*
 * Local function bindings must be looked for first.
 */
        {   Lisp_Object p;
            for (p=env; p!=nil; p=qcdr(p))
            {   Lisp_Object w = qcar(p);
/*
 * The form (<list> . sym) is used in an environment to indicate a local
 * binding of a function, either as a regular function or as a macro
 * (i.e. flet or macrolet).  The structure of the list distinguishes
 * between these two cases.
 */
                if (qcdr(w) == fn && is_cons(w = qcar(w)) && w!=nil)
                {
                    p = qcar(w);
                    if (p == funarg) /* ordinary function */
                    {   fn = w;      /* (funarg ...) is OK to apply */
                        goto ordinary_function;
                    }
/*
 * Here it is a local macro. Observe that the macroexpansion is done
 * with respect to an empty environment.  Macros that are defined at the same
 * time may seem to be mutually recursive but there is a sense in which they
 * are not (as well as a sense in which they are) - self and cross references
 * only happen AFTER an expansion and can not happen during one.
 */
                    push2(u, env);
                    w = cons(lambda, w);
                    nil = C_nil;
                    if (!exception_pending())
                        p = Lfuncalln(nil, 4, qvalue(macroexpand_hook),
                                      w, u, nil);
                    pop2(env, u);
                    nil = C_nil;
                    if (exception_pending())
                    {   flip_exception();
                        if ((exit_reason & UNWIND_ERROR) != 0)
                        {   err_printf("\nMacroexpanding: ");
                            loop_print_error(u);
                            nil = C_nil;
                            if (exception_pending()) flip_exception();
                        }
                        flip_exception();
                        return nil;
                    }
                    u = p;
                    goto restart;
                }
            }
        }
#endif
        if (is_symbol(fn))
        {
/*
 * Special forms and macros are checked for next.  Special forms
 * take precedence over macros.
 */
            Header h = qheader(fn);
            if (h & SYM_SPECIAL_FORM)
            {   Lisp_Object v;
#ifdef DEBUG
                if (qfn1(fn) == NULL)
                {   term_printf("Illegal special form\n");
                    my_exit(EXIT_FAILURE);
                }
#endif
                v = ((Special_Form *)qfn1(fn))(args, env);
                return v;
            }
            else if (h & SYM_MACRO)
            {
                push2(u, env);
/*
 * the environment passed to macroexpand should only be needed to cope
 * with macrolet, I think.  Since I use just one datastructure for the
 * whole environment I also pass along lexical bindings etc, but I hope that
 * they will never be accessed.  I do not think that macrolet is important
 * enough to call for complication and slow-down in the interpreter this
 * way - but then I am not exactly what you would call a Common Lisp Fan!
 */
                fn = macroexpand(u, env);
                pop2(env, u);
                nil = C_nil;
                if (exception_pending())
                {   flip_exception();
                    if ((exit_reason & UNWIND_ERROR) != 0)
                    {   err_printf("\nMacroexpanding: ");
                        loop_print_error(u);
                        nil = C_nil;
                        if (exception_pending()) flip_exception();
                    }
                    flip_exception();
                    return nil;
                }
                return eval(fn, env);
            }
        }
/*
 * Otherwise we have a regular function call.  I prepare the args and
 * call APPLY.
 */
#ifdef COMMON
ordinary_function:
#endif
        {   int nargs = 0;
            Lisp_Object *save_stack = stack;
/*
 * Args are built up on the stack here...
 */
            while (consp(args))
            {   Lisp_Object w;
                push3(fn, args, env);
                w = qcar(args);
                w = eval(w, env);
                pop3(env, args, fn);
/*
 * nil having its mark bit set indicates that a special sort of exit
 * is in progress.  Multiple values can be ignored in this case.
 */
                nil = C_nil;
                if (exception_pending())
                {   flip_exception();
                    stack = save_stack;
                    if ((exit_reason & UNWIND_ERROR) != 0)
                    {   err_printf("\nEvaluating: ");
                        loop_print_error(qcar(args));
                        nil = C_nil;
                        if (exception_pending()) flip_exception();
                    }
                    flip_exception();
                    return nil;
                }
                push(w);        /* args build up on the Lisp stack */
                nargs++;
                args = qcdr(args);
            }

/*
 * I pass the environment down to apply() because it will be used if the
 * function was a simple lambda expression.  If the function is a symbol
 * or a closure, env will be irrelevant.  The arguments are on the Lisp
 * stack, and it is the responsibility of apply() to pop them.
 */
            return apply(fn, nargs, env, fn);
        }
    }
}

#ifdef COMMON
/*
 * Keyword arguments are not supported in CSL mode - but &optional
 * and &rest and &aux will be (at least for now).  Removal of
 * support for keywords will save a little space and an even smaller
 * amount of time.
 */

static CSLbool check_no_unwanted_keys(Lisp_Object restarg, Lisp_Object ok_keys)
/*
 * verify that there were no unwanted keys in the actual arg list
 */
{
    Lisp_Object nil = C_nil;
    CSLbool odd_key_found = NO;
    while (restarg!=nil)
    {   Lisp_Object k = qcar(restarg);
        Lisp_Object w;
        for (w=ok_keys; w!=nil; w=qcdr(w))
            if (k == qcar(w)) goto is_ok;
        odd_key_found = YES;
    is_ok:
        restarg = qcdr(restarg);
        if (restarg==nil) return YES;  /* odd length list */
        if (k == allow_key_key && qcar(restarg) != nil) return NO; /* OK */
        restarg = qcdr(restarg);
    }
    return odd_key_found;
}

static CSLbool check_keyargs_even(Lisp_Object restarg)
/*
 * check that list is even length with alternate items symbols in
 * the keyword package.
 */
{
    Lisp_Object nil = C_nil;
    while (restarg!=nil)
    {   Lisp_Object q = qcar(restarg);
        if (!is_symbol(q) || qpackage(q) != qvalue(keyword_package)) return YES;
        restarg = qcdr(restarg);
        if (restarg==nil) return YES;      /* Odd length is wrong */
        restarg = qcdr(restarg);
    }
    return NO;                               /* OK */
}

static Lisp_Object keywordify(Lisp_Object v)
{
/*
 * arg is a non-nil symbol.  Should nil be permitted - I think not
 * since there seems too much chance of confusion.
 */
    Lisp_Object nil, name = get_pname(v);
    errexit();
    return Lintern_2(nil, name, qvalue(keyword_package));
}

static Lisp_Object key_lookup(Lisp_Object keyname, Lisp_Object args)
{
    Lisp_Object nil = C_nil;
    while (args!=nil)
    {   Lisp_Object next = qcdr(args);
        if (next==nil) return nil;
        if (qcar(args) == keyname) return next;
        else args = qcdr(next);
    }
    return nil;
}

#endif

Lisp_Object apply_lambda(Lisp_Object def, int nargs,
                         Lisp_Object env, Lisp_Object name)
/*
 * Here def is a lambda expression (sans the initial lambda) that is to
 * be applied.  Much horrible messing about is needed so that I can cope
 * with &optional and &rest args (including initialisers and supplied-p
 * variables, also &key, &allow-other-keys and &aux).  Note the need to find
 * any special declarations at the head of the body of the lambda-form.
 * Must pop (nargs) items from the stack at exit.
 */
{
/*
 * lambda-lists are parsed using a finite state engine with the
 * following states, plus an exit state.
 */
#define STATE_NULL     0        /* at start and during regular args */
#define STATE_OPT      1        /* after &optional */
#define STATE_OPT1     2        /* after &optional + at least one var */
#define STATE_REST     3        /* immediately after &rest */
#define STATE_REST1    4        /* after &rest vv */
#ifdef COMMON
#define STATE_KEY      5        /* &key with no &rest */
#define STATE_ALLOW    6        /* &allow-other-keys */
#endif
#define STATE_AUX      7        /* &aux */

    Lisp_Object nil = C_nil;
    int opt_rest_state = STATE_NULL;
    Lisp_Object *next_arg;
    int args_left = nargs;
    Lisp_Object w;
    if (!consp(def))
    {   popv(nargs);
        return onevalue(nil);    /* Should never happen */
    }
    stackcheck3(0, def, env, name);
    w = qcar(def);
    next_arg = &stack[1-nargs];         /* Points to arg1               */
    push4(w,                            /* bvl                          */
          qcdr(def),                    /* body                         */
          env, name);
/*
 * Here I need to macroexpand the first few items in body and
 * look for declare/special items.  I will only bother with SPECIAL decls.
 * Note that args have been pushed onto the stack first to avoid corruption
 * while the interpreter performs macroexpansion.  This is the sort of place
 * where I feel that Common Lisp has built in causes of inefficiency.
 * Well oh well!!! The Common Lisp standardisation group thought so too,
 * and have now indicated that DECLARE forms can not be hidden away as
 * the result of macros, so some of this is unnecessary.
 */
    push5(nil, nil,                  /* local_decs, ok_keys         */
          nil, nil, nil);            /* restarg, specenv, val1      */
    push5(nil, nil,                  /* arg, v1                     */
          nil, nil, nil);            /* v, p, w                     */
/*
 * On computers which have unsigned offsets in indexed memory reference
 * instructions the negative indexes off the stack suggested here might
 * be more expensive than I would like - maybe on such machines the stack
 * pointer should be kept offset by 64 bytes (say).  Doing so in general
 * would be to the disadvantage of machines with auto-index address modes
 * that might be used when pushing/popping single items on the stack.
 */
#define w           stack[0]
#define p           stack[-1]
#define v           stack[-2]
#define v1          stack[-3]
#define arg         stack[-4]
#define val1        stack[-5]
#define specenv     stack[-6]
#define restarg     stack[-7]
#ifdef COMMON
#define ok_keys     stack[-8]
#define local_decs  stack[-9]
#endif
#define name        stack[-10]
#define env         stack[-11]
#define body        stack[-12]
#define bvl         stack[-13]
#define arg1        stack[-14]
#define stack_used ((int)(nargs + 14))

#ifdef COMMON
    for (;;)
    {   if (!consp(body)) break;
        p = macroexpand(qcar(body), env);
        nil = C_nil;
        if (exception_pending())
        {   Lisp_Object qname = name;
            popv(stack_used);
            return qname;
        }
        body = qcdr(body);
        if (!consp(p))
        {   if (stringp(p) && consp(body)) continue;
            body = cons(p, body);
            break;
        }
        if (qcar(p) != declare_symbol)
        {   body = cons(p, body);
            break;
        }
        for (v = qcdr(v); consp(v); v = qcdr(v))
        {   v1 = qcar(v);
            if (!consp(v1) || qcar(v1) != special_symbol) continue;
            /* here v1 says (special ...) */
            for (v1=qcdr(v1); consp(v1); v1 = qcdr(v1))
            {   local_decs = cons(qcar(v1), local_decs);
                if (exception_pending()) break;
            }
        }
    }
    nil = C_nil;
    if (exception_pending())
    {   Lisp_Object qname = name;
        popv(stack_used);
        return qname;
    }
#endif
/*
 * Parse the BVL
 */
    for (p = bvl; consp(p); p=qcdr(p))
    {   v = qcar(p);
        v1 = nil;
        arg = nil;
        val1 = nil;
/*
 * I can break from this switch statement with v a variable to bind
 * and arg the value to bind to it, also v1 (if not nil) is a second
 * variable to be bound (a supplied-p value) and val1 the value to bind it to.
 * If I see &rest or &key the remaining actual args get collected into
 * restarg, which takes the place of arg in some respects.
 */
        switch (opt_rest_state)
        {

    case STATE_NULL:
            if (v == opt_key)
            {   opt_rest_state = STATE_OPT;
                continue;
            }

#define BAD1(msg)    { error(0, msg);    goto unwind_special_bindings; }
#define BAD2(msg, a) { error(1, msg, a); goto unwind_special_bindings; }

#define collect_rest_arg()                                  \
    while (args_left-- != 0)                                \
    {   if (!exception_pending())                           \
            restarg = cons(next_arg[args_left], restarg);   \
        nil = C_nil;                                        \
    }

            if (v == rest_key)
            {   collect_rest_arg();
                if (exception_pending()) goto unwind_special_bindings;
                opt_rest_state = STATE_REST;
                continue;
            }
#ifdef COMMON
            if (v == key_key)
            {   collect_rest_arg();
                if (exception_pending()) goto unwind_special_bindings;
                if (check_keyargs_even(restarg)) BAD2(err_bad_keyargs, restarg);
                opt_rest_state = STATE_KEY;
                continue;
            }

            if (v == aux_key)
            {   if (args_left != 0) BAD1(err_excess_args);
                opt_rest_state = STATE_AUX;
                continue;
            }
            if (v == allow_other_keys) BAD2(err_bad_bvl, v);
#endif
            if (args_left == 0) BAD1(err_insufficient_args);
            arg = *next_arg++;
            args_left--;
            v1 = nil;       /* no suppliedp mess here, I'm glad to say */
            break;

    case STATE_OPT:
            if (v == opt_key
                || v == rest_key
#ifdef COMMON
                || v == key_key
                || v == allow_other_keys
                || v == aux_key
#endif
                ) BAD2(err_bad_bvl, v);
/*
 * Here v may be a simple variable, or a list (var init suppliedp)
 */
            opt_rest_state = STATE_OPT1;
process_optional_parameter:
            if (args_left != 0)
            {   arg = *next_arg++;
                args_left--;
                val1 = lisp_true;
            }
            else
            {   arg = nil;
                val1 = nil;
            }
            v1 = nil;
            if (!consp(v)) break;       /* Simple case */
            {   w = qcdr(v);
                v = qcar(v);
                if (!consp(w)) break;   /* (var) */
                if (val1 == nil)        /* use the init form */
                {   arg = qcar(w);
                    arg = eval(arg, env);
                    nil = C_nil;
                    if (exception_pending()) goto unwind_special_bindings;
                }
                w = qcdr(w);
                if (consp(w)) v1 = qcar(w); /* suppliedp name */
                break;
            }

    case STATE_OPT1:
            if (v == rest_key)
            {   collect_rest_arg();
                if (exception_pending()) goto unwind_special_bindings;
                opt_rest_state = STATE_REST;
                continue;
            }
#ifdef COMMON
            if (v == key_key)
            {   collect_rest_arg();
                if (exception_pending()) goto unwind_special_bindings;
                if (check_keyargs_even(restarg)) BAD2(err_bad_keyargs, restarg);
                opt_rest_state = STATE_KEY;
                continue;
            }
            if (v == aux_key)
            {   if (args_left != 0) BAD1(err_excess_args);
                opt_rest_state = STATE_AUX;
                continue;
            }
#endif
            if (v == opt_key
#ifdef COMMON
             || v == allow_other_keys
#endif
               ) BAD2(err_bad_bvl, v);
            goto process_optional_parameter;

    case STATE_REST:
            if (v == opt_key
                || v == rest_key
#ifdef COMMON
                || v == key_key
                || v == allow_other_keys
                || v == aux_key
#endif
                ) BAD2(err_bad_bvl, v);
            opt_rest_state = STATE_REST1;
            arg = restarg;
            break;

    case STATE_REST1:
#ifdef COMMON
            if (v == key_key)
            {   if (check_keyargs_even(restarg)) BAD2(err_bad_keyargs, restarg);
                opt_rest_state = STATE_KEY;
                continue;
            }
            if (v == aux_key)
            {
                opt_rest_state = STATE_AUX;
                continue;
            }
#endif
            BAD2(err_bad_bvl, rest_key);

#ifdef COMMON
    case STATE_KEY:
            if (v == allow_other_keys)
            {   opt_rest_state = STATE_ALLOW;
                continue;
            }
            if (v == aux_key)
            {   if (check_no_unwanted_keys(restarg, ok_keys))
                    BAD2(err_bad_keyargs, restarg);
                opt_rest_state = STATE_AUX;
                continue;
            }
            if (v == opt_key || v == rest_key || v == key_key)
                BAD2(err_bad_bvl, v);
process_keyword_parameter:
/*
 * v needs to expand to ((:kv v) init svar) in effect here.
 */
            {   Lisp_Object keyname = nil;
                w = nil;
                if (!consp(v))
                {   if (!is_symbol(v)) BAD2(err_bad_bvl, v);
                    keyname = keywordify(v);
                }
                else
                {   w = qcdr(v);
                    v = qcar(v);
                    if (!consp(v))
                    {   if (!is_symbol(v)) BAD2(err_bad_bvl, v);
                        keyname = keywordify(v);
                        nil = C_nil;
                        if (exception_pending()) goto unwind_special_bindings;
                    }
                    else
                    {   keyname = qcar(v);
                        if (!is_symbol(keyname)) BAD2(err_bad_bvl, v);
                        keyname = keywordify(keyname);
                        nil = C_nil;
                        if (exception_pending()) goto unwind_special_bindings;
                        v = qcdr(v);
                        if (consp(v)) v = qcar(v);
                        else BAD2(err_bad_bvl, v);
                    }
                }
                ok_keys = cons(keyname, ok_keys);
                nil = C_nil;
                if (exception_pending()) goto unwind_special_bindings;
                arg = key_lookup(qcar(ok_keys), restarg);
                if (arg == nil) val1 = nil;
                else
                {   arg = qcar(arg);
                    val1 = lisp_true;
                }
                v1 = nil;
                if (!consp(w)) break;   /* (var) */
                if (val1 == nil)        /* use the init form */
                {   arg = qcar(w);
                    arg = eval(arg, env);
                    nil = C_nil;
                    if (exception_pending()) goto unwind_special_bindings;
                }
                w = qcdr(w);
                if (consp(w)) v1 = qcar(w); /* suppliedp name */
                break;
            }

    case STATE_ALLOW:
            if (v == aux_key)
            {   opt_rest_state = STATE_AUX;
                continue;
            }
            if (v == opt_key || v == rest_key || v == key_key ||
                v == allow_other_keys) BAD2(err_bad_bvl, v);
            goto process_keyword_parameter;

    case STATE_AUX:
            if (v == opt_key || v == rest_key ||
                v == key_key || v == allow_other_keys ||
                v == aux_key) BAD2(err_bad_bvl, v);
            if (consp(v))
            {   w = qcdr(v);
                v = qcar(v);
                if (consp(w))
                {   arg = qcar(w);
                    arg = eval(arg, env);
                    nil = C_nil;
                    if (exception_pending()) goto unwind_special_bindings;
                }
            }
            else arg = nil;
            v1 = nil;
            break;
#endif
        }
/*
 * This is where I get when I have one or two vars to bind.
 */

#ifndef COMMON
/*
 * CSL mode does not have to mess about looking for local special bindings
 * and so is MUCH shorter and neater. I always shallow bind
 */
#define instate_binding(var, val, local_decs1, lab)                     \
        {   if (!is_symbol(var)) BAD2(err_bad_bvl, var);                \
            w = acons(var, qvalue(var), specenv);                       \
            nil = C_nil;                                                \
            if (exception_pending()) goto unwind_special_bindings;      \
            specenv = w;                                                \
            qvalue(var) = val;                                          \
        }
#else
#define instate_binding(var, val, local_decs1, lab)                     \
        {   Header h;                                                   \
            if (!is_symbol(var)) BAD2(err_bad_bvl, var);                \
            h = qheader(var);                                           \
            if ((h & SYM_SPECIAL_VAR) != 0)                             \
            {   w = acons(var, qvalue(var), specenv);                   \
                nil = C_nil;                                            \
                if (exception_pending()) goto unwind_special_bindings;  \
                specenv = w;                                            \
                qvalue(var) = val;                                      \
            }                                                           \
            else                                                        \
            {   for (w = local_decs1; w!=nil; w = qcdr(w))              \
                {   if (qcar(w) == var)                                 \
                    {   qcar(w) = fixnum_of_int(0);/* decl is used up */\
                        w = acons(var, work_symbol, env);               \
                        nil = C_nil;                                    \
                        if (exception_pending())                        \
                            goto unwind_special_bindings;               \
                        env = w;                                        \
                        w = acons(var, qvalue(var), specenv);           \
                        nil = C_nil;                                    \
                        if (exception_pending())                        \
                            goto unwind_special_bindings;               \
                        specenv = w;                                    \
                        qvalue(var) = val;                              \
                        goto lab;                                       \
                    }                                                   \
                }                                                       \
                w = acons(var, val, env);                               \
                nil = C_nil;                                            \
                if (exception_pending()) goto unwind_special_bindings;  \
                env = w;                                                \
        lab:    ;                                                       \
            }                                                           \
        }
#endif

#ifdef COMMON
/*
 * Must check about local special declarations here...
 */
#endif
        instate_binding(v, arg, local_decs, label1);
        if (v1 != nil) instate_binding(v1, val1, local_decs, label2);

    }   /* End of for loop that scans BVL */

#ifdef COMMON
/*
 * As well as local special declarations that have applied to bindings here
 * there can be some that apply just to variable references within the body.
 */
    while (local_decs!=nil)
    {   Lisp_Object q = qcar(local_decs);
        local_decs=qcdr(local_decs);
        if (!is_symbol(q)) continue;
        w = acons(q, work_symbol, env);
        nil = C_nil;
        if (exception_pending()) goto unwind_special_bindings;
        env = w;
    }
#endif

    switch (opt_rest_state)
    {
case STATE_NULL:
case STATE_OPT1:        /* Ensure there had not been too many args */
        if (args_left != 0) BAD1(err_excess_args);
        break;

case STATE_OPT:         /* error if bvl finishes here */
case STATE_REST:
        BAD2(err_bad_bvl, opt_rest_state == STATE_OPT ? opt_key : rest_key);

#ifdef COMMON
case STATE_KEY:         /* ensure only valid keys were given */
        if (check_no_unwanted_keys(restarg, ok_keys))
            BAD2(err_bad_keyargs, restarg);
        break;
#endif

default:
/*                             in the following cases all is known to be well
case STATE_REST1:
case STATE_ALLOW:
case STATE_AUX:
*/
        break;
    }

/*
 * Now all the argument bindings have been performed - it remains to
 * process the body of the lambda-expression.
 */
    if (specenv == nil)
    {   Lisp_Object bodyx = body, envx = env;
        Lisp_Object qname = name;
        popv(stack_used);
        push(qname);
        bodyx = progn_fn(bodyx, envx);
        pop(qname);
        nil = C_nil;
        if (exception_pending()) return qname;
        return bodyx;
    }
    {   body = progn_fn(body, env);
        nil = C_nil;
        if (exception_pending()) goto unwind_special_bindings;
        while (specenv != nil)
        {
            Lisp_Object bv = qcar(specenv);
            qvalue(qcar(bv)) = qcdr(bv);
            specenv = qcdr(specenv);
        }
        {   Lisp_Object bodyx = body;
            popv(stack_used);
/*
 * note that exit_count has not been disturbed since I called progn_fn,
 * so the numbert of values that will be returned remains correctly
 * established (in Common Lisp mode where it is needed.
 */
            return bodyx;
        }
    }

unwind_special_bindings:
/*
 * I gete here ONLY if nil has its mark bit set, which means that (for
 * one reason or another) I am having to unwind the stack, restoring
 * special bindings as I go.
 */
    nil = C_nil;
    flip_exception();
    while (specenv != nil)
    {   Lisp_Object bv = qcar(specenv);
        qvalue(qcar(bv)) = qcdr(bv);
        specenv = qcdr(specenv);
    }
    flip_exception();
    {   Lisp_Object qname = name;
        popv(stack_used);
        return qname;
    }
#undef w
#undef p
#undef v
#undef v1
#undef arg
#undef val1
#undef specenv
#undef restarg
#undef ok_keys
#undef local_decs
#undef name
#undef env
#undef body
#undef bvl
#undef stack_used
}

Lisp_Object Leval(Lisp_Object nil, Lisp_Object a)
{
    return eval(a, nil);     /* Multiple values may be returned */
}

Lisp_Object Levlis(Lisp_Object nil, Lisp_Object a)
{
    Lisp_Object r;
    stackcheck1(0, a);
    r = nil;
    while (consp(a))
    {   push2(qcdr(a), r);
        a = qcar(a);
        a = eval(a, nil);
        errexitn(2);
        pop(r);
        r = cons(a, r);
        pop(a);
        errexit();
    }
    return onevalue(nreverse(r));
}

Lisp_Object MS_CDECL Lapply_n(Lisp_Object nil, int nargs, ...)
{
    va_list a;
    int i;
    Lisp_Object *stack_save = stack, last, fn = nil;
    if (nargs == 0) return aerror("apply");
    else if (nargs > 1)
    {   va_start(a, nargs);
        fn = va_arg(a, Lisp_Object);
        push_args_1(a, nargs);
        pop(last);
        i = nargs-2;
        while (consp(last))
        {   push(qcar(last));
            last = qcdr(last);
            i++;
        }
    }
    else i = 0;
    stackcheck1(stack-stack_save, fn);
    return apply(fn, i, nil, fn);
}

Lisp_Object Lapply_1(Lisp_Object nil, Lisp_Object fn)
{
    return Lapply_n(nil, 1, fn);
}

Lisp_Object Lapply_2(Lisp_Object nil, Lisp_Object fn, Lisp_Object a1)
{
    return Lapply_n(nil, 2, fn, a1);
}

Lisp_Object Lapply0(Lisp_Object nil, Lisp_Object fn)
{
    if (is_symbol(fn)) return (*qfnn(fn))(qenv(fn), 0);
    stackcheck1(0, fn);
    return apply(fn, 0, C_nil, fn);
}

Lisp_Object Lapply1(Lisp_Object nil, Lisp_Object fn, Lisp_Object a)
{
    if (is_symbol(fn)) return (*qfn1(fn))(qenv(fn), a);
    push(a);
    stackcheck1(1, fn);
    return apply(fn, 1, C_nil, fn);
}

Lisp_Object MS_CDECL Lapply2(Lisp_Object nil, int nargs, ...)
{
    va_list aa;
    Lisp_Object fn, a, b;
    argcheck(nargs, 3, "apply2");
    va_start(aa, nargs);
    fn = va_arg(aa, Lisp_Object);
    a = va_arg(aa, Lisp_Object);
    b = va_arg(aa, Lisp_Object);
    va_end(aa);
    if (is_symbol(fn)) return (*qfn2(fn))(qenv(fn), a, b);
    push2(a, b);
    stackcheck1(2, fn);
    return apply(fn, 2, C_nil, fn);
}

Lisp_Object MS_CDECL Lapply3(Lisp_Object nil, int nargs, ...)
{
    va_list aa;
    Lisp_Object fn, a, b, c;
    argcheck(nargs, 4, "apply3");
    va_start(aa, nargs);
    fn = va_arg(aa, Lisp_Object);
    a = va_arg(aa, Lisp_Object);
    b = va_arg(aa, Lisp_Object);
    c = va_arg(aa, Lisp_Object);
    va_end(aa);
    if (is_symbol(fn)) return (*qfnn(fn))(qenv(fn), 3, a, b, c);
    push3(a, b, c);
    stackcheck1(3, fn);
    return apply(fn, 3, C_nil, fn);
}

Lisp_Object Lfuncall1(Lisp_Object nil, Lisp_Object fn)
{
    if (is_symbol(fn)) return (*qfnn(fn))(qenv(fn), 0);
    stackcheck1(0, fn);
    return apply(fn, 0, nil, fn);
}

Lisp_Object Lfuncall2(Lisp_Object nil, Lisp_Object fn, Lisp_Object a1)
{
    if (is_symbol(fn)) return (*qfn1(fn))(qenv(fn), a1);
    push(a1);
    stackcheck1(1, fn);
    return apply(fn, 1, nil, fn);
}

static Lisp_Object MS_CDECL Lfuncalln_sub(Lisp_Object nil, int nargs, va_list a)
{
    Lisp_Object *stack_save = stack, fn;
    fn = va_arg(a, Lisp_Object);
    push_args_1(a, nargs);
    stackcheck1(stack-stack_save, fn);
    return apply(fn, nargs-1, nil, fn);
}

Lisp_Object MS_CDECL Lfuncalln(Lisp_Object nil, int nargs, ...)
{
    va_list a;
    Lisp_Object fn, a1, a2, a3, a4;
    va_start(a, nargs);
    switch (nargs)
    {
case 0: return aerror("funcall");
case 1: /* cases 1 and 2 should go through Lfuncall1,2 not here */
case 2: return aerror("funcall wrong call");
case 3: fn = va_arg(a, Lisp_Object);
        a1 = va_arg(a, Lisp_Object);
        a2 = va_arg(a, Lisp_Object);
        if (is_symbol(fn)) return (*qfn2(fn))(qenv(fn), a1, a2);
        push2(a1, a2);
        return apply(fn, 2, nil, fn);
case 4: fn = va_arg(a, Lisp_Object);
        a1 = va_arg(a, Lisp_Object);
        a2 = va_arg(a, Lisp_Object);
        a3 = va_arg(a, Lisp_Object);
        if (is_symbol(fn)) return (*qfnn(fn))(qenv(fn), 3, a1, a2, a3);
        push3(a1, a2, a3);
        return apply(fn, 3, nil, fn);
case 5: fn = va_arg(a, Lisp_Object);
        a1 = va_arg(a, Lisp_Object);
        a2 = va_arg(a, Lisp_Object);
        a3 = va_arg(a, Lisp_Object);
        a4 = va_arg(a, Lisp_Object);
        if (is_symbol(fn)) return (*qfnn(fn))(qenv(fn), 4, a1, a2, a3, a4);
        push4(a1, a2, a3, a4);
        return apply(fn, 4, nil, fn);
default:
        return Lfuncalln_sub(nil, nargs, a);
    }
}

#ifdef COMMON

Lisp_Object MS_CDECL Lvalues(Lisp_Object nil, int nargs, ...)
{
    va_list a;
    Lisp_Object *p = &mv_2, w;
    int i;
/*
 * Because multiple-values get passed back in static storage there is
 * a fixed upper limit to how many I can handle - truncate here to allow
 * for that.
 */
    if (nargs > 50) nargs = 50;
    if (nargs == 0) return nvalues(nil, 0);
    va_start(a, nargs);
    push_args(a, nargs);
    for (i=1; i<nargs; i++)
    {   pop(w);
        p[nargs-i-1] = w;
    }
    pop(w);
    return nvalues(w, nargs);
}

Lisp_Object Lvalues_2(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
    return Lvalues(nil, 2, a, b);
}

Lisp_Object Lvalues_1(Lisp_Object nil, Lisp_Object a)
{
    return Lvalues(nil, 1, a);
}

Lisp_Object mv_call_fn(Lisp_Object args, Lisp_Object env)
/*
 * here with the rest of the interpreter rather than in specforms.c
 */
{
    Lisp_Object nil = C_nil;
    Lisp_Object fn, *stack_save = stack;
    int i=0, j=0;
    if (!consp(args)) return nil;       /* (multiple-value-call) => nil */
    stackcheck2(0, args, env);
    push2(args, env);
    fn = qcar(args);
    fn = eval(fn, env);
    pop2(env, args);
    errexit();
    args = qcdr(args);
    while (consp(args))
    {   Lisp_Object r1;
        push2(args, env);
        r1 = qcar(args);
        r1  = eval(r1, env);
        nil = C_nil;
        if (exception_pending())
        {   stack = stack_save;
            return nil;
        }
/*
 * It is critical here that push does not check for stack overflow and
 * thus can not call the garbage collector, or otherwise lead to calculation
 * that could possibly clobber the multiple results that I am working with
 * here.
 */
        pop2(env, args);
        push(r1);
        i++;
        for (j = 2; j<=exit_count; j++)
        {   push((&work_0)[j]);
            i++;
        }
        args = qcdr(args);
    }
    stackcheck2(stack-stack_save, fn, env);
    return apply(fn, i, env, fn);
}

#endif

Lisp_Object interpreted1(Lisp_Object def, Lisp_Object a1)
{
    Lisp_Object nil = C_nil;
    push(a1);
    stackcheck1(1, def);
    return apply_lambda(def, 1, nil, def);
}

Lisp_Object interpreted2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
{
    Lisp_Object nil = C_nil;
    push2(a1, a2);
    stackcheck1(2, def);
    return apply_lambda(def, 2, nil, def);
}

Lisp_Object MS_CDECL interpretedn(Lisp_Object def, int nargs, ...)
{
/*
 * The messing about here is to get the (unknown number of) args
 * into a nice neat vector so that they can be indexed into. If I knew
 * that the args were in consecutive locations on the stack I could
 * probably save a copying operation.
 */
    Lisp_Object nil = C_nil;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    return apply_lambda(def, nargs, nil, def);
}

Lisp_Object funarged1(Lisp_Object def, Lisp_Object a1)
{
    Lisp_Object nil = C_nil;
    push(a1);
    stackcheck1(1, def);
    return apply_lambda(qcdr(def), 1, qcar(def), qcdr(def));
}

Lisp_Object funarged2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
{
    Lisp_Object nil = C_nil;
    push2(a1, a2);
    stackcheck1(2, def);
    return apply_lambda(qcdr(def), 2, qcar(def), qcdr(def));
}

Lisp_Object MS_CDECL funargedn(Lisp_Object def, int nargs, ...)
{
    Lisp_Object nil = C_nil;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    return apply_lambda(qcdr(def), nargs, qcar(def), qcdr(def));
}

/*
 * Now some execution-doubling versions...
 */

Lisp_Object double_interpreted1(Lisp_Object def, Lisp_Object a1)
{
    Lisp_Object nil = C_nil;
    push(a1);
    stackcheck1(1, def);
    return apply_lambda(def, 1, nil, def);
}

Lisp_Object double_interpreted2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
{
    Lisp_Object nil = C_nil;
    push2(a1, a2);
    stackcheck1(2, def);
    return apply_lambda(def, 2, nil, def);
}

Lisp_Object MS_CDECL double_interpretedn(Lisp_Object def, int nargs, ...)
{
/*
 * The messing about here is to get the (unknown number of) args
 * into a nice neat vector so that they can be indexed into. If I knew
 * that the args were in consecutive locations on the stack I could
 * probably save a copying operation.
 */
    Lisp_Object nil = C_nil;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    return apply_lambda(def, nargs, nil, def);
}

Lisp_Object double_funarged1(Lisp_Object def, Lisp_Object a1)
{
    Lisp_Object nil = C_nil;
    push(a1);
    stackcheck1(1, def);
    return apply_lambda(qcdr(def), 1, qcar(def), qcdr(def));
}

Lisp_Object double_funarged2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
{
    Lisp_Object nil = C_nil;
    push2(a1, a2);
    stackcheck1(2, def);
    return apply_lambda(qcdr(def), 2, qcar(def), qcdr(def));
}

Lisp_Object MS_CDECL double_funargedn(Lisp_Object def, int nargs, ...)
{
    Lisp_Object nil = C_nil;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    return apply_lambda(qcdr(def), nargs, qcar(def), qcdr(def));
}

Lisp_Object traceinterpreted1(Lisp_Object def, Lisp_Object a1)
/*
 * Like interpreted() but the definition has the fn name consed on the front
 */
{
    Lisp_Object nil = C_nil, r;
    push(a1);
    stackcheck1(1, def);
    freshline_trace();
    trace_printf("Entering ");
    loop_print_trace(qcar(def));
    trace_printf(" (1 arg)\n");
    trace_printf("Arg1: ");
    loop_print_trace(stack[0]);
    trace_printf("\n");
    r = apply_lambda(qcdr(def), 1, nil, def);
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

Lisp_Object traceinterpreted2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
/*
 * Like interpreted() but the definition has the fn name consed on the front
 */
{
    Lisp_Object nil = C_nil, r;
    int i;
    push2(a1, a2);
    stackcheck1(2, def);
    freshline_trace();
    trace_printf("Entering ");
    loop_print_trace(qcar(def));
    trace_printf(" (2 args)\n");
    for (i=1; i<=2; i++)
    {   trace_printf("Arg%d: ", i);
        loop_print_trace(stack[i-2]);
        trace_printf("\n");
    }
    r = apply_lambda(qcdr(def), 2, nil, def);
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

Lisp_Object MS_CDECL traceinterpretedn(Lisp_Object def, int nargs, ...)
/*
 * Like interpreted() but the definition has the fn name consed on the front
 */
{
    int i;
    Lisp_Object nil = C_nil, r;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    freshline_trace();
    trace_printf("Entering ");
    loop_print_trace(qcar(def));
    trace_printf(" (%d args)\n", nargs);
    for (i=1; i<=nargs; i++)
    {   trace_printf("Arg%d: ", i);
        loop_print_trace(stack[i-nargs]);
        trace_printf("\n");
    }
    r = apply_lambda(qcdr(def), nargs, nil, def);
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

Lisp_Object tracefunarged1(Lisp_Object def, Lisp_Object a1)
/*
 * Like funarged() but with some printing
 */
{
    Lisp_Object nil = C_nil, r;
    push(a1);
    stackcheck1(1, def);
    freshline_trace();
    trace_printf("Entering funarg ");
    loop_print_trace(qcar(def));
    trace_printf(" (1 arg)\n");
    def = qcdr(def);
    r = apply_lambda(qcdr(def), 1, qcar(def), qcdr(def));
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

Lisp_Object tracefunarged2(Lisp_Object def, Lisp_Object a1, Lisp_Object a2)
/*
 * Like funarged() but with some printing
 */
{
    Lisp_Object nil = C_nil, r;
    push2(a1, a2);
    stackcheck1(2, def);
    freshline_trace();
    trace_printf("Entering funarg ");
    loop_print_trace(qcar(def));
    trace_printf(" (2 args)\n");
    def = qcdr(def);
    r = apply_lambda(qcdr(def), 2, qcar(def), qcdr(def));
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

Lisp_Object MS_CDECL tracefunargedn(Lisp_Object def, int nargs, ...)
/*
 * Like funarged() but with some printing
 */
{
    Lisp_Object nil = C_nil, r;
    Lisp_Object *stack_save = stack;
    va_list a;
    if (nargs != 0)
    {   va_start(a, nargs);
        push_args(a, nargs);
    }
    stackcheck1(stack-stack_save, def);
    freshline_trace();
    trace_printf("Entering funarg ");
    loop_print_trace(qcar(def));
    trace_printf(" (%d args)\n", nargs);
    def = qcdr(def);
    r = apply_lambda(qcdr(def), nargs, qcar(def), qcdr(def));
    errexit();
    push(r);
    trace_printf("Value = ");
    loop_print_trace(r);
    trace_printf("\n");
    pop(r);
    return r;
}

static Lisp_Object macroexpand_1(Lisp_Object form, Lisp_Object env)
{   /* The environment here seems only necessary for macrolet */
    Lisp_Object done;
    Lisp_Object f, nil;
    nil = C_nil;
    stackcheck2(0, form, env);
    done = nil;
    if (consp(form))
    {   f = qcar(form);
#ifdef COMMON
/*
 * look for local macro definitions
 */
        {   Lisp_Object p;
            for (p=env; p!=nil; p=qcdr(p))
            {   Lisp_Object w = qcar(p);
                if (qcdr(w) == f && is_cons(w = qcar(w)) && w!=nil)
                {
                    p = qcar(w);
                    if (p == funarg) /* ordinary function */
                    {   mv_2 = nil;
                        return nvalues(form, 2);
                    }
                    push2(form, done);
                    w = cons(lambda, w);
                    errexitn(1);
                    p = Lfuncalln(nil, 4, qvalue(macroexpand_hook),
                                  w, stack[-1], nil);
                    pop2(done, form);
                    nil = C_nil;
                    if (exception_pending())
                    {   flip_exception();
                        if ((exit_reason & UNWIND_ERROR) != 0)
                        {   err_printf("\nMacroexpanding: ");
                            loop_print_error(form);
                            nil = C_nil;
                            if (exception_pending()) flip_exception();
                        }
                        flip_exception();
                        return nil;
                    }
                    mv_2 = lisp_true;
                    return nvalues(p, 2);
                }
            }
        }
/*
 * If there is no local macro definition I need to look for a global one
 */
#endif
        if (symbolp(f) && (qheader(f) & SYM_MACRO) != 0)
        {
            done = qvalue(macroexpand_hook);
            if (done == unset_var)
                return error(1, err_macroex_hook, macroexpand_hook);
            push3(form, env, done);
            f = cons(lambda, qenv(f));
            pop3(done, env, form);
            nil = C_nil;
            if (!exception_pending())
            {
#ifndef COMMON
/* CSL does not pass an environment down here, so does not demand &opt arg */
                form = Lfuncalln(nil, 3, done, f, form);
#else
                form = Lfuncalln(nil, 4, done, f, form, env);
#endif
                nil = C_nil;
            }
            if (exception_pending()) return nil;
            done = lisp_true;
        }
    }
    mv_2 = done;
    return nvalues(form, 2);    /* Multiple values handed back */
}

Lisp_Object macroexpand(Lisp_Object form, Lisp_Object env)
{   /* The environment here seems only necessary for macrolet */
    Lisp_Object done, nil;
    nil = C_nil;
    stackcheck2(0, form, env);
    done = nil;
    for (;;)
    {   push2(env, done);
        form = macroexpand_1(form, env);
        pop2(done, env);
        errexit();
        if (mv_2 == nil) break;
        done = lisp_true;
    }
    mv_2 = done;
    return nvalues(form, 2);    /* Multiple values handed back */
}

Lisp_Object Lmacroexpand(Lisp_Object nil, Lisp_Object a)
{
    return macroexpand(a, nil);
}

#ifdef COMMON
Lisp_Object Lmacroexpand_2(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
    CSL_IGNORE(nil);
    return macroexpand(a, b);
}
#endif

Lisp_Object Lmacroexpand_1(Lisp_Object nil, Lisp_Object a)
{
    return macroexpand_1(a, nil);
}

#ifdef COMMON
Lisp_Object Lmacroexpand_1_2(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
    CSL_IGNORE(nil);
    return macroexpand_1(a, b);
}

#endif

/*
 * To make something autoloadable I should set the environment cell to
 *    (name-of-self module-name-1 module-name-2 ...)
 * and when invoked the function will do a load-module on each of the
 * modules specified and then re-attempt to call.  Loading the
 * modules is expected to establish a proper definition for the
 * function involved.
 */

Lisp_Object autoload1(Lisp_Object fname, Lisp_Object a1)
{
    Lisp_Object nil = C_nil;
    push2(a1, qcar(fname));
    set_fns(qcar(fname), undefined1, undefined2, undefinedn);
    qenv(qcar(fname)) = qcar(fname);
    fname = qcdr(fname);
    while (consp(fname))
    {   push(qcdr(fname));
        Lload_module(nil, qcar(fname));
        errexitn(3);
        pop(fname);
    }
    pop(fname);
    return apply(fname, 1, nil, fname);
}

Lisp_Object autoload2(Lisp_Object fname, Lisp_Object a1, Lisp_Object a2)
{
    Lisp_Object nil = C_nil;
    push3(a1, a2, qcar(fname));
    set_fns(qcar(fname), undefined1, undefined2, undefinedn);
    qenv(qcar(fname)) = qcar(fname);
    fname = qcdr(fname);
    while (consp(fname))
    {   push(qcdr(fname));
        Lload_module(nil, qcar(fname));
        errexitn(4);
        pop(fname);
    }
    pop(fname);
    return apply(fname, 2, nil, fname);
}

Lisp_Object MS_CDECL autoloadn(Lisp_Object fname, int nargs, ...)
{
    Lisp_Object nil = C_nil;
    va_list a;
    va_start(a, nargs);
    push_args(a, nargs);
    push(qcar(fname));
    set_fns(qcar(fname), undefined1, undefined2, undefinedn);
    qenv(qcar(fname)) = qcar(fname);
    fname = qcdr(fname);
    while (consp(fname))
    {   push(qcdr(fname));
        Lload_module(nil, qcar(fname));
        errexitn(nargs+2);
        pop(fname);
    }
    pop(fname);
    return apply(fname, nargs, nil, fname);
}

Lisp_Object undefined1(Lisp_Object fname, Lisp_Object a1)
{
/*
 * It would be perfectly possible to grab and save the args here, and retry
 * the function call after error has patched things up.  Again
 * this entrypoint is for compiled code calling something that is undefined,
 * and so no lexical environment is needed.
 */
    CSL_IGNORE(a1);
    return error(1, err_undefined_function_1, fname);
}

Lisp_Object undefined2(Lisp_Object fname, Lisp_Object a1, Lisp_Object a2)
{
    CSL_IGNORE(a1);
    CSL_IGNORE(a2);
    return error(1, err_undefined_function_2, fname);
}

Lisp_Object MS_CDECL undefinedn(Lisp_Object fname, int nargs, ...)
{
    CSL_IGNORE(nargs);
    return error(1, err_undefined_function_n, fname);
}

/*
 * The next few functions allow me to create variants on things! The
 * entrypoint fX_as_Y goes in the function cell of a symbol, and the name
 * of a function with Y arguments goes in is environment cell. The result will
 * be a function that accepts X arguments and discards all but the first Y of
 * them, then chains to the other function. The purpose is to support goo
 * compilation of things like
 *   (de funny_equal (a b c) (equal a b))
 */

Lisp_Object MS_CDECL f0_as_0(Lisp_Object env, int nargs, ...)
{
    if (nargs != 0) return aerror1("wrong number of args (0->0)", env);
    return (*qfnn(env))(qenv(env), 0);
}

Lisp_Object f1_as_0(Lisp_Object env, Lisp_Object a)
{
    CSL_IGNORE(a);
    return (*qfnn(env))(qenv(env), 0);
}

Lisp_Object f2_as_0(Lisp_Object env, Lisp_Object a, Lisp_Object b)
{
    CSL_IGNORE(a);
    CSL_IGNORE(b);
    return (*qfnn(env))(qenv(env), 0);
}

Lisp_Object MS_CDECL f3_as_0(Lisp_Object env, int nargs, ...)
{
    if (nargs != 3) return aerror1("wrong number of args (3->0)", env);
    return (*qfnn(env))(qenv(env), 0);
}

Lisp_Object f1_as_1(Lisp_Object env, Lisp_Object a)
{
    return (*qfn1(env))(qenv(env), a);
}

Lisp_Object f2_as_1(Lisp_Object env, Lisp_Object a, Lisp_Object b)
{
    CSL_IGNORE(b);
    return (*qfn1(env))(qenv(env), a);
}

Lisp_Object MS_CDECL f3_as_1(Lisp_Object env, int nargs, ...)
{
    va_list a;
    Lisp_Object a1;
    if (nargs != 3) return aerror1("wrong number of args (3->1)", env);
    va_start(a, nargs);
    a1 = va_arg(a, Lisp_Object);
    va_end(a);
    return (*qfn1(env))(qenv(env), a1);
}

Lisp_Object f2_as_2(Lisp_Object env, Lisp_Object a, Lisp_Object b)
{
    return (*qfn2(env))(qenv(env), a, b);
}

Lisp_Object MS_CDECL f3_as_2(Lisp_Object env, int nargs, ...)
{
    va_list a;
    Lisp_Object a1, a2;
    if (nargs != 3) return aerror1("wrong number of args (3->2)", env);
    va_start(a, nargs);
    a1 = va_arg(a, Lisp_Object);
    a2 = va_arg(a, Lisp_Object);
    va_end(a);
    return (*qfn2(env))(qenv(env), a1, a2);
}

Lisp_Object MS_CDECL f3_as_3(Lisp_Object env, int nargs, ...)
{
    va_list a;
    Lisp_Object a1, a2, a3;
    if (nargs != 3) return aerror1("wrong number of args (3->3)", env);
    va_start(a, nargs);
    a1 = va_arg(a, Lisp_Object);
    a2 = va_arg(a, Lisp_Object);
    a3 = va_arg(a, Lisp_Object);
    va_end(a);
    return (*qfnn(env))(qenv(env), 3, a1, a2, a3);
}

/*
 * The next function is EXPERIMENTAL and is only available if there is
 * a "fork" function available. It is probably only even partially useful
 * if the operating system and libraries used implement that using a
 * "copy on write" strategy. This is the case with Linux, and I believe it to
 * be so in MacOSX. But Windows does not provide that sort of functionality
 * comfortably, so this stuff will not be available there. Observe that I
 * make fairly extreme use of the autoconf detection stuff to try to avoid
 * trying this where it might not make sense!
 */

/*
 * Expected behaviour
 *   (parallel f a)
 *      runs two tasks, one of which is f(a, nil), the other is f(a, t).
 *      when the first of those tasks completes the other is killed.
 *      The result is a pair (fg . val)
 *      If fg > 0 it is 1 or 2 to indicate which of the two calls
 *      "won". In that case the value is the result returned by the
 *      call, but NOTE that it has been in effect through print/read, and
 *      so gensym identity and structure sharing will have been lost.
 *      If fg < 0 then the true result was computed, but its printed
 *      representation was longer than around 2K characters. The absolute
 *      value of fg again indicates which task won, but the value is now
 *      a string consisting of the first segment of characters in a printed
 *      representation of the result. If creating parallel processes
 *      fails or if the first task to finish does so by failing then this
 *      call will report an error.
 *      While it may be legal to use nested instaces of parallel to get
 *      extra concurrency the memory demands that will result could be
 *      severe. The overhead associated with starting and finishing a
 *      task may also be significant, and so this is only liable to make
 *      sense on a multi-cpu system for sub-tasks that are fairly demanding.
 *      Note that the longer running task will be cancelled and no output
 *      from it will be available at all.
 *      Tasks run this way should probably avoid all input and output
 *      operations.
 *
 *      If the computer on which CSL has been built does not supprt "fork"
 *      and the shared memory operations required here the parallel function
 *      will just always report an error.
 *
 *      While this code is in development it may genatate a certain amount
 *      of unwanted trace or logging information.
 */

#if defined HAVE_UNISTD_H && \
    defined HAVE_SYS_TYPES_H && \
    defined HAVE_SYS_STAT_H && \
    defined HAVE_SYS_WAIT_H && \
    defined HAVE_SIGNAL_H && \
    defined HAVE_SYS_SHM_H && \
    defined HAVE_SYS_IPC_H && \
    defined HAVE_FORK && \
    defined HAVE_WAIT && \
    defined HAVE_WAITPID && \
    defined HAVE_SHMGET && \
    defined HAVE_SHMAT && \
    defined HAVE_SHMDT && \
    defined HAVE_SHMCTL

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <signal.h>
#include <errno.h>

#define PARSIZE 2048

static void write_result(Lisp_Object nil, Lisp_Object r, char *shared)
{
/*
 * This converts an arbitrary resulty into a string so I can pass it back.
 */
    int32_t i, len, ok = 1;
/*
 * Cyclic and re-entrant structures could lead to failure here, and
 * uninterned symbols (eg gensyms) will not be coped with very well. But
 * SIMPLE data types should all be safe.
 */
    r = Lexplode(nil, r);
    if (exception_pending())
    {   strcpy(shared, "Failed");
        exit(2);
    }
    r = Llist_to_string(nil, r);
    if (exception_pending())
    {   strcpy(shared, "Failed");
        exit(3);
    }
    len = length_of_header(vechdr(r)) - CELL;
/*
 * If the displayed form ou the output was too long I just truncate it
 * at present. A more agressive attitude would be to count that as a form
 * of failure. As an intermediate step I use the first character in my
 * buffer as an "overflow flag" and leave a blank in it if all is well.
 */
    if (len > PARSIZE-2)
    {   len=PARSIZE-2;
        ok = 0;
    }
    shared[0] = ok ? ' ' : '#';
    for (i=0; i<len; i++) shared[i+1] = celt(r, i);
    shared[len+1] = 0;
}

Lisp_Object Lparallel(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
    pid_t pid1, pid2, pidx, pidy;
/*
 * Create an identifier for a private shared segment of memory of size
 * 2*PARSIZE. This will be used for passing a result from the sub-task
 * to the main one. Give up if such a segment can not be allocated.
 */
    int status, segid = shmget(IPC_PRIVATE, (size_t)(2*PARSIZE),
        IPC_CREAT | S_IRUSR | S_IWUSR);
    char *shared, *w;
    int overflow;
    Lisp_Object r;
    if (segid == -1) return aerror("Unable to allocate a shared segment");
/*
 * Attach to the shared segment to obtain a memory address via which it can be
 * accessed. Again raise an error if this fails.
 */
    shared = (char *)shmat(segid, NULL, 0);
    if (shared == (char *)(-1))
        return aerror("Unable to attach to shared segment");
/*
 * the shared segment is set up to contain null strings in the two places
 * where it might be used to hold return values.
 */
    shared[0] = shared[PARSIZE] = 0;
/*
 * Split off a clone of the current process that can be used to do the
 * first evaluation. If this succeeds call a(b, nil) in it. Note that
 * processes created via "fork" inherit shared memory segments from their
 * parent.
 */
    pid1 = fork();
    if (pid1 < 0)     /* Task not created, must tidy up. */
    {   shmdt(shared);
        shmctl(segid, IPC_RMID, 0);
        return aerror("Fork 1 failed");
    }
    else if (pid1 == 0)
    {   /* TASK 1 created OK */
        Lisp_Object r1 = Lapply2(nil, 3, a, b, nil);
        nil = C_nil;
/*
 * If the evaluation failed I will exit indicating a failure.
 */
        if (exception_pending())
        {   strcpy(shared, "Failed");
            exit(1);
        }
/*
 * Write result from first task into the first half of the shared memory block.
 */
        write_result(nil, r1, shared);
/*
 * Exiting from the sub-task would in fact detach from the shared data
 * segment, but I do it explictly to feel tidy.
 */
        shmdt(shared);
        exit(0);
    }
    else
    {
/*
 * This is the continuation of the main process. Create a second task in
 * much the same way.
 */
        pid2 = fork();
        if (pid2 < 0)    /* If task 2 can not be created then kill task 1 */
        {   kill(pid1, SIGKILL);
            waitpid(pid1, &status, 0);
            shmdt(shared);
            shmctl(segid, IPC_RMID, 0);
            return aerror("Fork 2 failed");
        }
        else if (pid2 == 0)
        {   /* TASK 2 */
            Lisp_Object r2 = Lapply2(nil, 3, a, b, lisp_true);
            nil = C_nil;
            if (exception_pending())
            {   strcpy(shared, "Failed");
                exit(1);
            }
            write_result(nil, r2, shared+PARSIZE);
            shmdt(shared);
            exit(0);
        }
        else
        {
/*
 * Wait for whichever of the two sub-tasks finishes first. Then kill the
 * other one, and return the result left by the winner.
 */
            pidx = wait(&status);
            term_printf("First signal was from task %d\n", pidx);
            if (!WIFEXITED(status) ||
                WEXITSTATUS(status) != 0)
            {
/*
 * If the first task to complete in fact failed rather than exited cleanly
 * I will count it as an overall failure and cancel everything. This
 * covers aborting (in which case WIFEXITED will return false) or
 * exiting cleanly but with a non-zero return code.
 */
                kill(pid1, SIGKILL);
                kill(pid2, SIGKILL);
                waitpid(pid1, &status, 0);
                waitpid(pid2, &status, 0);
                shmdt(shared);
                shmctl(segid, IPC_RMID, 0);
                return aerror("Task did not exit cleanly");
            }
            if (pidx == pid1)
            {    w = shared;
                 pidy = pid2;
                 overflow = 1;
            }
            else
            {    w = shared+PARSIZE;
                 pidy = pid1;
                 overflow = 2;
            }
            kill(pidy, SIGKILL);        /* Kill alternate task */
            waitpid(pidy, &status, 0);
/*
 * If the first character of the buffer is a blank then there was no
 * overflow and all is well.
 */
            if (w[0] == ' ') r = read_from_vector(w + 1);
            else
            {   overflow = -overflow;
                r = make_string(w + 1);
            }
/*
 * Need to tidy up the shared segment at the end.
 */
            shmdt(shared);
            shmctl(segid, IPC_RMID, 0);
            errexit();
            r = cons(fixnum_of_int(overflow), r);
            errexit();
            return onevalue(r);
        }
    }
}

#else

Lisp_Object Lparallel(Lisp_Object nil, Lisp_Object a, Lisp_Object b)
{
    return aerror("parallel not supported on this platform");
}

#endif

setup_type const eval1_setup[] =
{
    {"bytecounts",              wrong_no_na, wrong_no_nb, bytecounts},
/*
 * PSL has a function idapply that is, as best I understand, just the
 * same as apply apart from the fact that it expects an identifier as
 * its first argument. But it them says it tests for that and moans if
 * given a list, so I find it hard to understand how or why it is liable
 * to be faster than plain apply! However to ease portability I provide
 * that name here...  I think I should mention funcall as a possible
 * optimisation in this area...
 */
    {"idapply",                 Lapply_1, Lapply_2, Lapply_n},
    {"apply",                   Lapply_1, Lapply_2, Lapply_n},
    {"apply0",                  Lapply0, too_many_1, wrong_no_1},
    {"apply1",                  too_few_2, Lapply1, wrong_no_2},
    {"apply2",                  wrong_no_na, wrong_no_nb, Lapply2},
    {"apply3",                  wrong_no_na, wrong_no_nb, Lapply3},
    {"evlis",                   Levlis, too_many_1, wrong_no_1},
    {"funcall",                 Lfuncall1, Lfuncall2, Lfuncalln},
    {"funcall*",                Lfuncall1, Lfuncall2, Lfuncalln},
    {"parallel",                too_few_2, Lparallel, wrong_no_2},
#ifdef COMMON
    {"values",                  Lvalues_1, Lvalues_2, Lvalues},
    {"macroexpand",             Lmacroexpand, Lmacroexpand_2, wrong_no_1},
    {"macroexpand-1",           Lmacroexpand_1, Lmacroexpand_1_2, wrong_no_1},
#else
    {"macroexpand",             Lmacroexpand, too_many_1, wrong_no_1},
    {"macroexpand-1",           Lmacroexpand_1, too_many_1, wrong_no_1},
#endif
    {NULL,                      0, 0, 0}
};

/* end of eval1.c */


REDUCE Historical
REDUCE Sourceforge Project | Historical SVN Repository | GitHub Mirror | SourceHut Mirror | NotABug Mirror | Chisel Mirror | Chisel RSS ]