/* proc.c: Eval, funcall, apply, map, lambda, etc. The main-loop of the * Scheme evaluator. * * $Id$ * * Copyright 1990, 1991, 1992, 1993, 1994, 1995, Oliver Laumann, Berlin * Copyright 2002, 2003 Sam Hocevar , Paris * * This software was derived from Elk 1.2, which was Copyright 1987, 1988, * 1989, Nixdorf Computer AG and TELES GmbH, Berlin (Elk 1.2 has been written * by Oliver Laumann for TELES Telematic Services, Berlin, in a joint project * between TELES and Nixdorf Microprocessor Engineering, Berlin). * * Oliver Laumann, TELES GmbH, Nixdorf Computer AG and Sam Hocevar, as co- * owners or individual owners of copyright in this software, grant to any * person or company a worldwide, royalty free, license to * * i) copy this software, * ii) prepare derivative works based on this software, * iii) distribute copies of this software or derivative works, * iv) perform this software, or * v) display this software, * * provided that this notice is not removed and that neither Oliver Laumann * nor Teles nor Nixdorf are deemed to have made any representations as to * the suitability of this software for any purpose nor are held responsible * for any defects of this software. * * THERE IS ABSOLUTELY NO WARRANTY FOR THIS SOFTWARE. */ #include "config.h" #include "kernel.h" #ifdef HAVE_ALLOCA # define MAX_ARGS_ON_STACK 4 #else # define MAX_ARGS_ON_STACK 8 #endif #define Get_Arglist_Length(_cnt, _lst, _x) \ for (_cnt = 0, _x = _lst; TYPE(_x) == T_Pair; _x = Cdr (_x), _cnt++) \ ; \ if (!Nullp(_x)) \ Primitive_Error("improper argument list"); \ #define Funcall_Switch(t,func,args,eval) \ if (t == T_Primitive) {\ return Funcall_Primitive (func, args, eval);\ } else if (t == T_Compound) {\ return Funcall_Compound (func, args, eval);\ } else if (t == T_Control_Point) {\ Funcall_Control_Point (func, args, eval);\ } else Primitive_Error ("application of non-procedure: ~s", func);\ extern void Switch_Environment (Object); extern unsigned int Stack_Size (); extern void Uncatchable_Error (char *); extern void Funcall_Control_Point (Object, Object, int) elk_attribute(__noreturn__); extern void Pop_Frame (); extern void Push_Frame (Object); /* Tail_Call indicates whether we are executing the last form in a * sequence of forms. If it is true and we are about to call a compound * procedure, we are allowed to check whether a tail-call can be * performed instead. */ int Tail_Call = 0; Object Sym_Lambda, Sym_Macro; static Object tc_fun, tc_argl, tc_env; Object Macro_Expand(), Funcall_Primitive(), Funcall_Compound(); void Init_Proc () { Define_Symbol (&Sym_Lambda, "lambda"); Define_Symbol (&Sym_Macro, "macro"); } void Check_Procedure (Object x) { register int t = TYPE(x); if (t != T_Primitive && t != T_Compound) Wrong_Type_Combination (x, "procedure"); if (t == T_Primitive && PRIM(x)->disc == NOEVAL) Primitive_Error ("invalid procedure: ~s", x); } Object P_Procedurep (Object x) { register int t = TYPE(x); return t == T_Primitive || t == T_Compound || t == T_Control_Point ? True : False; } Object P_Primitivep (Object x) { return TYPE(x) == T_Primitive ? True : False; } Object P_Compoundp (Object x) { return TYPE(x) == T_Compound ? True : False; } Object P_Macrop (Object x) { return TYPE(x) == T_Macro ? True : False; } Object Make_Compound () { Object proc; proc = Alloc_Object (sizeof (struct S_Compound), T_Compound, 0); COMPOUND(proc)->closure = COMPOUND(proc)->env = COMPOUND(proc)->name = Null; return proc; } Object Make_Primitive (Object (*fun)(), char const *name, int min, int max, enum discipline disc) { Object prim; register struct S_Primitive *pr; prim = Alloc_Object (sizeof (struct S_Primitive), T_Primitive, 0); pr = PRIM(prim); pr->tag = Null; pr->fun = fun; pr->name = name; pr->minargs = min; pr->maxargs = max; pr->disc = disc; return prim; } Object Eval (Object form) { register int t; register struct S_Symbol *sym; Object fun, binding, ret; static unsigned int tick; GC_Node; TC_Prolog; again: t = TYPE(form); if (t == T_Symbol) { sym = SYMBOL(form); if (TYPE(sym->value) == T_Unbound) { binding = Lookup_Symbol (form, 1); sym->value = Cdr (binding); } ret = sym->value; if (TYPE(ret) == T_Autoload) ret = Do_Autoload (form, ret); return ret; } if (t != T_Pair) { if (t == T_Null) Primitive_Error ("no subexpression in procedure call"); if (t == T_Vector) Primitive_Error ("unevaluable object: ~s", form); return form; } if ((tick++ & 7) == 0) if (Stack_Size () > Max_Stack) Uncatchable_Error ("Out of stack space"); /* * Avoid recursive Eval() for the most common case: */ fun = Car (form); if (TYPE(fun) != T_Symbol || (fun = SYMBOL(fun)->value, TYPE(fun) == T_Unbound) || TYPE(fun) == T_Autoload) { GC_Link (form); TC_Disable; fun = Eval (Car (form)); TC_Enable; GC_Unlink; } form = Cdr (form); t = TYPE(fun); if (t == T_Macro) { form = Macro_Expand (fun, form); goto again; } Funcall_Switch (t, fun, form, 1); /*NOTREACHED*/ } Object P_Eval (int argc, Object *argv) { Object ret, oldenv; GC_Node; if (argc == 1) return Eval (argv[0]); Check_Type (argv[1], T_Environment); oldenv = The_Environment; GC_Link (oldenv); Switch_Environment (argv[1]); ret = Eval (argv[0]); Switch_Environment (oldenv); GC_Unlink; return ret; } Object P_Apply (int argc, Object *argv) { Object ret, list, tail, cell, last; register int i; GC_Node3; Check_Procedure (argv[0]); /* Make a list of all args but the last, then append the * last arg (which must be a proper list) to this list. */ list = tail = last = Null; GC_Link3 (list, tail, last); for (i = 1; i < argc-1; i++, tail = cell) { cell = Cons (argv[i], Null); if (Nullp (list)) list = cell; else (void)P_Set_Cdr (tail, cell); } for (last = argv[argc-1]; !Nullp (last); last = Cdr (last), tail = cell) { cell = Cons (P_Car (last), Null); if (Nullp (list)) list = cell; else (void)P_Set_Cdr (tail, cell); } ret = Funcall (argv[0], list, 0); GC_Unlink; return ret; } Object Funcall_Primitive (Object fun, Object argl, int eval) { register struct S_Primitive *prim; register int argc, i; char const *last_tag; register Object *argv = NULL; Object abuf[MAX_ARGS_ON_STACK], r, e; GC_Node4; GCNODE gcv; TC_Prolog; Alloca_Begin; prim = PRIM(fun); last_tag = Error_Tag; /* avoid function calls here */ Error_Tag = prim->name; Get_Arglist_Length (argc, argl, r); /* r is temporary variable */ if (argc < prim->minargs || (prim->maxargs != MANY && argc > prim->maxargs)) Primitive_Error ("wrong number of arguments"); e = The_Environment; GC_Link4_Tag_Primitive (argl, fun, e, r); if (prim->disc == NOEVAL) { r = (prim->fun)(argl); } else { TC_Disable; /* * Skip the loop if argc==0 or argc==1 (special case below). */ if (prim->disc != EVAL || argc >= 2) { if (argc <= MAX_ARGS_ON_STACK) argv = abuf; else Alloca (argv, Object*, argc * sizeof (Object)); gcv.gclen = 1; gcv.gcobj = argv; gcv.next = &gc4; GC_List = &gcv; for (r = argl, i = 0; i < argc; i++, r = Cdr (r)) { argv[i] = eval ? Eval (Car (r)) : Car (r); gcv.gclen++; } TC_Enable; prim = PRIM(fun); /* fun has possibly been moved during gc */ } if (prim->disc == VARARGS) { r = (prim->fun)(argc, argv); } else { switch (argc) { case 0: r = (prim->fun)(); break; case 1: TC_Disable; r = eval ? Eval (Car (argl)) : Car (argl); TC_Enable; r = (PRIM(fun)->fun)(r); break; case 2: r = (prim->fun)(argv[0], argv[1]); break; case 3: r = (prim->fun)(argv[0], argv[1], argv[2]); break; case 4: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3]); break; case 5: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4]); break; case 6: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); break; case 7: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]); break; case 8: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7]); break; case 9: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8]); break; case 10: r = (prim->fun)(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9]); break; default: Panic ("too many args for primitive"); } } Alloca_End; } GC_Unlink; Error_Tag = last_tag; return r; } /* This macro is used by Funcall_Compound() below. Note that * if we are in a tail recursion, we are reusing the old procedure * frame; we just assign new values to the formal parameters. * Add_Binding() has been inlined here for speed. r is used as * a temporary variable. */ #define Lambda_Bind(var,val) {\ r = Cons (var, val);\ if (tail_calling)\ newframe = Cons (r, newframe);\ else\ frame = Cons (r, frame);\ } Object Funcall_Compound (Object fun, Object argl, int eval) { register int argc, min, max, i, tail_calling = 0; register Object *argv = NULL; Object abuf[MAX_ARGS_ON_STACK], rest, r, frame, tail, tail_call_env, oldenv, newframe; register GCNODE *p; GC_Node7; GCNODE gcv; Alloca_Begin; if (Tail_Call && eval) { for (p = GC_List; p && p->gclen != TAG_FUN; p = p->next) { if (p->gclen == TAG_TCFUN && EQ(*(p->gcobj), fun)) { SET(r, T_Special, 0); tc_fun = fun; tc_argl = argl; tc_env = The_Environment; return r; } } } r = frame = tail = newframe = Null; oldenv = The_Environment; GC_Link7_Tag_Compound (argl, fun, oldenv, frame, tail, newframe, r); again: Get_Arglist_Length (argc, argl, r); /* r is temporary variable here */ min = COMPOUND(fun)->min_args; max = COMPOUND(fun)->max_args; if (argc < min) Primitive_Error ("too few arguments for ~s", fun); if (max >= 0 && argc > max) Primitive_Error ("too many arguments for ~s", fun); if (tail_calling) { tail = The_Environment; Switch_Environment (tail_call_env); } else { if (argc <= MAX_ARGS_ON_STACK) argv = abuf; else Alloca (argv, Object*, argc * sizeof (Object)); } Tail_Call = 0; gcv.gclen = 1; gcv.gcobj = argv; gcv.next = &gc7; GC_List = &gcv; for (r = argl, i = 0; i < argc; i++, r = Cdr (r)) { argv[i] = eval ? Eval (Car (r)) : Car (r); gcv.gclen++; } if (tail_calling) Switch_Environment (tail); tail = Car (Cdr (COMPOUND(fun)->closure)); for (i = 0; i < min; i++, tail = Cdr (tail)) Lambda_Bind (Car (tail), argv[i]); if (max == -1) { rest = P_List (argc-i, argv+i); Lambda_Bind (tail, rest); } if (tail_calling) { Pop_Frame (); Push_Frame (newframe); } else { Switch_Environment (COMPOUND(fun)->env); Push_Frame (frame); } tail = Cdr (Cdr (COMPOUND(fun)->closure)); for (i = COMPOUND(fun)->numforms; i > 1; i--, tail = Cdr (tail)) (void)Eval (Car (tail)); Tail_Call = 1; r = Eval (Car (tail)); /* * If evaluation of the function body returned a T_Special object, * a tail-call has been taken place. If it is a tail-call to a * different function, just return, otherwise unpack new arguments * and environment and jump to the beginning. */ if (TYPE(r) == T_Special && EQ(fun, tc_fun)) { argl = tc_argl; tail_call_env = tc_env; tail_calling = 1; eval = 1; newframe = Null; goto again; } Tail_Call = 0; Pop_Frame (); Switch_Environment (oldenv); GC_Unlink; Alloca_End; return r; } Object Funcall (Object fun, Object argl, int eval) { register int t = TYPE(fun); Funcall_Switch (t, fun, argl, eval); /*NOTREACHED*/ } void Check_Formals (Object x, int *min, int *max) { Object s, t1, t2; *min = *max = 0; for (t1 = Car (x); !Nullp (t1); t1 = Cdr (t1)) { s = TYPE(t1) == T_Pair ? Car (t1) : t1; Check_Type (s, T_Symbol); for (t2 = Car (x); !EQ(t2, t1); t2 = Cdr (t2)) if (EQ(s, Car (t2))) Primitive_Error ("~s: duplicate variable binding", s); if (TYPE(t1) != T_Pair) break; (*min)++; (*max)++; } if (TYPE(t1) == T_Symbol) *max = -1; else if (!Nullp (t1)) Wrong_Type_Combination (t1, "list or symbol"); } Object P_Lambda (Object argl) { Object proc, closure; GC_Node2; proc = Null; GC_Link2 (argl, proc); proc = Make_Compound (); closure = Cons (Sym_Lambda, argl); COMPOUND(proc)->closure = closure; COMPOUND(proc)->env = The_Environment; COMPOUND(proc)->numforms = Fast_Length (Cdr (argl)); Check_Formals (argl, &COMPOUND(proc)->min_args, &COMPOUND(proc)->max_args); GC_Unlink; return proc; } Object P_Procedure_Lambda (Object p) { Check_Type (p, T_Compound); return Copy_List (COMPOUND(p)->closure); } Object P_Procedure_Environment (Object p) { Check_Type (p, T_Compound); return COMPOUND(p)->env; } Object General_Map (int argc, Object *argv, register int accum) { register int i; Object *args; Object head, list, tail, cell, arglist, val; GC_Node2; GCNODE gcv; TC_Prolog; Alloca_Begin; Check_Procedure (argv[0]); Alloca (args, Object*, (argc-1) * sizeof (Object)); list = tail = Null; GC_Link2 (list, tail); gcv.gclen = argc; gcv.gcobj = args; gcv.next = &gc2; GC_List = &gcv; while (1) { for (i = 1; i < argc; i++) { head = argv[i]; if (Nullp (head)) { GC_Unlink; Alloca_End; return list; } Check_Type (head, T_Pair); args[i-1] = Car (head); argv[i] = Cdr (head); } arglist = P_List (argc-1, args); TC_Disable; val = Funcall (argv[0], arglist, 0); TC_Enable; if (!accum) continue; cell = Cons (val, Null); if (Nullp (list)) list = cell; else (void)P_Set_Cdr (tail, cell); tail = cell; } /*NOTREACHED*/ } Object P_Map (int argc, Object *argv) { return General_Map (argc, argv, 1); } Object P_For_Each (int argc, Object *argv) { return General_Map (argc, argv, 0); } Object Make_Macro () { Object mac; mac = Alloc_Object (sizeof (struct S_Macro), T_Macro, 0); MACRO(mac)->body = MACRO(mac)->name = Null; return mac; } Object P_Macro (Object argl) { Object mac, body; GC_Node2; mac = Null; GC_Link2 (argl, mac); mac = Make_Macro (); body = Cons (Sym_Macro, argl); MACRO(mac)->body = body; Check_Formals (argl, &MACRO(mac)->min_args, &MACRO(mac)->max_args); GC_Unlink; return mac; } Object P_Macro_Body (Object m) { Check_Type (m, T_Macro); return Copy_List (MACRO(m)->body); } Object Macro_Expand (Object mac, Object argl) { register int argc, min, max, i; Object frame, r, tail; GC_Node4; TC_Prolog; frame = tail = Null; GC_Link4 (argl, frame, tail, mac); Get_Arglist_Length (argc, argl, r); min = MACRO(mac)->min_args; max = MACRO(mac)->max_args; if (argc < min) Primitive_Error ("too few arguments for ~s", mac); if (max >= 0 && argc > max) Primitive_Error ("too many arguments for ~s", mac); tail = Car (Cdr (MACRO(mac)->body)); for (i = 0; i < min; i++, tail = Cdr (tail), argl = Cdr (argl)) frame = Add_Binding (frame, Car (tail), Car (argl)); if (max == -1) frame = Add_Binding (frame, tail, argl); Push_Frame (frame); TC_Disable; r = Begin (Cdr (Cdr (MACRO(mac)->body))); TC_Enable; Pop_Frame (); GC_Unlink; return r; } Object P_Macro_Expand (Object form) { Object ret, mac; GC_Node; Check_Type (form, T_Pair); GC_Link (form); mac = Eval (Car (form)); if (TYPE(mac) != T_Macro) ret = form; else ret = Macro_Expand (mac, Cdr (form)); GC_Unlink; return ret; }