package jscheme;
import java.io.*;
/** This class represents a Scheme interpreter.
* See http://www.norvig.com/jscheme.html for more documentation.
* This is version 1.4.
* @author Peter Norvig, peter@norvig.com http://www.norvig.com
* Copyright 1998 Peter Norvig, see http://www.norvig.com/license.html **/
public class Scheme extends SchemeUtils {
InputPort input = new InputPort(System.in);
PrintWriter output = new PrintWriter(System.out, true);
Environment globalEnvironment = new Environment();
/** Create a Scheme interpreter and load an array of files into it.
* Also load SchemePrimitives.CODE. **/
public Scheme(String[] files) {
Primitive.installPrimitives(globalEnvironment);
try {
load(new InputPort(new StringReader(SchemePrimitives.CODE)));
for (int i = 0; i < (files == null ? 0 : files.length); i++) {
load(files[i]);
}
} catch (RuntimeException e) {
;
}
}
//////////////// Main Loop
/** Create a new Scheme interpreter, passing in the command line args
* as files to load, and then enter a read eval write loop. **/
public static void main(String[] files) {
new Scheme(files).readEvalWriteLoop();
}
/** Prompt, read, eval, and write the result.
* Also sets up a catch for any RuntimeExceptions encountered. **/
public void readEvalWriteLoop() {
Object x;
for (;;) {
try {
output.print("> ");
output.flush();
if (input.isEOF(x = input.read()))
return;
write(eval(x), output, true);
output.println();
output.flush();
} catch (RuntimeException e) {
;
}
}
}
/** Eval all the expressions in a file. Calls load(InputPort). **/
public Object load(Object fileName) {
String name = stringify(fileName, false);
try {
return load(new InputPort(new FileInputStream(name)));
} catch (IOException e) {
return error("can't load " + name);
}
}
/** Eval all the expressions coming from an InputPort. **/
public Object load(InputPort in) {
Object x = null;
for (;;) {
if (in.isEOF(x = in.read()))
return TRUE;
eval(x);
}
}
//////////////// Evaluation
/** Evaluate an object, x, in an environment. **/
public Object eval(Object x, Environment env) {
// The purpose of the while loop is to allow tail recursion.
// The idea is that in a tail recursive position, we do "x = ..."
// and loop, rather than doing "return eval(...)".
while (true) {
if (x instanceof String) { // VARIABLE
return env.lookup((String) x);
} else if (!(x instanceof Pair)) { // CONSTANT
return x;
} else {
Object fn = first(x);
Object args = rest(x);
if (fn == "quote") { // QUOTE
return first(args);
} else if (fn == "begin") { // BEGIN
for (; rest(args) != null; args = rest(args)) {
eval(first(args), env);
}
x = first(args);
} else if (fn == "define") { // DEFINE
if (first(args) instanceof Pair)
return env.define(first(first(args)), eval(cons(
"lambda", cons(rest(first(args)), rest(args))),
env));
else
return env.define(first(args), eval(second(args), env));
} else if (fn == "set!") { // SET!
return env.set(first(args), eval(second(args), env));
} else if (fn == "if") { // IF
x = (truth(eval(first(args), env))) ? second(args)
: third(args);
} else if (fn == "cond") { // COND
x = reduceCond(args, env);
} else if (fn == "lambda") { // LAMBDA
return new Closure(first(args), rest(args), env);
} else if (fn == "macro") { // MACRO
return new Macro(first(args), rest(args), env);
} else { // PROCEDURE CALL:
fn = eval(fn, env);
if (fn instanceof Macro) { // (MACRO CALL)
x = ((Macro) fn).expand(this, (Pair) x, args);
} else if (fn instanceof Closure) { // (CLOSURE CALL)
Closure f = (Closure) fn;
x = f.body;
env = new Environment(f.parms, evalList(args, env),
f.env);
} else { // (OTHER PROCEDURE CALL)
return Procedure.proc(fn).apply(this,
evalList(args, env));
}
}
}
}
}
/** Eval in the global environment. **/
public Object eval(Object x) {
return eval(x, this.globalEnvironment);
}
/** Evaluate each of a list of expressions. **/
Pair evalList(Object list, Environment env) {
if (list == null)
return null;
else if (!(list instanceof Pair)) {
error("Illegal arg list: " + list);
return null;
} else
return cons(eval(first(list), env), evalList(rest(list), env));
}
/** Reduce a cond expression to some code which, when evaluated,
* gives the value of the cond expression. We do it that way to
* maintain tail recursion. **/
Object reduceCond(Object clauses, Environment env) {
Object result = null;
for (;;) {
if (clauses == null)
return FALSE;
Object clause = first(clauses);
clauses = rest(clauses);
if (first(clause) == "else"
|| truth(result = eval(first(clause), env)))
if (rest(clause) == null)
return list("quote", result);
else if (second(clause) == "=>")
return list(third(clause), list("quote", result));
else
return cons("begin", rest(clause));
}
}
}