diff options
| author | Joel Martin <github@martintribe.org> | 2014-12-18 20:33:49 -0600 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2015-01-09 16:16:50 -0600 |
| commit | b8ee29b22fbaa7a01f2754b4d6dd9af52e02017c (patch) | |
| tree | f4d977ed220e9a3f665cfbf4f68770a81e4c2095 /js | |
| parent | aaba249304b184e12e2445ab22d66df1f39a51a5 (diff) | |
| download | mal-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.js | 10 | ||||
| -rw-r--r-- | js/env.js | 20 | ||||
| -rw-r--r-- | js/printer.js | 8 | ||||
| -rw-r--r-- | js/reader.js | 2 | ||||
| -rw-r--r-- | js/step3_env.js | 10 | ||||
| -rw-r--r-- | js/step4_if_fn_do.js | 4 | ||||
| -rw-r--r-- | js/step5_tco.js | 4 | ||||
| -rw-r--r-- | js/step6_file.js | 9 | ||||
| -rw-r--r-- | js/step7_quote.js | 9 | ||||
| -rw-r--r-- | js/step8_macros.js | 15 | ||||
| -rw-r--r-- | js/step9_try.js | 17 | ||||
| -rw-r--r-- | js/stepA_interop.js | 15 | ||||
| -rw-r--r-- | js/types.js | 16 |
13 files changed, 86 insertions, 53 deletions
@@ -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, @@ -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; |
