509 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			509 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| #
 | |
| # $Id$
 | |
| #
 | |
| # This program is a modified version of awklisp originally written 
 | |
| # by Darius Bacon. The only modification is to append a semicolon
 | |
| # onto the end of each statement to cater for the semicolon requirement
 | |
| # of ASEAWK. The original file of this file has been renamed to awklisp.org.
 | |
| #
 | |
| 
 | |
| # --- Representation of Lisp data
 | |
| 
 | |
| BEGIN {
 | |
|     a_number = 0;
 | |
|     pair_ptr = a_pair = 1;
 | |
|     symbol_ptr = a_symbol = 2;
 | |
|     
 | |
|     type_name[a_number] = "number";
 | |
|     type_name[a_pair] = "pair";
 | |
|     type_name[a_symbol] = "symbol";
 | |
| }
 | |
| 
 | |
| function is(type, expr)	
 | |
| { 
 | |
|     if (expr % 4 != type) 
 | |
|         error("Expected a " type_name[type] ", not a " type_name[expr % 4]) ;
 | |
|     return expr;
 | |
| }
 | |
| 
 | |
| function is_number(expr)	{ return expr % 4 == 0; }
 | |
| function is_pair(expr)		{ return expr % 4 == 1; }
 | |
| function is_symbol(expr)	{ return expr % 4 == 2; }
 | |
| function is_atom(expr)		{ return expr % 4 != 1; }
 | |
| 
 | |
| function make_number(n)		{ return n * 4; }
 | |
| 
 | |
| function numeric_value(expr)
 | |
| { 
 | |
|     if (expr % 4 != 0) error("Not a number");
 | |
|     return expr / 4;
 | |
| }
 | |
| 
 | |
| # Return the symbol :string names.
 | |
| function string_to_symbol(string)
 | |
| {
 | |
|     if (string in intern)
 | |
|         return intern[string];
 | |
|     symbol_ptr += 4;
 | |
|     intern[string] = symbol_ptr;
 | |
|     printname[symbol_ptr] = string;
 | |
|     return symbol_ptr;
 | |
| }
 | |
| 
 | |
| # Define a primitive procedure, with :nparams parameters,
 | |
| # bound to the symbol named :name.
 | |
| function def_prim(name, nparams,	sym)
 | |
| {
 | |
|     sym = string_to_symbol(name);
 | |
|     value[sym] = string_to_symbol(sprintf("#<Primitive %s>", name));
 | |
|     if (nparams != "")
 | |
|         num_params[value[sym]] = nparams;
 | |
|     return value[sym];
 | |
| }
 | |
| 
 | |
| # --- Garbage collection
 | |
| 
 | |
| # Make a new pair.
 | |
| function cons(the_car, the_cdr)
 | |
| {
 | |
|     while (pair_ptr in marks) {
 | |
| 	delete marks[pair_ptr];
 | |
| 	pair_ptr += 4;
 | |
|     }
 | |
|     if (pair_ptr == pair_limit)
 | |
|         gc(the_car, the_cdr);
 | |
|     car[pair_ptr] = the_car;
 | |
|     cdr[pair_ptr] = the_cdr;
 | |
|     pair_ptr += 4;
 | |
|     return pair_ptr - 4;
 | |
| }
 | |
| 
 | |
| function protect(object)	{ protected[++protected_ptr] = object; }
 | |
| function unprotect()		{ --protected_ptr; }
 | |
| 
 | |
| function mark(object)
 | |
| {
 | |
|     while (is_pair(object) && !(object in marks)) {		#** speed
 | |
|         marks[object] = 1;
 | |
|         mark(car[object]);
 | |
|         object = cdr[object];
 | |
|     }
 | |
| }
 | |
| 
 | |
| function gc(the_car, the_cdr,	p, i)
 | |
