(function () {
/**
 * Some basic utility functions.  I want to avoid coupling this to large
 * JavaScript libraries.
 */
if(typeof window.Utils != 'undefined') {
    var _Utils = window.Utils;
}

function ArcError(msg, expr) {
    this.message = msg;
    this.expr = expr;
};
ArcError.prototype = new Error();
ArcError.prototype.name = 'ArcError';

var Utils = window.Utils = {

    no_conflict: function() {
        window.Utils = _Utils;
        return Utils;
    },

    extend: function(dest, src) {
        for(var prop in src) {
            if(src.hasOwnProperty(prop)) {
                dest[prop] = src[prop];
            }
        }
        return dest;
    },

    class_decorator: function(init, new_proto) {
        new_proto.__init = init;
        var constr =  function() {
            var self = init.apply(this, arguments);
            var args = [self.__proto__].concat([].slice.call(arguments, 1));
            self.__proto__ = new_proto;
            return self;
        };
        constr.__fake_proto = new_proto;
        return constr;
    },

    invoke: function(collection, method) {
        var accum = [],
            args = [].slice.call(arguments, 2);
        for(var i = 0, len = collection.length; i < len; ++i) {
            var item = collection[i];
            accum.push(item[method].apply(item, args));
        }
        return accum;
    },

    map: function(collection, fn) {
        var accum = [];
        for(var i = 0, len = collection.length; i < len; ++i) {
            accum.push(fn(collection[i], i));
        }
        return accum;
    },

    prop_map: function(collection, fn) {
        for(var prop in collection) {
            if(collection.hasOwnProperty(prop)) {
                collection[prop] = fn(collection[prop], prop);
            }
        }
        return collection;
    },

    ensure_fn: function(fn) {
        if(typeof fn == 'string') {
            fn = eval('false || function(x, y) { return ' + fn + '; }');
        };
        return fn;
    },

    all: function(args, pred) {
        pred = Utils.ensure_fn(pred);
        for(var i = 0, len = args.length; i < len; ++i) {
            if(!pred(args[i])) {
                return false;
            }
        }
        return true;
    },

    pairwise: function(args, base, pred) {
        if(args.length == 0) {
            return base;
        }
        pred = Utils.ensure_fn(pred);

        for(var i = 0, len = args.length - 1; i < len; ++i) {
            if(!pred(args[i], args[i + 1])) {
                return false;
            }
        }
        return true;
    },

    fold1: function(args, fn) {
        if(args.length == 0) {
            return 0;
        } else if(args.length == 1) {
            return args[0];
        }
        fn = Utils.ensure_fn(fn);

        var base = args[0];
        for(var i = 1, len = args.length; i < len; ++i) {
            base = fn(base, args[i]);
        }
        return base;
    },

    assert: function(msg, condition) {
        if(!condition) {
            Utils.error('Assertion failure: ' + msg);
        }
    },

    error: function(msg, expr) {
        throw new ArcError(msg, expr);
    },

    id: function(val) {
        return val;
    },

    ArcError: ArcError

};

})();
(function() {

if(typeof window.Buffer != 'undefined') {
    var _Buffer = window.Buffer;
}

var Buffer = window.Buffer = function(args) {
    if(this instanceof arguments.callee) {
        this.init.apply(this, args && args.callee ? args : arguments);
    } else {
        return new Buffer(arguments);
    }
};

Buffer.no_conflict = function() {
    window.Buffer = _Buffer;
    return Buffer;
};

Buffer.prototype = {

    init: function(text) {
        this._text = text;
        this._pos = 0;
    },

    read: function() {
        var c = this.peek();
        if(c === false) {
            return false;        
        }

        this.eat();
        return c;
    },

    read_until: function(pred) {
        var start = this._pos
        while(this.peek() !== false && !pred(this.peek())) {
            this.eat();
        };
        return this._text.substring(start, this._pos);
    },

    eat: function() {
        ++this._pos;
        if(this._pos > this._text.length) {
            throw new Error('Unexpected end of file at position ' + this._pos);
        }
    },

    peek: function() {
        if(this._pos >= this._text.length) {
            return false;
        }

        return this._text.charAt(this._pos);
    },

    get: function(index) {
        return this._text.charAt(index != undefined ? index : this._pos);
    }

};

})();
(function ($) {
/**
 * A representation for primitive types.  Arc types inherit from a common
 * base class that provides defaults (all of which just raise an error)
 * for some of the basic operations that the interpreter performs.
 * 
 * @dependency utils.js
 */

if(typeof window.Types != 'undefined') {
    var _Types = window.Types;
}

var ArcBase = {

    _not_defined: function(method) {
        $.error('' + method + ' not defined on ' + this._type);
    },
    _cant_be_coerced: function(type) {
        $.error(this._type + " object can't be coerced to " + type);
    },

    __type: function() { return this._type; },
    __call: function(args) { return Types.bool(this.__veq(args[0])); },
    __set: function(index, val) { this._not_defined('__set'); },
    __coerce: function(type) { this._not_defined('coerce'); },
    __to_js: function() { return this; },
    __eq: function(js_obj) { return this.__to_js() == js_obj; },
    __teq: function(js_obj) { return this.__type().__eq(js_obj); },
    __veq: function(arc_obj) { return this.__eq(arc_obj.__to_js()); },
    __print: function() { return Types.str(this.toString()); }
};

function new_arc_type(base_proto, init, methods) {
    $.extend(base_proto, ArcBase);
    $.extend(base_proto, methods);
    return $.class_decorator(init, base_proto);
};

function copy_number(val) {
    return new Number(val);
};

function copy_string(val) {
    return new String(val);
};

function make_tagged(type, val) {
    return { _val: val, _type: type, _tagged: true };
};

function make_cons(car, cdr) {
    $.assert('Passed a non-arc object as car of a list', car && car.__type);
    $.assert('Passed a non-arc object as cdr of a list', cdr && cdr.__type);
    return { _car: car, _cdr: cdr };
};

function make_table(val) {
    return val || {};
};

function make_function(args, body, env) {
    return {
        _args: args,
        _body: body,
        _env: env
    }
};

function decorate_primitive(fn, name) {
    fn._name = name || 'unnamed';
    return fn;
};

// Must be declared outside because it's referenced in the definition of Types
var sym = new_arc_type(new String(), copy_string, {});
$.extend(sym.__fake_proto, {
    _type: sym('sym'),
    __coerce: function(type) {
        if(type.__eq('string')) {
            return Types.str(this == Types.NIL ? '' : this.__to_js())
        } else {
            return this._not_defined(type);
        }
    },
    __to_list: function() { return []; }, // For nil
    __to_js: function() { 
        var retval = this.toString();
        if(retval == 't') return true;
        if(retval == 'nil') return false;
        return this.toString(); 
    }
});

var Types = window.Types = {

    no_conflict: function() {
        window.Types = _Types;
        return Types;
    },

    tagged: new_arc_type(new Object(), make_tagged, {
        __call: function(args) { return this._val.__call.call(this._val, args); },
        __set: function(index, val) { return this._val.__set.call(this._val, index, val); },
        __print: function() {
            return Types.str('(tagged ' + this._type + ' ' + this._val.__print() + ')');
        }
    }),

    sym: sym,
    NIL: sym('nil'),
    T: sym('t'),
    bool: function(val) {
        return val ? Types.T : Types.NIL;
    },

    chr: new_arc_type(new String(), copy_string, { 
        _type: sym('char'),
        __to_js: function() { return this.toString(); },
        __coerce: function(type) {
            switch(type.__to_js()) {
                case 'int': return Types.int_(this.charCodeAt(0));
                case 'string': return Types.str(this);
                case 'sym': return Types.sym(this);
                default: return this._cant_be_coerced(type);
            }
        },
        __print: function() { return Types.str('#\\' + this); }
    }),

    str: new_arc_type(new String(), copy_string, { 
        _type: sym('string'),
        __call: function(args) {
            return Types.chr(this.charAt(args[0]));
        },
        __set: function(index, new_val) {
            // The spec says to mutate the string, but JavaScript strings are
            // immutable.  Instead we return a copy with the specified character
            // changed, and hope that nobody was depending upon mutation
            return Types.str(this.substr(0, index) + new_val.toString() 
                            + this.substr(index + 1));
        },
        __coerce: function(type) {
            switch(type.__to_js()) {
                case 'sym': return Types.sym(this);
                case 'cons': 
                    var retval = [];
                    for(var i = 0, len = this.length; i < len; ++i) {
                        retval.push(Types.chr(this.charAt(i)));
                    }
                    return Types.list(retval);
                case 'int': 
                    var parsed = parseInt(this);
                    if(isNaN(parsed)) {
                        $.error("Can't coerce " + this + ' to int');
                    }
                    return Types.int_(parsed);
                default: return this._cant_be_coerced(type);
            }
        },
        __to_js: function() { return this.toString(); },
        __print: function() { return Types.str('"' + this + '"'); }
    }),

    int_: new_arc_type(new Number(), copy_number, { 
        _type: sym('int'),
        __coerce: function(type) {
            switch(type.__to_js()) {
                case 'char': return Types.chr(String.fromCharCode(this));
                case 'string': return Types.str(this.toString());
                default: return this._cant_be_coerced(type);
            }
        },
        __to_js: function() { return parseInt(this.toString()); }
    }),

    num: new_arc_type(new Number(), copy_number, { 
        _type: sym('num'),
        __coerce: function(type) {
            switch(type.__to_js()) {
                case 'int': return Types.int_(Math.round(this));
                case 'char': return Types.chr(String.fromCharCode(Math.round(this)));
                case 'string': return Types.str(this.toString());
                default: return this._cant_be_coerced(type);
            }
        },
        __to_js: function() { return parseFloat(this.toString()); }
    }),

    cons: new_arc_type(new Object(), make_cons, { 
        _type: sym('cons'),
        __index: function(index) {
            var current = this;
            for(var i = 0; i < index; ++i) {
                current = current._cdr;
                if(!current.__type().__eq('cons')) {
                    $.error('Index out of range: ' + index);
                }
            }
            return current;
        },
        __call: function(args) {
            return this.__index(args[0])._car;
        },
        __set: function(index, new_val) {
            this.__index(index)._car = new_val;
            return new_val;
        },
        __each: function(fn) {
            var current = this;
            while(current.__teq('cons')) {
                fn(current._car);
                current = current._cdr;
            }
            return current;
        },
        __coerce: function(type) {
            if(type.__to_js() == 'string') {
                var accum = '';
                function add_char(chr) { 
                    accum += String.fromCharCode(chr.__to_js());
                };
                var last = this.__each(add_char);
                if(!last == Types.NIL) {
                    add_char(last);
                }
                return Types.str(accum);
            } else {
                return this._cant_be_coerced(type);
            }
        },
        __to_list: function() {
            var retval = [];
            var last = this.__each(function(elem) { retval.push(elem); });
            if(last != Types.NIL) {
                retval.push(last);
            }
            return retval;
        },
        __print: function() {
            var segments = [];
            var last = this.__each(function(elem) { 
                segments.push(elem.__print().toString());
            });
            if(last != Types.NIL) {
                segments.push('.');
                segments.push(last.__print().toString());
            }
            return Types.str('(' + segments.join(' ') + ')');
        },
        __to_js: function() { return this.__to_list(); }
    }),

    list: function(arr, improper) {
        if(arr.length == 0) {
            return Types.NIL;
        }

        var i = arr.length - 1;
        for(var current = improper ? arr[i--] : Types.NIL; i >= 0; --i) {
            current = Types.cons(arr[i], current);
        }
        return current;
    },

    table: new_arc_type(new Object(), make_table, { 
        _type: sym('table'),
        __call: function(args) {
            return this[args[0].toString()] || Types.NIL;
        },
        __set: function(index, new_val) {
            if(new_val == Types.NIL) {
                delete this[index];
            } else {
                this[index] = new_val;
            }
            return new_val;
        },
        __length: function() {
            var len = 0;
            Utils.prop_map(this, function() { len++; });
            return len;
        },
        __print: function() {
            var text = '#hash(';
            Utils.prop_map(this, function(v, key) { text += '(' + key + ' . ' + ')'; });
            return Types.str(text + ')');
        },
        __to_js: function() {
            return Utils.extend({}, this);
        }
    }),

    fn: new_arc_type(new Object(), make_function, { 
        _type: sym('fn'),
        __call: 'native',
        __print: function() {
            var argstr = this._args.__teq('cons') 
                ? '(' + this._args.__to_list().join(' ') + ')'
                : this._args.toString();
            return Types.str('#procedure: ' + argstr);
        }
    }),

    primitive: new_arc_type(new Function(), decorate_primitive, {
        _type: sym('fn'),
        __call: function(args) {
            if(this._varargs === undefined && args.length != this.length) {
                Utils.error(this._name + ' takes ' + this.length + 
                            ' arguments, found ' + Types.list(args).__print());
            }
            if(this._varargs !== undefined && args.length < this._varargs) {
                Utils.error(this._name + ' takes at least ' + this.length + 
                            ' arguments, found ' + Types.list(args).__print());

            }
            var result = this.apply(null, args);
            Utils.assert(this._name + ' did not return a value.', result);
            return result;
        },
        __print: function() {
            return Types.str('#primitive procedure: ' + this._name);
        }
    }),

    to_js: function(arc_obj) {
        var result = arc_obj.__to_js();
        return Utils.prop_map(result, Types.to_js);
    },

    to_arc: function(js_obj) {
        switch(typeof js_obj) {
            case 'number': return Types.wrap_num(js_obj);
            case 'string': return Types.str(js_obj);
            case 'boolean': return Types.bool(js_obj);
            case 'function': return Types.primitive(js_obj);
            case 'undefined': return Types.NIL;
            default:
                if(js_obj instanceof Array) {
                    return Types.list(Utils.map(js_obj, Types.to_arc));
                } else {
                    return Types.table(Utils.prop_map(Utils.extend({}, js_obj), 
                                                      Types._to_arc));
                }
        }
    },

    /** Detects the type of the result and wraps it with a num or int_ */
    wrap_num: function(result) {
        var constr = result == Math.round(result) ? Types.int_ : Types.num;
        return constr(result);
    }

};

})(Utils);
(function (t) {
/**
 * S-expression reader, capable of reading one or multiple S-expressions
 * out of a string of text and returning them as nested JavaScript lists.
 * 
 * @dependency buffer.js
 * @dependency types.js
 */


if(typeof window.Read != 'undefined') {
    var _read = window.Read;
}

function is_newline(c) {
    return c == '\n' || c == '\r';
};

function is_whitespace(c) {
    return c == ' ' ||  c == '\t' || is_newline(c);
};

function is_not_whitespace(c) {
    return !is_whitespace(c);
};

function is_delim(c) {
    return c === false || is_whitespace(c) || c == '(' || c == ')' 
        || c == '[' || c == ']';
};

function decorate_form(keyword, buffer, eat_char) {
    eat_char = eat_char == undefined ? true : eat_char;
    if(eat_char) {
        buffer.eat();
    }
    var form = read(buffer);
    buffer.read_until(is_not_whitespace);
    return Types.list([Types.sym(keyword), form]);
};

function read_token(buffer) {
    var token = buffer.read_until(is_delim);
    buffer.read_until(is_not_whitespace);
    return token;
};

function read_string(buffer) {
    function is_segment_break(c) {
        return c == '\\' || c == '"';
    };
    buffer.eat();   // "
    var str = '';
    while(buffer.peek() != '"') {
        if(buffer.peek() === false) {
            Utils.error('Unclosed string literal');
        }

        if(buffer.peek() == '\\') {
            buffer.eat()    // \
            var c = buffer.read();
            switch(c) {
                case 'n': str += '\n'; break;
                case 'r': str += '\r'; break;
                case 't': str += '\t'; break;
                case '"': str += '"'; break;
                case '0': str += '\0'; break;
                default: Utils.error('Invalid escape char: ' + c);
            }
        };
        str += buffer.read_until(is_segment_break);
    };
    buffer.eat();   // trailing "
    buffer.read_until(is_not_whitespace);
    return Types.str(str);
};

function read_delimited(delim, buffer) {
    var elements = [];

    buffer.eat();   // open (
    buffer.read_until(is_not_whitespace);
    var c;
    while((c = buffer.peek()) != delim) {
        if(c === false) {
            Utils.error('Unclosed delimiter');
        }
        elements.push(read(buffer));
    }
    buffer.eat();   // close )
    buffer.read_until(is_not_whitespace);

    var len = elements.length;
    if(len >= 3) {
        var is_dotted = false,
            possible_dot = elements[len - 2];
        if(possible_dot.__type().__eq('sym') && possible_dot.__eq('.')) {
            elements[len - 2] = elements[len - 1];
            elements.length--;
            is_dotted = true;
        }
    }

    return Types.list(elements, is_dotted);
};

function read_list(buffer) {
    return read_delimited(')', buffer);
};

function read_brackets(buffer) {
    return Types.list([Types.sym('fn'), Types.list([Types.sym('_')]), 
                       read_delimited(']', buffer)]);
};

function parse_char_literal(token) {
    var c = token.substr(2);
    switch(c) {
        case 'newline': return '\n';
        case 'space': return ' ';
        case 'tab': return '\t';
        default: return c.charAt(0) == 'x' && c.length > 1 ? 
            String.fromCharCode(parseInt(c.substr(1), 16)) : c;
    };
};

function parse_token(token) {
    if(token.match(/^-?\d+\.\d+(e\d+)?$/i)) {
        return Types.num(parseFloat(token));
    } else if(token.match(/^-?\d+$/)) {
        return Types.int_(parseInt(token));
    } else if(token.match(/^#\\.+$/)) {
        return Types.chr(parse_char_literal(token));
    } else if(token == 'nil') {
        return Types.NIL;
    } else {
        return Types.sym(token);
    }
};

function read(buffer) {
    var c = buffer.peek();
    switch(c) {
        case false: return false;
        case '"': return read_string(buffer);
        case "'": return decorate_form('quote', buffer);
        case '`': return decorate_form('quasiquote', buffer);
        case '(': return read_list(buffer);
        case '[': return read_brackets(buffer);
        case ',':
            buffer.eat();
            if(buffer.peek() == '@') {
                return decorate_form('unquote-splicing', buffer);
            } else {
                return decorate_form('unquote', buffer, false);
            }
        case ';': 
            buffer.read_until(is_newline);
            buffer.read_until(is_not_whitespace);
            return read(buffer);
        default: 
            return parse_token(read_token(buffer));
    }
};

window.Read = function(text) {
    if(!(text instanceof Buffer)) {
        text = Buffer(text);
    }
    return read(text);
};

Utils.extend(window.Read, {
    no_conflict: function() {
        window.Read = _read;
        return read;
    },
    is_not_whitespace: is_not_whitespace,

    // Exposed for testing...
    test: {
        is_delim: is_delim,
        is_not_whitespace: is_not_whitespace,
        read_token: read_token
    }
});

})(Types);
(function () {

if(typeof window.Primitives != 'undefined') {
    var _Primitives = window.Primitives;
}

var gensym_count = 0;


function varargs(fn, minimum) {
    fn._varargs = minimum || 0;
    return fn;
};

var Primitives = window.Primitives = {
    _no_conflict: function() {
        window.Primitives = _Primitives;
        return Primitives;
    },

    _varargs: varargs,

    /*----- Generic interpreter functions -----*/
    error: function() {
        var msg = Utils.invoke(arguments, '__to_js').join('');
        Utils.error(msg);
    },
    
    is: varargs(function() {
        return Types.bool(Utils.pairwise(arguments, true, function(v1, v2) {
            return v1.__teq(v2.__type().__to_js()) && v1.__veq(v2);
        }));
    }, 2),

    uniq: function() { 
        return Types.sym('gs' + (++gensym_count)); 
    },

    sig: Types.table({}),

    sref: function(collection, new_val, index) {
        collection.__set(index, new_val);
        return new_val;
    },

    /*----- Types -----*/

    t: Types.T,
    nil: Types.NIL,
    // Wrapper so that fn.length is properly set
    annotate: function(tag, val) { return Types.tagged(tag, val); },

    type: function(val) { return val.__type(); },
    rep: function(val) { return val._tagged ? val._val : val; },
    coerce: function(val, type) { return val.__coerce(type); },
    // TODO: radix argument for coerce

    /*----- Lists -----*/
    cons: function(car, cdr) { return Types.cons(car, cdr); },

    car: function(list) {
        if(list == Types.NIL) return list;
        Utils.assert("Can't take car of " + list, list._car);
        return list._car;
    },

    cdr: function(list) {
        if(list == Types.NIL) return list; 
        Utils.assert("Can't take cdr of " + list, list._cdr);
        return list._cdr;
    },

    scar: function(cons, new_val) {
        Utils.assert(cons + ' is not a list', cons._car);
        cons._car = new_val;
        return new_val;
    },

    scdr: function(cons, new_val) {
        Utils.assert(cons + ' is not a list', cons._cdr);
        cons._cdr = new_val;
        return new_val;
    },

    /*----- Hashtables -----*/
    table: Types.table,

    /*----- Strings -----*/
    newstring: varargs(function(length, fill) {
        fill = fill != undefined ? fill.__to_js() : '\0';
        var str = '';
        for(var i = 0; i < length; ++i) {
            str += fill;
        }
        return Types.str(str);
    }, 1),

    /*----- Arithmetic -----*/
    '+': varargs(function() { 
        var op = function(x, y) { return x + y; };
        if(Utils.all(arguments, function(x) { return x.__teq('string'); })) {
            op = function(x, y) { return x.toString() + y.toString(); };
        } else if(Utils.all(arguments, function(x) { 
                    return x.__teq("cons") || x == Types.NIL; })) {
            op = function(x, y) { 
                var accum = x.__to_list ? x.__to_list() : x;
                return accum.concat(y.__to_list()); 
            };
        }
            
        var result = Utils.fold1(arguments, op);
        if(result instanceof Array) {
            return Types.list(result);
        } else if(typeof result == 'string') {
            return Types.str(result);
        } else {
            return Types.wrap_num(result)
        }
    }),
    '-': varargs(function() { return Types.wrap_num(Utils.fold1(arguments, 'x - y')); }),
    '*': varargs(function() { return Types.wrap_num(Utils.fold1(arguments, 'x * y')); }),
    '/': varargs(function() { return Types.wrap_num(Utils.fold1(arguments, 'x / y')); }),
    'mod': function(x, y) { return Types.int_(x % y); },
    'expt': function(base, pow) { return Types.wrap_num(Math.pow(base, pow)); },
    'sqrt': function(x) { return Types.wrap_num(Math.sqrt(x)); },

    '>': varargs(function() { return Types.bool(Utils.pairwise(arguments, true, 'x > y')); }),
    '<': varargs(function() { return Types.bool(Utils.pairwise(arguments, true, 'x < y')); }),

    len: function(x) {
        switch(x.__type().__to_js()) {
            case 'cons': return Types.int_(x.__to_list().length);
            case 'string': return Types.int_(x.length);
            case 'table': return Types.int_(x.__length());
            default: Utils.error(x.__type() + ' has no len method');
        }
    },

    rand: function(max) {
        return Types.int_(Math.floor(Math.random() * max));
    },

    // No-ops for now; dunno if they're doable in Javascript
    truncate: Utils.id,
    exact: Utils.id

};

})();
(function () {

if(typeof window.Eval != 'undefined') {
    var _Eval = window.val;
}

function is_literal(expr) {
    switch(expr.__type().__to_js()) {
        case 'char':
        case 'string':
        case 'int':
        case 'num':
            return true;
        default:
            return false;
    }
};

function xcar(expr) {
    return expr.__teq('cons') ? expr._car : false;
};

function car_eq(expr, val) {
    var car = xcar(expr);
    return car && car.__eq(val);
};

function cadr(expr) {
    return expr._cdr._car;
};

function apply(fn, args) {
    Utils.assert('Null function object passed', fn);
    while(fn._tagged) {
        fn = fn._val;
    }
    if(fn.__call == 'native') {
        return apply_fn(fn, args);
    } else {
        return fn.__call(args);
    }
};

function apply_fn(fn, args) {
    var new_env = {};
    new_env.__proto__ = fn._env;
    args = Types.list(args);

    function bind_var(fn_arg, call_arg) {
        var recurse = true;
        if(fn_arg.__teq('cons')) {
            // Not the end of the list...
            var sym = fn_arg._car;
            if(sym.__teq('sym')) {
                // Normal binding
                if(call_arg.__teq('cons')) {
                    new_env[sym] = call_arg._car;
                    bind_var(fn_arg._cdr, call_arg._cdr);
                } else {
                    Utils.error('Wrong number of args for function: expected ' +
                        fn._args.__print() + ', found ' + args.__print());
                }
            } else if(car_eq(sym, 'o')) {
                // Optional parameters
                var default_expr = cadr(sym._cdr);
                new_env[cadr(sym)] = call_arg._car || 
                    (default_expr ? eval(default_expr, new_env) : Types.NIL);
                bind_var(fn_arg._cdr, call_arg._cdr || call_arg);
            } else if(sym.__teq('cons')) {
                // Destructuring
                bind_var(sym, call_arg._car);
            } else {
                Utils.error('Unknown object type in arg list ' + fn._args.__print());
            }
        } else if(fn_arg == Types.NIL && call_arg == Types.NIL) {
            // End of the arg list; do nothing
        } else if(fn_arg.__teq('sym')) {
            // Rest parameter
            new_env[fn_arg] = call_arg;
        } else {
            Utils.error("Couldn't bind parameters " + args + ' to list ' + fn._args);
        }
    };

    bind_var(fn._args, args);
    var last_result = Types.NIL;
    fn._body.__each(function(expr) {
        last_result = eval(expr, new_env);
    });
    return last_result;
};

function eval_quasiquote(expr, env) {
    var accum = [];
    do {
        var current = expr._car;
        if(car_eq(current, 'unquote')) {
            accum.push(eval(cadr(current), env));
        } else if(car_eq(current, 'unquote-splicing')) {
            var results = eval(cadr(current), env).__to_list();
            for(var i = 0, len = results.length; i < len; ++i) {
                accum.push(results[i]);
            }
        } else if(current.__teq('cons')) {
            accum.push(eval_quasiquote(current, env));
        } else {
            accum.push(current);
        }
        expr = expr._cdr;
    } while(expr.__teq('cons'));
    return Types.list(accum);
};

function eval_symbol(expr, env) {
    if(!is_symbol_macro(expr)) {
        var result = env[expr];
        if(!result) {
            Utils.error('Unbound variable ' + expr);
        }
        return result;
    } else { 
        return eval(symbol_expand(expr), env);
    }
};

function eval_if(expr, env) {
    Utils.assert('No test for conditional', expr._car);
    var test = eval(expr._car, env);

    if(expr._cdr == Types.NIL) {
        return test;
    }

    if(test == Types.NIL) {
        var alternative = expr._cdr._cdr;
        return alternative != Types.NIL ? eval_if(alternative, env) : Types.NIL;
    } else {
        var consequent = cadr(expr);
        Utils.assert('No if-true clause', consequent);
        return eval(consequent, env);
    }
};

function eval_fn(expr, env) {
    return Types.fn(expr._car, expr._cdr, env);
};

function eval_symbol_macro(expr, env, macrodefs) {
    var name = cadr(expr),
        def = Types.fn(Types.list(Types.sym('expr')), expr._cdr._cdr, env);
    return macrodefs[name] = env[name] = def;
};

function eval_set(expr, env) {
    function set_value(varname, value, env_segment) {
        if(!env_segment) {
            eval.global_env[varname] = value;
            return;
        }

        // hasOwnProperty requires a JS string, for some reason, even
        // though the actual key is a symbol string
        if(env_segment.hasOwnProperty(varname.__to_js())) {
            env_segment[varname] = value;
        } else {
            set_value(varname, value, env_segment.__proto__);
        }
    };
    
    var varname, value;
    while(expr != Types.NIL) {
        varname = expr._car;
        value = eval(cadr(expr), env);
        set_value(varname, value, env);
        expr = expr._cdr._cdr;
    };
    return value;
};

function is_macro(expr, env) {
    env = env || eval.global_env;
    var sym = expr._car;
    return sym && env[sym] && env[sym].__teq('mac');
};

function eval_call(expr, env) {
    if(is_macro(expr, env)) {
        return eval_macro_call(expr, env);
    } else {
        return eval_fn_call(expr, env);
    }
};

function is_symbol_macro(expr) {
    return expr.indexOf('~') != -1 || expr.indexOf(':') != -1;
};

function symbol_expand(expr, env) {
    function complement(section) {
        return section.charAt(0) == '~' 
            ? Types.list(Types.sym('complement'), Types.sym(section.substr(1)))
            : Types.sym(section);
    };
    var sections = expr.split(':');
    return sections.length > 1 
        ? Types.list([Types.sym('compose')].concat(Utils.map(sections, complement)))
        : complement(sections[0]);
};

function macro_expand(expr, env) {
    // Assumes that we've already checked and made sure the call is a macro
    env = env || eval.global_env;
    var macro_def = env[expr._car],
        macro_result = apply(macro_def, expr._cdr.__to_list());
    Utils.assert('Macro call has no return value', macro_result);
    return macro_result;
};

function eval_macro_call(expr, env) {
    return eval(macro_expand(expr, env), env);
};

function eval_fn_call(expr, env) {
    var form = expr.__to_list();
    for(var i = 0, len = form.length; i < len; ++i) {
        var result = eval(form[i], env);
        Utils.assert("Eval of " + form[i].__print() + " yielded undefined", result);
        form[i] = result;
    }
    return apply(form[0], form.slice(1));
};

var eval = window.Eval = function(expr, env) {
    env = env || eval.global_env;

    try {
        if(is_literal(expr)) {
            return expr;
        } else if(expr.__teq('sym')) {
            return eval_symbol(expr, env);
        } else if(car_eq(expr, 'quote')) {
            return cadr(expr);
        } else if(car_eq(expr, 'quasiquote')) {
            return eval_quasiquote(cadr(expr), env);
        } else if(car_eq(expr, 'if')) {
            return eval_if(expr._cdr, env);
        } else if(car_eq(expr, 'fn')) {
            return eval_fn(expr._cdr, env);
        } else if(car_eq(expr, 'set')) {
            return eval_set(expr._cdr, env);
        } else if(expr.__teq('cons')) {
            return eval_call(expr, env);
        } else {
            Utils.error('Unknown form type ' + expr);
        }
    } catch(e) {
        if(!(e instanceof Utils.ArcError)) {
            Utils.error('Internal error: '  + e.message, expr);
        } else {
            e.expr = expr;
            throw e;
        }
    }
};

var exposed_interpreter_functions = {
    
    bound: function(name) {
        return eval.global_env[name] || Types.NIL;
    },

    apply: Primitives._varargs(function() {
        // The behavior of apply is to flatten the last element into the arg list
        // if it is itself a list.  I have no idea why it does this; it leads to
        // odd behavior like (apply + 1 2 '(3 4)) => 10 but 
        // (apply + 1 '(2 3) 4) => error.  But I need to do it for compatibility.
        var args = [].slice.call(arguments, 1),
            last_elem = args[args.length - 1];
        if(last_elem.__teq('cons')) {
            args = args.slice(0, -1).concat(last_elem.__to_list());
        }
        return apply(arguments[0], args);
    }, 1),
    
    'atomic-invoke': function(f) { return apply(f, []); },

    ssyntax: function(expr) { return Types.bool(is_symbol_macro(expr)); },
    ssexpand: function(expr) { return symbol_expand(expr, eval.global_env); },
    macex1: function(expr) { return is_macro(expr) ? macro_expand(expr) : expr; },
    macex: function(expr) {
        while(is_macro(expr)) {
            expr = macro_expand(expr);
        }
        return expr;
    },

    // Fake I/O:
    stdout: function() { return Types.T; },
    stdin: function() { return Types.T; },
    sread: function(arc_string) { return Read(arc_string.__to_js()); },
    disp: Primitives._varargs(function() {
        for(var i = 0, len = arguments.length; i < len; ++i) {
            eval.stdout += arguments[i];
        }
        return Types.NIL;
    }),

    writec: Primitives._varargs(function(c) {
        eval.stdout += c;
        return Types.NIL;
    }, 1)

};

function init_globals() {
    var env = {};
    function add_primitive(namespace, name) {
        var prim = namespace[name];
        if(typeof prim == 'function') {
            env[name] = Types.primitive(prim, name);
        } else if(name.charAt(0) != '_') {
            env[name] = prim;
        }
    };
    for(var prop in Primitives) {
        add_primitive(Primitives, prop);
    }
    for(var prop in exposed_interpreter_functions) {
        add_primitive(exposed_interpreter_functions, prop);
    }
    return env;
};

Utils.extend(eval, {

    no_conflict: function() {
        window.Eval = _eval;
        return eval;
    },

    init_globals: init_globals,
    global_env: init_globals(),
    character_macros: {},
    symbol_macros: {},
    
    eval_line: function(text) {
        return eval(Read(text));
    },

    eval_text: function(text) {
        var last_result,
            buf = Buffer(text);
        while(buf.peek() !== false) {
            var text = Read(buf);
            if(text === false) break;   // Last token is zero-length
            last_result = eval(text);
            buf.read_until(Read.is_not_whitespace);
        }
        return last_result;
    },

    stdout: '',
    stdin: '',
    clear_stdout: function() {
        eval.stdout = '';
    }
});

})();
