diff --git a/mps/example/scheme/scheme.c b/mps/example/scheme/scheme.c index 5751d5aee71..72f1251d4d3 100644 --- a/mps/example/scheme/scheme.c +++ b/mps/example/scheme/scheme.c @@ -1 +1,2831 @@ -/* scheme.c -- SCHEME INTERPRETER EXAMPLE FOR THE MEMORY POOL SYSTEM * * $Id$ * Copyright (c) 2001-2012 Ravenbrook Limited. See end of file for license. * * This is a simple interpreter for a subset of the Scheme programming * language . * It is by no means the best or even the right way to implement Scheme, * but it serves the purpose of showing how the Memory Pool System can be * used as part of a programming language run-time system. * * TO DO * - unbounded integers, other number types. * - do, named let. * - Quasiquote implementation is messy. * - Lots of library. * - \#foo unsatisfactory in read and print * - tail recursion (pass current function to eval) */ #include #include #include #include #include #include #include #include #include "mps.h" #include "mpsavm.h" #include "mpscamc.h" /* LANGUAGE EXTENSION */ #define unless(c) if(!(c)) #define LENGTH(array) (sizeof(array) / sizeof(array[0])) /* CONFIGURATION PARAMETERS */ #define SYMMAX ((size_t)255) /* max length of a symbol */ #define MSGMAX ((size_t)255) /* max length of error message */ #define STRMAX ((size_t)255) /* max length of a string */ /* DATA TYPES */ /* obj_t -- scheme object type * * obj_t is a pointer to a union, obj_u, which has members for * each scheme representation. * * The obj_u also has a "type" member. Each representation * structure also has a "type" field first. ANSI C guarantees * that these type fields correspond [section?]. * * Objects are allocated by allocating one of the representation * structures and casting the pointer to it to type obj_t. This * allows objects of different sizes to be represented by the * same type. * * To access an object, check its type by reading TYPE(obj), then * access the fields of the representation, e.g. * if(TYPE(obj) == TYPE_PAIR) fiddle_with(CAR(obj)); */ typedef union obj_u *obj_t; typedef obj_t (*entry_t)(obj_t env, obj_t op_env, obj_t operator, obj_t rands); typedef int type_t; enum { TYPE_PAIR, TYPE_INTEGER, TYPE_SYMBOL, TYPE_SPECIAL, TYPE_OPERATOR, TYPE_STRING, TYPE_PORT, TYPE_PROMISE, TYPE_CHARACTER, TYPE_VECTOR, TYPE_FWD2, /* two-word broken heart */ TYPE_FWD, /* three-words and up broken heart */ TYPE_PAD1 /* one-word padding object */ }; typedef struct type_s { type_t type; } type_s; typedef struct pair_s { type_t type; /* TYPE_PAIR */ obj_t car, cdr; /* first and second projections */ } pair_s; typedef struct symbol_s { type_t type; /* TYPE_SYMBOL */ size_t length; /* length of symbol string (excl. NUL) */ char string[1]; /* symbol string, NUL terminated */ } symbol_s; typedef struct integer_s { type_t type; /* TYPE_INTEGER */ long integer; /* the integer */ } integer_s; typedef struct special_s { type_t type; /* TYPE_SPECIAL */ char *name; /* printed representation, NUL terminated */ } special_s; typedef struct operator_s { type_t type; /* TYPE_OPERATOR */ char *name; /* printed name, NUL terminated */ entry_t entry; /* entry point -- see eval() */ obj_t arguments, body; /* function arguments and code */ obj_t env, op_env; /* closure environments */ } operator_s; typedef struct string_s { type_t type; /* TYPE_STRING */ size_t length; /* number of chars in string */ char string[1]; /* string, NUL terminated */ } string_s; typedef struct port_s { type_t type; /* TYPE_PORT */ obj_t name; /* name of stream */ FILE *stream; } port_s; typedef struct character_s { type_t type; /* TYPE_CHARACTER */ char c; /* the character */ } character_s; typedef struct vector_s { type_t type; /* TYPE_VECTOR */ size_t length; /* number of elements */ obj_t vector[1]; /* vector elements */ } vector_s; /* fwd, fwd2, pad1 -- MPS forwarding and padding objects * * These object types are here to satisfy the MPS Format Protocol for * format variant "A". * * The MPS needs to be able to replace any object with forwarding object * or [broken heart](http://www.memorymanagement.org/glossary/b.html#broken.heart) * and since the smallest normal object defined above is two words long, * we have two kinds of forwarding objects: FWD2 is exactly two words * long, and FWD stores a size for larger objects. There are cleverer * ways to do this with bit twiddling, of course. * * The MPS needs to be able to pad out any area of memory that's a * multiple of the pool alignment. We've chosen an single word alignment * for this Scheme, so we have to have a special padding object, PAD1, * for single words. For larger objects we can just use forwarding objects * with NULL in their `fwd` fields. See `obj_isfwd` for details. * * See obj_pad, obj_fwd etc. to see how these are used. */ typedef struct fwd2_s { type_t type; /* TYPE_FWD2 */ obj_t fwd; /* forwarded object */ } fwd2_s; typedef struct fwd_s { type_t type; /* TYPE_FWD */ obj_t fwd; /* forwarded object */ size_t size; /* total size of this object */ } fwd_s; typedef struct pad1_s { type_t type; /* TYPE_PAD1 */ } pad1_s; typedef union obj_u { type_s type; /* one of TYPE_* */ pair_s pair; symbol_s symbol; integer_s integer; special_s special; operator_s operator; string_s string; port_s port; character_s character; vector_s vector; fwd2_s fwd2; fwd_s fwd; } obj_s; /* structure macros */ #define TYPE(obj) ((obj)->type.type) #define CAR(obj) ((obj)->pair.car) #define CDR(obj) ((obj)->pair.cdr) #define CAAR(obj) CAR(CAR(obj)) #define CADR(obj) CAR(CDR(obj)) #define CDAR(obj) CDR(CAR(obj)) #define CDDR(obj) CDR(CDR(obj)) #define CADDR(obj) CAR(CDDR(obj)) #define CDDDR(obj) CDR(CDDR(obj)) #define CDDAR(obj) CDR(CDAR(obj)) #define CADAR(obj) CAR(CDAR(obj)) /* GLOBAL DATA */ /* total -- total allocated bytes */ static size_t total; /* symtab -- symbol table * * The symbol table is a hash-table containing objects of TYPE_SYMBOL. * When a string is "interned" it is looked up in the table, and added * only if it is not there. This guarantees that all symbols which * are equal are actually the same object. * * The symbol table is simply a malloc'd array of obj_t pointers. Since * it's outside the MPS and refers to objects we want the MPS to keep * alive, it must be declared to the MPS as a root. Search for * occurrences of `symtab_root` to see how this is done. */ static obj_t *symtab; static size_t symtab_size; static mps_root_t symtab_root; /* special objects * * These global variables are initialized to point to objects of * TYPE_SPECIAL by main. They are used as markers for various * special purposes. */ static obj_t obj_empty; /* (), the empty list */ static obj_t obj_eof; /* end of file */ static obj_t obj_error; /* error indicator */ static obj_t obj_true; /* #t, boolean true */ static obj_t obj_false; /* #f, boolean false */ static obj_t obj_undefined; /* undefined result indicator */ /* predefined symbols * * These global variables are initialized to point to interned * objects of TYPE_SYMBOL. They have special meaning in the * Scheme language, and are used by the evaluator to parse code. */ static obj_t obj_quote; /* "quote" symbol */ static obj_t obj_quasiquote; /* "quasiquote" symbol */ static obj_t obj_lambda; /* "lambda" symbol */ static obj_t obj_begin; /* "begin" symbol */ static obj_t obj_else; /* "else" symbol */ static obj_t obj_unquote; /* "unquote" symbol */ static obj_t obj_unquote_splic; /* "unquote-splicing" symbol */ /* error handler * * The error_handler variable is initialized to point at a * jmp_buf to which the "error" function longjmps if there is * any kind of error during evaluation. It can be set up by * any enclosing function that wants to catch errors. There * is a default error handler in main, in the read-eval-print * loop. The error function also writes an error message * into "error_message" before longjmping, and this can be * displayed to the user when catching the error. * * [An error code should also be passed so that the error can * be decoded by enclosing code.] */ static jmp_buf *error_handler; static char error_message[MSGMAX+1]; /* MPS pools */ mps_arena_t arena; mps_pool_t obj_pool; mps_ap_t obj_ap; /* SUPPORT FUNCTIONS */ /* error -- throw an error condition * * The "error" function takes a printf-style format string * and arguments, writes the message into error_message and * longjmps to *error_handler. There must be a setjmp at * the other end to catch the condition and display the * message. */ static void error(char *format, ...) { va_list args; assert(error_handler != NULL); va_start(args, format); vsprintf(error_message, format, args); va_end(args); longjmp(*error_handler, 1); } /* make_* -- object constructors * * Each object type has a function here that allocates an instance of * that type. * * These functions illustrate the two-phase MPS Allocation Point Protocol * with `reserve` and `commmit`. This protocol allows very fast in-line * allocation without locking, but there is a very tiny chance that the * object must be re-initialized. In nearly all cases, however, it's * just a pointer bump. */ #define ALIGN(size) \ (((size) + sizeof(mps_word_t) - 1) & ~(sizeof(mps_word_t) - 1)) static obj_t make_pair(obj_t car, obj_t cdr) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(pair_s)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_pair"); obj = addr; obj->pair.type = TYPE_PAIR; CAR(obj) = car; CDR(obj) = cdr; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(pair_s); return obj; } static obj_t make_integer(long integer) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(integer_s)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_integer"); obj = addr; obj->integer.type = TYPE_INTEGER; obj->integer.integer = integer; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(integer_s); return obj; } static obj_t make_symbol(size_t length, char string[]) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(offsetof(symbol_s, string) + length+1); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_symbol"); obj = addr; obj->symbol.type = TYPE_SYMBOL; obj->symbol.length = length; memcpy(obj->symbol.string, string, length+1); } while(!mps_commit(obj_ap, addr, size)); total += size; return obj; } static obj_t make_string(size_t length, char string[]) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(offsetof(string_s, string) + length+1); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_string"); obj = addr; obj->string.type = TYPE_STRING; obj->string.length = length; memcpy(obj->string.string, string, length+1); } while(!mps_commit(obj_ap, addr, size)); total += size; return obj; } static obj_t make_special(char *string) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(special_s)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_special"); obj = addr; obj->special.type = TYPE_SPECIAL; obj->special.name = string; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(special_s); return obj; } static obj_t make_operator(char *name, entry_t entry, obj_t arguments, obj_t body, obj_t env, obj_t op_env) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(operator_s)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_operator"); obj = addr; obj->operator.type = TYPE_OPERATOR; obj->operator.name = name; obj->operator.entry = entry; obj->operator.arguments = arguments; obj->operator.body = body; obj->operator.env = env; obj->operator.op_env = op_env; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(operator_s); return obj; } static obj_t make_port(obj_t name, FILE *stream) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(port_s)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_operator"); obj = addr; obj->port.type = TYPE_PORT; obj->port.name = name; obj->port.stream = stream; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(port_s); return obj; } static obj_t make_character(char c) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(sizeof(character_s)); do { /* FIXME: Alignment! */ mps_res_t res = mps_reserve(&addr, obj_ap, size); if (res != MPS_RES_OK) error("out of memory in make_character"); obj = addr; obj->character.type = TYPE_CHARACTER; obj->character.c = c; } while(!mps_commit(obj_ap, addr, size)); total += sizeof(character_s); return obj; } static obj_t make_vector(size_t length, obj_t fill) { obj_t obj; mps_addr_t addr; size_t size = ALIGN(offsetof(vector_s, vector) + length * sizeof(obj_t)); do { mps_res_t res = mps_reserve(&addr, obj_ap, size); size_t i; if (res != MPS_RES_OK) error("out of memory in make_vector"); obj = addr; obj->vector.type = TYPE_VECTOR; obj->vector.length = length; for(i = 0; i < length; ++i) obj->vector.vector[i] = fill; } while(!mps_commit(obj_ap, addr, size)); total += size; return obj; } /* getnbc -- get next non-blank char from stream */ static int getnbc(FILE *stream) { int c; do c = getc(stream); while(isspace(c)); return c; } /* isealpha -- test for "extended alphabetic" char * * Scheme symbols may contain any "extended alphabetic" * character (see section 2.1 of R4RS). This function * returns non-zero if a character is in the set of * extended characters. */ static int isealpha(int c) { return strchr("+-.*/<=>!?:$%_&~^", c) != NULL; } /* hash -- hash a string to an unsigned long * * This hash function was derived (with permission) from * Paul Haahr's hash in the most excellent rc 1.4. */ static unsigned long hash(const char *s) { char c; unsigned long h=0; do { c=*s++; if(c=='\0') break; else h+=(c<<17)^(c<<11)^(c<<5)^(c>>1); c=*s++; if(c=='\0') break; else h^=(c<<14)+(c<<7)+(c<<4)+c; c=*s++; if(c=='\0') break; else h^=(~c<<11)|((c<<3)^(c>>1)); c=*s++; if(c=='\0') break; else h-=(c<<16)|(c<<9)|(c<<2)|(c&3); } while(c); return h; } /* find -- find entry for symbol in symbol table * * Look for a symbol matching the string in the symbol table. * If the symbol was found, returns the address of the symbol * table entry which points to the symbol. Otherwise it * either returns the address of a NULL entry into which the * new symbol should be inserted, or NULL if the symbol table * is full. */ static obj_t *find(char *string) { unsigned long i, h; h = hash(string) & (symtab_size-1); i = h; do { if(symtab[i] == NULL || strcmp(string, symtab[i]->symbol.string) == 0) return &symtab[i]; i = (i+h+1) & (symtab_size-1); } while(i != h); return NULL; } /* rehash -- double size of symbol table */ static void rehash(void) { obj_t *old_symtab = symtab; unsigned old_symtab_size = symtab_size; mps_root_t old_symtab_root = symtab_root; unsigned i; mps_res_t res; symtab_size *= 2; symtab = malloc(sizeof(obj_t) * symtab_size); if(symtab == NULL) error("out of memory"); /* Initialize the new table to NULL so that "find" will work. */ for(i = 0; i < symtab_size; ++i) symtab[i] = NULL; /* Once the symbol table is initialized with scannable references (NULL in this case) we must register it as a root before we copy objects across from the old symbol table. The MPS might be moving objects in memory at any time, and will arrange that both copies are updated atomically to the mutator (this Scheme interpreter). */ res = mps_root_create_table(&symtab_root, arena, mps_rank_exact(), 0, (mps_addr_t *)symtab, symtab_size); if(res != MPS_RES_OK) error("Couldn't register new symtab root"); for(i = 0; i < old_symtab_size; ++i) if(old_symtab[i] != NULL) { obj_t *where = find(old_symtab[i]->symbol.string); assert(where != NULL); /* new table shouldn't be full */ assert(*where == NULL); /* shouldn't be in new table */ *where = old_symtab[i]; } mps_root_destroy(old_symtab_root); free(old_symtab); } /* union-find string in symbol table, rehashing if necessary */ static obj_t intern(char *string) { obj_t *where; where = find(string); if(where == NULL) { rehash(); where = find(string); assert(where != NULL); /* shouldn't be full after rehash */ } if(*where == NULL) /* symbol not found in table */ *where = make_symbol(strlen(string), string); return *where; } static void print(obj_t obj, unsigned depth, FILE *stream) { switch(TYPE(obj)) { case TYPE_INTEGER: { fprintf(stream, "%ld", obj->integer.integer); } break; case TYPE_SYMBOL: { fputs(obj->symbol.string, stream); } break; case TYPE_SPECIAL: { fputs(obj->special.name, stream); } break; case TYPE_PORT: { assert(TYPE(obj->port.name) == TYPE_STRING); fprintf(stream, "#[port \"%s\"]", obj->port.name->string.string); } break; case TYPE_STRING: { size_t i; putc('"', stream); for(i = 0; i < obj->string.length; ++i) { char c = obj->string.string[i]; switch(c) { case '\\': fputs("\\\\", stream); break; case '"': fputs("\\\"", stream); break; default: putc(c, stream); break; } } putc('"', stream); } break; case TYPE_PROMISE: { assert(CAR(obj) == obj_true || CAR(obj) == obj_false); fprintf(stream, "#[%sevaluated promise ", CAR(obj) == obj_false ? "un" : ""); print(CDR(obj), depth - 1, stream); putc(']', stream); } break; case TYPE_PAIR: { if(TYPE(CAR(obj)) == TYPE_SYMBOL && TYPE(CDR(obj)) == TYPE_PAIR && CDDR(obj) == obj_empty) { if(CAR(obj) == obj_quote) { putc('\'', stream); if(depth == 0) fputs("...", stream); else print(CADR(obj), depth - 1, stream); break; } if(CAR(obj) == obj_quasiquote) { putc('`', stream); if(depth == 0) fputs("...", stream); else print(CADR(obj), depth - 1, stream); break; } if(CAR(obj) == obj_unquote) { putc(',', stream); if(depth == 0) fputs("...", stream); else print(CADR(obj), depth - 1, stream); break; } if(CAR(obj) == obj_unquote_splic) { fputs(",@", stream); if(depth == 0) fputs("...", stream); else print(CADR(obj), depth - 1, stream); break; } } putc('(', stream); if(depth == 0) fputs("...", stream); else { for(;;) { print(CAR(obj), depth - 1, stream); obj = CDR(obj); if(TYPE(obj) != TYPE_PAIR) break; putc(' ', stream); } if(obj != obj_empty) { fputs(" . ", stream); print(obj, depth - 1, stream); } } putc(')', stream); } break; case TYPE_VECTOR: { fputs("#(", stream); if(depth == 0) fputs("...", stream); else { size_t i; for(i = 0; i < obj->vector.length; ++i) { if(i > 0) putc(' ', stream); print(obj->vector.vector[i], depth - 1, stream); } } putc(')', stream); } break; case TYPE_OPERATOR: { fprintf(stream, "#[operator \"%s\" %p %p ", obj->operator.name, (void *)obj, (void *)obj->operator.entry); if(depth == 0) fputs("...", stream); else { print(obj->operator.arguments, depth - 1, stream); putc(' ', stream); print(obj->operator.body, depth - 1, stream); putc(' ', stream); print(obj->operator.env, depth - 1, stream); putc(' ', stream); print(obj->operator.op_env, depth - 1, stream); } putc(']', stream); } break; case TYPE_CHARACTER: { fprintf(stream, "#\\%c", obj->character.c); } break; default: assert(0); abort(); } } static obj_t read_integer(FILE *stream, int c) { long integer = 0; do { integer = integer*10 + c-'0'; c = getc(stream); } while(isdigit(c)); ungetc(c, stream); return make_integer(integer); } static obj_t read_symbol(FILE *stream, int c) { int length = 0; char string[SYMMAX+1]; do { string[length++] = tolower(c); c = getc(stream); } while(length < SYMMAX && (isalnum(c) || isealpha(c))); if(isalnum(c) || isealpha(c)) error("read: symbol too long"); string[length] = '\0'; ungetc(c, stream); return intern(string); } static obj_t read_string(FILE *stream, int c) { int length = 0; char string[STRMAX+1]; for(;;) { c = getc(stream); if(c == EOF) error("read: end of file during string"); if(c == '"') break; if(length >= STRMAX) error("read: string too long"); if(c == '\\') { c = getc(stream); switch(c) { case '\\': break; case '"': break; case 'n': c = '\n'; break; case 't': c = '\t'; break; case EOF: error("read: end of file in escape sequence in string"); default: error("read: unknown escape '%c'", c); } } string[length++] = c; } string[length] = '\0'; return make_string(length, string); } static obj_t read(FILE *stream); static obj_t read_quote(FILE *stream, int c) { return make_pair(obj_quote, make_pair(read(stream), obj_empty)); } static obj_t read_quasiquote(FILE *stream, int c) { return make_pair(obj_quasiquote, make_pair(read(stream), obj_empty)); } static obj_t read_unquote(FILE *stream, int c) { c = getc(stream); if(c == '@') return make_pair(obj_unquote_splic, make_pair(read(stream), obj_empty)); ungetc(c, stream); return make_pair(obj_unquote, make_pair(read(stream), obj_empty)); } static obj_t read_list(FILE *stream, int c) { obj_t list, new, end; list = obj_empty; for(;;) { c = getnbc(stream); if(c == ')' || c == '.') break; ungetc(c, stream); new = make_pair(read(stream), obj_empty); if(list == obj_empty) { list = new; end = new; } else { CDR(end) = new; end = new; } } if(c == '.') { if(list == obj_empty) error("read: unexpected dot"); CDR(end) = read(stream); c = getnbc(stream); } if(c != ')') error("read: expected close parenthesis"); return list; } static obj_t list_to_vector(obj_t list) { size_t i; obj_t l, vector; i = 0; l = list; while(TYPE(l) == TYPE_PAIR) { ++i; l = CDR(l); } if(l != obj_empty) return obj_error; vector = make_vector(i, obj_undefined); i = 0; l = list; while(TYPE(l) == TYPE_PAIR) { vector->vector.vector[i] = CAR(l); ++i; l = CDR(l); } return vector; } static obj_t read_special(FILE *stream, int c) { c = getnbc(stream); switch(tolower(c)) { case 't': return obj_true; case 'f': return obj_false; case '\\': { /* character (R4RS 6.6) */ c = getc(stream); if(c == EOF) error("read: end of file reading character literal"); return make_character(c); } case '(': { /* vector (R4RS 6.8) */ obj_t list = read_list(stream, c); obj_t vector = list_to_vector(list); if(vector == obj_error) error("read: illegal vector syntax"); return vector; } } error("read: unknown special '%c'", c); return obj_error; } static obj_t read(FILE *stream) { int c; c = getnbc(stream); if(c == EOF) return obj_eof; if(isdigit(c)) return read_integer(stream, c); switch(c) { case '\'': return read_quote(stream, c); case '`': return read_quasiquote(stream, c); case ',': return read_unquote(stream, c); case '(': return read_list(stream, c); case '#': return read_special(stream, c); case '"': return read_string(stream, c); case '-': case '+': { int next = getc(stream); if(isdigit(next)) { obj_t integer = read_integer(stream, next); if(c == '-') integer->integer.integer = -integer->integer.integer; return integer; } ungetc(next, stream); } break; /* fall through to read as symbol */ } if(isalpha(c) || isealpha(c)) return read_symbol(stream, c); error("read: illegal char '%c'", c); return obj_error; } /* lookup_in_frame -- look up a symbol in single frame * * Search a single frame of the environment for a symbol binding. */ static obj_t lookup_in_frame(obj_t frame, obj_t symbol) { while(frame != obj_empty) { assert(TYPE(frame) == TYPE_PAIR); assert(TYPE(CAR(frame)) == TYPE_PAIR); assert(TYPE(CAAR(frame)) == TYPE_SYMBOL); if(CAAR(frame) == symbol) return CAR(frame); frame = CDR(frame); } return obj_undefined; } /* lookup -- look up symbol in environment * * Search an entire environment for a binding of a symbol. */ static obj_t lookup(obj_t env, obj_t symbol) { obj_t binding; while(env != obj_empty) { assert(TYPE(env) == TYPE_PAIR); binding = lookup_in_frame(CAR(env), symbol); if(binding != obj_undefined) return binding; env = CDR(env); } return obj_undefined; } /* define -- define symbol in environment * * In Scheme, define will actually rebind (i.e. set) a symbol in the * same frame of the environment, or add a binding if it wasn't already * set. This has the effect of making bindings local to functions * (see how entry_interpret adds an empty frame to the environments), * allowing recursion, and allowing redefinition at the top level. * See R4R2 section 5.2 for details. */ static void define(obj_t env, obj_t symbol, obj_t value) { obj_t binding; assert(TYPE(env) == TYPE_PAIR); /* always at least one frame */ binding = lookup_in_frame(CAR(env), symbol); if(binding != obj_undefined) CDR(binding) = value; else CAR(env) = make_pair(make_pair(symbol, value), CAR(env)); } static obj_t eval(obj_t env, obj_t op_env, obj_t exp); static obj_t eval(obj_t env, obj_t op_env, obj_t exp) { /* self-evaluating */ if(TYPE(exp) == TYPE_INTEGER || (TYPE(exp) == TYPE_SPECIAL && exp != obj_empty) || TYPE(exp) == TYPE_STRING || TYPE(exp) == TYPE_CHARACTER) return exp; /* symbol lookup */ if(TYPE(exp) == TYPE_SYMBOL) { obj_t binding = lookup(env, exp); if(binding == obj_undefined) error("eval: unbound symbol \"%s\"", exp->symbol.string); return CDR(binding); } /* apply operator or function */ if(TYPE(exp) == TYPE_PAIR) { obj_t operator; if(TYPE(CAR(exp)) == TYPE_SYMBOL) { obj_t binding = lookup(op_env, CAR(exp)); if(binding != obj_undefined) { operator = CDR(binding); assert(TYPE(operator) == TYPE_OPERATOR); return (*operator->operator.entry)(env, op_env, operator, CDR(exp)); } } operator = eval(env, op_env, CAR(exp)); unless(TYPE(operator) == TYPE_OPERATOR) error("eval: application of non-function"); return (*operator->operator.entry)(env, op_env, operator, CDR(exp)); } error("eval: unknown syntax"); return obj_error; } /* OPERATOR UTILITIES */ /* eval_list -- evaluate list of expressions giving list of results * * eval_list evaluates a list of expresions and yields a list of their * results, in order. If the list is badly formed, an error is thrown * using the message given. */ static obj_t eval_list(obj_t env, obj_t op_env, obj_t list, char *message) { obj_t result, end, pair; result = obj_empty; while(list != obj_empty) { if(TYPE(list) != TYPE_PAIR) error(message); pair = make_pair(eval(env, op_env, CAR(list)), obj_empty); if(result == obj_empty) result = pair; else CDR(end) = pair; end = pair; list = CDR(list); } return result; } /* eval_args1 -- evaluate some operator arguments * * See eval_args and eval_args_rest for usage. */ static obj_t eval_args1(char *name, obj_t env, obj_t op_env, obj_t operands, unsigned n, va_list args) { unsigned i; for(i = 0; i < n; ++i) { unless(TYPE(operands) == TYPE_PAIR) error("eval: too few arguments to %s", name); *va_arg(args, obj_t *) = eval(env, op_env, CAR(operands)); operands = CDR(operands); } return operands; } /* eval_args -- evaluate operator arguments without rest list * * eval_args evaluates the first "n" expressions from the list of * expressions in "operands", returning the rest of the operands * unevaluated. It puts the results of evaluation in the addresses * passed in the vararg list. If the operands list is badly formed * an error is thrown using the operator name passed. For example: * * eval_args("foo", env, op_env, operands, 2, &arg1, &arg2); */ static void eval_args(char *name, obj_t env, obj_t op_env, obj_t operands, unsigned n, ...) { va_list args; va_start(args, n); operands = eval_args1(name, env, op_env, operands, n, args); unless(operands == obj_empty) error("eval: too many arguments to %s", name); va_end(args); } /* eval_args_rest -- evaluate operator arguments with rest list * * eval_args_rest evaluates the first "n" expressions from the list of * expressions in "operands", then evaluates the rest of the operands * using eval_list and puts the result at *restp. It puts the results * of evaluating the first "n" operands in the addresses * passed in the vararg list. If the operands list is badly formed * an error is thrown using the operator name passed. For example: * * eval_args_rest("foo", env, op_env, operands, &rest, 2, &arg1, &arg2); */ static void eval_args_rest(char *name, obj_t env, obj_t op_env, obj_t operands, obj_t *restp, unsigned n, ...) { va_list args; va_start(args, n); operands = eval_args1(name, env, op_env, operands, n, args); va_end(args); *restp = eval_list(env, op_env, operands, "eval: badly formed argument list"); } /* BUILT-IN OPERATORS */ /* entry_interpret -- interpreted function entry point * * When a function is made using lambda (see entry_lambda) an operator * is created with entry_interpret as its entry point, and the arguments * and body of the function. The entry_interpret function evaluates * the operands of the function and binds them to the argument names * in a new frame added to the lambda's closure environment. It then * evaluates the body in that environment, executing the function. */ static obj_t entry_interpret(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t arguments, fun_env, fun_op_env; assert(TYPE(operator) == TYPE_OPERATOR); /* Make a new frame so that bindings are local to the function. */ /* Arguments will be bound in this new frame. */ fun_env = make_pair(obj_empty, operator->operator.env); fun_op_env = make_pair(obj_empty, operator->operator.op_env); arguments = operator->operator.arguments; while(operands != obj_empty) { if(arguments == obj_empty) error("eval: function applied to too many arguments"); if(TYPE(arguments) == TYPE_SYMBOL) { define(fun_env, arguments, eval_list(env, op_env, operands, "eval: badly formed argument list")); operands = obj_empty; arguments = obj_empty; } else { assert(TYPE(arguments) == TYPE_PAIR && TYPE(CAR(arguments)) == TYPE_SYMBOL); define(fun_env, CAR(arguments), eval(env, op_env, CAR(operands))); operands = CDR(operands); arguments = CDR(arguments); } } if(arguments != obj_empty) error("eval: function applied to too few arguments"); return eval(fun_env, fun_op_env, operator->operator.body); } /* entry_quote -- return operands unevaluated * * In Scheme, (quote foo) evaluates to foo (i.e. foo is not evaluated). * See R4RS 4.1.2. The reader expands "'x" to "(quote x)". */ static obj_t entry_quote(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { unless(TYPE(operands) == TYPE_PAIR && CDR(operands) == obj_empty) error("%s: illegal syntax", operator->operator.name); return CAR(operands); } /* entry_define -- bind a symbol in the top frame of the environment * * In Scheme, "(define )" evaluates expressions * and binds it to symbol in the top frame of the environment (see * R4RS 5.2). This code also allows the non-essential syntax for * define, "(define ( ) )" as a short-hand for * "(define (lambda () ))". */ static obj_t entry_define(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t symbol, value; unless(TYPE(operands) == TYPE_PAIR && TYPE(CDR(operands)) == TYPE_PAIR && CDDR(operands) == obj_empty) error("%s: illegal syntax", operator->operator.name); if(TYPE(CAR(operands)) == TYPE_SYMBOL) { symbol = CAR(operands); value = eval(env, op_env, CADR(operands)); } else if(TYPE(CAR(operands)) == TYPE_PAIR && TYPE(CAAR(operands)) == TYPE_SYMBOL) { symbol = CAAR(operands); value = eval(env, op_env, make_pair(obj_lambda, make_pair(CDAR(operands), CDR(operands)))); } else error("%s: applied to binder", operator->operator.name); define(env, symbol, value); return symbol; } /* entry_if -- one- or two-armed conditional * * "(if )" and "(if )". * See R4RS 4.1.5. */ static obj_t entry_if(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t test; unless(TYPE(operands) == TYPE_PAIR && TYPE(CDR(operands)) == TYPE_PAIR && (CDDR(operands) == obj_empty || (TYPE(CDDR(operands)) == TYPE_PAIR && CDDDR(operands) == obj_empty))) error("%s: illegal syntax", operator->operator.name); test = eval(env, op_env, CAR(operands)); /* Anything which is not #f counts as true [R4RS 6.1]. */ if(test != obj_false) return eval(env, op_env, CADR(operands)); if(TYPE(CDDR(operands)) == TYPE_PAIR) return eval(env, op_env, CADDR(operands)); return obj_undefined; } /* entry_cond -- general conditional * * "(cond ( ...) ( ...) ... [(else ...)])" */ static obj_t entry_cond(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { unless(TYPE(operands) == TYPE_PAIR) error("%s: illegal syntax", operator->operator.name); while(TYPE(operands) == TYPE_PAIR) { obj_t clause = CAR(operands); obj_t result; unless(TYPE(clause) == TYPE_PAIR && TYPE(CDR(clause)) == TYPE_PAIR) error("%s: illegal clause syntax", operator->operator.name); if(CAR(clause) == obj_else) { unless(CDR(operands) == obj_empty) error("%s: else clause must come last", operator->operator.name); result = obj_true; } else result = eval(env, op_env, CAR(clause)); if(result != obj_false) { for(;;) { clause = CDR(clause); if(TYPE(clause) != TYPE_PAIR) break; result = eval(env, op_env, CAR(clause)); } if(clause != obj_empty) error("%s: illegal clause syntax", operator->operator.name); return result; } operands = CDR(operands); } return obj_undefined; } /* entry_and -- (and ...) */ static obj_t entry_and(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { while(TYPE(operands) == TYPE_PAIR) { obj_t test = eval(env, op_env, CAR(operands)); if(test == obj_false) return obj_false; operands = CDR(operands); } if(operands != obj_empty) error("%s: illegal syntax", operator->operator.name); return obj_true; } /* entry_or -- (or ...) */ static obj_t entry_or(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { while(TYPE(operands) == TYPE_PAIR) { obj_t test = eval(env, op_env, CAR(operands)); if(test != obj_false) return obj_true; operands = CDR(operands); } if(operands != obj_empty) error("%s: illegal syntax", operator->operator.name); return obj_false; } /* entry_let -- (let ) */ /* @@@@ Too much common code with let* */ static obj_t entry_let(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t inner_env, bindings, result; unless(TYPE(operands) == TYPE_PAIR && TYPE(CDR(operands)) == TYPE_PAIR) error("%s: illegal syntax", operator->operator.name); inner_env = make_pair(obj_empty, env); /* @@@@ common with interpret */ bindings = CAR(operands); while(TYPE(bindings) == TYPE_PAIR) { obj_t binding = CAR(bindings); unless(TYPE(binding) == TYPE_PAIR && TYPE(CAR(binding)) == TYPE_SYMBOL && TYPE(CDR(binding)) == TYPE_PAIR && CDDR(binding) == obj_empty) error("%s: illegal binding", operator->operator.name); define(inner_env, CAR(binding), eval(env, op_env, CADR(binding))); bindings = CDR(bindings); } if(bindings != obj_empty) error("%s: illegal bindings list", operator->operator.name); operands = CDR(operands); while(TYPE(operands) == TYPE_PAIR) { result = eval(inner_env, op_env, CAR(operands)); operands = CDR(operands); } if(operands != obj_empty) error("%s: illegal expression list", operator->operator.name); return result; } /* entry_let_star -- (let* ) */ /* @@@@ Too much common code with let */ static obj_t entry_let_star(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t inner_env, bindings, result; unless(TYPE(operands) == TYPE_PAIR && TYPE(CDR(operands)) == TYPE_PAIR) error("%s: illegal syntax", operator->operator.name); inner_env = make_pair(obj_empty, env); /* @@@@ common with interpret */ bindings = CAR(operands); while(TYPE(bindings) == TYPE_PAIR) { obj_t binding = CAR(bindings); unless(TYPE(binding) == TYPE_PAIR && TYPE(CAR(binding)) == TYPE_SYMBOL && TYPE(CDR(binding)) == TYPE_PAIR && CDDR(binding) == obj_empty) error("%s: illegal binding", operator->operator.name); define(inner_env, CAR(binding), eval(inner_env, op_env, CADR(binding))); bindings = CDR(bindings); } if(bindings != obj_empty) error("%s: illegal bindings list", operator->operator.name); operands = CDR(operands); while(TYPE(operands) == TYPE_PAIR) { result = eval(inner_env, op_env, CAR(operands)); operands = CDR(operands); } if(operands != obj_empty) error("%s: illegal expression list", operator->operator.name); return result; } /* entry_letrec -- (letrec ) */ /* @@@@ Too much common code with let and let* */ static obj_t entry_letrec(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t inner_env, bindings, result; unless(TYPE(operands) == TYPE_PAIR && TYPE(CDR(operands)) == TYPE_PAIR) error("%s: illegal syntax", operator->operator.name); inner_env = make_pair(obj_empty, env); /* @@@@ common with interpret */ bindings = CAR(operands); while(TYPE(bindings) == TYPE_PAIR) { obj_t binding = CAR(bindings); unless(TYPE(binding) == TYPE_PAIR && TYPE(CAR(binding)) == TYPE_SYMBOL && TYPE(CDR(binding)) == TYPE_PAIR && CDDR(binding) == obj_empty) error("%s: illegal binding", operator->operator.name); define(inner_env, CAR(binding), obj_undefined); bindings = CDR(bindings); } if(bindings != obj_empty) error("%s: illegal bindings list", operator->operator.name); bindings = CAR(operands); while(TYPE(bindings) == TYPE_PAIR) { obj_t binding = CAR(bindings); define(inner_env, CAR(binding), eval(inner_env, op_env, CADR(binding))); bindings = CDR(bindings); } operands = CDR(operands); while(TYPE(operands) == TYPE_PAIR) { result = eval(inner_env, op_env, CAR(operands)); operands = CDR(operands); } if(operands != obj_empty) error("%s: illegal expression list", operator->operator.name); return result; } /* entry_do -- (do (( ) ...) ( ...) ...) */ static obj_t entry_do(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { error("%s: unimplemented", operator->operator.name); return obj_error; } /* entry_delay -- (delay ) */ static obj_t entry_delay(obj_t env, obj_t op_env, obj_t operator, obj_t operands) { obj_t promise; unless(TYPE(operands) == TYPE_PAIR && CDR(operands) == obj_empty) error("%s: illegal syntax", operator->operator.name); promise = make_pair(obj_false, make_operator("anonymous promise", entry_interpret, obj_empty, CAR(operands), env, op_env)); TYPE(promise) = TYPE_PROMISE; return promise; } /* entry_quasiquote -- (quasiquote