aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-04-06 16:49:45 -0500
committerJoel Martin <github@martintribe.org>2014-04-06 16:49:45 -0500
commit7da12c9947aa8b22012e90ca6569ccc76dd6ce68 (patch)
treea44d5b3a9cc7f8022eed5172c761c91c8a82f333
parent31b4416181158b0e49e0a09da8056298b8522ba2 (diff)
downloadmal-7da12c9947aa8b22012e90ca6569ccc76dd6ce68.tar.gz
mal-7da12c9947aa8b22012e90ca6569ccc76dd6ce68.zip
Create live page.
-rw-r--r--.gitignore1
-rw-r--r--index.html (renamed from mal.html)0
-rw-r--r--js/mal_web.js1231
3 files changed, 1231 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 2d3759a..e3d5f4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,6 @@
make/mal.mk
js/node_modules
js/mal.js
-js/mal_web.js
bash/mal.sh
c/*.o
*.pyc
diff --git a/mal.html b/index.html
index b8b3dd0..b8b3dd0 100644
--- a/mal.html
+++ b/index.html
diff --git a/js/mal_web.js b/js/mal_web.js
new file mode 100644
index 0000000..d54437f
--- /dev/null
+++ b/js/mal_web.js
@@ -0,0 +1,1231 @@
+/* ------------------------------------------------------------------------*
+ * Copyright 2013 Arne F. Claassen
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *-------------------------------------------------------------------------*/
+
+var Josh = Josh || {};
+(function(root, $, _) {
+ Josh.Shell = function(config) {
+ config = config || {};
+
+ // instance fields
+ var _console = config.console || (Josh.Debug && root.console ? root.console : {
+ log: function() {
+ }
+ });
+ var _prompt = config.prompt || 'jsh$';
+ var _action = config.action || function(str) {
+ return "<div>No action defined for: " + str + "</div>";
+ };
+ var _shell_view_id = config.shell_view_id || 'shell-view';
+ var _shell_panel_id = config.shell_panel_id || 'shell-panel';
+ var _input_id = config.input_id || 'shell-cli';
+ var _blinktime = config.blinktime || 500;
+ var _history = config.history || new Josh.History();
+ var _readline = config.readline || new Josh.ReadLine({history: _history, console: _console});
+ var _active = false;
+ var _cursor_visible = false;
+ var _activationHandler;
+ var _deactivationHandler;
+ var _cmdHandlers = {
+ clear: {
+ exec: function(cmd, args, callback) {
+ $(id(_input_id)).parent().empty();
+ callback();
+ }
+ },
+ help: {
+ exec: function(cmd, args, callback) {
+ callback(self.templates.help({commands: commands()}));
+ }
+ },
+ history: {
+ exec: function(cmd, args, callback) {
+ if(args[0] == "-c") {
+ _history.clear();
+ callback();
+ return;
+ }
+ callback(self.templates.history({items: _history.items()}));
+ }
+ },
+ _default: {
+ exec: function(cmd, args, callback) {
+ callback(self.templates.bad_command({cmd: cmd}));
+ },
+ completion: function(cmd, arg, line, callback) {
+ if(!arg) {
+ arg = cmd;
+ }
+ return callback(self.bestMatch(arg, self.commands()))
+ }
+ }
+ };
+ var _line = {
+ text: '',
+ cursor: 0
+ };
+ var _searchMatch = '';
+ var _view, _panel;
+ var _promptHandler;
+ var _initializationHandler;
+ var _initialized;
+
+ // public methods
+ var self = {
+ commands: commands,
+ templates: {
+ history: _.template("<div><% _.each(items, function(cmd, i) { %><div><%- i %>&nbsp;<%- cmd %></div><% }); %></div>"),
+ help: _.template("<div><div><strong>Commands:</strong></div><% _.each(commands, function(cmd) { %><div>&nbsp;<%- cmd %></div><% }); %></div>"),
+ bad_command: _.template('<div><strong>Unrecognized command:&nbsp;</strong><%=cmd%></div>'),
+ input_cmd: _.template('<div id="<%- id %>"><span class="prompt"></span>&nbsp;<span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'),
+ empty_input_cmd: _.template('<div id="<%- id %>"></div>'),
+ input_search: _.template('<div id="<%- id %>">(reverse-i-search)`<span class="searchterm"></span>\':&nbsp;<span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'),
+ suggest: _.template("<div><% _.each(suggestions, function(suggestion) { %><div><%- suggestion %></div><% }); %></div>")
+ },
+ isActive: function() {
+ return _readline.isActive();
+ },
+ activate: function() {
+ if($(id(_shell_view_id)).length == 0) {
+ _active = false;
+ return;
+ }
+ _readline.activate();
+ },
+ deactivate: function() {
+ _console.log("deactivating");
+ _active = false;
+ _readline.deactivate();
+ },
+ setCommandHandler: function(cmd, cmdHandler) {
+ _cmdHandlers[cmd] = cmdHandler;
+ },
+ getCommandHandler: function(cmd) {
+ return _cmdHandlers[cmd];
+ },
+ setPrompt: function(prompt) {
+ _prompt = prompt;
+ if(!_active) {
+ return;
+ }
+ self.refresh();
+ },
+ onEOT: function(completionHandler) {
+ _readline.onEOT(completionHandler);
+ },
+ onCancel: function(completionHandler) {
+ _readline.onCancel(completionHandler);
+ },
+ onInitialize: function(completionHandler) {
+ _initializationHandler = completionHandler;
+ },
+ onActivate: function(completionHandler) {
+ _activationHandler = completionHandler;
+ },
+ onDeactivate: function(completionHandler) {
+ _deactivationHandler = completionHandler;
+ },
+ onNewPrompt: function(completionHandler) {
+ _promptHandler = completionHandler;
+ },
+ render: function() {
+ var text = _line.text || '';
+ var cursorIdx = _line.cursor || 0;
+ if(_searchMatch) {
+ cursorIdx = _searchMatch.cursoridx || 0;
+ text = _searchMatch.text || '';
+ $(id(_input_id) + ' .searchterm').text(_searchMatch.term);
+ }
+ var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, '&nbsp;');
+ var cursor = text.substr(cursorIdx, 1);
+ var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, '&nbsp;');
+ $(id(_input_id) + ' .prompt').html(_prompt);
+ $(id(_input_id) + ' .input .left').html(left);
+ if(!cursor) {
+ $(id(_input_id) + ' .input .cursor').html('&nbsp;').css('textDecoration', 'underline');
+ } else {
+ $(id(_input_id) + ' .input .cursor').text(cursor).css('textDecoration', 'underline');
+ }
+ $(id(_input_id) + ' .input .right').html(right);
+ _cursor_visible = true;
+ self.scrollToBottom();
+ _console.log('rendered "' + text + '" w/ cursor at ' + cursorIdx);
+ },
+ refresh: function() {
+ $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id}));
+ self.render();
+ _console.log('refreshed ' + _input_id);
+
+ },
+ println: function(text) {
+ var lines = text.split(/\n/);
+ for (var i=0; i<lines.length; i++) {
+ var line = lines[i];
+ if (line == "\\n") {
+ continue;
+ }
+ $(id(_input_id)).after(line);
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', '');
+ $(id(_input_id)).removeAttr('id');
+ $(id(_shell_view_id)).append(self.templates.empty_input_cmd({id:_input_id}));
+ }
+ },
+ scrollToBottom: function() {
+ _panel.animate({scrollTop: _view.height()}, 0);
+ },
+ bestMatch: function(partial, possible) {
+ _console.log("bestMatch on partial '" + partial + "'");
+ var result = {
+ completion: null,
+ suggestions: []
+ };
+ if(!possible || possible.length == 0) {
+ return result;
+ }
+ var common = '';
+ if(!partial) {
+ if(possible.length == 1) {
+ result.completion = possible[0];
+ result.suggestions = possible;
+ return result;
+ }
+ if(!_.every(possible, function(x) {
+ return possible[0][0] == x[0]
+ })) {
+ result.suggestions = possible;
+ return result;
+ }
+ }
+ for(var i = 0; i < possible.length; i++) {
+ var option = possible[i];
+ if(option.slice(0, partial.length) == partial) {
+ result.suggestions.push(option);
+ if(!common) {
+ common = option;
+ _console.log("initial common:" + common);
+ } else if(option.slice(0, common.length) != common) {
+ _console.log("find common stem for '" + common + "' and '" + option + "'");
+ var j = partial.length;
+ while(j < common.length && j < option.length) {
+ if(common[j] != option[j]) {
+ common = common.substr(0, j);
+ break;
+ }
+ j++;
+ }
+ }
+ }
+ }
+ result.completion = common.substr(partial.length);
+ return result;
+ }
+ };
+
+ function id(id) {
+ return "#"+id;
+ }
+
+ function commands() {
+ return _.chain(_cmdHandlers).keys().filter(function(x) {
+ return x[0] != "_"
+ }).value();
+ }
+
+ function blinkCursor() {
+ if(!_active) {
+ return;
+ }
+ root.setTimeout(function() {
+ if(!_active) {
+ return;
+ }
+ _cursor_visible = !_cursor_visible;
+ if(_cursor_visible) {
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', 'underline');
+ } else {
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', '');
+ }
+ blinkCursor();
+ }, _blinktime);
+ }
+
+ function split(str) {
+ return _.filter(str.split(/\s+/), function(x) {
+ return x;
+ });
+ }
+
+ function getHandler(cmd) {
+ return _cmdHandlers[cmd] || _cmdHandlers._default;
+ }
+
+ function renderOutput(output, callback) {
+ if(output) {
+ $(id(_input_id)).after(output);
+ }
+ $(id(_input_id) + ' .input .cursor').css('textDecoration', '');
+ $(id(_input_id)).removeAttr('id');
+ $(id(_shell_view_id)).append(self.templates.input_cmd({id:_input_id}));
+ if(_promptHandler) {
+ return _promptHandler(function(prompt) {
+ self.setPrompt(prompt);
+ return callback();
+ });
+ }
+ return callback();
+ }
+
+ function activate() {
+ _console.log("activating shell");
+ if(!_view) {
+ _view = $(id(_shell_view_id));
+ }
+ if(!_panel) {
+ _panel = $(id(_shell_panel_id));
+ }
+ if($(id(_input_id)).length == 0) {
+ _view.append(self.templates.input_cmd({id:_input_id}));
+ }
+ self.refresh();
+ _active = true;
+ blinkCursor();
+ if(_promptHandler) {
+ _promptHandler(function(prompt) {
+ self.setPrompt(prompt);
+ })
+ }
+ if(_activationHandler) {
+ _activationHandler();
+ }
+ }
+
+ // init
+ _readline.onActivate(function() {
+ if(!_initialized) {
+ _initialized = true;
+ if(_initializationHandler) {
+ return _initializationHandler(activate);
+ }
+ }
+ return activate();
+ });
+ _readline.onDeactivate(function() {
+ if(_deactivationHandler) {
+ _deactivationHandler();
+ }
+ });
+ _readline.onChange(function(line) {
+ _line = line;
+ self.render();
+ });
+ _readline.onClear(function() {
+ _cmdHandlers.clear.exec(null, null, function() {
+ renderOutput(null, function() {
+ });
+ });
+ });
+ _readline.onSearchStart(function() {
+ $(id(_input_id)).replaceWith(self.templates.input_search({id:_input_id}));
+ _console.log('started search');
+ });
+ _readline.onSearchEnd(function() {
+ $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id}));
+ _searchMatch = null;
+ self.render();
+ _console.log("ended search");
+ });
+ _readline.onSearchChange(function(match) {
+ _searchMatch = match;
+ self.render();
+ });
+ _readline.onEnter(function(cmdtext, callback) {
+ _console.log("got command: " + cmdtext);
+ var result;
+ try {
+ result = "<div>" + _action(cmdtext) + "</div>";
+ } catch (e) {
+ result = "<div>" + e.stack + "</div>";
+ }
+ renderOutput(result, function() {
+ callback("");
+ });
+ });
+ _readline.onCompletion(function(line, callback) {
+ if(!line) {
+ return callback();
+ }
+ var text = line.text.substr(0, line.cursor);
+ var parts = split(text);
+
+ var cmd = parts.shift() || '';
+ var arg = parts.pop() || '';
+ _console.log("getting completion handler for " + cmd);
+ var handler = getHandler(cmd);
+ if(handler != _cmdHandlers._default && cmd && cmd == text) {
+
+ _console.log("valid cmd, no args: append space");
+ // the text to complete is just a valid command, append a space
+ return callback(' ');
+ }
+ if(!handler.completion) {
+ // handler has no completion function, so we can't complete
+ return callback();
+ }
+ _console.log("calling completion handler for " + cmd);
+ return handler.completion(cmd, arg, line, function(match) {
+ _console.log("completion: " + JSON.stringify(match));
+ if(!match) {
+ return callback();
+ }
+ if(match.suggestions && match.suggestions.length > 1) {
+ return renderOutput(self.templates.suggest({suggestions: match.suggestions}), function() {
+ callback(match.completion);
+ });
+ }
+ return callback(match.completion);
+ });
+ });
+ return self;
+ }
+})(this, $, _);
+
+var readline = {};
+readline.rlwrap = function(action) {
+ var history = new Josh.History({ key: 'josh.helloworld'});
+ var shell = Josh.Shell({history: history,
+ action: action});
+ var promptCounter = 0;
+ shell.onNewPrompt(function(callback) {
+ promptCounter++;
+ callback("user>");
+ });
+ shell.activate();
+
+ // map output/print to josh.js output
+ readline.println = function () {
+ shell.println(Array.prototype.slice.call(arguments).join(" "));
+ };
+}
+// Node vs browser behavior
+var types = {};
+if (typeof module === 'undefined') {
+ var exports = types;
+}
+
+// General fucnctions
+
+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 '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':
+ var akeys = Object.keys(a).sort(),
+ bkeys = Object.keys(b).sort();
+ if (akeys.length !== bkeys.length) { return false; }
+ for (var i=0; i<akeys.length; i++) {
+ if (akeys[i] !== bkeys[i]) { return false; }
+ if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { 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));
+ }
+ 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; }
+
+
+// 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; }
+
+
+// Functions
+function _function(Eval, Env, exp, env, params) {
+ var f = function() {
+ // TODO: figure out why this throws with 'and' macro
+ //throw new Error("Attempt to invoke mal function directly");
+ return Eval(exp, new Env(env, params, arguments));
+ };
+ f.__meta__ = {exp: exp, env: env, params: params};
+ return f;
+}
+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;
+};
+
+
+// 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 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];
+ // TODO: support more than string keys
+ //if (list_Q(ktoken) && hash_map_Q(ktoken)) {
+ // throw new Error("expected hash-map key atom, got collection");
+ //}
+ 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._symbol = types._symbol = _symbol;
+exports._symbol_Q = types._symbol_Q = _symbol_Q;
+exports._function = types._function = _function;
+exports._function_Q = types._function_Q = _function_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] === "\"") {
+ return token.slice(1,token.length-1)
+ .replace(/\\"/g, '"')
+ .replace(/\\n/g, "\n"); // string
+ } 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);
+ };
+} else {
+ var exports = printer;
+ printer.println = function() {
+ readline.println.apply(null, arguments); // josh_readline.js
+ }
+}
+
+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 (print_readably) {
+ return '"' + obj.replace(/\\/, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n") + '"'; // string
+ } else {
+ return obj;
+ }
+ 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 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 in this.data) { return this; }
+ else if (this.outer) { return this.outer.find(key); }
+ else { return null; }
+};
+Env.prototype.set = function(key, value) { this.data[key] = value; return value; },
+Env.prototype.get = function(key) {
+ var env = this.find(key);
+ if (!env) { throw new Error("'" + key + "' not found"); }
+ return env.data[key];
+};
+
+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);
+ }));
+}
+
+
+// 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 (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) { return lst[idx]; }
+
+function first(lst) { return lst[0]; }
+
+function rest(lst) { return lst.slice(1); }
+
+function empty_Q(lst) { return lst.length === 0; }
+
+function count(s) {
+ if (Array.isArray(s)) { return s.length; }
+ 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 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;
+}
+
+
+// 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,
+ 'symbol': types._symbol,
+ 'symbol?': types._symbol_Q,
+ 'pr-str': pr_str,
+ 'str': str,
+ 'prn': prn,
+ 'println': println,
+ '<' : 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;},
+
+ '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,
+ 'conj': conj,
+ 'apply': apply,
+ 'map': map,
+
+ 'with-meta': with_meta,
+ 'meta': meta,
+ 'atom': types._atom,
+ 'atom?': types._atom_Q,
+ "deref": deref,
+ "reset!": reset_BANG,
+ "swap!": swap_BANG};
+
+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 (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].value) &&
+ env.get(ast[0].value)._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:", types._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 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].value, EVAL(a1[i+1], let_env));
+ }
+ return EVAL(a2, let_env);
+ case "quote":
+ return a1;
+ case "quasiquote":
+ return EVAL(quasiquote(a1), env);
+ case 'defmacro!':
+ var func = EVAL(a2, env);
+ func._ismacro_ = true;
+ return env.set(a1, func);
+ case 'macroexpand':
+ return macroexpand(a1, env);
+ case "js*":
+ return eval(a1.toString());
+ case ".":
+ var el = eval_ast(ast.slice(2), env),
+ f = eval(a1.toString());
+ return f.apply(f, el);
+ 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], meta = f.__meta__;
+ if (meta && meta.exp) {
+ ast = meta.exp;
+ env = new Env(meta.env, meta.params, 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)); };
+_ref = function (k,v) { repl_env.set(k, v); }
+
+// Import core functions
+for (var n in core.ns) { repl_env.set(n, core.ns[n]); }
+
+_ref('readline', readline.readline)
+_ref('read-string', reader.read_str);
+_ref('eval', function(ast) { return EVAL(ast, repl_env); });
+_ref('slurp', function(f) {
+ return require('fs').readFileSync(f, 'utf-8');
+});
+
+// Defined using the language itself
+rep("(def! not (fn* (a) (if a false true)))");
+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("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
+rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+
+if (typeof process !== 'undefined' && process.argv.length > 2) {
+ for (var i=2; i < process.argv.length; i++) {
+ rep('(load-file "' + process.argv[i] + '")');
+ }
+} else if (typeof require === 'undefined') {
+ // Asynchronous browser mode
+ readline.rlwrap(function(line) { return rep(line); },
+ function(exc) {
+ if (exc instanceof reader.BlankException) { return; }
+ if (exc.stack) { printer.println(exc.stack); }
+ else { printer.println(exc); }
+ });
+} else if (require.main === module) {
+ // Synchronous node.js commandline mode
+ 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.stack) { printer.println(exc.stack); }
+ else { printer.println(exc); }
+ }
+ }
+} else {
+ exports.rep = rep;
+}