aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-04-27 18:33:00 -0500
committerJoel Martin <github@martintribe.org>2014-04-27 18:33:00 -0500
commit6b72e6078a7d505ecf9d711eb4a16fc4dfac36b6 (patch)
tree74f31beb3c8358e9429203d47ca122a819df494e
parent72599899e3d3ae1face46a81f01979b040d19c61 (diff)
downloadmal-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 %>&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();
- }
+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;
}