| {
 | |
|     if (loud_gc) 
 | |
|         printf("\nGC...") >"/dev/stderr";
 | |
|     mark(the_car); mark(the_cdr);
 | |
|     for (p in protected)
 | |
|         mark(protected[p]);
 | |
|     for (p in stack)
 | |
|         mark(stack[p]);
 | |
|     for (p in value)
 | |
|         mark(value[p]);
 | |
|     for (p in property) {
 | |
|         i = index(SUBSEP, p);
 | |
|         mark(substr(p, 1, i-1));
 | |
|         mark(substr(p, i+1));
 | |
|         mark(property[p]);
 | |
|     }
 | |
|     pair_ptr = a_pair;
 | |
|     while (pair_ptr in marks) {
 | |
| 	delete marks[pair_ptr];
 | |
| 	pair_ptr += 4;
 | |
|     }
 | |
|     if (pair_ptr == pair_limit) {
 | |
| 	if (loud_gc);
 | |
| 	    printf("Expanding heap...") >"/dev/stderr";
 | |
| 	pair_limit += 4 * heap_increment;
 | |
|     }
 | |
| }
 | |
| 
 | |
| # --- Set up
 | |
| 
 | |
| BEGIN {	
 | |
|     srand();
 | |
|     
 | |
|     frame_ptr = stack_ptr = 0;
 | |
| 
 | |
|     if (heap_increment == "") heap_increment = 1500;
 | |
|     pair_limit = a_pair + 4 * heap_increment;
 | |
| 
 | |
|     NIL 	= string_to_symbol("nil");
 | |
|     T 		= string_to_symbol("t");
 | |
|     value[NIL] = NIL;
 | |
|     value[T] = T;
 | |
|     car[NIL] = cdr[NIL] = NIL; # this is convenient in a couple places...
 | |
| 
 | |
|     THE_EOF_OBJECT = string_to_symbol("#eof");
 | |
|     value[string_to_symbol("the-eof-object")] = THE_EOF_OBJECT;
 | |
|     eof = "(eof)";
 | |
| 
 | |
|     QUOTE 	= string_to_symbol("quote");	is_special[QUOTE] = 1;
 | |
|     LAMBDA 	= string_to_symbol("lambda");	is_special[LAMBDA] = 1;
 | |
|     IF 		= string_to_symbol("if");	is_special[IF] = 1;
 | |
|     SETQ 	= string_to_symbol("set!");	is_special[SETQ] = 1;
 | |
|     DEFINE 	= string_to_symbol("define");	is_special[DEFINE] = 1;
 | |
|     PROGN 	= string_to_symbol("begin");	is_special[PROGN] = 1;
 | |
|     WHILE 	= string_to_symbol("while");	is_special[WHILE] = 1;
 | |
| 
 | |
|     EQ		= def_prim("eq?", 2);
 | |
|     NULL 	= def_prim("null?", 1);
 | |
|     CAR 	= def_prim("car", 1);
 | |
|     CDR 	= def_prim("cdr", 1);
 | |
|     CADR 	= def_prim("cadr", 1);
 | |
|     CDDR 	= def_prim("cddr", 1);
 | |
|     CONS 	= def_prim("cons", 2);
 | |
|     LIST 	= def_prim("list");
 | |
|     EVAL 	= def_prim("eval", 1);
 | |
|     APPLY 	= def_prim("apply", 2);
 | |
|     READ 	= def_prim("read", 0);
 | |
|     WRITE 	= def_prim("write", 1);
 | |
|     NEWLINE 	= def_prim("newline", 0);
 | |
|     ADD		= def_prim("+", 2);
 | |
|     SUB 	= def_prim("-", 2);
 | |
|     MUL 	= def_prim("*", 2);
 | |
|     DIV 	= def_prim("quotient", 2);
 | |
|     MOD 	= def_prim("remainder", 2);
 | |
|     LT 		= def_prim("<", 2);
 | |
|     GET 	= def_prim("get", 2);
 | |
|     PUT 	= def_prim("put", 3);
 | |
|     ATOMP 	= def_prim("atom?", 1);
 | |
|     PAIRP 	= def_prim("pair?", 1);
 | |
|     SYMBOLP 	= def_prim("symbol?", 1);
 | |
|     NUMBERP 	= def_prim("number?", 1);
 | |
|     SETCAR 	= def_prim("set-car!", 2);
 | |
|     SETCDR 	= def_prim("set-cdr!", 2);
 | |
|     NREV 	= def_prim("reverse!", 1);
 | |
|     GENSYM 	= def_prim("gensym", 0);
 | |
|     RANDOM	= def_prim("random", 1);
 | |
|     ERROR	= def_prim("error");
 | |
| 
 | |
|     DRIVER 	= string_to_symbol("top-level-driver");
 | |
| }
 | |
