diff options
Diffstat (limited to 'js')
| -rw-r--r-- | js/Makefile | 29 | ||||
| -rw-r--r-- | js/josh_readline.js | 402 | ||||
| -rw-r--r-- | js/node_readline.js | 38 | ||||
| -rw-r--r-- | js/package.json | 8 | ||||
| -rw-r--r-- | js/reader.js | 127 | ||||
| -rw-r--r-- | js/step0_repl.js | 42 | ||||
| -rw-r--r-- | js/step1_read_print.js | 47 | ||||
| -rw-r--r-- | js/step2_eval.js | 83 | ||||
| -rw-r--r-- | js/step3_env.js | 97 | ||||
| -rw-r--r-- | js/step4_if_fn_do.js | 112 | ||||
| -rw-r--r-- | js/step5_tco.js | 119 | ||||
| -rw-r--r-- | js/step6_file.js | 133 | ||||
| -rw-r--r-- | js/step7_quote.js | 154 | ||||
| -rw-r--r-- | js/step8_macros.js | 178 | ||||
| -rw-r--r-- | js/step9_interop.js | 184 | ||||
| -rw-r--r-- | js/stepA_more.js | 198 | ||||
| -rw-r--r-- | js/tests/common.js | 15 | ||||
| l--------- | js/tests/node_modules | 1 | ||||
| -rw-r--r-- | js/tests/reader.js | 68 | ||||
| -rw-r--r-- | js/tests/step5_tco.js | 22 | ||||
| -rw-r--r-- | js/tests/types.js | 94 | ||||
| -rw-r--r-- | js/types.js | 429 |
22 files changed, 2580 insertions, 0 deletions
diff --git a/js/Makefile b/js/Makefile new file mode 100644 index 0000000..cb57644 --- /dev/null +++ b/js/Makefile @@ -0,0 +1,29 @@ + +TESTS = tests/types.js tests/reader.js tests/step5_tco.js + +SOURCES = node_readline.js types.js reader.js stepA_more.js +WEB_SOURCES = $(SOURCES:node_readline.js=josh_readline.js) + +all: mal.js mal_web.js + +mal.js: $(SOURCES) + echo "#!/usr/bin/env node" > $@ + cat $+ | grep -v "= *require('./" >> $@ + chmod +x $@ + +mal_web.js: $(WEB_SOURCES) + cat $+ | grep -v "= *require('./" > $@ + +clean: + rm -f mal.js mal_web.js + +.PHONY: stats tests $(TESTS) + +stats: $(SOURCES) + @wc $^ + +tests: $(TESTS) + +$(TESTS): + @echo "Running $@"; \ + node $@ || exit 1; \ diff --git a/js/josh_readline.js b/js/josh_readline.js new file mode 100644 index 0000000..ff4d201 --- /dev/null +++ b/js/josh_readline.js @@ -0,0 +1,402 @@ +/* ------------------------------------------------------------------------* + * Copyright 2013 Arne F. Claassen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *-------------------------------------------------------------------------*/ + +var Josh = Josh || {}; +(function(root, $, _) { + Josh.Shell = function(config) { + config = config || {}; + + // instance fields + var _console = config.console || (Josh.Debug && root.console ? root.console : { + log: function() { + } + }); + var _prompt = config.prompt || 'jsh$'; + var _action = config.action || function(str) { + return "<div>No action defined for: " + str + "</div>"; + }; + var _shell_view_id = config.shell_view_id || 'shell-view'; + var _shell_panel_id = config.shell_panel_id || 'shell-panel'; + var _input_id = config.input_id || 'shell-cli'; + var _blinktime = config.blinktime || 500; + var _history = config.history || new Josh.History(); + var _readline = config.readline || new Josh.ReadLine({history: _history, console: _console}); + var _active = false; + var _cursor_visible = false; + var _activationHandler; + var _deactivationHandler; + var _cmdHandlers = { + clear: { + exec: function(cmd, args, callback) { + $(id(_input_id)).parent().empty(); + callback(); + } + }, + help: { + exec: function(cmd, args, callback) { + callback(self.templates.help({commands: commands()})); + } + }, + history: { + exec: function(cmd, args, callback) { + if(args[0] == "-c") { + _history.clear(); + callback(); + return; + } + callback(self.templates.history({items: _history.items()})); + } + }, + _default: { + exec: function(cmd, args, callback) { + callback(self.templates.bad_command({cmd: cmd})); + }, + completion: function(cmd, arg, line, callback) { + if(!arg) { + arg = cmd; + } + return callback(self.bestMatch(arg, self.commands())) + } + } + }; + var _line = { + text: '', + cursor: 0 + }; + var _searchMatch = ''; + var _view, _panel; + var _promptHandler; + var _initializationHandler; + var _initialized; + + // public methods + var self = { + commands: commands, + templates: { + history: _.template("<div><% _.each(items, function(cmd, i) { %><div><%- i %> <%- cmd %></div><% }); %></div>"), + help: _.template("<div><div><strong>Commands:</strong></div><% _.each(commands, function(cmd) { %><div> <%- cmd %></div><% }); %></div>"), + bad_command: _.template('<div><strong>Unrecognized command: </strong><%=cmd%></div>'), + input_cmd: _.template('<div id="<%- id %>"><span class="prompt"></span> <span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'), + input_search: _.template('<div id="<%- id %>">(reverse-i-search)`<span class="searchterm"></span>\': <span class="input"><span class="left"/><span class="cursor"/><span class="right"/></span></div>'), + suggest: _.template("<div><% _.each(suggestions, function(suggestion) { %><div><%- suggestion %></div><% }); %></div>") + }, + isActive: function() { + return _readline.isActive(); + }, + activate: function() { + if($(id(_shell_view_id)).length == 0) { + _active = false; + return; + } + _readline.activate(); + }, + deactivate: function() { + _console.log("deactivating"); + _active = false; + _readline.deactivate(); + }, + setCommandHandler: function(cmd, cmdHandler) { + _cmdHandlers[cmd] = cmdHandler; + }, + getCommandHandler: function(cmd) { + return _cmdHandlers[cmd]; + }, + setPrompt: function(prompt) { + _prompt = prompt; + if(!_active) { + return; + } + self.refresh(); + }, + onEOT: function(completionHandler) { + _readline.onEOT(completionHandler); + }, + onCancel: function(completionHandler) { + _readline.onCancel(completionHandler); + }, + onInitialize: function(completionHandler) { + _initializationHandler = completionHandler; + }, + onActivate: function(completionHandler) { + _activationHandler = completionHandler; + }, + onDeactivate: function(completionHandler) { + _deactivationHandler = completionHandler; + }, + onNewPrompt: function(completionHandler) { + _promptHandler = completionHandler; + }, + render: function() { + var text = _line.text || ''; + var cursorIdx = _line.cursor || 0; + if(_searchMatch) { + cursorIdx = _searchMatch.cursoridx || 0; + text = _searchMatch.text || ''; + $(id(_input_id) + ' .searchterm').text(_searchMatch.term); + } + var left = _.escape(text.substr(0, cursorIdx)).replace(/ /g, ' '); + var cursor = text.substr(cursorIdx, 1); + var right = _.escape(text.substr(cursorIdx + 1)).replace(/ /g, ' '); + $(id(_input_id) + ' .prompt').html(_prompt); + $(id(_input_id) + ' .input .left').html(left); + if(!cursor) { + $(id(_input_id) + ' .input .cursor').html(' ').css('textDecoration', 'underline'); + } else { + $(id(_input_id) + ' .input .cursor').text(cursor).css('textDecoration', 'underline'); + } + $(id(_input_id) + ' .input .right').html(right); + _cursor_visible = true; + self.scrollToBottom(); + _console.log('rendered "' + text + '" w/ cursor at ' + cursorIdx); + }, + refresh: function() { + $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id})); + self.render(); + _console.log('refreshed ' + _input_id); + + }, + scrollToBottom: function() { + _panel.animate({scrollTop: _view.height()}, 0); + }, + bestMatch: function(partial, possible) { + _console.log("bestMatch on partial '" + partial + "'"); + var result = { + completion: null, + suggestions: [] + }; + if(!possible || possible.length == 0) { + return result; + } + var common = ''; + if(!partial) { + if(possible.length == 1) { + result.completion = possible[0]; + result.suggestions = possible; + return result; + } + if(!_.every(possible, function(x) { + return possible[0][0] == x[0] + })) { + result.suggestions = possible; + return result; + } + } + for(var i = 0; i < possible.length; i++) { + var option = possible[i]; + if(option.slice(0, partial.length) == partial) { + result.suggestions.push(option); + if(!common) { + common = option; + _console.log("initial common:" + common); + } else if(option.slice(0, common.length) != common) { + _console.log("find common stem for '" + common + "' and '" + option + "'"); + var j = partial.length; + while(j < common.length && j < option.length) { + if(common[j] != option[j]) { + common = common.substr(0, j); + break; + } + j++; + } + } + } + } + result.completion = common.substr(partial.length); + return result; + } + }; + + function id(id) { + return "#"+id; + } + + function commands() { + return _.chain(_cmdHandlers).keys().filter(function(x) { + return x[0] != "_" + }).value(); + } + + function blinkCursor() { + if(!_active) { + return; + } + root.setTimeout(function() { + if(!_active) { + return; + } + _cursor_visible = !_cursor_visible; + if(_cursor_visible) { + $(id(_input_id) + ' .input .cursor').css('textDecoration', 'underline'); + } else { + $(id(_input_id) + ' .input .cursor').css('textDecoration', ''); + } + blinkCursor(); + }, _blinktime); + } + + function split(str) { + return _.filter(str.split(/\s+/), function(x) { + return x; + }); + } + + function getHandler(cmd) { + return _cmdHandlers[cmd] || _cmdHandlers._default; + } + + function renderOutput(output, callback) { + if(output) { + $(id(_input_id)).after(output); + } + $(id(_input_id) + ' .input .cursor').css('textDecoration', ''); + $(id(_input_id)).removeAttr('id'); + $(id(_shell_view_id)).append(self.templates.input_cmd({id:_input_id})); + if(_promptHandler) { + return _promptHandler(function(prompt) { + self.setPrompt(prompt); + return callback(); + }); + } + return callback(); + } + + function activate() { + _console.log("activating shell"); + if(!_view) { + _view = $(id(_shell_view_id)); + } + if(!_panel) { + _panel = $(id(_shell_panel_id)); + } + if($(id(_input_id)).length == 0) { + _view.append(self.templates.input_cmd({id:_input_id})); + } + self.refresh(); + _active = true; + blinkCursor(); + if(_promptHandler) { + _promptHandler(function(prompt) { + self.setPrompt(prompt); + }) + } + if(_activationHandler) { + _activationHandler(); + } + } + + // init + _readline.onActivate(function() { + if(!_initialized) { + _initialized = true; + if(_initializationHandler) { + return _initializationHandler(activate); + } + } + return activate(); + }); + _readline.onDeactivate(function() { + if(_deactivationHandler) { + _deactivationHandler(); + } + }); + _readline.onChange(function(line) { + _line = line; + self.render(); + }); + _readline.onClear(function() { + _cmdHandlers.clear.exec(null, null, function() { + renderOutput(null, function() { + }); + }); + }); + _readline.onSearchStart(function() { + $(id(_input_id)).replaceWith(self.templates.input_search({id:_input_id})); + _console.log('started search'); + }); + _readline.onSearchEnd(function() { + $(id(_input_id)).replaceWith(self.templates.input_cmd({id:_input_id})); + _searchMatch = null; + self.render(); + _console.log("ended search"); + }); + _readline.onSearchChange(function(match) { + _searchMatch = match; + self.render(); + }); + _readline.onEnter(function(cmdtext, callback) { + _console.log("got command: " + cmdtext); + var result; + try { + result = "<div>" + _action(cmdtext) + "</div>"; + } catch (e) { + result = "<div>" + e.stack + "</div>"; + } + renderOutput(result, function() { + callback(""); + }); + }); + _readline.onCompletion(function(line, callback) { + if(!line) { + return callback(); + } + var text = line.text.substr(0, line.cursor); + var parts = split(text); + + var cmd = parts.shift() || ''; + var arg = parts.pop() || ''; + _console.log("getting completion handler for " + cmd); + var handler = getHandler(cmd); + if(handler != _cmdHandlers._default && cmd && cmd == text) { + + _console.log("valid cmd, no args: append space"); + // the text to complete is just a valid command, append a space + return callback(' '); + } + if(!handler.completion) { + // handler has no completion function, so we can't complete + return callback(); + } + _console.log("calling completion handler for " + cmd); + return handler.completion(cmd, arg, line, function(match) { + _console.log("completion: " + JSON.stringify(match)); + if(!match) { + return callback(); + } + if(match.suggestions && match.suggestions.length > 1) { + return renderOutput(self.templates.suggest({suggestions: match.suggestions}), function() { + callback(match.completion); + }); + } + return callback(match.completion); + }); + }); + return self; + } +})(this, $, _); + +var readline = {}; +readline.rlwrap = function(action) { + var history = new Josh.History({ key: 'josh.helloworld'}); + var shell = Josh.Shell({history: history, + action: action}); + var promptCounter = 0; + shell.onNewPrompt(function(callback) { + promptCounter++; + //callback("[" + promptCounter + "] $"); + callback("user>"); + }); + shell.activate(); +} diff --git a/js/node_readline.js b/js/node_readline.js new file mode 100644 index 0000000..bfd1982 --- /dev/null +++ b/js/node_readline.js @@ -0,0 +1,38 @@ +// IMPORTANT: choose one +var RL_LIB = "libreadline"; // NOTE: libreadline is GPL +//var RL_LIB = "libedit"; + +var HISTORY_FILE = require('path').join(process.env.HOME, '.mal-history'); + +var rlwrap = {}; // namespace for this module in web context + +var ffi = require('ffi'), + fs = require('fs'); + +var rllib = ffi.Library(RL_LIB, { + 'readline': [ 'string', [ 'string' ] ], + 'add_history': [ 'int', [ 'string' ] ]}); + +var rl_history_loaded = false; + +exports.readline = rlwrap.readline = function(prompt) { + prompt = prompt || "user> "; + + if (!rl_history_loaded) { + rl_history_loaded = true; + var lines = fs.readFileSync(HISTORY_FILE).toString().split("\n"); + // Max of 2000 lines + lines = lines.slice(Math.max(lines.length - 2000, 0)); + for (var i=0; i<lines.length; i++) { + if (lines[i]) { rllib.add_history(lines[i]); } + } + } + + var line = rllib.readline(prompt); + if (line) { + rllib.add_history(line); + fs.appendFileSync(HISTORY_FILE, line + "\n"); + } + + return line; +} diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..976e6ae --- /dev/null +++ b/js/package.json @@ -0,0 +1,8 @@ +{ + "name": "mal", + "version": "0.0.1", + "description": "Make a Lisp (mal) language implemented in Javascript", + "dependencies": { + "ffi": "1.2.x" + } +} diff --git a/js/reader.js b/js/reader.js new file mode 100644 index 0000000..da51088 --- /dev/null +++ b/js/reader.js @@ -0,0 +1,127 @@ +// Node vs browser behavior +var reader = {}; +if (typeof module !== 'undefined') { + var types = require('./types'); +} 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] === "\"") { + return token.slice(1,token.length-1).replace(/\\"/g, '"'); // string + } 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(types.vector, lst); +} + +// read hash-map key/value pairs +function read_hash_map(reader) { + var lst = read_list(reader, '{', '}'); + return types.hash_map.apply(types.hash_map, 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; diff --git a/js/step0_repl.js b/js/step0_repl.js new file mode 100644 index 0000000..5fa10f2 --- /dev/null +++ b/js/step0_repl.js @@ -0,0 +1,42 @@ +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return str; +} + +// eval +function EVAL(ast, env) { + return eval(ast); +} + +// print +function PRINT(exp) { + return exp; +} + +// repl +var rep = function(str) { return PRINT(EVAL(READ(str), {})); }; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step1_read_print.js b/js/step1_read_print.js new file mode 100644 index 0000000..ee027d7 --- /dev/null +++ b/js/step1_read_print.js @@ -0,0 +1,47 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function EVAL(ast, env) { + return ast; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var re = function(str) { return EVAL(READ(str), {}); }; +var rep = function(str) { return PRINT(EVAL(READ(str), {})); }; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step2_eval.js b/js/step2_eval.js new file mode 100644 index 0000000..f2cb8b1 --- /dev/null +++ b/js/step2_eval.js @@ -0,0 +1,83 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +function eval_ast(ast, env) { + if (types.symbol_Q(ast)) { + return env[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) { + if (!types.list_Q(ast)) { + return eval_ast(ast, env); + } + + // apply list + var el = eval_ast(ast, env), f = el[0]; + 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 types._pr_str(exp, true); +} + +// repl +repl_env = {}; +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; + +repl_env['+'] = function(a,b){return a+b;}; +repl_env['-'] = function(a,b){return a-b;}; +repl_env['*'] = function(a,b){return a*b;}; +repl_env['/'] = function(a,b){return a/b;}; + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step3_env.js b/js/step3_env.js new file mode 100644 index 0000000..5b6e802 --- /dev/null +++ b/js/step3_env.js @@ -0,0 +1,97 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +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) { + 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 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); + default: + var el = eval_ast(ast, env), f = el[0]; + 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 types._pr_str(exp, true); +} + +// repl +var repl_env = new types.Env(); +var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); }; +_ref = function (k,v) { repl_env.set(k, v); } + +_ref('+', function(a,b){return a+b;}); +_ref('-', function(a,b){return a-b;}); +_ref('*', function(a,b){return a*b;}); +_ref('/', function(a,b){return a/b;}); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step4_if_fn_do.js b/js/step4_if_fn_do.js new file mode 100644 index 0000000..d33ec04 --- /dev/null +++ b/js/step4_if_fn_do.js @@ -0,0 +1,112 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +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) { + 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 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 "do": + var el = eval_ast(ast.slice(1), env); + return el[el.length-1]; + case "if": + var cond = EVAL(a1, env); + if (cond === null || cond === false) { + return typeof a3 !== "undefined" ? EVAL(a3, env) : null; + } else { + return EVAL(a2, env); + } + case "fn*": + return function() { + return EVAL(a2, new types.Env(env, a1, arguments)); + }; + default: + var el = eval_ast(ast, env), f = el[0]; + 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 types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step5_tco.js b/js/step5_tco.js new file mode 100644 index 0000000..20a9583 --- /dev/null +++ b/js/step5_tco.js @@ -0,0 +1,119 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +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) { + 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 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 "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)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); + +if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step6_file.js b/js/step6_file.js new file mode 100644 index 0000000..b9ec187 --- /dev/null +++ b/js/step6_file.js @@ -0,0 +1,133 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// read +function READ(str) { + return reader.read_str(str); +} + +// eval +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) { + 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 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 "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)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step7_quote.js b/js/step7_quote.js new file mode 100644 index 0000000..832e47b --- /dev/null +++ b/js/step7_quote.js @@ -0,0 +1,154 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// 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 (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 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) { + //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 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)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step8_macros.js b/js/step8_macros.js new file mode 100644 index 0000000..766f750 --- /dev/null +++ b/js/step8_macros.js @@ -0,0 +1,178 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// 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 (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].value) && + env.get(ast[0].value)._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) { + //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 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)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/step9_interop.js b/js/step9_interop.js new file mode 100644 index 0000000..a811c52 --- /dev/null +++ b/js/step9_interop.js @@ -0,0 +1,184 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// 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 (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].value) && + env.get(ast[0].value)._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) { + //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 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)); + } + } + } +} + +function EVAL(ast, env) { + var result = _EVAL(ast, env); + return (typeof result !== "undefined") ? result : null; +} + +// print +function PRINT(exp) { + return types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/stepA_more.js b/js/stepA_more.js new file mode 100644 index 0000000..fca3744 --- /dev/null +++ b/js/stepA_more.js @@ -0,0 +1,198 @@ +var types = require('./types'); +var reader = require('./reader'); +if (typeof module !== 'undefined') { + var readline = require('./node_readline'); +} + +// 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 (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].value) && + env.get(ast[0].value)._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) { + //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 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)); + } 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 types._pr_str(exp, true); +} + +// repl +var repl_env = new types.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]); } + +_ref('readline', readline.readline) +_ref('read-string', reader.read_str); +_ref('eval', function(ast) { return EVAL(ast, repl_env); }); +_ref('slurp', function(f) { + return require('fs').readFileSync(f, 'utf-8'); +}); +_ref('slurp-do', function(f) { + return '(do ' + require('fs').readFileSync(f, 'utf-8') + ')'; +}); + +// Defined using the language itself +rep("(def! not (fn* (a) (if a false true)))"); +rep("(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))"); +rep("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))"); +rep("(def! load-file (fn* (f) (eval (read-string (slurp-do f)))))"); + +if (typeof process !== 'undefined' && process.argv.length > 2) { + for (var i=2; i < process.argv.length; i++) { + rep('(load-file "' + process.argv[i] + '")'); + } +} else if (typeof require === 'undefined') { + // Asynchronous browser mode + readline.rlwrap(function(line) { return rep(line); }, + function(exc) { + if (exc instanceof reader.BlankException) { return; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + }); +} else if (require.main === module) { + // Synchronous node.js commandline mode + while (true) { + var line = readline.readline("user> "); + if (line === null) { break; } + try { + if (line) { console.log(rep(line)); } + } catch (exc) { + if (exc instanceof reader.BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } else { console.log(exc); } + } + } +} else { + exports.rep = rep; +} diff --git a/js/tests/common.js b/js/tests/common.js new file mode 100644 index 0000000..a95d79b --- /dev/null +++ b/js/tests/common.js @@ -0,0 +1,15 @@ +fs = require('fs'); +assert = require('assert'); + +function assert_eq(a, b) { + GLOBAL.assert.deepEqual(a, b, a + " !== " + b); +} + +function load(file) { + console.log(process.cwd()); + //process.chdir('../'); + eval(fs.readFileSync(file,'utf8')); +} + +exports.assert_eq = assert_eq; +exports.load = load; diff --git a/js/tests/node_modules b/js/tests/node_modules new file mode 120000 index 0000000..b870225 --- /dev/null +++ b/js/tests/node_modules @@ -0,0 +1 @@ +../
\ No newline at end of file diff --git a/js/tests/reader.js b/js/tests/reader.js new file mode 100644 index 0000000..2aa81c6 --- /dev/null +++ b/js/tests/reader.js @@ -0,0 +1,68 @@ +common = require('./common.js'); +types = require('../types'); +reader = require('../reader'); +var assert_eq = common.assert_eq, + read_str = reader.read_str, + nth = types.ns.nth; + +console.log("Testing read of constants/strings"); +assert_eq(2,read_str('2')); +assert_eq(12345,read_str('12345')); +assert_eq(12345,read_str('12345 "abc"')); +assert_eq('abc',read_str('"abc"')); +assert_eq('a string (with parens)',read_str('"a string (with parens)"')); + +console.log("Testing read of symbols"); +assert(types.symbol_Q(read_str('abc'))); +assert_eq('abc',read_str('abc').value); +assert_eq('.',read_str('.').value); + +console.log("Testing READ_STR of strings"); +assert_eq('a string',read_str('"a string"')); +assert_eq('a string (with parens)',read_str('"a string (with parens)"')); +assert_eq('a string',read_str('"a string"()')); +assert_eq('a string',read_str('"a string"123')); +assert_eq('a string',read_str('"a string"abc')); +assert_eq('',read_str('""')); +assert_eq('abc ',read_str('"abc "')); +assert_eq(' abc',read_str('" abc"')); +assert_eq('$abc',read_str('"$abc"')); +assert_eq('abc$()',read_str('"abc$()"')); +assert_eq('"xyz"',read_str('"\\"xyz\\""')); + + +console.log("Testing READ_STR of lists"); +assert_eq(2,types.ns.count(read_str('(2 3)'))); +assert_eq(2,types.ns.first(read_str('(2 3)'))); +assert_eq(3,types.ns.first(types.ns.rest(read_str('(2 3)')))); +L = read_str('(+ 1 2 "str1" "string (with parens) and \'single quotes\'")'); +assert_eq(5,types.ns.count(L)); +assert_eq('str1',nth(L,3)); +assert_eq('string (with parens) and \'single quotes\'',nth(L,4)); +assert_eq([2,3],read_str('(2 3)')); +assert_eq([2,3, 'string (with parens)'],read_str('(2 3 "string (with parens)")')); + + +console.log("Testing READ_STR of quote/quasiquote"); +assert_eq('quote',nth(read_str('\'1'),0).value); +assert_eq(1,nth(read_str('\'1'),1)); +assert_eq('quote',nth(read_str('\'(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('\'(1 2 3)'),1),2)); + +assert_eq('quasiquote',nth(read_str('`1'),0).value); +assert_eq(1,nth(read_str('`1'),1)); +assert_eq('quasiquote',nth(read_str('`(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('`(1 2 3)'),1),2)); + +assert_eq('unquote',nth(read_str('~1'),0).value); +assert_eq(1,nth(read_str('~1'),1)); +assert_eq('unquote',nth(read_str('~(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('~(1 2 3)'),1),2)); + +assert_eq('splice-unquote',nth(read_str('~@1'),0).value); +assert_eq(1,nth(read_str('~@1'),1)); +assert_eq('splice-unquote',nth(read_str('~@(1 2 3)'),0).value); +assert_eq(3,nth(nth(read_str('~@(1 2 3)'),1),2)); + + +console.log("All tests completed"); diff --git a/js/tests/step5_tco.js b/js/tests/step5_tco.js new file mode 100644 index 0000000..60c0576 --- /dev/null +++ b/js/tests/step5_tco.js @@ -0,0 +1,22 @@ +common = require('./common.js'); +var assert_eq = common.assert_eq; +var rep = require('../step5_tco.js').rep; + +console.log("Testing Stack Exhaustion Function"); +rep('(def! sum-to (fn* (n) (if (= n 0) 0 (+ n (sum-to (- n 1))))))'); +try { + rep('(sum-to 10000)'); + throw new Error("Did not get expected stack exhaustion"); +} catch (e) { + if (e.toString().match(/RangeError/)) { + console.log("Got expected stack exhaustion"); + } else { + throw new Error("Unexpected error: " + e); + } +} + +console.log("Testing Tail Call Optimization/Elimination"); +rep('(def! sum2 (fn* (n acc) (if (= n 0) acc (sum2 (- n 1) (+ n acc)))))'); +rep('(sum2 10000 0)'); + +console.log("All tests completed"); diff --git a/js/tests/types.js b/js/tests/types.js new file mode 100644 index 0000000..71b276f --- /dev/null +++ b/js/tests/types.js @@ -0,0 +1,94 @@ +common = require('./common.js'); +var assert_eq = common.assert_eq; +var types = require('../types.js'); +var symbol = types.symbol, + hash_map = types.ns['hash-map'], + hash_map_Q = types.ns['map?'], + assoc = types.ns['assoc'], + dissoc = types.ns['dissoc'], + get = types.ns['get'], + contains_Q = types.ns['contains?'], + count = types.ns['count'], + equal_Q = types.ns['=']; + + +console.log("Testing hash_maps"); +X = hash_map(); +assert_eq(true, hash_map_Q(X)); + +assert_eq(null, get(X,'a')); +assert_eq(false, contains_Q(X, 'a')); +X1 = assoc(X, 'a', "value of X a"); +assert_eq(null, get(X,'a')); +assert_eq(false, contains_Q(X, 'a')); +assert_eq("value of X a", get(X1, 'a')); +assert_eq(true, contains_Q(X1, 'a')); + +Y = hash_map(); +assert_eq(0, count(Y)); +Y1 = assoc(Y, 'a', "value of Y a"); +assert_eq(1, count(Y1)); +Y2 = assoc(Y1, 'b', "value of Y b"); +assert_eq(2, count(Y2)); +assert_eq("value of Y a", get(Y2, 'a')); +assert_eq("value of Y b", get(Y2, 'b')); + +X2 = assoc(X1, 'b', Y2); +assert_eq(2, count(Y2)); + +assert_eq(true, hash_map_Q(get(X2,'b'))); + +assert_eq('value of Y a', get(get(X2,'b'),'a')); +assert_eq('value of Y b', get(get(X2,'b'),'b')); + +Y3 = dissoc(Y2, 'a'); +assert_eq(2, count(Y2)); +assert_eq(1, count(Y3)); +assert_eq(null, get(Y3, 'a')); +Y4 = dissoc(Y3, 'b'); +assert_eq(0, count(Y4)); +assert_eq(null, get(Y4, 'b')); + + +console.log("Testing equal? function"); +assert_eq(true, equal_Q(2,2)); +assert_eq(false, equal_Q(2,3)); +assert_eq(false, equal_Q(2,3)); +assert_eq(true, equal_Q("abc","abc")); +assert_eq(false, equal_Q("abc","abz")); +assert_eq(false, equal_Q("zbc","abc")); +assert_eq(true, equal_Q(symbol("abc"),symbol("abc"))); +assert_eq(false, equal_Q(symbol("abc"),symbol("abz"))); +assert_eq(false, equal_Q(symbol("zbc"),symbol("abc"))); +L6 = [1, 2, 3]; +L7 = [1, 2, 3]; +L8 = [1, 2, "Z"]; +L9 = ["Z", 2, 3]; +L10 = [1, 2]; +assert_eq(true, equal_Q(L6, L7)); +assert_eq(false, equal_Q(L6, L8)); +assert_eq(false, equal_Q(L6, L9)); +assert_eq(false, equal_Q(L6, L10)); +assert_eq(false, equal_Q(L10, L6)); + + +console.log("Testing ENV (1 level)") +env1 = new types.Env(); +assert_eq('val_a',env1.set('a','val_a')); +assert_eq('val_b',env1.set('b','val_b')); +assert_eq('val_eq',env1.set('=','val_eq')); +assert_eq('val_a',env1.get('a')); +assert_eq('val_b',env1.get('b')); +assert_eq('val_eq',env1.get('=')); + +console.log("Testing ENV (2 levels)"); +env2 = new types.Env(env1); +assert_eq('val_b2',env2.set('b','val_b2')); +assert_eq('val_c',env2.set('c','val_c')); +assert_eq(env1,env2.find('a')); +assert_eq(env2,env2.find('b')); +assert_eq(env2,env2.find('c')); +assert_eq('val_a', env2.get('a')); +assert_eq('val_b2',env2.get('b')); +assert_eq('val_c', env2.get('c')); + diff --git a/js/types.js b/js/types.js new file mode 100644 index 0000000..062b0dd --- /dev/null +++ b/js/types.js @@ -0,0 +1,429 @@ +// Node vs browser behavior +var types = {}; +if (typeof module === 'undefined') { + var exports = types; +} + +// 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'; } + else { + switch (typeof(obj)) { + case 'number': return 'number'; + case 'function': return 'function'; + case 'string': return 'string'; + default: throw new Error("Unknown type '" + typeof(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() { + console.log.apply(console, Array.prototype.map.call(arguments,function(exp) { + return _pr_str(exp, true); + })); +} + +function println() { + console.log.apply(console, 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 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': + var akeys = Object.keys(a).sort(), + bkeys = Object.keys(b).sort(); + if (akeys.length !== bkeys.length) { return false; } + for (var i=0; i<akeys.length; i++) { + if (akeys[i] !== bkeys[i]) { return false; } + if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { return false; } + } + return true; + default: + return a === b; + } +} + + + +// 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; } + + +// Functions +function new_function(func, exp, env, params) { + var f = function() { + // TODO: figure out why this throws with 'and' macro + //throw new Error("Attempt to invoke mal function directly"); + return func(exp, new Env(env, params, arguments)); + }; + f.__meta__ = {exp: exp, env: env, params: params}; + return f; + +} +function function_Q(f) { return typeof f == "function"; } + + + +// Errors/Exceptions +function mal_throw(exc) { throw exc; } + + +// Vectors +function vector() { + var v = Array.prototype.slice.call(arguments, 0); + v.__isvector__ = true; + return v; +} + +function vector_Q(v) { return Array.isArray(v) && v.__isvector__; } + + +// Lists + +function list() { + return Array.prototype.slice.call(arguments, 0); +} + +function list_Q(lst) { return Array.isArray(lst) && !lst.__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 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]; + // 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)); + } + hm[ktoken] = vtoken; + } + return hm; +} + +function assoc(src_hm) { + var hm = _clone(src_hm); + var args = [hm].concat(Array.prototype.slice.call(arguments, 1)); + return assoc_BANG.apply(null, args); +} + +function dissoc_BANG(hm) { + for (var i=1; i<arguments.length; i++) { + var ktoken = arguments[i]; + delete hm[ktoken]; + } + return hm; +} + +function dissoc(src_hm) { + var hm = _clone(src_hm); + var args = [hm].concat(Array.prototype.slice.call(arguments, 1)); + return 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]; }); } + + +// Atoms +function Atom(val) { this.val = val; } +function atom(val) { return new Atom(val); } +function atom_Q(atm) { return atm instanceof Atom; } +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; +} + + +// Sequence operations +function sequential_Q(lst) { return list_Q(lst) || vector_Q(lst); } + +function nth(lst, idx) { return lst[idx]; } + +function count(s) { + if (Array.isArray(s)) { return s.length; } + else { return Object.keys(s).length; } +} + +function empty_Q(lst) { return lst.length === 0; } + +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 conj(lst) { + return lst.concat(Array.prototype.slice.call(arguments, 1)); +} + +function first(lst) { return lst[0]; } + +function rest(lst) { return lst.slice(1); } + + + +// General list related functions +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); }); +} + + +// 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 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.get = function(key) { + var env = this.find(key); + if (!env) { throw new Error("'" + key + "' not found"); } + return env.data[key]; +}; + +// types.ns is namespace of type functions +var ns = {'pr-str': pr_str, 'str': str, 'prn': prn, 'println': println, + 'with-meta': with_meta, 'meta': meta, + type: obj_type, '=': equal_Q, + symbol: symbol, 'symbol?': symbol_Q, + 'nil?': nil_Q, 'true?': true_Q, 'false?': false_Q, + '<' : 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;}, + '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; |
