import java.util.*;
import java.lang.reflect.*;

class Scm extends ScmFuns {

    static Symbol lambda = string_Gsymbol("lambda");
    static Symbol quote  = string_Gsymbol("quote");
    static Symbol define = string_Gsymbol("define");
    static Symbol if_sym = string_Gsymbol("if");
    static Symbol cond   = string_Gsymbol("cond");

    public static Object apply(Object proc, Object args) {
	if (proc.getClass() == Scm.Procedure.class) {
	    Procedure the_proc = (Procedure) proc;
	    return eval_seq(the_proc.body,
			    the_proc.env.extend(the_proc.formals, args));
	} 
	// primitive
	// should make sure it is a method...
	Object result = null;
	Object arg_array [] = list_to_array(args);
	if (proc.getClass() != java.lang.reflect.Method.class) 
	    throw(new RuntimeException("attempt to apply non Method: " + proc.toString()));
	try {
	    result = ((java.lang.reflect.Method) proc).invoke(null, arg_array);
	} catch(java.lang.IllegalAccessException err) {stdout.pwtr.print(err);}
	catch(java.lang.reflect.InvocationTargetException err) {
	    Throwable thrown = err.getTargetException();
	    // pw.print("embedded exception: " + thrown + "\n"); pw.flush(); 
	    thrown.printStackTrace();
	}
	return result;
    }
	
    // evaluate forms in sequence, return value of last 
    static Object eval_seq(Object the_list, Environment env) {
	if (cdr(the_list) == null) return eval(car(the_list), env);
	else {
	    eval(car(the_list), env);
	    return eval_seq(cdr(the_list), env);
	}
    }

    static Object eval_list(Object the_list, Environment env) {
	if (the_list == null) return(null);
	return( cons( eval( car(the_list), env),
		      eval_list( cdr(the_list), env)));
    }

    static Object eval_if(Object rest, Environment env) {
	boolean pred = ((Boolean) eval(car(rest), env)).booleanValue();
	if (pred) return eval(second(rest), env);
	else {
	    Object alternate_clause = cddr(rest);
	    if (alternate_clause != null) return eval(car(alternate_clause), env);
	    else return null;
	}
    }

    static Object eval_cond(Object clauses, Environment env) {
	if (clauses == null) return null;
	else {
	    Object clause = car(clauses);
	    boolean pred = ((Boolean) eval(car(clause), env)).booleanValue();
	    if (pred) return eval_seq(cdr(clause), env);
	}
	return eval_cond(cdr(clauses), env);
    }

    static Object eval_define(Object rest, Environment env) {
	Object head = car(rest);
	if (pairP_j(head)) {
	    Procedure proc = new Procedure(cdr(head), cdr(rest), env);
	    return env.define(car(head), proc);
	}
	return env.define(head, eval(second(rest), env));
    }

    static Object eval(Object exp, Environment env) {
	Class type = exp.getClass();
	if (type == Scm.Symbol.class) return(env.lookup(exp));
	if (type == Scm.Pair.class) {
	    Object head = car(exp);
	    if (head == lambda) return new Procedure(second(exp), cddr(exp), env);
	    if (head == quote)  return second(exp);
	    if (head == define) return eval_define(cdr(exp), env);
	    if (head == if_sym) return eval_if(cdr(exp), env);
	    if (head == cond)   return eval_cond(cdr(exp), env);

	    // an application...
	    return( apply( eval( car(exp), env),
			   eval_list( cdr(exp), env)));	
	}
	return exp;
    }
    
    public static Object with_input_from_file(Object fn, Object thunk) {
	InputPort oldPort = stdin;
	stdin = new InputPort((String) fn);
	Object result = apply(thunk, (Object) null);
	stdin.finalize();
	stdin = oldPort;
	return result;
    }

    public static Object with_output_to_file(Object fn, Object thunk) {
	OutputPort oldPort = stdout;
	stdout = new OutputPort((String) fn);
	Object result = apply(thunk, (Object) null);
	stdout.finalize();
	stdout = oldPort;
	return result;
    }

    public static Object read_eval_print() {
	display("> "); 
	Object exp = read();
	if (exp == eof_sym) return exp;
	else {
	    write(exp); display(" -> ");  
	    write(eval(exp, global_env));
	    newline();
	    return read_eval_print();
	}
    }

    static Object read_eval_print_proc = null;

    public static Object load(Object fn) {
	return with_input_from_file(fn, read_eval_print_proc);
    }

    static String scmName(String javaName) {
	String buf = new String();
	for (int i = 0; i < javaName.length(); i++) {
	    char c = javaName.charAt(i);
	    if (c == 'D') c = '!';
	    if (c == 'P') c = '?';
	    if (c == 'G') c = '>';
	    if (c == '_') c = '-';
	    buf += c;
	}
	// pw.print(buf + "\n");pw.flush();
	return buf;
    }

    // This loads the primitive scm functions into the global envt.
    // These functions are the public methods of class Scm and its
    // ancestors that are not public methods of class Object.
    static void loadScmFuns() {
	Method objectMethods[] = Object.class.getMethods();
	Method scmMethods[] = Scm.class.getMethods();
	for (int i = 0; i < scmMethods.length; i++) {
	    boolean found = false;
	    Method theMethod = scmMethods[i];
	    String name = theMethod.getName();
	    for (int j = 0; j < objectMethods.length; j++) 
		if (theMethod.equals(objectMethods[j])) found = true;
	    if ((!found) && (! name.equals("main"))) {
		// stdout.pwtr.print(scmName(name)); newline();
		global_env.define(string_Gsymbol(scmName(name)), theMethod);
	    }
	}
    }

    public static void main(String[] args) {
	loadScmFuns();
	global_env.define(string_Gsymbol("#t"), box(true));
	global_env.define(string_Gsymbol("#f"), box(false));
	read_eval_print_proc = global_env.lookup(string_Gsymbol("read-eval-print"));
	InputPort defaultStdin = new InputPort(System.in); 
	OutputPort defaultStdout = new OutputPort(System.out);

	while (true) {
	    stdin = defaultStdin;
	    stdout = defaultStdout;
	    try {read_eval_print(); System.exit(0);}
	    catch (java.lang.RuntimeException err) {
		err.printStackTrace();
	    }
	}
    }
}

