597 lines
17 KiB
C
597 lines
17 KiB
C
/* 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 <sam@zoy.org>, 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;
|
|
}
|