| 
 | |
| # --- The interpreter
 | |
| 
 | |
| BEGIN {	
 | |
|     for (;;) {
 | |
|         if (DRIVER in value && value[DRIVER] != NIL)
 | |
|             apply(value[DRIVER]);
 | |
|         else {
 | |
|             expr = read();
 | |
|             if (expr == THE_EOF_OBJECT)
 | |
|                 break;
 | |
|             protect(expr);
 | |
|             print_expr(eval(expr));
 | |
|             unprotect();
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     if (profiling)
 | |
|         for (proc in call_count) {
 | |
|             printf("%5d ", call_count[proc]);
 | |
|             print_expr(proc);
 | |
|         }
 | |
| }
 | |
| 
 | |
| # All the interpretation routines have the precondition that their
 | |
| # arguments are protected from garbage collection.
 | |
| 
 | |
| function eval(expr,	old_frame_ptr)
 | |
| {
 | |
|     if (is_atom(expr))			#** speed
 | |
|         if (is_symbol(expr)) {
 | |
|             if (!(expr in value)) error("Unbound variable: " printname[expr]);
 | |
|             return value[expr];
 | |
|         } else
 | |
|             return expr;
 | |
| 
 | |
|     op = car[expr];	# op is global to save awk stack space
 | |
| 
 | |
|     if (!(op in is_special)) {
 | |
|         old_frame_ptr = frame_ptr;
 | |
|         frame_ptr = stack_ptr;
 | |
| 
 | |
|         eval_rands(cdr[expr]);
 | |
|         protect(proc = eval(car[expr]));
 | |
|         result = apply(proc);
 | |
|         unprotect();
 | |
| 
 | |
|         stack_ptr = frame_ptr;
 | |
|         frame_ptr = old_frame_ptr;
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     if (op == QUOTE)	return car[cdr[expr]];
 | |
|     if (op == LAMBDA)	return expr;
 | |
|     if (op == IF)	return eval(car[cdr[expr]]) != NIL 
 | |
|                                 ? eval(car[cdr[cdr[expr]]])	
 | |
|                                 : eval(car[cdr[cdr[cdr[expr]]]]);
 | |
|     if (op == PROGN)	return progn(cdr[expr]);
 | |
|     if (op == SETQ)	{
 | |
|         if (!(car[cdr[expr]] in value))
 | |
|             error("Unbound variable: " printname[car[cdr[expr]]]);
 | |
|         return value[car[cdr[expr]]] = eval(car[cdr[cdr[expr]]]);
 | |
|     }
 | |
|     if (op == WHILE) {
 | |
|         while (eval(car[cdr[expr]]) != NIL)
 | |
|             progn(cdr[cdr[expr]]);
 | |
|         return NIL;
 | |
|     }
 | |
|     if (op == DEFINE) {
 | |
|         value[car[cdr[expr]]] = eval(car[cdr[cdr[expr]]]);
 | |
|         return car[cdr[expr]];
 | |
|     }
 | |
|     
 | |
|     error("BUG: Unknown special form");
 | |
| }
 | |
| 
 | |
| # Evaluate a sequence of expressions, returning the last value.
 | |
| function progn(exprs)
 | |
| {
 | |
|     for (; cdr[exprs] != NIL; exprs = cdr[exprs])
 | |
|         eval(car[exprs]);
 | |
|     return eval(car[exprs]);
 | |
| }
 | |
| 
 | |
| # Evaluate the operands of a procedure, pushing the results on the stack.
 | |
| function eval_rands(rands)
 | |
| {
 | |
|     for (; rands != NIL; rands = cdr[rands])
 | |
|         stack[stack_ptr++] = eval(car[rands]);
 | |
| }
 | |
| 
 | |
| # Call the procedure :proc, with args stack[frame_ptr]..stack[stack_ptr-1]
 | |
| # (in that order).
 | |
| function apply(proc)
 | |
| {
 | |
|     if (profiling) 
 | |
|         ++call_count[proc];
 | |
|     if (car[proc] == LAMBDA) {
 | |
|         extend_env(car[cdr[proc]]);
 | |
|         result = progn(cdr[cdr[proc]]); # result is global to save stack space
 | |
|         unwind_env(car[cdr[proc]]);
 | |
|         return result;
 | |
|     }
 | |
|     if (proc in num_params && num_params[proc] != stack_ptr - frame_ptr)
 | |
|         error("Wrong number of arguments to " printname[cdr[proc]]);
 | |
| 
 | |
|     if (proc == CAR)	return car[is(a_pair, stack[frame_ptr])];
 | |
|     if (proc == CDR)	return cdr[is(a_pair, stack[frame_ptr])];
 | |
|     if (proc == CONS)	return cons(stack[frame_ptr], stack[frame_ptr+1]);
 | |
|     if (proc == NULL)	return stack[frame_ptr] == NIL ? T : NIL;
 | |
|     if (proc == EQ)	return stack[frame_ptr] == stack[frame_ptr+1] ? T : NIL;
 | |
|     if (proc == ATOMP)	return is_atom(stack[frame_ptr]) ? T : NIL;
 | |
|     if (proc == ADD)	return is(a_number, stack[frame_ptr]) + is(a_number, stack[frame_ptr+1]);
 | |
|     if (proc == SUB)	return is(a_number, stack[frame_ptr]) - is(a_number, stack[frame_ptr+1]);
 | |
|     if (proc == MUL)	return make_number(numeric_value(stack[frame_ptr]) * numeric_value(stack[frame_ptr+1]));
 | |
|     if (proc == DIV)	return make_number(int(numeric_value(stack[frame_ptr]) / numeric_value(stack[frame_ptr+1])));
 | |
|     if (proc == MOD)	return make_number(numeric_value(stack[frame_ptr]) % numeric_value(stack[frame_ptr+1]));
 | |
|     if (proc == LT)	return (stack[frame_ptr] + 0 < stack[frame_ptr+1] + 0) ? T : NIL;
 | |
|     if (proc == GET)	return (stack[frame_ptr], stack[frame_ptr+1]) in property ? property[stack[frame_ptr], stack[frame_ptr+1]] : NIL;
 | |
|     if (proc == PUT) 	return property[stack[frame_ptr], stack[frame_ptr+1]] = stack[frame_ptr+2];
 | |
|     if (proc == CADR)	return car[is(a_pair, cdr[is(a_pair, stack[frame_ptr])])];
 | |
|     if (proc == CDDR)	return cdr[is(a_pair, cdr[is(a_pair, stack[frame_ptr])])];
 | |
|     if (proc == LIST)	return listify_args();
 | |
|     if (proc == SYMBOLP)return is_symbol(stack[frame_ptr]) ? T : NIL;
 | |
|     if (proc == PAIRP)	return is_pair(stack[frame_ptr]) ? T : NIL;
 | |
|     if (proc == NUMBERP)return is_number(stack[frame_ptr]) ? T : NIL;
 | |
|     if (proc == SETCAR)	return car[is(a_pair, stack[frame_ptr])] = stack[frame_ptr+1];
 | |
|     if (proc == SETCDR)	return cdr[is(a_pair, stack[frame_ptr])] = stack[frame_ptr+1];
 | |
|     if (proc == APPLY)	return do_apply(stack[frame_ptr], stack[frame_ptr+1]);
 | |
|     if (proc == EVAL)	return eval(stack[frame_ptr]);
 | |
|     if (proc == NREV)	return nreverse(stack[frame_ptr], NIL);
 | |
|     if (proc == WRITE)	{ write_expr(stack[frame_ptr]); printf(" "); return NIL; }
 | |
|     if (proc == NEWLINE){ printf("\n"); return NIL;}
 | |
|     if (proc == READ)	return read();
 | |
|     if (proc == RANDOM)	return make_number(int(rand() * numeric_value(stack[frame_ptr])));
 | |
|     if (proc == GENSYM)	return string_to_symbol("#G" ++gensym_counter);
 | |
|     if (proc == ERROR)	{ printf("Error!\n"); print_expr(listify_args()); exit(1); }
 | |
|     error("Unknown procedure type");
 | |
| }
 | |
| 
 | |
| function do_apply(proc, args,		old_frame_ptr)
 | |
| {
 | |
|     old_frame_ptr = frame_ptr;
 | |
|     frame_ptr = stack_ptr;
 | |
| 
 | |
|     for (; is_pair(args); args = cdr[args])
 | |
|         stack[stack_ptr++] = car[args];
 | |
|     if (args != NIL)
 | |
|         error("Bad argument to APPLY: not a proper list");
 | |
|     result = apply(proc);
 | |
| 
 | |
|     stack_ptr = frame_ptr;
 | |
|     frame_ptr = old_frame_ptr;
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| function listify_args(		p, result)
 | |
| {
 | |
|     result = NIL;
 | |
|     for (p = stack_ptr - 1; frame_ptr <= p; --p)
 | |
|         result = cons(stack[p], result);
 | |
|     return result;
 | |
| }
 | |
| 
 | |
| # --- The environment
 | |
| # The current environment is represented by the set of values
 | |
| # value[sym] where sym is a symbol.  extend_env(vars) adds a new
 | |
| # set of bindings for vars, saving the old values; unwind_env(vars)
 | |
| # restores those old values.  The new value for the nth member of
 | |
| # vars is frame_ptr[n]; coincidentally, that's also where we 
 | |
| # store away the old value, since that stack frame's not needed 
 | |
| # for anything else after the extend_env() call.
 | |
| 
 | |
| function extend_env(vars,	p, temp)
 | |
| {
 | |
|     for (p = frame_ptr; vars != NIL; vars = cdr[vars]) {
 | |
|         if (p == stack_ptr) 
 | |
| 	    error("Too many arguments to procedure");
 | |
|         temp = value[car[vars]];
 | |
|         value[car[vars]] = stack[p];
 | |
|         stack[p] = temp;
 | |
|         ++p;
 | |
|     }
 | |
|     if (p != stack_ptr) 
 | |
| 	error("Not enough arguments to procedure");
 | |
| }
 | |
| 
 | |
| function unwind_env(vars,	p)
 | |
| {
 | |
|     for (p = frame_ptr; vars != NIL; vars = cdr[vars]) {
 | |
|         if (stack[p] == "")
 | |
| 	    delete value[car[vars]];
 | |
| 	else
 | |
| 	    value[car[vars]] = stack[p];
 | |
| 	++p;
 | |
|     }
 | |
| }
 | |
| 
 | |
| # --- Output
 | |
| 
 | |
| function print_expr(expr)
 | |
| {
 | |
|     write_expr(expr);
 | |
|     print "";
 | |
| }
 | |
| 
 | |
| function write_expr(expr)
 | |
| {
 | |
|     if (is_atom(expr)) {
 | |
|         if (!is_symbol(expr))
 | |
|             printf("%d", numeric_value(expr));
 | |
|         else {
 | |
|             if (!(expr in printname))
 | |
|                 error("BUG: " expr " has no printname");
 | |
|             printf("%s", printname[expr]);
 | |
|         }
 | |
|     } else {
 | |
|         printf("(");
 | |
|         write_expr(car[expr]);
 | |
|         for (expr = cdr[expr]; is_pair(expr); expr = cdr[expr]) {
 | |
|             printf(" ");
 | |
|             write_expr(car[expr]);
 | |
|         }
 | |
|         if (expr != NIL) {
 | |
|             printf(" . ");
 | |
|             write_expr(expr);
 | |
|         }
 | |
|         printf(")");
 | |
|     }
 | |
| }
 | |
| 
 | |
| # --- Input
 | |
| 
 | |
| function read(		committed,	result)
 | |
| {
 | |
|     skip_blanks();
 | |
|     if (token == eof)
 | |
|         if (committed)
 | |
|             error("Unexpected EOF");
 | |
|         else
 | |
|             return THE_EOF_OBJECT;
 | |
|     if (token == "(") {			# read a list
 | |
|         advance();
 | |
|         result = NIL;
 | |
|         for (;;) {
 | |
|             skip_blanks();
 | |
|             if (token == ".") {
 | |
|                 advance();
 | |
|                 after_dot = read(1);
 | |
|                 skip_blanks();
 | |
|                 if (token != ")")
 | |
|                     error("')' expected");
 | |
|                 advance();
 | |
|                 return nreverse(result, after_dot);
 | |
|             } else if (token == ")") {
 | |
|                 advance();
 | |
|                 return nreverse(result, NIL);
 | |
|             } else {
 | |
|                 protect(result);
 | |
|                 result = cons(read(1), result);
 | |
|                 unprotect();
 | |
|             }
 | |
|         }
 | |
|     } else if (token == "'") {		# a quoted expression
 | |
|         advance();
 | |
|         return cons(QUOTE, cons(read(1), NIL));
 | |
|     } else if (token ~ /^-?[0-9]+$/) {	# a number
 | |
|         result = make_number(token);
 | |
|         advance();
 | |
|         return result;
 | |
|     } else {				# a symbol
 | |
|         result = string_to_symbol(token);
 | |
|         advance();
 | |
|         return result;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function skip_blanks()
 | |
| {
 | |
|     while (token ~ /^[ \t]*$/)
 | |
|         advance();
 | |
| }
 | |
| 
 | |
| function advance()
 | |
| {
 | |
|     if (token == eof) return eof;
 | |
|     if (token == "") {
 | |
|         if (getline line <= 0) {
 | |
|             token = eof;
 | |
|             return;
 | |
|         }
 | |
|     }
 | |
|     if (match(line, "^[()'.]") ||
 | |
|         match(line, "^[_A-Za-z0-9=!@$%&*<>?+\\-*/:]+") ||
 | |
|         match(line, "^[ \\t]+")) {
 | |
|         token = substr(line, RSTART, RLENGTH);
 | |
|         line = substr(line, RLENGTH+1);
 | |
|     } else if (line == "" || substr(line, 1, 1) == ";")
 | |
|         token = ""; # this kludge permits interactive use
 | |
|     else
 | |
|         error("Lexical error starting at " line);
 | |
| }
 | |
| 
 | |
| # --- Miscellany
 | |
| 
 | |
| # Destructively reverse :list and append :reversed_head.
 | |
| function nreverse(list, reversed_head,		tail)
 | |
| {
 | |
|     while (is_pair(list)) {		#** speed?
 | |
|         tail = cdr[list];
 | |
|         cdr[list] = reversed_head;
 | |
|         reversed_head = list;
 | |
|         list = tail;
 | |
|     }
 | |
|     if (list != NIL)
 | |
|  	error("Not a proper list - reverse!");
 | |
|     return reversed_head;
 | |
| }
 | |
| 
 | |
| function error(reason)
 | |
| {
 | |
|     print "ERROR: " reason >"/dev/stderr";
 | |
|     exit(1);
 | |
| }
 | |
| 
 |