aboutsummaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-12-18 20:33:49 -0600
committerJoel Martin <github@martintribe.org>2015-01-09 16:16:50 -0600
commitb8ee29b22fbaa7a01f2754b4d6dd9af52e02017c (patch)
treef4d977ed220e9a3f665cfbf4f68770a81e4c2095 /js
parentaaba249304b184e12e2445ab22d66df1f39a51a5 (diff)
downloadmal-b8ee29b22fbaa7a01f2754b4d6dd9af52e02017c.tar.gz
mal-b8ee29b22fbaa7a01f2754b4d6dd9af52e02017c.zip
All: add keywords.
Also, fix nth and count to match cloure.
Diffstat (limited to 'js')
-rw-r--r--js/core.js10
-rw-r--r--js/env.js20
-rw-r--r--js/printer.js8
-rw-r--r--js/reader.js2
-rw-r--r--js/step3_env.js10
-rw-r--r--js/step4_if_fn_do.js4
-rw-r--r--js/step5_tco.js4
-rw-r--r--js/step6_file.js9
-rw-r--r--js/step7_quote.js9
-rw-r--r--js/step8_macros.js15
-rw-r--r--js/step9_try.js17
-rw-r--r--js/stepA_interop.js15
-rw-r--r--js/types.js16
13 files changed, 86 insertions, 53 deletions
diff --git a/js/core.js b/js/core.js
index 3ab2117..d2be63b 100644
--- a/js/core.js
+++ b/js/core.js
@@ -95,7 +95,10 @@ function concat(lst) {
return lst.concat.apply(lst, Array.prototype.slice.call(arguments, 1));
}
-function nth(lst, idx) { return lst[idx]; }
+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[0]; }
@@ -105,7 +108,8 @@ function empty_Q(lst) { return lst.length === 0; }
function count(s) {
if (Array.isArray(s)) { return s.length; }
- else { return Object.keys(s).length; }
+ else if (s === null) { return 0; }
+ else { return Object.keys(s).length; }
}
function conj(lst) {
@@ -165,6 +169,8 @@ var ns = {'type': types._obj_type,
'false?': types._false_Q,
'symbol': types._symbol,
'symbol?': types._symbol_Q,
+ 'keyword': types._keyword,
+ 'keyword?': types._keyword_Q,
'pr-str': pr_str,
'str': str,
diff --git a/js/env.js b/js/env.js
index 3c9eac8..421b220 100644
--- a/js/env.js
+++ b/js/env.js
@@ -26,15 +26,27 @@ function Env(outer, binds, exprs) {
return this;
}
Env.prototype.find = function (key) {
- if (key in this.data) { return this; }
+ 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) { this.data[key] = value; return value; },
+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 + "' not found"); }
- return env.data[key];
+ if (!env) { throw new Error("'" + key.value + "' not found"); }
+ return env.data[key.value];
};
exports.Env = env.Env = Env;
diff --git a/js/printer.js b/js/printer.js
index f3836e0..4f267e7 100644
--- a/js/printer.js
+++ b/js/printer.js
@@ -26,13 +26,17 @@ function _pr_str(obj, print_readably) {
}
return "{" + ret.join(' ') + "}";
case 'string':
- if (_r) {
- return '"' + obj.replace(/\\/, "\\\\")
+ 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':
diff --git a/js/reader.js b/js/reader.js
index 3f2f6ca..dd4de9a 100644
--- a/js/reader.js
+++ b/js/reader.js
@@ -35,6 +35,8 @@ function read_atom (reader) {
return token.slice(1,token.length-1)
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n"); // string
+ } else if (token[0] === ":") {
+ return types._keyword(token.slice(1));
} else if (token === "nil") {
return null;
} else if (token === "true") {
diff --git a/js/step3_env.js b/js/step3_env.js
index 1f5efb7..ca8f818 100644
--- a/js/step3_env.js
+++ b/js/step3_env.js
@@ -47,7 +47,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
return EVAL(a2, let_env);
default:
@@ -70,10 +70,10 @@ function PRINT(exp) {
var repl_env = new Env();
var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
-repl_env.set('+', function(a,b){return a+b;});
-repl_env.set('-', function(a,b){return a-b;});
-repl_env.set('*', function(a,b){return a*b;});
-repl_env.set('/', function(a,b){return a/b;});
+repl_env.set(types._symbol('+'), function(a,b){return a+b;});
+repl_env.set(types._symbol('-'), function(a,b){return a-b;});
+repl_env.set(types._symbol('*'), function(a,b){return a*b;});
+repl_env.set(types._symbol('/'), function(a,b){return a/b;});
// repl loop
if (typeof require !== 'undefined' && require.main === module) {
diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js
index 27715fa..937d0ea 100644
--- a/js/step4_if_fn_do.js
+++ b/js/step4_if_fn_do.js
@@ -48,7 +48,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
return EVAL(a2, let_env);
case "do":
@@ -86,7 +86,7 @@ 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(n, core.ns[n]); }
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step5_tco.js b/js/step5_tco.js
index 659ac40..03de2cc 100644
--- a/js/step5_tco.js
+++ b/js/step5_tco.js
@@ -50,7 +50,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -97,7 +97,7 @@ 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(n, core.ns[n]); }
+for (var n in core.ns) { repl_env.set(types._symbol(n), core.ns[n]); }
// core.mal: defined using the language itself
rep("(def! not (fn* (a) (if a false true)))");
diff --git a/js/step6_file.js b/js/step6_file.js
index 4c8ed17..813c66d 100644
--- a/js/step6_file.js
+++ b/js/step6_file.js
@@ -50,7 +50,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -97,9 +97,10 @@ 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(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+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! not (fn* (a) (if a false true)))");
diff --git a/js/step7_quote.js b/js/step7_quote.js
index 6259672..b39ebbd 100644
--- a/js/step7_quote.js
+++ b/js/step7_quote.js
@@ -70,7 +70,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -122,9 +122,10 @@ 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(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+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! not (fn* (a) (if a false true)))");
diff --git a/js/step8_macros.js b/js/step8_macros.js
index f51592b..397379e 100644
--- a/js/step8_macros.js
+++ b/js/step8_macros.js
@@ -36,8 +36,8 @@ function quasiquote(ast) {
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_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +88,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -146,9 +146,10 @@ 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(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+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! not (fn* (a) (if a false true)))");
@@ -157,7 +158,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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))))))))");
if (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/js/step9_try.js b/js/step9_try.js
index ff02f72..6be4474 100644
--- a/js/step9_try.js
+++ b/js/step9_try.js
@@ -36,8 +36,8 @@ function quasiquote(ast) {
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_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +88,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -157,19 +157,19 @@ 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(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+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("(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))))))))");
if (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
@@ -177,7 +177,6 @@ if (typeof process !== 'undefined' && process.argv.length > 2) {
// 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; }
diff --git a/js/stepA_interop.js b/js/stepA_interop.js
index 0955b7f..456c006 100644
--- a/js/stepA_interop.js
+++ b/js/stepA_interop.js
@@ -36,8 +36,8 @@ function quasiquote(ast) {
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_;
+ env.find(ast[0]) &&
+ env.get(ast[0])._ismacro_;
}
function macroexpand(ast, env) {
@@ -88,7 +88,7 @@ function _EVAL(ast, env) {
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));
+ let_env.set(a1[i], EVAL(a1[i+1], let_env));
}
ast = a2;
env = let_env;
@@ -163,9 +163,10 @@ 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(n, core.ns[n]); }
-repl_env.set('eval', function(ast) { return EVAL(ast, repl_env); });
-repl_env.set('*ARGV*', []);
+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\")")
@@ -175,7 +176,7 @@ rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (
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))))))))");
if (typeof process !== 'undefined' && process.argv.length > 2) {
- repl_env.set('*ARGV*', process.argv.slice(3));
+ repl_env.set(types._symbol('*ARGV*'), process.argv.slice(3));
rep('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
diff --git a/js/types.js b/js/types.js
index de90d54..848a484 100644
--- a/js/types.js
+++ b/js/types.js
@@ -19,7 +19,7 @@ function _obj_type(obj) {
switch (typeof(obj)) {
case 'number': return 'number';
case 'function': return 'function';
- case 'string': return 'string';
+ case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string';
default: throw new Error("Unknown type '" + typeof(obj) + "'");
}
}
@@ -99,6 +99,13 @@ function _symbol(name) { return new Symbol(name); }
function _symbol_Q(obj) { return obj instanceof Symbol; }
+// Keywords
+function _keyword(name) { return "\u029e" + name; }
+function _keyword_Q(obj) {
+ return typeof obj === 'string' && obj[0] === '\u029e';
+}
+
+
// Functions
function _function(Eval, Env, ast, env, params) {
var fn = function() {
@@ -148,6 +155,7 @@ function _hash_map_Q(hm) {
return typeof hm === "object" &&
!Array.isArray(hm) &&
!(hm === null) &&
+ !(hm instanceof Symbol) &&
!(hm instanceof Atom);
}
function _assoc_BANG(hm) {
@@ -157,10 +165,6 @@ function _assoc_BANG(hm) {
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));
}
@@ -193,6 +197,8 @@ 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._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._list = types._list = _list;