Move web files into docs/. Build docs/web/mal.js
To update the docs/web/mal.js script run `make -C js web/mal.js` from the top-level (js/web is a symlink to docs/web).
@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Copyright (c) 2014 Joel Martin
|
||||
Copyright (c) 2019 Joel Martin
|
||||
Copyright (c) 2012 Fogus, Jen Myers and Relevance Inc.
|
||||
All rights reserved. The use and distribution terms for this software
|
||||
are covered by the Eclipse Public License 1.0
|
||||
@ -14,12 +14,12 @@ this software.
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/base.css" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/layout.css" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/skeleton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/himera.css" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/ansi.css" />
|
||||
<link rel="stylesheet" type="text/css" href="js/web/console.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/base.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/layout.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/skeleton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/himera.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/ansi.css" />
|
||||
<link rel="stylesheet" type="text/css" href="web/console.css" />
|
||||
<style type="text/css" media="screen">
|
||||
</style>
|
||||
<title>Mal Web REPL</title>
|
||||
@ -49,7 +49,7 @@ this software.
|
||||
</div>
|
||||
<div class="eight columns">
|
||||
<div class="source">
|
||||
<a href="http://github.com/kanaka/mal">View source on Github <img src="js/web/github-icon.png" /></a></p>
|
||||
<a href="http://github.com/kanaka/mal">View source on Github <img src="web/github-icon.png" /></a></p>
|
||||
</div><!-- /source -->
|
||||
</div>
|
||||
|
||||
@ -214,14 +214,14 @@ this software.
|
||||
<div class="rule sixteen columns"></div>
|
||||
|
||||
<div class="column footer-logo">
|
||||
<div>Mal © 2013 Joel Martin</div>
|
||||
<div>Mal © 2019 Joel Martin</div>
|
||||
<div>Himera design © 2012-2013 <a ref="http://www.fogus.me">Fogus</a>, <a href="http://jenmyers.net/">Jen Myers</a> and <a href="http://www.thinkrelevance.com">Relevance Inc.</a></div>
|
||||
</div>
|
||||
|
||||
</div><!-- / container -->
|
||||
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="js/web/jqconsole.min.js"></script>
|
||||
<script type="text/javascript" src="js/web/mal.js"></script>
|
||||
<script type="text/javascript" src="web/jqconsole.min.js"></script>
|
||||
<script type="text/javascript" src="web/mal.js"></script>
|
||||
<script>
|
||||
$(function() {
|
||||
// Creating the console.
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 308 KiB |
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 958 B |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
976
docs/web/mal.js
Normal file
@ -0,0 +1,976 @@
|
||||
var max_history_length = 1000;
|
||||
|
||||
function jq_load_history(jq) {
|
||||
if (localStorage['mal_history']) {
|
||||
var lines = JSON.parse(localStorage['mal_history']);
|
||||
if (lines.length > max_history_length) {
|
||||
lines = lines.slice(lines.length-max_history_length);
|
||||
}
|
||||
jq.SetHistory(lines);
|
||||
}
|
||||
}
|
||||
|
||||
function jq_save_history(jq) {
|
||||
var lines = jq.GetHistory();
|
||||
localStorage['mal_history'] = JSON.stringify(lines);
|
||||
}
|
||||
|
||||
|
||||
var readline = {
|
||||
'readline': function(prompt_str) {
|
||||
return prompt(prompt_str);
|
||||
}};
|
||||
|
||||
// Node vs browser behavior
|
||||
var types = {};
|
||||
if (typeof module === 'undefined') {
|
||||
var exports = types;
|
||||
}
|
||||
|
||||
// General functions
|
||||
|
||||
function _obj_type(obj) {
|
||||
if (_symbol_Q(obj)) { return 'symbol'; }
|
||||
else if (_list_Q(obj)) { return 'list'; }
|
||||
else if (_vector_Q(obj)) { return 'vector'; }
|
||||
else if (_hash_map_Q(obj)) { return 'hash-map'; }
|
||||
else if (_nil_Q(obj)) { return 'nil'; }
|
||||
else if (_true_Q(obj)) { return 'true'; }
|
||||
else if (_false_Q(obj)) { return 'false'; }
|
||||
else if (_atom_Q(obj)) { return 'atom'; }
|
||||
else {
|
||||
switch (typeof(obj)) {
|
||||
case 'number': return 'number';
|
||||
case 'function': return 'function';
|
||||
case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string';
|
||||
default: throw new Error("Unknown type '" + typeof(obj) + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _sequential_Q(lst) { return _list_Q(lst) || _vector_Q(lst); }
|
||||
|
||||
|
||||
function _equal_Q (a, b) {
|
||||
var ota = _obj_type(a), otb = _obj_type(b);
|
||||
if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) {
|
||||
return false;
|
||||
}
|
||||
switch (ota) {
|
||||
case 'symbol': return a.value === b.value;
|
||||
case 'list':
|
||||
case 'vector':
|
||||
if (a.length !== b.length) { return false; }
|
||||
for (var i=0; i<a.length; i++) {
|
||||
if (! _equal_Q(a[i], b[i])) { return false; }
|
||||
}
|
||||
return true;
|
||||
case 'hash-map':
|
||||
if (Object.keys(a).length !== Object.keys(b).length) { return false; }
|
||||
for (var k in a) {
|
||||
if (! _equal_Q(a[k], b[k])) { return false; }
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return a === b;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _clone (obj) {
|
||||
var new_obj;
|
||||
switch (_obj_type(obj)) {
|
||||
case 'list':
|
||||
new_obj = obj.slice(0);
|
||||
break;
|
||||
case 'vector':
|
||||
new_obj = obj.slice(0);
|
||||
new_obj.__isvector__ = true;
|
||||
break;
|
||||
case 'hash-map':
|
||||
new_obj = {};
|
||||
for (var k in obj) {
|
||||
if (obj.hasOwnProperty(k)) { new_obj[k] = obj[k]; }
|
||||
}
|
||||
break;
|
||||
case 'function':
|
||||
new_obj = obj.clone();
|
||||
break;
|
||||
default:
|
||||
throw new Error("clone of non-collection: " + _obj_type(obj));
|
||||
}
|
||||
Object.defineProperty(new_obj, "__meta__", {
|
||||
enumerable: false,
|
||||
writable: true
|
||||
});
|
||||
return new_obj;
|
||||
}
|
||||
|
||||
|
||||
// Scalars
|
||||
function _nil_Q(a) { return a === null ? true : false; }
|
||||
function _true_Q(a) { return a === true ? true : false; }
|
||||
function _false_Q(a) { return a === false ? true : false; }
|
||||
function _number_Q(obj) { return typeof obj === 'number'; }
|
||||
function _string_Q(obj) {
|
||||
return typeof obj === 'string' && obj[0] !== '\u029e';
|
||||
}
|
||||
|
||||
|
||||
// Symbols
|
||||
function Symbol(name) {
|
||||
this.value = name;
|
||||
return this;
|
||||
}
|
||||
Symbol.prototype.toString = function() { return this.value; }
|
||||
function _symbol(name) { return new Symbol(name); }
|
||||
function _symbol_Q(obj) { return obj instanceof Symbol; }
|
||||
|
||||
|
||||
// Keywords
|
||||
function _keyword(obj) {
|
||||
if (typeof obj === 'string' && obj[0] === '\u029e') {
|
||||
return obj;
|
||||
} else {
|
||||
return "\u029e" + obj;
|
||||
}
|
||||
}
|
||||
function _keyword_Q(obj) {
|
||||
return typeof obj === 'string' && obj[0] === '\u029e';
|
||||
}
|
||||
|
||||
|
||||
// Functions
|
||||
function _function(Eval, Env, ast, env, params) {
|
||||
var fn = function() {
|
||||
return Eval(ast, new Env(env, params, arguments));
|
||||
};
|
||||
fn.__meta__ = null;
|
||||
fn.__ast__ = ast;
|
||||
fn.__gen_env__ = function(args) { return new Env(env, params, args); };
|
||||
fn._ismacro_ = false;
|
||||
return fn;
|
||||
}
|
||||
function _function_Q(obj) { return typeof obj == "function"; }
|
||||
Function.prototype.clone = function() {
|
||||
var that = this;
|
||||
var temp = function () { return that.apply(this, arguments); };
|
||||
for( key in this ) {
|
||||
temp[key] = this[key];
|
||||
}
|
||||
return temp;
|
||||
};
|
||||
function _fn_Q(obj) { return _function_Q(obj) && !obj._ismacro_; }
|
||||
function _macro_Q(obj) { return _function_Q(obj) && !!obj._ismacro_; }
|
||||
|
||||
|
||||
// Lists
|
||||
function _list() { return Array.prototype.slice.call(arguments, 0); }
|
||||
function _list_Q(obj) { return Array.isArray(obj) && !obj.__isvector__; }
|
||||
|
||||
|
||||
// Vectors
|
||||
function _vector() {
|
||||
var v = Array.prototype.slice.call(arguments, 0);
|
||||
v.__isvector__ = true;
|
||||
return v;
|
||||
}
|
||||
function _vector_Q(obj) { return Array.isArray(obj) && !!obj.__isvector__; }
|
||||
|
||||
|
||||
|
||||
// Hash Maps
|
||||
function _hash_map() {
|
||||
if (arguments.length % 2 === 1) {
|
||||
throw new Error("Odd number of hash map arguments");
|
||||
}
|
||||
var args = [{}].concat(Array.prototype.slice.call(arguments, 0));
|
||||
return _assoc_BANG.apply(null, args);
|
||||
}
|
||||
function _hash_map_Q(hm) {
|
||||
return typeof hm === "object" &&
|
||||
!Array.isArray(hm) &&
|
||||
!(hm === null) &&
|
||||
!(hm instanceof Symbol) &&
|
||||
!(hm instanceof Atom);
|
||||
}
|
||||
function _assoc_BANG(hm) {
|
||||
if (arguments.length % 2 !== 1) {
|
||||
throw new Error("Odd number of assoc arguments");
|
||||
}
|
||||
for (var i=1; i<arguments.length; i+=2) {
|
||||
var ktoken = arguments[i],
|
||||
vtoken = arguments[i+1];
|
||||
if (typeof ktoken !== "string") {
|
||||
throw new Error("expected hash-map key string, got: " + (typeof ktoken));
|
||||
}
|
||||
hm[ktoken] = vtoken;
|
||||
}
|
||||
return hm;
|
||||
}
|
||||
function _dissoc_BANG(hm) {
|
||||
for (var i=1; i<arguments.length; i++) {
|
||||
var ktoken = arguments[i];
|
||||
delete hm[ktoken];
|
||||
}
|
||||
return hm;
|
||||
}
|
||||
|
||||
|
||||
// Atoms
|
||||
function Atom(val) { this.val = val; }
|
||||
function _atom(val) { return new Atom(val); }
|
||||
function _atom_Q(atm) { return atm instanceof Atom; }
|
||||
|
||||
|
||||
// Exports
|
||||
exports._obj_type = types._obj_type = _obj_type;
|
||||
exports._sequential_Q = types._sequential_Q = _sequential_Q;
|
||||
exports._equal_Q = types._equal_Q = _equal_Q;
|
||||
exports._clone = types._clone = _clone;
|
||||
exports._nil_Q = types._nil_Q = _nil_Q;
|
||||
exports._true_Q = types._true_Q = _true_Q;
|
||||
exports._false_Q = types._false_Q = _false_Q;
|
||||
exports._number_Q = types._number_Q = _number_Q;
|
||||
exports._string_Q = types._string_Q = _string_Q;
|
||||
exports._symbol = types._symbol = _symbol;
|
||||
exports._symbol_Q = types._symbol_Q = _symbol_Q;
|
||||
exports._keyword = types._keyword = _keyword;
|
||||
exports._keyword_Q = types._keyword_Q = _keyword_Q;
|
||||
exports._function = types._function = _function;
|
||||
exports._function_Q = types._function_Q = _function_Q;
|
||||
exports._fn_Q = types._fn_Q = _fn_Q;
|
||||
exports._macro_Q = types._macro_Q = _macro_Q;
|
||||
exports._list = types._list = _list;
|
||||
exports._list_Q = types._list_Q = _list_Q;
|
||||
exports._vector = types._vector = _vector;
|
||||
exports._vector_Q = types._vector_Q = _vector_Q;
|
||||
exports._hash_map = types._hash_map = _hash_map;
|
||||
exports._hash_map_Q = types._hash_map_Q = _hash_map_Q;
|
||||
exports._assoc_BANG = types._assoc_BANG = _assoc_BANG;
|
||||
exports._dissoc_BANG = types._dissoc_BANG = _dissoc_BANG;
|
||||
exports._atom = types._atom = _atom;
|
||||
exports._atom_Q = types._atom_Q = _atom_Q;
|
||||
// Node vs browser behavior
|
||||
var reader = {};
|
||||
if (typeof module !== 'undefined') {
|
||||
} else {
|
||||
var exports = reader;
|
||||
}
|
||||
|
||||
function Reader(tokens) {
|
||||
// copy
|
||||
this.tokens = tokens.map(function (a) { return a; });
|
||||
this.position = 0;
|
||||
}
|
||||
Reader.prototype.next = function() { return this.tokens[this.position++]; }
|
||||
Reader.prototype.peek = function() { return this.tokens[this.position]; }
|
||||
|
||||
function tokenize(str) {
|
||||
var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"?|;.*|[^\s\[\]{}('"`,;)]*)/g;
|
||||
var results = [];
|
||||
while ((match = re.exec(str)[1]) != '') {
|
||||
if (match[0] === ';') { continue; }
|
||||
results.push(match);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
function read_atom (reader) {
|
||||
var token = reader.next();
|
||||
//console.log("read_atom:", token);
|
||||
if (token.match(/^-?[0-9]+$/)) {
|
||||
return parseInt(token,10) // integer
|
||||
} else if (token.match(/^-?[0-9][0-9.]*$/)) {
|
||||
return parseFloat(token,10); // float
|
||||
} else if (token[0] === "\"") {
|
||||
if (token.slice(-1) !== "\"") {
|
||||
throw new Error("expected '\"', got EOF");
|
||||
}
|
||||
return token.slice(1,token.length-1)
|
||||
.replace(/\\(.)/g, function (_, c) { return c === "n" ? "\n" : c})
|
||||
} else if (token[0] === ":") {
|
||||
return types._keyword(token.slice(1));
|
||||
} else if (token === "nil") {
|
||||
return null;
|
||||
} else if (token === "true") {
|
||||
return true;
|
||||
} else if (token === "false") {
|
||||
return false;
|
||||
} else {
|
||||
return types._symbol(token); // symbol
|
||||
}
|
||||
}
|
||||
|
||||
// read list of tokens
|
||||
function read_list(reader, start, end) {
|
||||
start = start || '(';
|
||||
end = end || ')';
|
||||
var ast = [];
|
||||
var token = reader.next();
|
||||
if (token !== start) {
|
||||
throw new Error("expected '" + start + "'");
|
||||
}
|
||||
while ((token = reader.peek()) !== end) {
|
||||
if (!token) {
|
||||
throw new Error("expected '" + end + "', got EOF");
|
||||
}
|
||||
ast.push(read_form(reader));
|
||||
}
|
||||
reader.next();
|
||||
return ast;
|
||||
}
|
||||
|
||||
// read vector of tokens
|
||||
function read_vector(reader) {
|
||||
var lst = read_list(reader, '[', ']');
|
||||
return types._vector.apply(null, lst);
|
||||
}
|
||||
|
||||
// read hash-map key/value pairs
|
||||
function read_hash_map(reader) {
|
||||
var lst = read_list(reader, '{', '}');
|
||||
return types._hash_map.apply(null, lst);
|
||||
}
|
||||
|
||||
function read_form(reader) {
|
||||
var token = reader.peek();
|
||||
switch (token) {
|
||||
// reader macros/transforms
|
||||
case ';': return null; // Ignore comments
|
||||
case '\'': reader.next();
|
||||
return [types._symbol('quote'), read_form(reader)];
|
||||
case '`': reader.next();
|
||||
return [types._symbol('quasiquote'), read_form(reader)];
|
||||
case '~': reader.next();
|
||||
return [types._symbol('unquote'), read_form(reader)];
|
||||
case '~@': reader.next();
|
||||
return [types._symbol('splice-unquote'), read_form(reader)];
|
||||
case '^': reader.next();
|
||||
var meta = read_form(reader);
|
||||
return [types._symbol('with-meta'), read_form(reader), meta];
|
||||
case '@': reader.next();
|
||||
return [types._symbol('deref'), read_form(reader)];
|
||||
|
||||
// list
|
||||
case ')': throw new Error("unexpected ')'");
|
||||
case '(': return read_list(reader);
|
||||
|
||||
// vector
|
||||
case ']': throw new Error("unexpected ']'");
|
||||
case '[': return read_vector(reader);
|
||||
|
||||
// hash-map
|
||||
case '}': throw new Error("unexpected '}'");
|
||||
case '{': return read_hash_map(reader);
|
||||
|
||||
// atom
|
||||
default: return read_atom(reader);
|
||||
}
|
||||
}
|
||||
|
||||
function BlankException(msg) {
|
||||
}
|
||||
|
||||
function read_str(str) {
|
||||
var tokens = tokenize(str);
|
||||
if (tokens.length === 0) { throw new BlankException(); }
|
||||
return read_form(new Reader(tokens))
|
||||
}
|
||||
|
||||
exports.Reader = reader.Reader = Reader;
|
||||
exports.BlankException = reader.BlankException = BlankException;
|
||||
exports.tokenize = reader.tokenize = tokenize;
|
||||
exports.read_form = reader.read_form = read_form;
|
||||
exports.read_str = reader.read_str = read_str;
|
||||
// Node vs browser behavior
|
||||
var printer = {};
|
||||
if (typeof module !== 'undefined') {
|
||||
// map output/print to console.log
|
||||
printer.println = exports.println = function () {
|
||||
console.log.apply(console, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
function _pr_str(obj, print_readably) {
|
||||
if (typeof print_readably === 'undefined') { print_readably = true; }
|
||||
var _r = print_readably;
|
||||
var ot = types._obj_type(obj);
|
||||
switch (ot) {
|
||||
case 'list':
|
||||
var ret = obj.map(function(e) { return _pr_str(e,_r); });
|
||||
return "(" + ret.join(' ') + ")";
|
||||
case 'vector':
|
||||
var ret = obj.map(function(e) { return _pr_str(e,_r); });
|
||||
return "[" + ret.join(' ') + "]";
|
||||
case 'hash-map':
|
||||
var ret = [];
|
||||
for (var k in obj) {
|
||||
ret.push(_pr_str(k,_r), _pr_str(obj[k],_r));
|
||||
}
|
||||
return "{" + ret.join(' ') + "}";
|
||||
case 'string':
|
||||
if (obj[0] === '\u029e') {
|
||||
return ':' + obj.slice(1);
|
||||
} else if (_r) {
|
||||
return '"' + obj.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, "\\n") + '"'; // string
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
case 'keyword':
|
||||
return ':' + obj.slice(1);
|
||||
case 'nil':
|
||||
return "nil";
|
||||
case 'atom':
|
||||
return "(atom " + _pr_str(obj.val,_r) + ")";
|
||||
default:
|
||||
return obj.toString();
|
||||
}
|
||||
}
|
||||
|
||||
exports._pr_str = printer._pr_str = _pr_str;
|
||||
|
||||
// Node vs browser behavior
|
||||
var interop = {};
|
||||
if (typeof module === 'undefined') {
|
||||
var exports = interop,
|
||||
GLOBAL = window;
|
||||
}
|
||||
|
||||
function resolve_js(str) {
|
||||
if (str.match(/\./)) {
|
||||
var re = /^(.*)\.[^\.]*$/,
|
||||
match = re.exec(str);
|
||||
return [eval(match[1]), eval(str)];
|
||||
} else {
|
||||
return [GLOBAL, eval(str)];
|
||||
}
|
||||
}
|
||||
|
||||
function js_to_mal(obj) {
|
||||
if (obj === null || obj === undefined) {
|
||||
return null;
|
||||
}
|
||||
var cache = [];
|
||||
var str = JSON.stringify(obj, function(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
// Circular reference found, discard key
|
||||
return;
|
||||
}
|
||||
// Store value in our collection
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
cache = null; // Enable garbage collection
|
||||
return JSON.parse(str);
|
||||
}
|
||||
|
||||
exports.resolve_js = interop.resolve_js = resolve_js;
|
||||
exports.js_to_mal = interop.js_to_mal = js_to_mal;
|
||||
// Node vs browser behavior
|
||||
var env = {};
|
||||
if (typeof module === 'undefined') {
|
||||
var exports = env;
|
||||
}
|
||||
|
||||
// Env implementation
|
||||
function Env(outer, binds, exprs) {
|
||||
this.data = {};
|
||||
this.outer = outer || null;
|
||||
|
||||
if (binds && exprs) {
|
||||
// Returns a new Env with symbols in binds bound to
|
||||
// corresponding values in exprs
|
||||
// TODO: check types of binds and exprs and compare lengths
|
||||
for (var i=0; i<binds.length;i++) {
|
||||
if (binds[i].value === "&") {
|
||||
// variable length arguments
|
||||
this.data[binds[i+1].value] = Array.prototype.slice.call(exprs, i);
|
||||
break;
|
||||
} else {
|
||||
this.data[binds[i].value] = exprs[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
Env.prototype.find = function (key) {
|
||||
if (!key.constructor || key.constructor.name !== 'Symbol') {
|
||||
throw new Error("env.find key must be a symbol")
|
||||
}
|
||||
if (key.value in this.data) { return this; }
|
||||
else if (this.outer) { return this.outer.find(key); }
|
||||
else { return null; }
|
||||
};
|
||||
Env.prototype.set = function(key, value) {
|
||||
if (!key.constructor || key.constructor.name !== 'Symbol') {
|
||||
throw new Error("env.set key must be a symbol")
|
||||
}
|
||||
this.data[key.value] = value;
|
||||
return value;
|
||||
};
|
||||
Env.prototype.get = function(key) {
|
||||
if (!key.constructor || key.constructor.name !== 'Symbol') {
|
||||
throw new Error("env.get key must be a symbol")
|
||||
}
|
||||
var env = this.find(key);
|
||||
if (!env) { throw new Error("'" + key.value + "' not found"); }
|
||||
return env.data[key.value];
|
||||
};
|
||||
|
||||
exports.Env = env.Env = Env;
|
||||
// Node vs browser behavior
|
||||
var core = {};
|
||||
if (typeof module === 'undefined') {
|
||||
var exports = core;
|
||||
} else {
|
||||
}
|
||||
|
||||
// Errors/Exceptions
|
||||
function mal_throw(exc) { throw exc; }
|
||||
|
||||
|
||||
// String functions
|
||||
function pr_str() {
|
||||
return Array.prototype.map.call(arguments,function(exp) {
|
||||
return printer._pr_str(exp, true);
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
function str() {
|
||||
return Array.prototype.map.call(arguments,function(exp) {
|
||||
return printer._pr_str(exp, false);
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function prn() {
|
||||
printer.println.apply({}, Array.prototype.map.call(arguments,function(exp) {
|
||||
return printer._pr_str(exp, true);
|
||||
}));
|
||||
}
|
||||
|
||||
function println() {
|
||||
printer.println.apply({}, Array.prototype.map.call(arguments,function(exp) {
|
||||
return printer._pr_str(exp, false);
|
||||
}));
|
||||
}
|
||||
|
||||
function slurp(f) {
|
||||
if (typeof require !== 'undefined') {
|
||||
return require('fs').readFileSync(f, 'utf-8');
|
||||
} else {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", f, false);
|
||||
req.send();
|
||||
if (req.status == 200) {
|
||||
return req.responseText;
|
||||
} else {
|
||||
throw new Error("Failed to slurp file: " + f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Number functions
|
||||
function time_ms() { return new Date().getTime(); }
|
||||
|
||||
|
||||
// Hash Map functions
|
||||
function assoc(src_hm) {
|
||||
var hm = types._clone(src_hm);
|
||||
var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
|
||||
return types._assoc_BANG.apply(null, args);
|
||||
}
|
||||
|
||||
function dissoc(src_hm) {
|
||||
var hm = types._clone(src_hm);
|
||||
var args = [hm].concat(Array.prototype.slice.call(arguments, 1));
|
||||
return types._dissoc_BANG.apply(null, args);
|
||||
}
|
||||
|
||||
function get(hm, key) {
|
||||
if (hm != null && key in hm) {
|
||||
return hm[key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function contains_Q(hm, key) {
|
||||
if (key in hm) { return true; } else { return false; }
|
||||
}
|
||||
|
||||
function keys(hm) { return Object.keys(hm); }
|
||||
function vals(hm) { return Object.keys(hm).map(function(k) { return hm[k]; }); }
|
||||
|
||||
|
||||
// Sequence functions
|
||||
function cons(a, b) { return [a].concat(b); }
|
||||
|
||||
function concat(lst) {
|
||||
lst = lst || [];
|
||||
return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
|
||||
function nth(lst, idx) {
|
||||
if (idx < lst.length) { return lst[idx]; }
|
||||
else { throw new Error("nth: index out of range"); }
|
||||
}
|
||||
|
||||
function first(lst) { return (lst === null) ? null : lst[0]; }
|
||||
|
||||
function rest(lst) { return (lst == null) ? [] : lst.slice(1); }
|
||||
|
||||
function empty_Q(lst) { return lst.length === 0; }
|
||||
|
||||
function count(s) {
|
||||
if (Array.isArray(s)) { return s.length; }
|
||||
else if (s === null) { return 0; }
|
||||
else { return Object.keys(s).length; }
|
||||
}
|
||||
|
||||
function conj(lst) {
|
||||
if (types._list_Q(lst)) {
|
||||
return Array.prototype.slice.call(arguments, 1).reverse().concat(lst);
|
||||
} else {
|
||||
var v = lst.concat(Array.prototype.slice.call(arguments, 1));
|
||||
v.__isvector__ = true;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
function seq(obj) {
|
||||
if (types._list_Q(obj)) {
|
||||
return obj.length > 0 ? obj : null;
|
||||
} else if (types._vector_Q(obj)) {
|
||||
return obj.length > 0 ? Array.prototype.slice.call(obj, 0): null;
|
||||
} else if (types._string_Q(obj)) {
|
||||
return obj.length > 0 ? obj.split('') : null;
|
||||
} else if (obj === null) {
|
||||
return null;
|
||||
} else {
|
||||
throw new Error("seq: called on non-sequence");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function apply(f) {
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
return f.apply(f, args.slice(0, args.length-1).concat(args[args.length-1]));
|
||||
}
|
||||
|
||||
function map(f, lst) {
|
||||
return lst.map(function(el){ return f(el); });
|
||||
}
|
||||
|
||||
|
||||
// Metadata functions
|
||||
function with_meta(obj, m) {
|
||||
var new_obj = types._clone(obj);
|
||||
new_obj.__meta__ = m;
|
||||
return new_obj;
|
||||
}
|
||||
|
||||
function meta(obj) {
|
||||
// TODO: support symbols and atoms
|
||||
if ((!types._sequential_Q(obj)) &&
|
||||
(!(types._hash_map_Q(obj))) &&
|
||||
(!(types._function_Q(obj)))) {
|
||||
throw new Error("attempt to get metadata from: " + types._obj_type(obj));
|
||||
}
|
||||
return obj.__meta__;
|
||||
}
|
||||
|
||||
|
||||
// Atom functions
|
||||
function deref(atm) { return atm.val; }
|
||||
function reset_BANG(atm, val) { return atm.val = val; }
|
||||
function swap_BANG(atm, f) {
|
||||
var args = [atm.val].concat(Array.prototype.slice.call(arguments, 2));
|
||||
atm.val = f.apply(f, args);
|
||||
return atm.val;
|
||||
}
|
||||
|
||||
function js_eval(str) {
|
||||
return interop.js_to_mal(eval(str.toString()));
|
||||
}
|
||||
|
||||
function js_method_call(object_method_str) {
|
||||
var args = Array.prototype.slice.call(arguments, 1),
|
||||
r = interop.resolve_js(object_method_str),
|
||||
obj = r[0], f = r[1];
|
||||
var res = f.apply(obj, args);
|
||||
return interop.js_to_mal(res);
|
||||
}
|
||||
|
||||
// types.ns is namespace of type functions
|
||||
var ns = {'type': types._obj_type,
|
||||
'=': types._equal_Q,
|
||||
'throw': mal_throw,
|
||||
'nil?': types._nil_Q,
|
||||
'true?': types._true_Q,
|
||||
'false?': types._false_Q,
|
||||
'number?': types._number_Q,
|
||||
'string?': types._string_Q,
|
||||
'symbol': types._symbol,
|
||||
'symbol?': types._symbol_Q,
|
||||
'keyword': types._keyword,
|
||||
'keyword?': types._keyword_Q,
|
||||
'fn?': types._fn_Q,
|
||||
'macro?': types._macro_Q,
|
||||
|
||||
'pr-str': pr_str,
|
||||
'str': str,
|
||||
'prn': prn,
|
||||
'println': println,
|
||||
'readline': readline.readline,
|
||||
'read-string': reader.read_str,
|
||||
'slurp': slurp,
|
||||
'<' : function(a,b){return a<b;},
|
||||
'<=' : function(a,b){return a<=b;},
|
||||
'>' : function(a,b){return a>b;},
|
||||
'>=' : function(a,b){return a>=b;},
|
||||
'+' : function(a,b){return a+b;},
|
||||
'-' : function(a,b){return a-b;},
|
||||
'*' : function(a,b){return a*b;},
|
||||
'/' : function(a,b){return a/b;},
|
||||
"time-ms": time_ms,
|
||||
|
||||
'list': types._list,
|
||||
'list?': types._list_Q,
|
||||
'vector': types._vector,
|
||||
'vector?': types._vector_Q,
|
||||
'hash-map': types._hash_map,
|
||||
'map?': types._hash_map_Q,
|
||||
'assoc': assoc,
|
||||
'dissoc': dissoc,
|
||||
'get': get,
|
||||
'contains?': contains_Q,
|
||||
'keys': keys,
|
||||
'vals': vals,
|
||||
|
||||
'sequential?': types._sequential_Q,
|
||||
'cons': cons,
|
||||
'concat': concat,
|
||||
'nth': nth,
|
||||
'first': first,
|
||||
'rest': rest,
|
||||
'empty?': empty_Q,
|
||||
'count': count,
|
||||
'apply': apply,
|
||||
'map': map,
|
||||
|
||||
'conj': conj,
|
||||
'seq': seq,
|
||||
|
||||
'with-meta': with_meta,
|
||||
'meta': meta,
|
||||
'atom': types._atom,
|
||||
'atom?': types._atom_Q,
|
||||
"deref": deref,
|
||||
"reset!": reset_BANG,
|
||||
"swap!": swap_BANG,
|
||||
|
||||
'js-eval': js_eval,
|
||||
'.': js_method_call
|
||||
};
|
||||
|
||||
exports.ns = core.ns = ns;
|
||||
if (typeof module !== 'undefined') {
|
||||
}
|
||||
|
||||
// read
|
||||
function READ(str) {
|
||||
return reader.read_str(str);
|
||||
}
|
||||
|
||||
// eval
|
||||
function is_pair(x) {
|
||||
return types._sequential_Q(x) && x.length > 0;
|
||||
}
|
||||
|
||||
function quasiquote(ast) {
|
||||
if (!is_pair(ast)) {
|
||||
return [types._symbol("quote"), ast];
|
||||
} else if (types._symbol_Q(ast[0]) && ast[0].value === 'unquote') {
|
||||
return ast[1];
|
||||
} else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
|
||||
return [types._symbol("concat"),
|
||||
ast[0][1],
|
||||
quasiquote(ast.slice(1))];
|
||||
} else {
|
||||
return [types._symbol("cons"),
|
||||
quasiquote(ast[0]),
|
||||
quasiquote(ast.slice(1))];
|
||||
}
|
||||
}
|
||||
|
||||
function is_macro_call(ast, env) {
|
||||
return types._list_Q(ast) &&
|
||||
types._symbol_Q(ast[0]) &&
|
||||
env.find(ast[0]) &&
|
||||
env.get(ast[0])._ismacro_;
|
||||
}
|
||||
|
||||
function macroexpand(ast, env) {
|
||||
while (is_macro_call(ast, env)) {
|
||||
var mac = env.get(ast[0]);
|
||||
ast = mac.apply(mac, ast.slice(1));
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
function eval_ast(ast, env) {
|
||||
if (types._symbol_Q(ast)) {
|
||||
return env.get(ast);
|
||||
} else if (types._list_Q(ast)) {
|
||||
return ast.map(function(a) { return EVAL(a, env); });
|
||||
} else if (types._vector_Q(ast)) {
|
||||
var v = ast.map(function(a) { return EVAL(a, env); });
|
||||
v.__isvector__ = true;
|
||||
return v;
|
||||
} else if (types._hash_map_Q(ast)) {
|
||||
var new_hm = {};
|
||||
for (k in ast) {
|
||||
new_hm[EVAL(k, env)] = EVAL(ast[k], env);
|
||||
}
|
||||
return new_hm;
|
||||
} else {
|
||||
return ast;
|
||||
}
|
||||
}
|
||||
|
||||
function _EVAL(ast, env) {
|
||||
while (true) {
|
||||
|
||||
//printer.println("EVAL:", printer._pr_str(ast, true));
|
||||
if (!types._list_Q(ast)) {
|
||||
return eval_ast(ast, env);
|
||||
}
|
||||
|
||||
// apply list
|
||||
ast = macroexpand(ast, env);
|
||||
if (!types._list_Q(ast)) {
|
||||
return eval_ast(ast, env);
|
||||
}
|
||||
if (ast.length === 0) {
|
||||
return ast;
|
||||
}
|
||||
|
||||
var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
|
||||
switch (a0.value) {
|
||||
case "def!":
|
||||
var res = EVAL(a2, env);
|
||||
return env.set(a1, res);
|
||||
case "let*":
|
||||
var let_env = new Env(env);
|
||||
for (var i=0; i < a1.length; i+=2) {
|
||||
let_env.set(a1[i], EVAL(a1[i+1], let_env));
|
||||
}
|
||||
ast = a2;
|
||||
env = let_env;
|
||||
break;
|
||||
case "quote":
|
||||
return a1;
|
||||
case "quasiquote":
|
||||
ast = quasiquote(a1);
|
||||
break;
|
||||
case 'defmacro!':
|
||||
var func = EVAL(a2, env);
|
||||
func._ismacro_ = true;
|
||||
return env.set(a1, func);
|
||||
case 'macroexpand':
|
||||
return macroexpand(a1, env);
|
||||
case "try*":
|
||||
try {
|
||||
return EVAL(a1, env);
|
||||
} catch (exc) {
|
||||
if (a2 && a2[0].value === "catch*") {
|
||||
if (exc instanceof Error) { exc = exc.message; }
|
||||
return EVAL(a2[2], new Env(env, [a2[1]], [exc]));
|
||||
} else {
|
||||
throw exc;
|
||||
}
|
||||
}
|
||||
case "do":
|
||||
eval_ast(ast.slice(1, -1), env);
|
||||
ast = ast[ast.length-1];
|
||||
break;
|
||||
case "if":
|
||||
var cond = EVAL(a1, env);
|
||||
if (cond === null || cond === false) {
|
||||
ast = (typeof a3 !== "undefined") ? a3 : null;
|
||||
} else {
|
||||
ast = a2;
|
||||
}
|
||||
break;
|
||||
case "fn*":
|
||||
return types._function(EVAL, Env, a2, env, a1);
|
||||
default:
|
||||
var el = eval_ast(ast, env), f = el[0];
|
||||
if (f.__ast__) {
|
||||
ast = f.__ast__;
|
||||
env = f.__gen_env__(el.slice(1));
|
||||
} else {
|
||||
return f.apply(f, el.slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function EVAL(ast, env) {
|
||||
var result = _EVAL(ast, env);
|
||||
return (typeof result !== "undefined") ? result : null;
|
||||
}
|
||||
|
||||
// print
|
||||
function PRINT(exp) {
|
||||
return printer._pr_str(exp, true);
|
||||
}
|
||||
|
||||
// repl
|
||||
var repl_env = new Env();
|
||||
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
|
||||
|
||||
// core.js: defined using javascript
|
||||
for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
|
||||
repl_env.set(types._symbol('eval'), function(ast) {
|
||||
return EVAL(ast, repl_env); });
|
||||
repl_env.set(types._symbol('*ARGV*'), []);
|
||||
|
||||
// core.mal: defined using the language itself
|
||||
rep("(def! *host-language* \"javascript\")")
|
||||
rep("(def! not (fn* (a) (if a false true)))");
|
||||
rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
|
||||
rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
|
||||
rep("(def! *gensym-counter* (atom 0))");
|
||||
rep("(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))");
|
||||
rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))");
|
||||
|
||||
if (typeof process !== 'undefined' && process.argv.length > 2) {
|
||||
repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
|
||||
rep('(load-file "' + process.argv[2] + '")');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// repl loop
|
||||
if (typeof require !== 'undefined' && require.main === module) {
|
||||
// Synchronous node.js commandline mode
|
||||
rep("(println (str \"Mal [\" *host-language* \"]\"))");
|
||||
while (true) {
|
||||
var line = readline.readline("user> ");
|
||||
if (line === null) { break; }
|
||||
try {
|
||||
if (line) { printer.println(rep(line)); }
|
||||
} catch (exc) {
|
||||
if (exc instanceof reader.BlankException) { continue }
|
||||
if (exc instanceof Error) { console.warn(exc.stack) }
|
||||
else { console.warn("Error: " + printer._pr_str(exc, true)) }
|
||||
}
|
||||
}
|
||||
}
|