--- /dev/null
+var max_history_length = 1000;
+
+function jq_load_history(jq) {
+ if (localStorage['mal_history']) {
+ var lines = JSON.parse(localStorage['mal_history']);
+ if (lines.length > max_history_length) {
+ lines = lines.slice(lines.length-max_history_length);
+ }
+ jq.SetHistory(lines);
+ }
+}
+
+function jq_save_history(jq) {
+ var lines = jq.GetHistory();
+ localStorage['mal_history'] = JSON.stringify(lines);
+}
+
+
+var readline = {
+ 'readline': function(prompt_str) {
+ return prompt(prompt_str);
+ }};
+
+// Node vs browser behavior
+var types = {};
+if (typeof module === 'undefined') {
+ var exports = types;
+}
+
+// General functions
+
+function _obj_type(obj) {
+ if (_symbol_Q(obj)) { return 'symbol'; }
+ else if (_list_Q(obj)) { return 'list'; }
+ else if (_vector_Q(obj)) { return 'vector'; }
+ else if (_hash_map_Q(obj)) { return 'hash-map'; }
+ else if (_nil_Q(obj)) { return 'nil'; }
+ else if (_true_Q(obj)) { return 'true'; }
+ else if (_false_Q(obj)) { return 'false'; }
+ else if (_atom_Q(obj)) { return 'atom'; }
+ else {
+ switch (typeof(obj)) {
+ case 'number': return 'number';
+ case 'function': return 'function';
+ case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string';
+ default: throw new Error("Unknown type '" + typeof(obj) + "'");
+ }
+ }
+}
+
+function _sequential_Q(lst) { return _list_Q(lst) || _vector_Q(lst); }
+
+
+function _equal_Q (a, b) {
+ var ota = _obj_type(a), otb = _obj_type(b);
+ if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) {
+ return false;
+ }
+ switch (ota) {
+ case 'symbol': return a.value === b.value;
+ case 'list':
+ case 'vector':
+ if (a.length !== b.length) { return false; }
+ for (var i=0; i<a.length; i++) {
+ if (! _equal_Q(a[i], b[i])) { return false; }
+ }
+ return true;
+ case 'hash-map':
+ if (Object.keys(a).length !== Object.keys(b).length) { return false; }
+ for (var k in a) {
+ if (! _equal_Q(a[k], b[k])) { return false; }
+ }
+ return true;
+ default:
+ return a === b;
+ }
+}
+
+
+function _clone (obj) {
+ var new_obj;
+ switch (_obj_type(obj)) {
+ case 'list':
+ new_obj = obj.slice(0);
+ break;
+ case 'vector':
+ new_obj = obj.slice(0);
+ new_obj.__isvector__ = true;
+ break;
+ case 'hash-map':
+ new_obj = {};
+ for (var k in obj) {
+ if (obj.hasOwnProperty(k)) { new_obj[k] = obj[k]; }
+ }
+ break;
+ case 'function':
+ new_obj = obj.clone();
+ break;
+ default:
+ throw new Error("clone of non-collection: " + _obj_type(obj));
+ }
+ Object.defineProperty(new_obj, "__meta__", {
+ enumerable: false,
+ writable: true
+ });
+ return new_obj;
+}
+
+
+// Scalars
+function _nil_Q(a) { return a === null ? true : false; }
+function _true_Q(a) { return a === true ? true : false; }
+function _false_Q(a) { return a === false ? true : false; }
+function _number_Q(obj) { return typeof obj === 'number'; }
+function _string_Q(obj) {
+ return typeof obj === 'string' && obj[0] !== '\u029e';
+}
+
+
+// Symbols
+function Symbol(name) {
+ this.value = name;
+ return this;
+}
+Symbol.prototype.toString = function() { return this.value; }
+function _symbol(name) { return new Symbol(name); }
+function _symbol_Q(obj) { return obj instanceof Symbol; }
+
+
+// Keywords
+function _keyword(obj) {
+ if (typeof obj === 'string' && obj[0] === '\u029e') {
+ return obj;
+ } else {
+ return "\u029e" + obj;
+ }
+}
+function _keyword_Q(obj) {
+ return typeof obj === 'string' && obj[0] === '\u029e';
+}
+
+
+// Functions
+function _function(Eval, Env, ast, env, params) {
+ var fn = function() {
+ return Eval(ast, new Env(env, params, arguments));
+ };
+ fn.__meta__ = null;
+ fn.__ast__ = ast;
+ fn.__gen_env__ = function(args) { return new Env(env, params, args); };
+ fn._ismacro_ = false;
+ return fn;
+}
+function _function_Q(obj) { return typeof obj == "function"; }
+Function.prototype.clone = function() {
+ var that = this;
+ var temp = function () { return that.apply(this, arguments); };
+ for( key in this ) {
+ temp[key] = this[key];
+ }
+ return temp;
+};
+function _fn_Q(obj) { return _function_Q(obj) && !obj._ismacro_; }
+function _macro_Q(obj) { return _function_Q(obj) && !!obj._ismacro_; }
+
+
+// Lists
+function _list() { return Array.prototype.slice.call(arguments, 0); }
+function _list_Q(obj) { return Array.isArray(obj) && !obj.__isvector__; }
+
+
+// Vectors
+function _vector() {
+ var v = Array.prototype.slice.call(arguments, 0);
+ v.__isvector__ = true;
+ return v;
+}
+function _vector_Q(obj) { return Array.isArray(obj) && !!obj.__isvector__; }
+
+
+
+// Hash Maps
+function _hash_map() {
+ if (arguments.length % 2 === 1) {
+ throw new Error("Odd number of hash map arguments");
+ }
+ var args = [{}].concat(Array.prototype.slice.call(arguments, 0));
+ return _assoc_BANG.apply(null, args);
+}
+function _hash_map_Q(hm) {
+ return typeof hm === "object" &&
+ !Array.isArray(hm) &&
+ !(hm === null) &&
+ !(hm instanceof Symbol) &&
+ !(hm instanceof Atom);
+}
+function _assoc_BANG(hm) {
+ if (arguments.length % 2 !== 1) {
+ throw new Error("Odd number of assoc arguments");
+ }
+ for (var i=1; i<arguments.length; i+=2) {
+ var ktoken = arguments[i],
+ vtoken = arguments[i+1];
+ if (typeof ktoken !== "string") {
+ throw new Error("expected hash-map key string, got: " + (typeof ktoken));
+ }
+ hm[ktoken] = vtoken;
+ }
+ return hm;
+}
+function _dissoc_BANG(hm) {
+ for (var i=1; i<arguments.length; i++) {
+ var ktoken = arguments[i];
+ delete hm[ktoken];
+ }
+ return hm;
+}
+
+
+// Atoms
+function Atom(val) { this.val = val; }
+function _atom(val) { return new Atom(val); }
+function _atom_Q(atm) { return atm instanceof Atom; }
+
+
+// Exports
+exports._obj_type = types._obj_type = _obj_type;
+exports._sequential_Q = types._sequential_Q = _sequential_Q;
+exports._equal_Q = types._equal_Q = _equal_Q;
+exports._clone = types._clone = _clone;
+exports._nil_Q = types._nil_Q = _nil_Q;
+exports._true_Q = types._true_Q = _true_Q;
+exports._false_Q = types._false_Q = _false_Q;
+exports._number_Q = types._number_Q = _number_Q;
+exports._string_Q = types._string_Q = _string_Q;
+exports._symbol = types._symbol = _symbol;
+exports._symbol_Q = types._symbol_Q = _symbol_Q;
+exports._keyword = types._keyword = _keyword;
+exports._keyword_Q = types._keyword_Q = _keyword_Q;
+exports._function = types._function = _function;
+exports._function_Q = types._function_Q = _function_Q;
+exports._fn_Q = types._fn_Q = _fn_Q;
+exports._macro_Q = types._macro_Q = _macro_Q;
+exports._list = types._list = _list;
+exports._list_Q = types._list_Q = _list_Q;
+exports._vector = types._vector = _vector;
+exports._vector_Q = types._vector_Q = _vector_Q;
+exports._hash_map = types._hash_map = _hash_map;
+exports._hash_map_Q = types._hash_map_Q = _hash_map_Q;
+exports._assoc_BANG = types._assoc_BANG = _assoc_BANG;
+exports._dissoc_BANG = types._dissoc_BANG = _dissoc_BANG;
+exports._atom = types._atom = _atom;
+exports._atom_Q = types._atom_Q = _atom_Q;
+// Node vs browser behavior
+var reader = {};
+if (typeof module !== 'undefined') {
+} else {
+ var exports = reader;
+}
+
+function Reader(tokens) {
+ // copy
+ this.tokens = tokens.map(function (a) { return a; });
+ this.position = 0;
+}
+Reader.prototype.next = function() { return this.tokens[this.position++]; }
+Reader.prototype.peek = function() { return this.tokens[this.position]; }
+
+function tokenize(str) {
+ var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g;
+ var results = [];
+ while ((match = re.exec(str)[1]) != '') {
+ if (match[0] === ';') { continue; }
+ results.push(match);
+ }
+ return results;
+}
+
+function read_atom (reader) {
+ var token = reader.next();
+ //console.log("read_atom:", token);
+ if (token.match(/^-?[0-9]+$/)) {
+ return parseInt(token,10) // integer
+ } else if (token.match(/^-?[0-9][0-9.]*$/)) {
+ return parseFloat(token,10); // float
+ } else if (token[0] === "\"") {
+ if (token.slice(-1) !== "\"") {
+ throw new Error("expected '\"', got EOF");
+ }
+ return token.slice(1,token.length-1)
+ .replace(/\\(.)/g, function (_, c) { return c === "n" ? "\n" : c})
+ } else if (token[0] === ":") {
+ return types._keyword(token.slice(1));
+ } else if (token === "nil") {
+ return null;
+ } else if (token === "true") {
+ return true;
+ } else if (token === "false") {
+ return false;
+ } else {
+ return types._symbol(token); // symbol
+ }
+}
+
+// read list of tokens
+function read_list(reader, start, end) {
+ start = start || '(';
+ end = end || ')';
+ var ast = [];
+ var token = reader.next();
+ if (token !== start) {
+ throw new Error("expected '" + start + "'");
+ }
+ while ((token = reader.peek()) !== end) {
+ if (!token) {
+ throw new Error("expected '" + end + "', got EOF");
+ }
+ ast.push(read_form(reader));
+ }
+ reader.next();
+ return ast;
+}
+
+// read vector of tokens
+function read_vector(reader) {
+ var lst = read_list(reader, '[', ']');
+ return types._vector.apply(null, lst);
+}
+
+// read hash-map key/value pairs
+function read_hash_map(reader) {
+ var lst = read_list(reader, '{', '}');
+ return types._hash_map.apply(null, lst);
+}
+
+function read_form(reader) {
+ var token = reader.peek();
+ switch (token) {
+ // reader macros/transforms
+ case ';': return null; // Ignore comments
+ case '\'': reader.next();
+ return [types._symbol('quote'), read_form(reader)];
+ case '`': reader.next();
+ return [types._symbol('quasiquote'), read_form(reader)];
+ case '~': reader.next();
+ return [types._symbol('unquote'), read_form(reader)];
+ case '~@': reader.next();
+ return [types._symbol('splice-unquote'), read_form(reader)];
+ case '^': reader.next();
+ var meta = read_form(reader);
+ return [types._symbol('with-meta'), read_form(reader), meta];
+ case '@': reader.next();
+ return [types._symbol('deref'), read_form(reader)];
+
+ // list
+ case ')': throw new Error("unexpected ')'");
+ case '(': return read_list(reader);
+
+ // vector
+ case ']': throw new Error("unexpected ']'");
+ case '[': return read_vector(reader);
+
+ // hash-map
+ case '}': throw new Error("unexpected '}'");
+ case '{': return read_hash_map(reader);
+
+ // atom
+ default: return read_atom(reader);
+ }
+}
+
+function BlankException(msg) {
+}
+
+function read_str(str) {
+ var tokens = tokenize(str);
+ if (tokens.length === 0) { throw new BlankException(); }
+ return read_form(new Reader(tokens))
+}
+
+exports.Reader = reader.Reader = Reader;
+exports.BlankException = reader.BlankException = BlankException;
+exports.tokenize = reader.tokenize = tokenize;
+exports.read_form = reader.read_form = read_form;
+exports.read_str = reader.read_str = read_str;
+// Node vs browser behavior
+var printer = {};
+if (typeof module !== 'undefined') {
+ // map output/print to console.log
+ printer.println = exports.println = function () {
+ console.log.apply(console, arguments);
+ };
+}
+
+function _pr_str(obj, print_readably) {
+ if (typeof print_readably === 'undefined') { print_readably = true; }
+ var _r = print_readably;
+ var ot = types._obj_type(obj);
+ switch (ot) {
+ case 'list':
+ var ret = obj.map(function(e) { return _pr_str(e,_r); });
+ return "(" + ret.join(' ') + ")";
+ case 'vector':
+ var ret = obj.map(function(e) { return _pr_str(e,_r); });
+ return "[" + ret.join(' ') + "]";
+ case 'hash-map':
+ var ret = [];
+ for (var k in obj) {
+ ret.push(_pr_str(k,_r), _pr_str(obj[k],_r));
+ }
+ return "{" + ret.join(' ') + "}";
+ case 'string':
+ if (obj[0] === '\u029e') {
+ return ':' + obj.slice(1);
+ } else if (_r) {
+ return '"' + obj.replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n") + '"'; // string
+ } else {
+ return obj;
+ }
+ case 'keyword':
+ return ':' + obj.slice(1);
+ case 'nil':
+ return "nil";
+ case 'atom':
+ return "(atom " + _pr_str(obj.val,_r) + ")";
+ default:
+ return obj.toString();
+ }
+}
+
+exports._pr_str = printer._pr_str = _pr_str;
+
+// Node vs browser behavior
+var interop = {};
+if (typeof module === 'undefined') {
+ var exports = interop,
+ GLOBAL = window;
+}
+
+function resolve_js(str) {
+ if (str.match(/\./)) {
+ var re = /^(.*)\.[^\.]*$/,
+ match = re.exec(str);
+ return [eval(match[1]), eval(str)];
+ } else {
+ return [GLOBAL, eval(str)];
+ }
+}
+
+function js_to_mal(obj) {
+ if (obj === null || obj === undefined) {
+ return null;
+ }
+ var cache = [];
+ var str = JSON.stringify(obj, function(key, value) {
+ if (typeof value === 'object' && value !== null) {
+ if (cache.indexOf(value) !== -1) {
+ // Circular reference found, discard key
+ return;
+ }
+ // Store value in our collection
+ cache.push(value);
+ }
+ return value;
+ });
+ cache = null; // Enable garbage collection
+ return JSON.parse(str);
+}
+
+exports.resolve_js = interop.resolve_js = resolve_js;
+exports.js_to_mal = interop.js_to_mal = js_to_mal;
+// Node vs browser behavior
+var env = {};
+if (typeof module === 'undefined') {
+ var exports = env;
+}
+
+// Env implementation
+function Env(outer, binds, exprs) {
+ this.data = {};
+ this.outer = outer || null;
+
+ if (binds && exprs) {
+ // Returns a new Env with symbols in binds bound to
+ // corresponding values in exprs
+ // TODO: check types of binds and exprs and compare lengths
+ for (var i=0; i<binds.length;i++) {
+ if (binds[i].value === "&") {
+ // variable length arguments
+ this.data[binds[i+1].value] = Array.prototype.slice.call(exprs, i);
+ break;
+ } else {
+ this.data[binds[i].value] = exprs[i];
+ }
+ }
+ }
+ return this;
+}
+Env.prototype.find = function (key) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.find key must be a symbol")
+ }
+ if (key.value in this.data) { return this; }
+ else if (this.outer) { return this.outer.find(key); }
+ else { return null; }
+};
+Env.prototype.set = function(key, value) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.set key must be a symbol")
+ }
+ this.data[key.value] = value;
+ return value;
+};
+Env.prototype.get = function(key) {
+ if (!key.constructor || key.constructor.name !== 'Symbol') {
+ throw new Error("env.get key must be a symbol")
+ }
+ var env = this.find(key);
+ if (!env) { throw new Error("'" + key.value + "' not found"); }
+ return env.data[key.value];
+};
+
+exports.Env = env.Env = Env;
+// Node vs browser behavior
+var core = {};
+if (typeof module === 'undefined') {
+ var exports = core;
+} else {
+}
+
+// Errors/Exceptions
+function mal_throw(exc) { throw exc; }
+
+
+// String functions
+function pr_str() {
+ return Array.prototype.map.call(arguments,function(exp) {
+ return printer._pr_str(exp, true);
+ }).join(" ");
+}
+
+function str() {
+ return Array.prototype.map.call(arguments,function(exp) {
+ return printer._pr_str(exp, false);
+ }).join("");
+}
+
+function prn() {
+ printer.println.apply({}, Array.prototype.map.call(arguments,function(exp) {
+ return printer._pr_str(exp, true);
+ }));
+}
+
+function println() {
+ printer.println.apply({}, Array.prototype.map.call(arguments,function(exp) {
+ return printer._pr_str(exp, false);
+ }));
+}
+
+function slurp(f) {
+ if (typeof require !== 'undefined') {
+ return require('fs').readFileSync(f, 'utf-8');
+ } else {
+ var req = new XMLHttpRequest();
+ req.open("GET", f, false);
+ req.send();
+ if (req.status == 200) {
+ return req.responseText;
+ } else {
+ throw new Error("Failed to slurp file: " + f);
+ }
+ }
+}
+
+
+// Number functions
+function time_ms() { return new Date().getTime(); }
+
+
+// Hash Map functions
+function assoc(src_hm) {
+ var hm = types._clone(src_hm);
+ var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
+ return types._assoc_BANG.apply(null, args);
+}
+
+function dissoc(src_hm) {
+ var hm = types._clone(src_hm);
+ var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
+ return types._dissoc_BANG.apply(null, args);
+}
+
+function get(hm, key) {
+ if (hm != null && key in hm) {
+ return hm[key];
+ } else {
+ return null;
+ }
+}
+
+function contains_Q(hm, key) {
+ if (key in hm) { return true; } else { return false; }
+}
+
+function keys(hm) { return Object.keys(hm); }
+function vals(hm) { return Object.keys(hm).map(function(k) { return hm[k]; }); }
+
+
+// Sequence functions
+function cons(a, b) { return [a].concat(b); }
+
+function concat(lst) {
+ lst = lst || [];
+ return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
+}
+
+function nth(lst, idx) {
+ if (idx < lst.length) { return lst[idx]; }
+ else { throw new Error("nth: index out of range"); }
+}
+
+function first(lst) { return (lst === null) ? null : lst[0]; }
+
+function rest(lst) { return (lst == null) ? [] : lst.slice(1); }
+
+function empty_Q(lst) { return lst.length === 0; }
+
+function count(s) {
+ if (Array.isArray(s)) { return s.length; }
+ else if (s === null) { return 0; }
+ else { return Object.keys(s).length; }
+}
+
+function conj(lst) {
+ if (types._list_Q(lst)) {
+ return Array.prototype.slice.call(arguments, 1).reverse().concat(lst);
+ } else {
+ var v = lst.concat(Array.prototype.slice.call(arguments, 1));
+ v.__isvector__ = true;
+ return v;
+ }
+}
+
+function seq(obj) {
+ if (types._list_Q(obj)) {
+ return obj.length > 0 ? obj : null;
+ } else if (types._vector_Q(obj)) {
+ return obj.length > 0 ? Array.prototype.slice.call(obj, 0): null;
+ } else if (types._string_Q(obj)) {
+ return obj.length > 0 ? obj.split('') : null;
+ } else if (obj === null) {
+ return null;
+ } else {
+ throw new Error("seq: called on non-sequence");
+ }
+}
+
+
+function apply(f) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return f.apply(f, args.slice(0, args.length-1).concat(args[args.length-1]));
+}
+
+function map(f, lst) {
+ return lst.map(function(el){ return f(el); });
+}
+
+
+// Metadata functions
+function with_meta(obj, m) {
+ var new_obj = types._clone(obj);
+ new_obj.__meta__ = m;
+ return new_obj;
+}
+
+function meta(obj) {
+ // TODO: support symbols and atoms
+ if ((!types._sequential_Q(obj)) &&
+ (!(types._hash_map_Q(obj))) &&
+ (!(types._function_Q(obj)))) {
+ throw new Error("attempt to get metadata from: " + types._obj_type(obj));
+ }
+ return obj.__meta__;
+}
+
+
+// Atom functions
+function deref(atm) { return atm.val; }
+function reset_BANG(atm, val) { return atm.val = val; }
+function swap_BANG(atm, f) {
+ var args = [atm.val].concat(Array.prototype.slice.call(arguments, 2));
+ atm.val = f.apply(f, args);
+ return atm.val;
+}
+
+function js_eval(str) {
+ return interop.js_to_mal(eval(str.toString()));
+}
+
+function js_method_call(object_method_str) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ r = interop.resolve_js(object_method_str),
+ obj = r[0], f = r[1];
+ var res = f.apply(obj, args);
+ return interop.js_to_mal(res);
+}
+
+// types.ns is namespace of type functions
+var ns = {'type': types._obj_type,
+ '=': types._equal_Q,
+ 'throw': mal_throw,
+ 'nil?': types._nil_Q,
+ 'true?': types._true_Q,
+ 'false?': types._false_Q,
+ 'number?': types._number_Q,
+ 'string?': types._string_Q,
+ 'symbol': types._symbol,
+ 'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
+ 'fn?': types._fn_Q,
+ 'macro?': types._macro_Q,
+
+ 'pr-str': pr_str,
+ 'str': str,
+ 'prn': prn,
+ 'println': println,
+ 'readline': readline.readline,
+ 'read-string': reader.read_str,
+ 'slurp': slurp,
+ '<' : function(a,b){return a<b;},
+ '<=' : function(a,b){return a<=b;},
+ '>' : function(a,b){return a>b;},
+ '>=' : function(a,b){return a>=b;},
+ '+' : function(a,b){return a+b;},
+ '-' : function(a,b){return a-b;},
+ '*' : function(a,b){return a*b;},
+ '/' : function(a,b){return a/b;},
+ "time-ms": time_ms,
+
+ 'list': types._list,
+ 'list?': types._list_Q,
+ 'vector': types._vector,
+ 'vector?': types._vector_Q,
+ 'hash-map': types._hash_map,
+ 'map?': types._hash_map_Q,
+ 'assoc': assoc,
+ 'dissoc': dissoc,
+ 'get': get,
+ 'contains?': contains_Q,
+ 'keys': keys,
+ 'vals': vals,
+
+ 'sequential?': types._sequential_Q,
+ 'cons': cons,
+ 'concat': concat,
+ 'nth': nth,
+ 'first': first,
+ 'rest': rest,
+ 'empty?': empty_Q,
+ 'count': count,
+ 'apply': apply,
+ 'map': map,
+
+ 'conj': conj,
+ 'seq': seq,
+
+ 'with-meta': with_meta,
+ 'meta': meta,
+ 'atom': types._atom,
+ 'atom?': types._atom_Q,
+ "deref": deref,
+ "reset!": reset_BANG,
+ "swap!": swap_BANG,
+
+ 'js-eval': js_eval,
+ '.': js_method_call
+};
+
+exports.ns = core.ns = ns;
+if (typeof module !== 'undefined') {
+}
+
+// read
+function READ(str) {
+ return reader.read_str(str);
+}
+
+// eval
+function is_pair(x) {
+ return types._sequential_Q(x) && x.length > 0;
+}
+
+function quasiquote(ast) {
+ if (!is_pair(ast)) {
+ return [types._symbol("quote"), ast];
+ } else if (types._symbol_Q(ast[0]) && ast[0].value === 'unquote') {
+ return ast[1];
+ } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
+ return [types._symbol("concat"),
+ ast[0][1],
+ quasiquote(ast.slice(1))];
+ } else {
+ return [types._symbol("cons"),
+ quasiquote(ast[0]),
+ quasiquote(ast.slice(1))];
+ }
+}
+
+function is_macro_call(ast, env) {
+ return types._list_Q(ast) &&
+ types._symbol_Q(ast[0]) &&
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
+}
+
+function macroexpand(ast, env) {
+ while (is_macro_call(ast, env)) {
+ var mac = env.get(ast[0]);
+ ast = mac.apply(mac, ast.slice(1));
+ }
+ return ast;
+}
+
+function eval_ast(ast, env) {
+ if (types._symbol_Q(ast)) {
+ return env.get(ast);
+ } else if (types._list_Q(ast)) {
+ return ast.map(function(a) { return EVAL(a, env); });
+ } else if (types._vector_Q(ast)) {
+ var v = ast.map(function(a) { return EVAL(a, env); });
+ v.__isvector__ = true;
+ return v;
+ } else if (types._hash_map_Q(ast)) {
+ var new_hm = {};
+ for (k in ast) {
+ new_hm[EVAL(k, env)] = EVAL(ast[k], env);
+ }
+ return new_hm;
+ } else {
+ return ast;
+ }
+}
+
+function _EVAL(ast, env) {
+ while (true) {
+
+ //printer.println("EVAL:", printer._pr_str(ast, true));
+ if (!types._list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+
+ // apply list
+ ast = macroexpand(ast, env);
+ if (!types._list_Q(ast)) {
+ return eval_ast(ast, env);
+ }
+ if (ast.length === 0) {
+ return ast;
+ }
+
+ var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
+ switch (a0.value) {
+ case "def!":
+ var res = EVAL(a2, env);
+ return env.set(a1, res);
+ case "let*":
+ var let_env = new Env(env);
+ for (var i=0; i < a1.length; i+=2) {
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
+ }
+ ast = a2;
+ env = let_env;
+ break;
+ case "quote":
+ return a1;
+ case "quasiquote":
+ ast = quasiquote(a1);
+ break;
+ case 'defmacro!':
+ var func = EVAL(a2, env);
+ func._ismacro_ = true;
+ return env.set(a1, func);
+ case 'macroexpand':
+ return macroexpand(a1, env);
+ case "try*":
+ try {
+ return EVAL(a1, env);
+ } catch (exc) {
+ if (a2 && a2[0].value === "catch*") {
+ if (exc instanceof Error) { exc = exc.message; }
+ return EVAL(a2[2], new Env(env, [a2[1]], [exc]));
+ } else {
+ throw exc;
+ }
+ }
+ case "do":
+ eval_ast(ast.slice(1, -1), env);
+ ast = ast[ast.length-1];
+ break;
+ case "if":
+ var cond = EVAL(a1, env);
+ if (cond === null || cond === false) {
+ ast = (typeof a3 !== "undefined") ? a3 : null;
+ } else {
+ ast = a2;
+ }
+ break;
+ case "fn*":
+ return types._function(EVAL, Env, a2, env, a1);
+ default:
+ var el = eval_ast(ast, env), f = el[0];
+ if (f.__ast__) {
+ ast = f.__ast__;
+ env = f.__gen_env__(el.slice(1));
+ } else {
+ return f.apply(f, el.slice(1));
+ }
+ }
+
+ }
+}
+
+function EVAL(ast, env) {
+ var result = _EVAL(ast, env);
+ return (typeof result !== "undefined") ? result : null;
+}
+
+// print
+function PRINT(exp) {
+ return printer._pr_str(exp, true);
+}
+
+// repl
+var repl_env = new Env();
+var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
+
+// core.js: defined using javascript
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
+repl_env.set(types._symbol('eval'), function(ast) {
+ return EVAL(ast, repl_env); });
+repl_env.set(types._symbol('*ARGV*'), []);
+
+// core.mal: defined using the language itself
+rep("(def! *host-language* \"javascript\")")
+rep("(def! not (fn* (a) (if a false true)))");
+rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
+rep("(def! *gensym-counter* (atom 0))");
+rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))");
+rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))");
+
+if (typeof process !== 'undefined' && process.argv.length > 2) {
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
+ rep('(load-file "' + process.argv[2] + '")');
+ process.exit(0);
+}
+
+// repl loop
+if (typeof require !== 'undefined' && require.main === module) {
+ // Synchronous node.js commandline mode
+ rep("(println (str \"Mal [\" *host-language* \"]\"))");
+ while (true) {
+ var line = readline.readline("user> ");
+ if (line === null) { break; }
+ try {
+ if (line) { printer.println(rep(line)); }
+ } catch (exc) {
+ if (exc instanceof reader.BlankException) { continue }
+ if (exc instanceof Error) { console.warn(exc.stack) }
+ else { console.warn("Error: " + printer._pr_str(exc, true)) }
+ }
+ }
+}