diff options
| author | Joel Martin <github@martintribe.org> | 2014-04-06 16:49:45 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-04-06 16:49:45 -0500 |
| commit | 7da12c9947aa8b22012e90ca6569ccc76dd6ce68 (patch) | |
| tree | a44d5b3a9cc7f8022eed5172c761c91c8a82f333 | |
| parent | 31b4416181158b0e49e0a09da8056298b8522ba2 (diff) | |
| download | mal-7da12c9947aa8b22012e90ca6569ccc76dd6ce68.tar.gz mal-7da12c9947aa8b22012e90ca6569ccc76dd6ce68.zip | |
Create live page.
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | index.html (renamed from mal.html) | 0 | ||||
| -rw-r--r-- | js/mal_web.js | 1231 |
3 files changed, 1231 insertions, 1 deletions
@@ -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/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 %> <%- cmd %></div><% }); %></div>"), + help: _.template("<div><div><strong>Commands:</strong></div><% _.each(commands, function(cmd) { %><div> <%- cmd %></div><% }); %></div>"), + bad_command: _.template('<div><strong>Unrecognized command: </strong><%=cmd%></div>'), + input_cmd: _.template('<div id="<%- id %>"><span class="prompt"></span> <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>\': <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, ' '); + var cursor = text.substr(cursorIdx, 1); + var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); + $(id(_input_id) + ' .prompt').html(_prompt); + $(id(_input_id) + ' .input .left').html(left); + if(!cursor) { + $(id(_input_id) + ' .input .cursor').html(' ').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; +} |
