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

class ScmPrims {
    static Hashtable obarray = new Hashtable();
    static PrintWriter pw = new PrintWriter(System.out);

    static class Symbol {
	String pname;
	Symbol(String arg) {
	    pname = arg;
	}
	public String toString() {
	    return pname;
	}
    }

    public static Symbol string_Gsymbol(String str) {
	Symbol result = (Symbol) obarray.get(str);
	if (result == null) {
	    Symbol sym = new Symbol(str);
	    obarray.put(str, sym);
	    return(sym);
	} else return result;
    }

    static Symbol eof_sym = (Symbol) string_Gsymbol("*eof*");

    static Object box(boolean arg) {
	return new Boolean(arg);
    }
    static Object box(int arg) {
	return new Integer(arg);
    }
    static Object box(double arg) {
	return new Double(arg);
    }

    static class Pair {
	public Object car;
	public Object cdr;
	Pair(Object a, Object b) {
	    car = a;
	    cdr = b;
	}
    }

    public static Pair cons (Object a, Object b) {
 	return new Pair(a,b);
    }

    // is this a standard scm fun?
    static Object lcons (Object list_a, Object list_b) 
    {
	if (list_a == null) return null;
	Object the_pair = cons(car(list_a), car(list_b));
	return cons( the_pair, lcons(cdr(list_a), cdr(list_b)));
    }

    public static Object car(Object obj) {
	if (obj.getClass() != Scm.Pair.class) 
	    throw new RuntimeException("attempt to take car of non-pair: " + obj.toString());
	return ((Pair) obj).car;
    }

    public static Object cdr(Object obj) {
	if (obj.getClass() != Scm.Pair.class) 
	    throw new RuntimeException("attempt to take cdr of non-pair: " + obj.toString());
	return ((Pair) obj).cdr;
    }

    public static Object set_carD(Object the_pair, Object val)
    {
	if (the_pair.getClass() != Scm.Pair.class) 
	    throw new RuntimeException("attempt to set-cdr! of non-pair: " + the_pair.toString());
	Pair result = (Pair) the_pair;
	result.car = val;
	return result;
    }

    public static Object set_cdrD(Object the_pair, Object val)
    {
	if (the_pair.getClass() != Scm.Pair.class) 
	    throw new RuntimeException("attempt to set-cdr! of non-pair: " + the_pair.toString());
	Pair result = (Pair) the_pair;
	result.cdr = val;
	return result;
    }

    static boolean pairP_j(Object obj) {
 	return (obj.getClass() == Scm.Pair.class);
    }

    static boolean listP_j(Object obj) {
	if (obj == null) return true;
	else {
	    if (pairP_j(obj) && listP_j(cdr(obj)))
		return true;
	    else return false;
	}
    }

    static class Environment {
	Object frameList;
	Environment() {	
	    frameList = cons(null, null); // one empty frame
	}

	Environment extend(Object var_list, Object val_list) {
	    Environment result = new Environment();
	    result.frameList = cons(lcons(var_list, val_list), frameList);
	    return result;
	}
	
	Object findBindingInFrame(Object var, Object alist)
	{
	    if (alist == null) return null;
	    if (var == car(car(alist))) return car(alist);
	    return findBindingInFrame(var, cdr(alist));
	}

	Object findBinding(Object var, Object flist) {
	    if (flist == null) return null;
	    else {
		Object binding = findBindingInFrame(var, car(flist));
		if (binding != null) return binding;
		return findBinding(var, cdr(flist));
	    }
	}

	Object lookup(Object var) {
	    Object binding = findBinding(var, frameList);
	    if (binding == null) 
		throw(new RuntimeException("unbound variable: " + var.toString()));
	    // print(binding);
	    return cdr(binding);
	}
	
	Object define(Object var, Object val) {
	    Object binding = findBinding(var, frameList);
	    if (binding == null) {
		// mutate the frame at the head 
		Object the_frame = car(frameList);
		Object new_pair  = cons(var,val);
		Object new_frame = cons(new_pair, the_frame);
		set_carD(frameList, new_frame);
	    } else set_cdrD(binding, val);  // was bound
	    return var;
	}
    }


    static Environment global_env = new Environment();

    static class Procedure {
	Object formals;
	Object body;
	Environment env;
	Procedure(Object a, Object b, Environment e) {
	    formals = a;
	    body = b;
	    env = e;
	}
    }

    static class InputPort {
	BufferedReader buf_reader;
	StreamTokenizer stk;
	FileInputStream fis = null;
	void wordchars(String chrs) {
	    int i;
	    for (i = 0; i < chrs.length(); i++) {
		stk.wordChars(chrs.charAt(i), chrs.charAt(i));
	    }
	}
	void init() {
	    stk = new StreamTokenizer(buf_reader);
	    stk.commentChar(';');
	    wordchars("*/<=>!?:$%_&~^+#"); // hash should be done differently
	}
	
	InputPort(InputStream is) {
	    buf_reader = new BufferedReader( new InputStreamReader(is));
	    init();
	}

	InputPort(String fn) {
	    try {
		fis = new FileInputStream((String) fn);
	    } catch (java.io.FileNotFoundException err) {pw.print(err);}
	    buf_reader = new BufferedReader( new InputStreamReader(fis));
	    init();
	}
	Object nextToken() {
	    try {stk.nextToken(); }
	    catch (java.io.IOException err) {pw.print(err);}
	    // pw.print(stk.ttype);
	    if (stk.ttype == stk.TT_EOF) return eof_sym;
	    if (stk.ttype == stk.TT_WORD) return string_Gsymbol(stk.sval); //symbol
	    if (stk.ttype == stk.TT_NUMBER) return box(stk.nval);  //number
	    if (stk.ttype == 34) return stk.sval; // quoted string

	    else return new Character((char) stk.ttype);
	}

	void pushBack() { stk.pushBack();}

	boolean eof() {
	    nextToken();
	    boolean result = (stk.ttype == stk.TT_EOF);
	    pushBack();
	    return result;
	}

	protected void finalize() {
	    try {
		if (fis != null) fis.close();
	    } catch (java.io.IOException err) {pw.print(err);}
	}
    }

    static class OutputPort {
	PrintWriter pwtr;
	FileOutputStream fos = null;
	OutputPort(PrintStream ps) {
	    pwtr = new PrintWriter(ps);
	}
	OutputPort(String fn) {
	    try {
		fos = new FileOutputStream(fn);
	    } 
	    catch (java.io.FileNotFoundException err) {pw.print(err);}
	    catch (java.io.IOException err) {pw.print(err);}
	    pwtr = new PrintWriter(fos);
	}
	void flush() {
	    pwtr.flush();
	}
	protected void finalize() {
	    try {
		if (fos != null) fos.close();
	    } catch (java.io.IOException err) {pw.print(err);}
	}
    }

    static int length_j(Object the_list) {
	if (the_list == null) return 0;
	return (1 + length_j( cdr( the_list)));
    }
    
    static boolean lparen(Object tok) {
	if (tok.getClass() == Character.class)
	    if (((Character) tok).charValue() == 40) return true;
	return false;
    }
    
    static boolean rparen(Object tok) {
	if (tok.getClass() == Character.class)
	    if (((Character) tok).charValue() == 41) return true;
	return false;
    }

    static Object [] list_to_array(Object the_list) {
	int len = length_j(the_list);
	Object [] result = new Object[len];
	int i;
	Object next = the_list;
	for( i = 0; i < len; i++) {
	    result[i] = car(next);
	    next = cdr(next);
	}
	return result;
    }
}

