diff options
| author | Joel Martin <github@martintribe.org> | 2014-04-27 18:33:00 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-04-27 18:33:00 -0500 |
| commit | 6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (patch) | |
| tree | 74f31beb3c8358e9429203d47ca122a819df494e | |
| parent | 72599899e3d3ae1face46a81f01979b040d19c61 (diff) | |
| download | mal-6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6.tar.gz mal-6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6.zip | |
Rebuild and move js/mal_web.js to js/web/mal.js
| -rw-r--r-- | js/web/mal.js (renamed from js/mal_web.js) | 545 |
1 files changed, 82 insertions, 463 deletions
diff --git a/js/mal_web.js b/js/web/mal.js index d54437f..62d7a02 100644 --- a/js/mal_web.js +++ b/js/web/mal.js @@ -1,423 +1,26 @@ -/* ------------------------------------------------------------------------* - * 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(); - } +var max_history_length = 1000; - function blinkCursor() { - if(!_active) { - return; - } - root.setTimeout(function() { - if(!_active) { - return; +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); } - _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; - }); + jq.SetHistory(lines); } +} - function getHandler(cmd) { - return _cmdHandlers[cmd] || _cmdHandlers._default; - } +function jq_save_history(jq) { + var lines = jq.GetHistory(); + localStorage['mal_history'] = JSON.stringify(lines); +} - 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(); - } - } +var readline = { + 'readline': function(prompt_str) { + return prompt(prompt_str); + }}; - // 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') { @@ -520,14 +123,15 @@ 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)); +function _function(Eval, Env, ast, env, params) { + var fn = function() { + return Eval(ast, new Env(env, params, arguments)); }; - f.__meta__ = {exp: exp, env: env, params: params}; - return f; + 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() { @@ -551,7 +155,7 @@ function _vector() { v.__isvector__ = true; return v; } -function _vector_Q(obj) { return Array.isArray(obj) && obj.__isvector__; } +function _vector_Q(obj) { return Array.isArray(obj) && !!obj.__isvector__; } @@ -759,11 +363,6 @@ if (typeof module !== 'undefined') { 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) { @@ -784,7 +383,7 @@ function _pr_str(obj, print_readably) { } return "{" + ret.join(' ') + "}"; case 'string': - if (print_readably) { + if (_r) { return '"' + obj.replace(/\\/, "\\\\") .replace(/"/g, '\\"') .replace(/\n/g, "\\n") + '"'; // string @@ -878,6 +477,25 @@ function println() { })); } +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) { @@ -893,7 +511,7 @@ function dissoc(src_hm) { } function get(hm, key) { - if (key in hm) { + if (hm != null && key in hm) { return hm[key]; } else { return null; @@ -986,10 +604,14 @@ var ns = {'type': types._obj_type, 'false?': types._false_Q, 'symbol': types._symbol, 'symbol?': types._symbol_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;}, @@ -998,6 +620,7 @@ var ns = {'type': types._obj_type, '-' : 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, @@ -1020,9 +643,9 @@ var ns = {'type': types._obj_type, 'rest': rest, 'empty?': empty_Q, 'count': count, - 'conj': conj, 'apply': apply, 'map': map, + 'conj': conj, 'with-meta': with_meta, 'meta': meta, @@ -1052,9 +675,13 @@ function quasiquote(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))]; + return [types._symbol("concat"), + ast[0][1], + quasiquote(ast.slice(1))]; } else { - return [types._symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + return [types._symbol("cons"), + quasiquote(ast[0]), + quasiquote(ast.slice(1))]; } } @@ -1096,7 +723,7 @@ function eval_ast(ast, env) { function _EVAL(ast, env) { while (true) { - //printer.println("EVAL:", types._pr_str(ast, true)); + //printer.println("EVAL:", printer._pr_str(ast, true)); if (!types._list_Q(ast)) { return eval_ast(ast, env); } @@ -1115,11 +742,14 @@ function _EVAL(ast, 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); + ast = a2; + env = let_env; + break; case "quote": return a1; case "quasiquote": - return EVAL(quasiquote(a1), env); + ast = quasiquote(a1); + break; case 'defmacro!': var func = EVAL(a2, env); func._ismacro_ = true; @@ -1158,10 +788,10 @@ function _EVAL(ast, env) { 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)); + 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)); } @@ -1183,38 +813,29 @@ function PRINT(exp) { // 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 +// core.js: defined using javascript for (var n in core.ns) { repl_env.set(n, core.ns[n]); } +repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); }); +repl_env.set('*ARGV*', []); -_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 +// 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("(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) { + repl_env.set('*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; } @@ -1226,6 +847,4 @@ if (typeof process !== 'undefined' && process.argv.length > 2) { else { printer.println(exc); } } } -} else { - exports.rep = rep; } |
