From ea81a8087bcd7953b083a2be9db447f75e7ebf56 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Wed, 2 Apr 2014 22:23:37 -0500 Subject: All: split types into types, env, printer, core. - types: low-level mapping to the implementation language. - core: functions on types that are exposed directly to mal. - printer: implementation called by pr-str, str, prn, println. - env: the environment implementation - Also, unindent all TCO while loops so that the diff of step4 and step5 are minimized. --- js/Makefile | 3 +- js/core.js | 193 ++++++++++++++++++++++ js/env.js | 40 +++++ js/printer.js | 44 +++++ js/reader.js | 18 +-- js/step1_read_print.js | 3 +- js/step2_eval.js | 13 +- js/step3_env.js | 18 ++- js/step4_if_fn_do.js | 25 +-- js/step5_tco.js | 96 +++++------ js/step6_file.js | 96 +++++------ js/step7_quote.js | 113 ++++++------- js/step8_macros.js | 135 ++++++++-------- js/step9_interop.js | 147 ++++++++--------- js/stepA_more.js | 165 +++++++++---------- js/types.js | 423 ++++++++++++------------------------------------- 16 files changed, 810 insertions(+), 722 deletions(-) create mode 100644 js/core.js create mode 100644 js/env.js create mode 100644 js/printer.js (limited to 'js') diff --git a/js/Makefile b/js/Makefile index cb57644..2b7aa88 100644 --- a/js/Makefile +++ b/js/Makefile @@ -1,7 +1,8 @@ TESTS = tests/types.js tests/reader.js tests/step5_tco.js -SOURCES = node_readline.js types.js reader.js stepA_more.js +SOURCES = node_readline.js types.js reader.js printer.js \ + env.js core.js stepA_more.js WEB_SOURCES = $(SOURCES:node_readline.js=josh_readline.js) all: mal.js mal_web.js diff --git a/js/core.js b/js/core.js new file mode 100644 index 0000000..48bbe16 --- /dev/null +++ b/js/core.js @@ -0,0 +1,193 @@ +// Node vs browser behavior +var core = {}; +if (typeof module === 'undefined') { + var exports = core; +} else { + var types = require('./types'), + printer = require('./printer'); +} + +// 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.print.apply({}, Array.prototype.map.call(arguments,function(exp) { + return printer._pr_str(exp, true); + })); +} + +function println() { + printer.print.apply({}, Array.prototype.map.call(arguments,function(exp) { + return printer._pr_str(exp, false); + })); +} + + +// 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 (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) { return lst[idx]; } + +function first(lst) { return lst[0]; } + +function rest(lst) { return lst.slice(1); } + +function empty_Q(lst) { return lst.length === 0; } + +function count(s) { + if (Array.isArray(s)) { return s.length; } + 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 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; +} + + +// 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, + 'symbol': types._symbol, + 'symbol?': types._symbol_Q, + 'pr-str': pr_str, + 'str': str, + 'prn': prn, + 'println': println, + '<' : function(a,b){return a' : 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;}, + + '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, + 'conj': conj, + 'apply': apply, + 'map': map, + + 'with-meta': with_meta, + 'meta': meta, + 'atom': types._atom, + 'atom?': types._atom_Q, + "deref": deref, + "reset!": reset_BANG, + "swap!": swap_BANG}; + +exports.ns = core.ns = ns; diff --git a/js/env.js b/js/env.js new file mode 100644 index 0000000..3c9eac8 --- /dev/null +++ b/js/env.js @@ -0,0 +1,40 @@ +// 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 0; + return types._sequential_Q(x) && x.length > 0; } function quasiquote(ast) { if (!is_pair(ast)) { - return [types.symbol("quote"), ast]; + return [types._symbol("quote"), 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))]; } } function eval_ast(ast, env) { - if (types.symbol_Q(ast)) { + if (types._symbol_Q(ast)) { return env.get(ast); - } else if (types.list_Q(ast)) { + } else if (types._list_Q(ast)) { return ast.map(function(a) { return EVAL(a, env); }); - } else if (types.vector_Q(ast)) { + } 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)) { + } else if (types._hash_map_Q(ast)) { var new_hm = {}; for (k in ast) { new_hm[EVAL(k, env)] = EVAL(ast[k], env); @@ -48,50 +51,52 @@ function eval_ast(ast, env) { function _EVAL(ast, env) { while (true) { - //console.log("EVAL:", types._pr_str(ast, true)); - if (!types.list_Q(ast)) { - return eval_ast(ast, env); + + //console.log("EVAL:", types._pr_str(ast, true)); + if (!types._list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + 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].value, EVAL(a1[i+1], let_env)); } - - // apply list - 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 types.Env(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); - case "quote": - return a1; - case "quasiquote": - return EVAL(quasiquote(a1), env); - 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.new_function(EVAL, 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 types.Env(meta.env, meta.params, el.slice(1)); - } else { - return f.apply(f, el.slice(1)); - } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + 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], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } } @@ -102,16 +107,16 @@ function EVAL(ast, env) { // print function PRINT(exp) { - return types._pr_str(exp, true); + return printer._pr_str(exp, true); } // repl -var repl_env = new types.Env(); +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 types functions -for (var n in types.ns) { repl_env.set(n, types.ns[n]); } +// Import core functions +for (var n in core.ns) { repl_env.set(n, core.ns[n]); } _ref('read-string', reader.read_str); _ref('eval', function(ast) { return EVAL(ast, repl_env); }); diff --git a/js/step8_macros.js b/js/step8_macros.js index 65d7a87..3ad3e31 100644 --- a/js/step8_macros.js +++ b/js/step8_macros.js @@ -1,5 +1,8 @@ var types = require('./types'); var reader = require('./reader'); +var printer = require('./printer'); +var Env = require('./env').Env; +var core = require('./core'); if (typeof module !== 'undefined') { var readline = require('./node_readline'); } @@ -11,24 +14,24 @@ function READ(str) { // eval function is_pair(x) { - return types.sequential_Q(x) && x.length > 0; + return types._sequential_Q(x) && x.length > 0; } function quasiquote(ast) { if (!is_pair(ast)) { - return [types.symbol("quote"), ast]; + return [types._symbol("quote"), 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))]; } } function is_macro_call(ast, env) { - return types.list_Q(ast) && - types.symbol_Q(ast[0]) && + return types._list_Q(ast) && + types._symbol_Q(ast[0]) && env.find(ast[0].value) && env.get(ast[0].value)._ismacro_; } @@ -42,15 +45,15 @@ function macroexpand(ast, env) { } function eval_ast(ast, env) { - if (types.symbol_Q(ast)) { + if (types._symbol_Q(ast)) { return env.get(ast); - } else if (types.list_Q(ast)) { + } else if (types._list_Q(ast)) { return ast.map(function(a) { return EVAL(a, env); }); - } else if (types.vector_Q(ast)) { + } 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)) { + } else if (types._hash_map_Q(ast)) { var new_hm = {}; for (k in ast) { new_hm[EVAL(k, env)] = EVAL(ast[k], env); @@ -63,59 +66,61 @@ function eval_ast(ast, env) { function _EVAL(ast, env) { while (true) { - //console.log("EVAL:", types._pr_str(ast, true)); - if (!types.list_Q(ast)) { - return eval_ast(ast, env); + + //console.log("EVAL:", types._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 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].value, EVAL(a1[i+1], let_env)); } - - // apply list - ast = macroexpand(ast, env); - if (!types.list_Q(ast)) { 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 types.Env(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); - case "quote": - return a1; - case "quasiquote": - return EVAL(quasiquote(a1), env); - case 'defmacro!': - var func = EVAL(a2, env); - func._ismacro_ = true; - return env.set(a1, func); - case 'macroexpand': - return macroexpand(a1, env); - 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.new_function(EVAL, 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 types.Env(meta.env, meta.params, el.slice(1)); - } else { - return f.apply(f, el.slice(1)); - } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + 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], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } } @@ -126,16 +131,16 @@ function EVAL(ast, env) { // print function PRINT(exp) { - return types._pr_str(exp, true); + return printer._pr_str(exp, true); } // repl -var repl_env = new types.Env(); +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 types functions -for (var n in types.ns) { repl_env.set(n, types.ns[n]); } +// Import core functions +for (var n in core.ns) { repl_env.set(n, core.ns[n]); } _ref('read-string', reader.read_str); _ref('eval', function(ast) { return EVAL(ast, repl_env); }); diff --git a/js/step9_interop.js b/js/step9_interop.js index bfc01cb..3c83e51 100644 --- a/js/step9_interop.js +++ b/js/step9_interop.js @@ -1,5 +1,8 @@ var types = require('./types'); var reader = require('./reader'); +var printer = require('./printer'); +var Env = require('./env').Env; +var core = require('./core'); if (typeof module !== 'undefined') { var readline = require('./node_readline'); } @@ -11,24 +14,24 @@ function READ(str) { // eval function is_pair(x) { - return types.sequential_Q(x) && x.length > 0; + return types._sequential_Q(x) && x.length > 0; } function quasiquote(ast) { if (!is_pair(ast)) { - return [types.symbol("quote"), ast]; + return [types._symbol("quote"), 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))]; } } function is_macro_call(ast, env) { - return types.list_Q(ast) && - types.symbol_Q(ast[0]) && + return types._list_Q(ast) && + types._symbol_Q(ast[0]) && env.find(ast[0].value) && env.get(ast[0].value)._ismacro_; } @@ -42,15 +45,15 @@ function macroexpand(ast, env) { } function eval_ast(ast, env) { - if (types.symbol_Q(ast)) { + if (types._symbol_Q(ast)) { return env.get(ast); - } else if (types.list_Q(ast)) { + } else if (types._list_Q(ast)) { return ast.map(function(a) { return EVAL(a, env); }); - } else if (types.vector_Q(ast)) { + } 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)) { + } else if (types._hash_map_Q(ast)) { var new_hm = {}; for (k in ast) { new_hm[EVAL(k, env)] = EVAL(ast[k], env); @@ -63,65 +66,67 @@ function eval_ast(ast, env) { function _EVAL(ast, env) { while (true) { - //console.log("EVAL:", types._pr_str(ast, true)); - if (!types.list_Q(ast)) { - return eval_ast(ast, env); + + //console.log("EVAL:", types._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 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].value, EVAL(a1[i+1], let_env)); } - - // apply list - ast = macroexpand(ast, env); - if (!types.list_Q(ast)) { 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 types.Env(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); - case "quote": - return a1; - case "quasiquote": - return EVAL(quasiquote(a1), env); - case 'defmacro!': - var func = EVAL(a2, env); - func._ismacro_ = true; - return env.set(a1, func); - case 'macroexpand': - return macroexpand(a1, env); - case "js*": - return eval(a1.toString()); - case ".": - var el = eval_ast(ast.slice(2), env), - f = eval(a1.toString()); - return f.apply(f, el); - 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.new_function(EVAL, 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 types.Env(meta.env, meta.params, el.slice(1)); - } else { - return f.apply(f, el.slice(1)); - } + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + case "js*": + return eval(a1.toString()); + case ".": + var el = eval_ast(ast.slice(2), env), + f = eval(a1.toString()); + return f.apply(f, el); + 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], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } } @@ -132,16 +137,16 @@ function EVAL(ast, env) { // print function PRINT(exp) { - return types._pr_str(exp, true); + return printer._pr_str(exp, true); } // repl -var repl_env = new types.Env(); +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 types functions -for (var n in types.ns) { repl_env.set(n, types.ns[n]); } +// Import core functions +for (var n in core.ns) { repl_env.set(n, core.ns[n]); } _ref('read-string', reader.read_str); _ref('eval', function(ast) { return EVAL(ast, repl_env); }); diff --git a/js/stepA_more.js b/js/stepA_more.js index 2778649..a4e1bda 100644 --- a/js/stepA_more.js +++ b/js/stepA_more.js @@ -1,5 +1,8 @@ var types = require('./types'); var reader = require('./reader'); +var printer = require('./printer'); +var Env = require('./env').Env; +var core = require('./core'); if (typeof module !== 'undefined') { var readline = require('./node_readline'); } @@ -11,24 +14,24 @@ function READ(str) { // eval function is_pair(x) { - return types.sequential_Q(x) && x.length > 0; + return types._sequential_Q(x) && x.length > 0; } function quasiquote(ast) { if (!is_pair(ast)) { - return [types.symbol("quote"), ast]; + return [types._symbol("quote"), 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))]; } } function is_macro_call(ast, env) { - return types.list_Q(ast) && - types.symbol_Q(ast[0]) && + return types._list_Q(ast) && + types._symbol_Q(ast[0]) && env.find(ast[0].value) && env.get(ast[0].value)._ismacro_; } @@ -42,15 +45,15 @@ function macroexpand(ast, env) { } function eval_ast(ast, env) { - if (types.symbol_Q(ast)) { + if (types._symbol_Q(ast)) { return env.get(ast); - } else if (types.list_Q(ast)) { + } else if (types._list_Q(ast)) { return ast.map(function(a) { return EVAL(a, env); }); - } else if (types.vector_Q(ast)) { + } 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)) { + } else if (types._hash_map_Q(ast)) { var new_hm = {}; for (k in ast) { new_hm[EVAL(k, env)] = EVAL(ast[k], env); @@ -63,76 +66,78 @@ function eval_ast(ast, env) { function _EVAL(ast, env) { while (true) { - //console.log("EVAL:", types._pr_str(ast, true)); - if (!types.list_Q(ast)) { - return eval_ast(ast, env); + + //console.log("EVAL:", types._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 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].value, EVAL(a1[i+1], let_env)); } - - // apply list - ast = macroexpand(ast, env); - if (!types.list_Q(ast)) { 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 types.Env(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); - case "quote": - return a1; - case "quasiquote": - return EVAL(quasiquote(a1), env); - case 'defmacro!': - var func = EVAL(a2, env); - func._ismacro_ = true; - return env.set(a1, func); - case 'macroexpand': - return macroexpand(a1, env); - case "js*": - return eval(a1.toString()); - case ".": - var el = eval_ast(ast.slice(2), env), - f = eval(a1.toString()); - return f.apply(f, el); - 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 types.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.new_function(EVAL, 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 types.Env(meta.env, meta.params, el.slice(1)); + return EVAL(a2, let_env); + case "quote": + return a1; + case "quasiquote": + return EVAL(quasiquote(a1), env); + case 'defmacro!': + var func = EVAL(a2, env); + func._ismacro_ = true; + return env.set(a1, func); + case 'macroexpand': + return macroexpand(a1, env); + case "js*": + return eval(a1.toString()); + case ".": + var el = eval_ast(ast.slice(2), env), + f = eval(a1.toString()); + return f.apply(f, el); + 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 { - return f.apply(f, el.slice(1)); + 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], meta = f.__meta__; + if (meta && meta.exp) { + ast = meta.exp; + env = new Env(meta.env, meta.params, el.slice(1)); + } else { + return f.apply(f, el.slice(1)); + } + } + } } @@ -143,16 +148,16 @@ function EVAL(ast, env) { // print function PRINT(exp) { - return types._pr_str(exp, true); + return printer._pr_str(exp, true); } // repl -var repl_env = new types.Env(); +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 types functions -for (var n in types.ns) { repl_env.set(n, types.ns[n]); } +// Import core functions +for (var n in core.ns) { repl_env.set(n, core.ns[n]); } _ref('readline', readline.readline) _ref('read-string', reader.read_str); diff --git a/js/types.js b/js/types.js index 18fad0a..7fd2962 100644 --- a/js/types.js +++ b/js/types.js @@ -7,59 +7,17 @@ if (typeof module === 'undefined') { var print = exports.print = function () { console.log.apply(console, arguments); }; } -// General utility functions - -// Clone a 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 _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)); - } - return new_obj; -} - - - - -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 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'; } +// General fucnctions + +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'; @@ -70,82 +28,12 @@ function obj_type(obj) { } } -function _pr_str(obj, print_readably) { - if (typeof print_readably === 'undefined') { print_readably = true; } - var _r = print_readably; - var ot = 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 (print_readably) { - return '"' + obj.replace(/\\/, "\\\\").replace(/"/g, '\\"') + '"'; - } else { - return obj; - } - case 'nil': - return "nil"; - case 'atom': - return "(atom " + _pr_str(obj.val,_r) + ")"; - default: - return obj.toString(); - } -} - -function pr_str() { - return Array.prototype.map.call(arguments,function(exp) { - return _pr_str(exp, true); - }).join(" "); -} - -function str() { - return Array.prototype.map.call(arguments,function(exp) { - return _pr_str(exp, false); - }).join(""); -} - -function prn() { - print.apply({}, Array.prototype.map.call(arguments,function(exp) { - return _pr_str(exp, true); - })); -} - -function println() { - print.apply({}, Array.prototype.map.call(arguments,function(exp) { - return _pr_str(exp, false); - })); -} - -function with_meta(obj, m) { - var new_obj = _clone(obj); - new_obj.__meta__ = m; - return new_obj; -} - -function meta(obj) { - // TODO: support symbols and atoms - if ((!sequential_Q(obj)) && - (!(hash_map_Q(obj))) && - (!(function_Q(obj)))) { - throw new Error("attempt to get metadata from: " + obj_type(obj)); - } - return obj.__meta__; -} +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)))) { +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) { @@ -154,7 +42,7 @@ function equal_Q (a, b) { case 'vector': if (a.length !== b.length) { return false; } for (var i=0; i' : 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;}, - 'throw': mal_throw, - 'list': list, 'list?': list_Q, - 'vector': vector, 'vector?': vector_Q, - 'hash-map': hash_map, 'map?': hash_map_Q, - 'assoc': assoc, 'dissoc': dissoc, 'get': get, - 'contains?': contains_Q, 'keys': keys, 'vals': vals, - 'atom': atom, 'atom?': atom_Q, - "deref": deref, "reset!": reset_BANG, "swap!": swap_BANG, - 'sequential?': sequential_Q, 'cons': cons, 'nth': nth, - 'empty?': empty_Q, 'count': count, 'concat': concat, - 'conj': conj, 'first': first, 'rest': rest, - 'apply': apply, 'map': map}; - -exports.ns = types.ns = ns; -exports._pr_str = types._pr_str = _pr_str; -exports.prn = types.prn = prn; -exports.Env = types.Env = Env; - -exports.symbol = types.symbol = symbol; -exports.symbol_Q = types.symbol_Q = symbol_Q; -exports.hash_map = types.hash_map = hash_map; -exports.hash_map_Q = types.hash_map_Q = hash_map_Q; -exports.new_function = types.new_function = new_function; -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.sequential_Q = types.sequential_Q = sequential_Q; -exports.cons = types.cons = cons; -exports.concat = types.concat = concat; -exports.first = types.first = first; -exports.rest = types.rest = rest; -exports.apply = types.apply = apply; -exports.map = types.map = map; +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._symbol = types._symbol = _symbol; +exports._symbol_Q = types._symbol_Q = _symbol_Q; +exports._function = types._function = _function; +exports._function_Q = types._function_Q = _function_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; -- cgit v1.2.3