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)); } } }