518 lines
14 KiB
Plaintext
518 lines
14 KiB
Plaintext
|
#!/usr/bin/gawk -f
|
||
|
|
||
|
# GNU Awk 3.0.4 crashes on the most recent awklisp, so here's the old
|
||
|
# version for you to use instead.
|
||
|
|
||
|
|
||
|
# --- 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
|
||
|
}
|
||
|
|
||
|
# Make a new pair.
|
||
|
function cons(the_car, the_cdr)
|
||
|
{
|
||
|
if (free_list == NIL)
|
||
|
gc(the_car, the_cdr)
|
||
|
result = free_list
|
||
|
free_list = cdr[free_list]
|
||
|
car[result] = the_car
|
||
|
cdr[result] = the_cdr
|
||
|
return result
|
||
|
}
|
||
|
|
||
|
# 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
|
||
|
|
||
|
function expand_heap( limit)
|
||
|
{
|
||
|
if (loud_gc)
|
||
|
printf("Expanding heap...") >"/dev/stderr"
|
||
|
limit = pair_ptr + 4 * (heap_increment ? heap_increment : 1500)
|
||
|
for (; pair_ptr < limit; pair_ptr += 4) {
|
||
|
cdr[pair_ptr] = free_list
|
||
|
free_list = pair_ptr
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 sweep()
|
||
|
{
|
||
|
free_list = NIL
|
||
|
for (p = a_pair; p < pair_ptr; p += 4) # for (p in car) might be faster
|
||
|
if (!(p in marks)) {
|
||
|
cdr[p] = free_list
|
||
|
free_list = p
|
||
|
} else
|
||
|
delete marks[p]
|
||
|
}
|
||
|
|
||
|
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])
|
||
|
}
|
||
|
sweep()
|
||
|
if (free_list == NIL)
|
||
|
expand_heap()
|
||
|
}
|
||
|
|
||
|
# --- Set up
|
||
|
|
||
|
BEGIN {
|
||
|
srand()
|
||
|
|
||
|
frame_ptr = stack_ptr = 0
|
||
|
|
||
|
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...
|
||
|
|
||
|
free_list = NIL; expand_heap()
|
||
|
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)
|
||
|
}
|