From 73bd649ff4e47b7d8ce05b9bd7da8e5f1a3932b1 Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 31 Jul 2015 00:29:44 -0500 Subject: [PATCH] ES6: add step6 and step7 basics. --- es6/Makefile | 8 ++- es6/core.js | 27 ++++++++++ es6/step6_file.js | 100 +++++++++++++++++++++++++++++++++++++ es6/step7_quote.js | 119 +++++++++++++++++++++++++++++++++++++++++++++ es6/types.js | 2 +- 5 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 es6/step6_file.js create mode 100644 es6/step7_quote.js diff --git a/es6/Makefile b/es6/Makefile index 83b8b6c4..6ca6952b 100644 --- a/es6/Makefile +++ b/es6/Makefile @@ -1,6 +1,6 @@ SOURCES = step0_repl.js step1_read_print.js step2_eval.js step3_env.js \ - step4_if_fn_do.js step5_tco.js + step4_if_fn_do.js step5_tco.js step6_file.js step7_quote.js all: $(foreach s,$(SOURCES),build/$(s)) @@ -8,15 +8,13 @@ build/%.js: %.js babel --source-maps true $< --out-file $@ build/step0_repl.js: step0_repl.js build/node_readline.js - build/step1_read_print.js: step1_read_print.js build/node_readline.js build/types.js build/reader.js build/printer.js - build/step2_eval.js: step2_eval.js build/node_readline.js build/types.js build/reader.js build/printer.js - build/step3_env.js: step3_env.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js - build/step4_if_fn_do.js: step4_if_fn_do.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js build/step5_tco.js: step5_tco.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js +build/step6_file.js: step6_file.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js +build/step7_quote.js: step7_quote.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js clean: rm -f build/* diff --git a/es6/core.js b/es6/core.js index 7e5e718a..81b9c864 100644 --- a/es6/core.js +++ b/es6/core.js @@ -1,5 +1,6 @@ import * as types from './types'; import { pr_str } from './printer'; +import { read_str } from './reader'; // String functions function do_pr_str(...args) { @@ -20,6 +21,28 @@ function println(...args) { return null; } +function slurp(f) { + if (typeof require !== 'undefined') { + return require('fs').readFileSync(f, 'utf-8'); + } else { + var req = new XMLHttpRequest(); + req.open("GET", f, false); + req.send(); + if (req.status == 200) { + return req.responseText; + } else { + throw new Error("Failed to slurp file: " + f); + } + } +} + +// 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)); +} // types.ns is namespace of type functions export const core_ns = new Map([ @@ -29,6 +52,8 @@ export const core_ns = new Map([ ['str', str], ['prn', prn], ['println', println], + ['read-string',read_str], + ['slurp', slurp], ['<' , (a,b) => a a], ['list?', types._list_Q], + ['cons', (a,b) => [a].concat(b)], + ['concat', (...a) => a.reduce((x,y) => x.concat(y), [])], ['empty?', a => a.length === 0], ['count', a => a === null ? 0 : a.length]]); diff --git a/es6/step6_file.js b/es6/step6_file.js new file mode 100644 index 00000000..f7cecbca --- /dev/null +++ b/es6/step6_file.js @@ -0,0 +1,100 @@ +import { readline } from './node_readline'; +import { Sym, _list_Q, _malfunc, _malfunc_Q } from './types'; +import { BlankException, read_str } from './reader'; +import { pr_str } from './printer'; +import { new_env, env_set, env_get } from './env'; +import { core_ns } from './core'; + +// read +const READ = (str) => read_str(str); + +// eval +const eval_ast = (ast, env) => { + if (ast instanceof Sym) { + return env_get(env, ast) + } else if (_list_Q(ast)) { + return ast.map((x) => EVAL(x, env)); + } else { + return ast; + } +} + +const EVAL = (ast, env) => { + while (true) { + //console.log("EVAL:", pr_str(ast, true)); + if (!_list_Q(ast)) { return eval_ast(ast, env) } + + let [{ name: a0sym }, a1, a2, a3] = ast; + switch (a0sym) { + case 'def!': + return env_set(env, a1, EVAL(a2, env)); + case 'let*': + let let_env = new_env(env); + for (let i=0; i < a1.length; i+=2) { + env_set(let_env, a1[i], EVAL(a1[i+1], let_env)); + } + env = let_env; + ast = a2; + break; // continue TCO loop + case "do": + eval_ast(ast.slice(1,ast.length-1), env); + ast = ast[ast.length-1]; + break; // continue TCO loop + case "if": + let cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; // continue TCO loop + case "fn*": + return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), + a2, env, a1); + default: + let [f, ...args] = eval_ast(ast, env); + if (_malfunc_Q(f)) { + env = new_env(f.env, f.params, args); + ast = f.ast; + break; // continue TCO loop + } else { + return f(...args); + } + } + } +} + +// print +const PRINT = (exp) => pr_str(exp, true); + +// repl +let repl_env = new_env(); +const REP = (str) => PRINT(EVAL(READ(str), repl_env)); + +// core.EXT: defined using ES6 +for (let [k, v] of core_ns) { env_set(repl_env, new Sym(k), v) } +env_set(repl_env, new Sym('eval'), a => EVAL(a, repl_env)); +env_set(repl_env, new Sym('*ARGV*'), []); + +// core.mal: defined using language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +if (process.argv.length > 2) { + env_set(repl_env, '*ARGV*', process.argv.slice(3)); + REPL('(load-file "' + process.argv[2] + '")'); + process.exit(0); +} + + +while (true) { + let line = readline("user> "); + if (line == null) break; + try { + if (line) { console.log(REP(line)); } + } catch (exc) { + if (exc instanceof BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } + else { console.log("Error: " + exc); } + } +} diff --git a/es6/step7_quote.js b/es6/step7_quote.js new file mode 100644 index 00000000..b0905b03 --- /dev/null +++ b/es6/step7_quote.js @@ -0,0 +1,119 @@ +import { readline } from './node_readline'; +import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types'; +import { BlankException, read_str } from './reader'; +import { pr_str } from './printer'; +import { new_env, env_set, env_get } from './env'; +import { core_ns } from './core'; + +// read +const READ = (str) => read_str(str); + +// eval +const is_pair = x => _sequential_Q(x) && x.length > 0 + +const quasiquote = ast => { + if (!is_pair(ast)) { + return [new Sym("quote"), ast]; + } else if (ast[0].name === 'unquote') { + return ast[1]; + } else if (is_pair(ast[0]) && ast[0][0].name === 'splice-unquote') { + return [new Sym("concat"), ast[0][1], quasiquote(ast.slice(1))]; + } else { + return [new Sym("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))]; + } +} + +const eval_ast = (ast, env) => { + if (ast instanceof Sym) { + return env_get(env, ast) + } else if (_list_Q(ast)) { + return ast.map((x) => EVAL(x, env)); + } else { + return ast; + } +} + +const EVAL = (ast, env) => { + while (true) { + //console.log("EVAL:", pr_str(ast, true)); + if (!_list_Q(ast)) { return eval_ast(ast, env) } + + let [{ name: a0sym }, a1, a2, a3] = ast; + switch (a0sym) { + case 'def!': + return env_set(env, a1, EVAL(a2, env)); + case 'let*': + let let_env = new_env(env); + for (let i=0; i < a1.length; i+=2) { + env_set(let_env, a1[i], EVAL(a1[i+1], let_env)); + } + env = let_env; + ast = a2; + break; // continue TCO loop + case "quote": + return a1; + case "quasiquote": + ast = quasiquote(a1); + break; // continue TCO loop + case "do": + eval_ast(ast.slice(1,ast.length-1), env); + ast = ast[ast.length-1]; + break; // continue TCO loop + case "if": + let cond = EVAL(a1, env); + if (cond === null || cond === false) { + ast = (typeof a3 !== "undefined") ? a3 : null; + } else { + ast = a2; + } + break; // continue TCO loop + case "fn*": + return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), + a2, env, a1); + default: + let [f, ...args] = eval_ast(ast, env); + if (_malfunc_Q(f)) { + env = new_env(f.env, f.params, args); + ast = f.ast; + break; // continue TCO loop + } else { + return f(...args); + } + } + } +} + +// print +const PRINT = (exp) => pr_str(exp, true); + +// repl +let repl_env = new_env(); +const REP = (str) => PRINT(EVAL(READ(str), repl_env)); + +// core.EXT: defined using ES6 +for (let [k, v] of core_ns) { env_set(repl_env, new Sym(k), v) } +env_set(repl_env, new Sym('eval'), a => EVAL(a, repl_env)); +env_set(repl_env, new Sym('*ARGV*'), []); + +// core.mal: defined using language itself +REP("(def! not (fn* (a) (if a false true)))") +REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))"); + +if (process.argv.length > 2) { + env_set(repl_env, '*ARGV*', process.argv.slice(3)); + REPL('(load-file "' + process.argv[2] + '")'); + process.exit(0); +} + + +while (true) { + let line = readline("user> "); + if (line == null) break; + try { + if (line) { console.log(REP(line)); } + } catch (exc) { + if (exc instanceof BlankException) { continue; } + if (exc.stack) { console.log(exc.stack); } + else { console.log("Error: " + exc); } + } +} diff --git a/es6/types.js b/es6/types.js index 046d6737..3d1c9310 100644 --- a/es6/types.js +++ b/es6/types.js @@ -26,7 +26,7 @@ export function _equal_Q (a, b) { return false; } switch (ota) { - case 'symbol': return a.value === b.value; + case 'symbol': return a.name === b.name; case 'list': case 'vector': if (a.length !== b.length) { return false; }