package jscheme;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;

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

}


