From a05c086f05ad88bdb65ca81615d8f4a5e1d5a4ae Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Fri, 10 Feb 2017 22:06:09 -0600 Subject: [PATCH] ES6: more use of ES6, simplifications, newer babel. - Use Vector class derived from Array - Use Array/Vector.from for initializing/cloning of Array/Vector - Remove most semi-colon line endings - More use of arrow functions - Use Object.assign to copy properties in _malfunc and function cloning. - Remove or inline a bunch of types.js functions that don't really need to be separate functions: _obj_type, _sequential_Q, _symbol, _symbol_Q, _vector, _vector_Q, _hash_map, _hash_map_Q - Simplify dependency list in Makefile - Remove some separate core.js functions by moving them into the core_ns declaration: _nth, keys, vals, with_meta. With node 7, babel is mostly just used for translating imports into CommonJS requires for node. --- es6/Dockerfile | 8 +- es6/Makefile | 35 +++++--- es6/core.js | 174 ++++++++++++++++------------------------ es6/env.js | 10 +-- es6/package.json | 4 + es6/printer.js | 18 ++--- es6/reader.js | 110 ++++++++++++------------- es6/step0_repl.js | 8 +- es6/step1_read_print.js | 14 ++-- es6/step2_eval.js | 39 ++++----- es6/step3_env.js | 44 +++++----- es6/step4_if_fn_do.js | 38 ++++----- es6/step5_tco.js | 48 +++++------ es6/step6_file.js | 56 ++++++------- es6/step7_quote.js | 74 +++++++++-------- es6/step8_macros.js | 88 +++++++++----------- es6/step9_try.js | 92 ++++++++++----------- es6/stepA_mal.js | 92 ++++++++++----------- es6/types.js | 97 +++++----------------- 19 files changed, 452 insertions(+), 597 deletions(-) diff --git a/es6/Dockerfile b/es6/Dockerfile index b2033c1e..208e7f66 100644 --- a/es6/Dockerfile +++ b/es6/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:vivid +FROM ubuntu:xenial MAINTAINER Joel Martin ########################################################## @@ -24,8 +24,8 @@ WORKDIR /mal # For building node modules RUN apt-get -y install g++ -# Add nodesource apt repo config for 0.12 stable -RUN curl -sL https://deb.nodesource.com/setup_0.12 | bash - +# Add nodesource apt repo config for 7.X +RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - # Install nodejs RUN apt-get -y install nodejs @@ -36,4 +36,4 @@ RUN ln -sf nodejs /usr/bin/node ENV NPM_CONFIG_CACHE /mal/.npm # ES6 -RUN npm install -g babel +RUN npm install -g babel-cli babel-plugin-transform-es2015-modules-commonjs diff --git a/es6/Makefile b/es6/Makefile index d576e5b5..775b814f 100644 --- a/es6/Makefile +++ b/es6/Makefile @@ -1,3 +1,7 @@ +export PATH := $(PATH):node_modules/.bin/ + +BABEL_OPTS = --source-maps true \ + --plugins transform-es2015-modules-commonjs SOURCES_BASE = node_readline.js types.js reader.js printer.js SOURCES_LISP = env.js core.js stepA_mal.js @@ -11,9 +15,9 @@ all: node_modules $(foreach s,$(STEPS),build/$(s).js) dist: mal.js mal -build/%.js: %.js +build/%.js: %.js node_modules @mkdir -p $(dir $@) - babel --source-maps true $< --out-file $@ + babel $(BABEL_OPTS) $< --out-file $@ @echo >> $@ # workaround node-uglifier bug mal.js: $(foreach s,$(SOURCES),build/$(s)) @@ -24,17 +28,22 @@ mal: mal.js cat $< >> $@ chmod +x $@ -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 -build/step8_macros.js: step8_macros.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/step9_try.js: step9_try.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js -build/stepA_mal.js: stepA_mal.js build/node_readline.js build/types.js build/reader.js build/printer.js build/env.js build/core.js +STEP0_DEPS = build/node_readline.js +STEP1_DEPS = $(STEP0_DEPS) build/types.js build/reader.js build/printer.js +STEP3_DEPS = $(STEP1_DEPS) build/env.js +STEP4_DEPS = $(STEP3_DEPS) build/core.js + +build/step0_repl.js: $(STEP0_DEPS) +build/step1_read_print.js: $(STEP1_DEPS) +build/step2_eval.js: $(STEP1_DEPS) +build/step3_env.js: $(STEP3_DEPS) +build/step4_if_fn_do.js: $(STEP4_DEPS) +build/step5_tco.js: $(STEP4_DEPS) +build/step6_file.js: $(STEP4_DEPS) +build/step7_quote.js: $(STEP4_DEPS) +build/step8_macros.js: $(STEP4_DEPS) +build/step9_try.js: $(STEP4_DEPS) +build/stepA_mal.js: $(STEP4_DEPS) node_modules: diff --git a/es6/core.js b/es6/core.js index 621b7c62..cd11a837 100644 --- a/es6/core.js +++ b/es6/core.js @@ -1,13 +1,10 @@ -import { _equal_Q, _clone, _list_Q, _sequential_Q, - _keyword, _keyword_Q, _vector, _vector_Q, - _hash_map, _hash_map_Q, _assoc_BANG, _dissoc_BANG, - _symbol, _symbol_Q, Atom } from './types' +import { _equal_Q, _clone, _keyword, _keyword_Q, + _list_Q, Vector, _assoc_BANG, _dissoc_BANG, Atom } from './types' import { pr_str } from './printer' import { readline } from './node_readline' import { read_str } from './reader' -// Errors/Exceptions -function mal_throw(exc) { throw exc; } +function _error(e) { throw new Error(e) } // String functions function slurp(f) { @@ -17,129 +14,96 @@ function slurp(f) { 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}`) + if (req.status !== 200) { + _error(`Failed to slurp file: ${f}`) } + return req.responseText } } // Sequence functions -function nth(lst, idx) { - if (idx < lst.length) { return lst[idx]; } - else { throw new Error('nth: index out of range'); } -} - -function conj(lst, ...args) { - if (_list_Q(lst)) { - return args.reverse().concat(lst) - } else { - return _vector(...lst.concat(args)) - } +function conj(o, ...a) { + return _list_Q(o) ? a.reverse().concat(o) : Vector.from(o.concat(a)) } function seq(obj) { if (_list_Q(obj)) { return obj.length > 0 ? obj : null - } else if (_vector_Q(obj)) { - return obj.length > 0 ? obj.slice(0) : null + } else if (obj instanceof Vector) { + return obj.length > 0 ? Array.from(obj.slice(0)) : null } else if (typeof obj === "string" && obj[0] !== '\u029e') { return obj.length > 0 ? obj.split('') : null } else if (obj === null) { return null } else { - throw new Error('seq: called on non-sequence') + _error('seq: called on non-sequence') } } -// hash-map functions - -function keys(hm) { - // TODO: Array.from(hm.keys()) when supported - let ks = [] - for (let k of hm.keys()) { ks.push(k) } - return ks -} - -function vals(hm) { - // TODO: Array.from(hm.keys()) when supported - let vs = [] - for (let v of hm.values()) { vs.push(v) } - return vs -} - -// Metadata functions -function with_meta(obj, m) { - let new_obj = _clone(obj) - new_obj.meta = m - return new_obj -} - // core_ns is namespace of type functions export const core_ns = new Map([ - ['=', _equal_Q], - ['throw', mal_throw], + ['=', _equal_Q], + ['throw', a => { throw a }], - ['nil?', a => a === null], - ['true?', a => a === true], - ['false?', a => a === false], - ['string?', a => typeof a === "string" && a[0] !== '\u029e'], - ['symbol', a => _symbol(a)], - ['symbol?', a => _symbol_Q(a)], - ['keyword', a => _keyword(a)], - ['keyword?', a => _keyword_Q(a)], + ['nil?', a => a === null], + ['true?', a => a === true], + ['false?', a => a === false], + ['string?', a => typeof a === "string" && a[0] !== '\u029e'], + ['symbol', a => Symbol.for(a)], + ['symbol?', a => typeof a === 'symbol'], + ['keyword', _keyword], + ['keyword?', _keyword_Q], - ['pr-str', (...a) => a.map(e => pr_str(e,1)).join(' ')], - ['str', (...a) => a.map(e => pr_str(e,0)).join('')], - ['prn', (...a) => console.log(...a.map(e => pr_str(e,1))) || null], - ['println', (...a) => console.log(...a.map(e => pr_str(e,0))) || null], - ['read-string', read_str], - ['readline', readline], - ['slurp', slurp], + ['pr-str', (...a) => a.map(e => pr_str(e,1)).join(' ')], + ['str', (...a) => a.map(e => pr_str(e,0)).join('')], + ['prn', (...a) => console.log(...a.map(e => pr_str(e,1))) || null], + ['println', (...a) => console.log(...a.map(e => pr_str(e,0))) || null], + ['read-string', read_str], + ['readline', readline], + ['slurp', slurp], - ['<' , (a,b) => a' , (a,b) => a>b], - ['>=', (a,b) => a>=b], - ['+' , (a,b) => a+b], - ['-' , (a,b) => a-b], - ['*' , (a,b) => a*b], - ['/' , (a,b) => a/b], - ["time-ms", () => new Date().getTime()], + ['<' , (a,b) => a' , (a,b) => a>b], + ['>=', (a,b) => a>=b], + ['+' , (a,b) => a+b], + ['-' , (a,b) => a-b], + ['*' , (a,b) => a*b], + ['/' , (a,b) => a/b], + ["time-ms", () => new Date().getTime()], - ['list', (...a) => a], - ['list?', _list_Q], - ['vector', _vector], - ['vector?', _vector_Q], - ['hash-map', _hash_map], - ['map?', _hash_map_Q], - ['assoc', (m,...a) => _assoc_BANG(_clone(m), ...a)], - ['dissoc', (m,...a) => _dissoc_BANG(_clone(m), ...a)], - ['get', (m,a) => m === null ? null : m.has(a) ? m.get(a) : null], - ['contains?', (m,a) => m.has(a)], - ['keys', keys], - ['vals', vals], + ['list', (...a) => a], + ['list?', _list_Q], + ['vector', (...a) => Vector.from(a)], + ['vector?', a => a instanceof Vector], + ['hash-map', (...a) => _assoc_BANG(new Map(), ...a)], + ['map?', a => a instanceof Map], + ['assoc', (m,...a) => _assoc_BANG(_clone(m), ...a)], + ['dissoc', (m,...a) => _dissoc_BANG(_clone(m), ...a)], + ['get', (m,a) => m === null ? null : m.has(a) ? m.get(a) : null], + ['contains?', (m,a) => m.has(a)], + ['keys', a => Array.from(a.keys())], + ['vals', a => Array.from(a.values())], - ['sequential?', _sequential_Q], - ['cons', (a,b) => [a].concat(b)], - ['concat', (...a) => a.reduce((x,y) => x.concat(y), [])], - ['nth', nth], - ['first', a => a !== null && a.length > 0 ? a[0] : null], - ['rest', a => a === null ? [] : a.slice(1)], - ['empty?', a => a.length === 0], - ['count', a => a === null ? 0 : a.length], - ['apply', (f,...a) => f(...a.slice(0, -1).concat(a[a.length-1]))], - ['map', (f,a) => a.map(x => f(x))], + ['sequential?', a => Array.isArray(a)], + ['cons', (a,b) => [a].concat(b)], + ['concat', (...a) => a.reduce((x,y) => x.concat(y), [])], + ['nth', (a,b) => b < a.length ? a[b] : _error('nth: index out of range')], + ['first', a => a !== null && a.length > 0 ? a[0] : null], + ['rest', a => a === null ? [] : Array.from(a.slice(1))], + ['empty?', a => a.length === 0], + ['count', a => a === null ? 0 : a.length], + ['apply', (f,...a) => f(...a.slice(0, -1).concat(a[a.length-1]))], + ['map', (f,a) => Array.from(a.map(x => f(x)))], - ['conj', conj], - ['seq', seq], + ['conj', conj], + ['seq', seq], - ['meta', a => 'meta' in a ? a['meta'] : null], - ['with-meta', with_meta], - ['atom', a => new Atom(a)], - ['atom?', a => a instanceof Atom], - ['deref', atm => atm.val], - ['reset!', (atm,a) => atm.val = a], - ['swap!', (atm,f,...args) => atm.val = f(...[atm.val].concat(args))] - ]) + ['meta', a => 'meta' in a ? a['meta'] : null], + ['with-meta', (a,b) => { let c = _clone(a); c.meta = b; return c }], + ['atom', a => new Atom(a)], + ['atom?', a => a instanceof Atom], + ['deref', atm => atm.val], + ['reset!', (atm,a) => atm.val = a], + ['swap!', (atm,f,...args) => atm.val = f(...[atm.val].concat(args))] + ]) diff --git a/es6/env.js b/es6/env.js index b2a9429c..f0321286 100644 --- a/es6/env.js +++ b/es6/env.js @@ -5,17 +5,13 @@ export function new_env(outer={}, binds=[], exprs=[]) { if (Symbol.keyFor(binds[i]) === "&") { e[binds[i+1]] = exprs.slice(i) // variable length arguments break - } else { - e[binds[i]] = exprs[i] } + e[binds[i]] = exprs[i] } return e } export const env_get = (env, sym) => { - if (sym in env) { - return env[sym] - } else { - throw Error(`'${Symbol.keyFor(sym)}' not found`) - } + if (sym in env) { return env[sym] } + throw Error(`'${Symbol.keyFor(sym)}' not found`) } export const env_set = (env, sym, val) => env[sym] = val diff --git a/es6/package.json b/es6/package.json index fb844da0..d836e6d6 100644 --- a/es6/package.json +++ b/es6/package.json @@ -5,5 +5,9 @@ "dependencies": { "ffi": "2.0.x", "node-uglifier": "0.4.3" + }, + "devDependencies": { + "babel-cli": "^6.0.0", + "babel-plugin-transform-es2015-modules-commonjs": "*" } } diff --git a/es6/printer.js b/es6/printer.js index c3951f8d..18e5d350 100644 --- a/es6/printer.js +++ b/es6/printer.js @@ -1,15 +1,13 @@ -import { _symbol, _symbol_Q, _list_Q, _vector_Q, _hash_map_Q, Atom } from './types' +import { _symbol, _list_Q, Vector, Atom } from './types' export function pr_str(obj, print_readably) { if (typeof print_readably === 'undefined') { print_readably = true } var _r = print_readably if (_list_Q(obj)) { - var ret = obj.map(function(e) { return pr_str(e,_r) }) - return "(" + ret.join(' ') + ")" - } else if (_vector_Q(obj)) { - var ret = obj.map(function(e) { return pr_str(e,_r) }) - return "[" + ret.join(' ') + "]" - } else if (_hash_map_Q(obj)) { + return "(" + obj.map(e => pr_str(e,_r)).join(' ') + ")" + } else if (obj instanceof Vector) { + return "[" + obj.map(e => pr_str(e,_r)).join(' ') + "]" + } else if (obj instanceof Map) { var ret = [] for (let [k,v] of obj) { ret.push(pr_str(k,_r), pr_str(v,_r)) @@ -20,12 +18,12 @@ export function pr_str(obj, print_readably) { return ':' + obj.slice(1) } else if (_r) { return '"' + obj.replace(/\\/g, "\\\\") - .replace(/"/g, '\\"') - .replace(/\n/g, "\\n") + '"' // string + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n") + '"' } else { return obj } - } else if (_symbol_Q(obj)) { + } else if (typeof obj === 'symbol') { return Symbol.keyFor(obj) } else if (obj === null) { return "nil" diff --git a/es6/reader.js b/es6/reader.js index 98b9861a..e7afe003 100644 --- a/es6/reader.js +++ b/es6/reader.js @@ -1,120 +1,120 @@ -import { _symbol, _keyword, _vector, _hash_map } from './types'; +import { _keyword, Vector, _assoc_BANG } from './types' export class BlankException extends Error {} class Reader { constructor(tokens) { - this.tokens = tokens; - this.position = 0; + this.tokens = tokens + this.position = 0 } - next() { return this.tokens[this.position++]; } - peek() { return this.tokens[this.position]; } + next() { return this.tokens[this.position++] } + peek() { return this.tokens[this.position] } } function tokenize(str) { - const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g; - let match = null; - let results = []; + const re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g + let match = null + let results = [] while ((match = re.exec(str)[1]) != '') { - if (match[0] === ';') { continue; } - results.push(match); + if (match[0] === ';') { continue } + results.push(match) } - return results; + return results } function read_atom (reader) { - const token = reader.next(); - //console.log("read_atom:", token); + const 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 + return parseFloat(token,10) // float } else if (token[0] === "\"") { return token.slice(1,token.length-1) .replace(/\\"/g, '"') .replace(/\\n/g, "\n") - .replace(/\\\\/g, "\\"); // string + .replace(/\\\\/g, "\\") // string } else if (token[0] === ":") { - return _keyword(token.slice(1)); + return _keyword(token.slice(1)) } else if (token === "nil") { - return null; + return null } else if (token === "true") { - return true; + return true } else if (token === "false") { - return false; + return false } else { - return _symbol(token); // symbol + return Symbol.for(token) // symbol } } // read list of tokens function read_list(reader, start, end) { - start = start || '('; - end = end || ')'; - var ast = []; - var token = reader.next(); + start = start || '(' + end = end || ')' + var ast = [] + var token = reader.next() if (token !== start) { - throw new Error("expected '" + start + "'"); + throw new Error("expected '" + start + "'") } while ((token = reader.peek()) !== end) { if (!token) { - throw new Error("expected '" + end + "', got EOF"); + throw new Error("expected '" + end + "', got EOF") } - ast.push(read_form(reader)); + ast.push(read_form(reader)) } - reader.next(); - return ast; + reader.next() + return ast } // read vector of tokens function read_vector(reader) { - return _vector(...read_list(reader, '[', ']')); + return Vector.from(read_list(reader, '[', ']')) } // read hash-map key/value pairs function read_hash_map(reader) { - return _hash_map(...read_list(reader, '{', '}')); + return _assoc_BANG(new Map(), ...read_list(reader, '{', '}')) } function read_form(reader) { - var token = reader.peek(); + var token = reader.peek() switch (token) { // reader macros/transforms - case ';': return null; // Ignore comments - case '\'': reader.next(); - return [_symbol('quote'), read_form(reader)]; - case '`': reader.next(); - return [_symbol('quasiquote'), read_form(reader)]; - case '~': reader.next(); - return [_symbol('unquote'), read_form(reader)]; - case '~@': reader.next(); - return [_symbol('splice-unquote'), read_form(reader)]; - case '^': reader.next(); - var meta = read_form(reader); - return [_symbol('with-meta'), read_form(reader), meta]; - case '@': reader.next(); - return [_symbol('deref'), read_form(reader)]; + case ';': return null // Ignore comments + case '\'': reader.next() + return [Symbol.for('quote'), read_form(reader)] + case '`': reader.next() + return [Symbol.for('quasiquote'), read_form(reader)] + case '~': reader.next() + return [Symbol.for('unquote'), read_form(reader)] + case '~@': reader.next() + return [Symbol.for('splice-unquote'), read_form(reader)] + case '^': reader.next() + var meta = read_form(reader) + return [Symbol.for('with-meta'), read_form(reader), meta] + case '@': reader.next() + return [Symbol.for('deref'), read_form(reader)] // list - case ')': throw new Error("unexpected ')'"); - case '(': return read_list(reader); + case ')': throw new Error("unexpected ')'") + case '(': return read_list(reader) // vector - case ']': throw new Error("unexpected ']'"); - case '[': return read_vector(reader); + case ']': throw new Error("unexpected ']'") + case '[': return read_vector(reader) // hash-map - case '}': throw new Error("unexpected '}'"); - case '{': return read_hash_map(reader); + case '}': throw new Error("unexpected '}'") + case '{': return read_hash_map(reader) // atom - default: return read_atom(reader); + default: return read_atom(reader) } } export function read_str(str) { - var tokens = tokenize(str); - if (tokens.length === 0) { throw new BlankException(); } + var tokens = tokenize(str) + if (tokens.length === 0) { throw new BlankException() } return read_form(new Reader(tokens)) } diff --git a/es6/step0_repl.js b/es6/step0_repl.js index 74585f4c..6679a092 100644 --- a/es6/step0_repl.js +++ b/es6/step0_repl.js @@ -1,19 +1,19 @@ import { readline } from './node_readline' // read -const READ = (str) => str +const READ = str => str // eval const EVAL = (ast, env) => ast // print -const PRINT = (exp) => exp +const PRINT = exp => exp // repl -const REP = (str) => PRINT(EVAL(READ(str), {})) +const REP = str => PRINT(EVAL(READ(str), {})) while (true) { let line = readline('user> ') if (line == null) break - if (line) { console.log(REP(line)); } + if (line) { console.log(REP(line)) } } diff --git a/es6/step1_read_print.js b/es6/step1_read_print.js index 6b1d1c80..6ff1cee7 100644 --- a/es6/step1_read_print.js +++ b/es6/step1_read_print.js @@ -3,25 +3,25 @@ import { BlankException, read_str } from './reader' import { pr_str } from './printer' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const EVAL = (ast, env) => ast // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl -const REP = (str) => PRINT(EVAL(READ(str), {})) +const REP = str => PRINT(EVAL(READ(str), {})) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step2_eval.js b/es6/step2_eval.js index 3622ab8d..40abe093 100644 --- a/es6/step2_eval.js +++ b/es6/step2_eval.js @@ -1,29 +1,24 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { if (ast in env) { return env[ast] } else { throw Error(`'${Symbol.keyFor(ast)}' not found`) } - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -39,23 +34,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl -var repl_env = {[_symbol('+')]: (a,b) => a+b, - [_symbol('-')]: (a,b) => a-b, - [_symbol('*')]: (a,b) => a*b, - [_symbol('/')]: (a,b) => a/b} -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +var repl_env = {[Symbol.for('+')]: (a,b) => a+b, + [Symbol.for('-')]: (a,b) => a-b, + [Symbol.for('*')]: (a,b) => a*b, + [Symbol.for('/')]: (a,b) => a/b} +const REP = str => PRINT(EVAL(READ(str), repl_env)) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step3_env.js b/es6/step3_env.js index 10de2c2f..0026b347 100644 --- a/es6/step3_env.js +++ b/es6/step3_env.js @@ -1,26 +1,21 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_Q } from './types' import { BlankException, read_str } from './reader' import { pr_str } from './printer' import { new_env, env_set, env_get } from './env' // read -const READ = (str) => read_str(str) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -33,9 +28,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -50,24 +44,24 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -env_set(repl_env, _symbol('+'), (a,b) => a+b) -env_set(repl_env, _symbol('-'), (a,b) => a-b) -env_set(repl_env, _symbol('*'), (a,b) => a*b) -env_set(repl_env, _symbol('/'), (a,b) => a/b) -const REP = (str) => PRINT(EVAL(READ(str), repl_env)) +env_set(repl_env, Symbol.for('+'), (a,b) => a+b) +env_set(repl_env, Symbol.for('-'), (a,b) => a-b) +env_set(repl_env, Symbol.for('*'), (a,b) => a*b) +env_set(repl_env, Symbol.for('/'), (a,b) => a/b) +const REP = str => PRINT(EVAL(READ(str), repl_env)) while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step4_if_fn_do.js b/es6/step4_if_fn_do.js index dca40df4..4fe1f90e 100644 --- a/es6/step4_if_fn_do.js +++ b/es6/step4_if_fn_do.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q } from './types' +import { _list_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) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -34,9 +29,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -62,14 +56,14 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -78,10 +72,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step5_tco.js b/es6/step5_tco.js index 37c7fc0d..7bcb7789 100644 --- a/es6/step5_tco.js +++ b/es6/step5_tco.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -35,9 +30,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -46,11 +40,11 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -58,16 +52,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -76,14 +70,14 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -92,10 +86,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step6_file.js b/es6/step6_file.js index 00314bf6..7396197d 100644 --- a/es6/step6_file.js +++ b/es6/step6_file.js @@ -1,27 +1,22 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -35,9 +30,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -46,11 +40,11 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -58,16 +52,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -76,23 +70,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*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, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -102,10 +96,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + 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 index f3094394..947c3a21 100644 --- a/es6/step7_quote.js +++ b/es6/step7_quote.js @@ -1,41 +1,40 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -49,9 +48,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -60,16 +58,16 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -77,16 +75,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -95,23 +93,23 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*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, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -121,10 +119,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step8_macros.js b/es6/step8_macros.js index 277a5ef5..9aa6e1cc 100644 --- a/es6/step8_macros.js +++ b/es6/step8_macros.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -94,7 +86,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -102,16 +94,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -120,16 +112,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -137,8 +129,8 @@ REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -148,10 +140,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/step9_try.js b/es6/step9_try.js index a50cc08e..ede914d9 100644 --- a/es6/step9_try.js +++ b/es6/step9_try.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -95,8 +87,8 @@ const EVAL = (ast, env) => { try { return EVAL(a1, env) } catch (exc) { - if (a2 && a2[0] === _symbol('catch*')) { - if (exc instanceof Error) { exc = exc.message; } + if (a2 && a2[0] === Symbol.for('catch*')) { + if (exc instanceof Error) { exc = exc.message } return EVAL(a2[2], new_env(env, [a2[1]], [exc])) } else { throw exc @@ -105,7 +97,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -113,16 +105,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -131,16 +123,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! not (fn* (a) (if a false true)))') @@ -148,8 +140,8 @@ REP('(def! load-file (fn* (f) (eval (read-string (str "(do " (slurp f) ")")))))' REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw "odd number of forms to cond")) (cons \'cond (rest (rest xs)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -159,10 +151,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + if (exc instanceof BlankException) { continue } + if (exc.stack) { console.log(exc.stack) } + else { console.log(`Error: ${exc}`) } } } diff --git a/es6/stepA_mal.js b/es6/stepA_mal.js index c6fda414..c883980e 100644 --- a/es6/stepA_mal.js +++ b/es6/stepA_mal.js @@ -1,57 +1,50 @@ import { readline } from './node_readline' -import { _symbol, _symbol_Q, _list_Q, _vector, _vector_Q, - _hash_map_Q, _sequential_Q, _malfunc, _malfunc_Q } from './types' +import { _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) +const READ = str => read_str(str) // eval -const is_pair = x => _sequential_Q(x) && x.length > 0 +const is_pair = x => Array.isArray(x) && x.length > 0 const quasiquote = ast => { if (!is_pair(ast)) { - return [_symbol('quote'), ast] - } else if (ast[0] === _symbol('unquote')) { + return [Symbol.for('quote'), ast] + } else if (ast[0] === Symbol.for('unquote')) { return ast[1] - } else if (is_pair(ast[0]) && ast[0][0] === _symbol('splice-unquote')) { - return [_symbol('concat'), ast[0][1], quasiquote(ast.slice(1))] + } else if (is_pair(ast[0]) && ast[0][0] === Symbol.for('splice-unquote')) { + return [Symbol.for('concat'), + ast[0][1], + quasiquote(ast.slice(1))] } else { - return [_symbol('cons'), quasiquote(ast[0]), quasiquote(ast.slice(1))] + return [Symbol.for('cons'), + quasiquote(ast[0]), + quasiquote(ast.slice(1))] } } -function is_macro_call(ast, env) { - return _list_Q(ast) && - _symbol_Q(ast[0]) && - ast[0] in env && - env_get(env, ast[0]).ismacro -} - function macroexpand(ast, env) { - while (is_macro_call(ast, env)) { - let mac = env_get(env, ast[0]) - ast = mac(...ast.slice(1)) + while (_list_Q(ast) && typeof ast[0] === 'symbol' && ast[0] in env) { + let f = env_get(env, ast[0]) + if (!f.ismacro) { break } + ast = f(...ast.slice(1)) } return ast } const eval_ast = (ast, env) => { - if (_symbol_Q(ast)) { + if (typeof ast === 'symbol') { return env_get(env, ast) - } else if (_list_Q(ast)) { - return ast.map((x) => EVAL(x, env)) - } else if (_vector_Q(ast)) { - return _vector(...ast.map((x) => EVAL(x, env))) - } else if (_hash_map_Q(ast)) { + } else if (ast instanceof Array) { + return ast.map(x => EVAL(x, env)) + } else if (ast instanceof Map) { let new_hm = new Map() - for (let [k, v] of ast) { - new_hm.set(EVAL(k, env), EVAL(v, env)) - } + ast.forEach((v, k) => new_hm.set(EVAL(k, env), EVAL(v, env))) return new_hm } else { return ast @@ -68,9 +61,8 @@ const EVAL = (ast, env) => { if (ast.length === 0) { return ast } const [a0, a1, a2, a3] = ast - const a0sym = _symbol_Q(a0) ? Symbol.keyFor(a0) : Symbol(':default') - switch (a0sym) { - case 'def!': + switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) { + case 'def!': return env_set(env, a1, EVAL(a2, env)) case 'let*': let let_env = new_env(env) @@ -79,12 +71,12 @@ const EVAL = (ast, env) => { } env = let_env ast = a2 - break; // continue TCO loop + break // continue TCO loop case 'quote': return a1 case 'quasiquote': ast = quasiquote(a1) - break; // continue TCO loop + break // continue TCO loop case 'defmacro!': let func = EVAL(a2, env) func.ismacro = true @@ -95,8 +87,8 @@ const EVAL = (ast, env) => { try { return EVAL(a1, env) } catch (exc) { - if (a2 && a2[0] === _symbol('catch*')) { - if (exc instanceof Error) { exc = exc.message; } + if (a2 && a2[0] === Symbol.for('catch*')) { + if (exc instanceof Error) { exc = exc.message } return EVAL(a2[2], new_env(env, [a2[1]], [exc])) } else { throw exc @@ -105,7 +97,7 @@ const EVAL = (ast, env) => { case 'do': eval_ast(ast.slice(1,-1), env) ast = ast[ast.length-1] - break; // continue TCO loop + break // continue TCO loop case 'if': let cond = EVAL(a1, env) if (cond === null || cond === false) { @@ -113,16 +105,16 @@ const EVAL = (ast, env) => { } else { ast = a2 } - break; // continue TCO loop + break // continue TCO loop case 'fn*': return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)), - a2, env, a1) + 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 + break // continue TCO loop } else { return f(...args) } @@ -131,16 +123,16 @@ const EVAL = (ast, env) => { } // print -const PRINT = (exp) => pr_str(exp, true) +const PRINT = exp => pr_str(exp, true) // repl let repl_env = new_env() -const REP = (str) => PRINT(EVAL(READ(str), repl_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, _symbol(k), v) } -env_set(repl_env, _symbol('eval'), a => EVAL(a, repl_env)) -env_set(repl_env, _symbol('*ARGV*'), []) +for (let [k, v] of core_ns) { env_set(repl_env, Symbol.for(k), v) } +env_set(repl_env, Symbol.for('eval'), a => EVAL(a, repl_env)) +env_set(repl_env, Symbol.for('*ARGV*'), []) // core.mal: defined using language itself REP('(def! *host-language* "ecmascript6")') @@ -151,8 +143,8 @@ REP('(def! *gensym-counter* (atom 0))') REP('(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))') REP('(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) (let* (condvar (gensym)) `(let* (~condvar ~(first xs)) (if ~condvar ~condvar (or ~@(rest xs)))))))))') -if (process.argv.length > 2) { - env_set(repl_env, _symbol('*ARGV*'), process.argv.slice(3)) +if (process.argv.length > 2) { + env_set(repl_env, Symbol.for('*ARGV*'), process.argv.slice(3)) REP(`(load-file "${process.argv[2]}")`) process.exit(0) } @@ -162,10 +154,10 @@ while (true) { let line = readline('user> ') if (line == null) break try { - if (line) { console.log(REP(line)); } + 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}`); } + 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 fb579b3c..458538ff 100644 --- a/es6/types.js +++ b/es6/types.js @@ -1,120 +1,59 @@ // General functions - -export const _sequential_Q = lst => _list_Q(lst) || _vector_Q(lst) - -export 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 (obj === null) { return 'nil' } - else if (obj === true) { return 'true' } - else if (obj === false) { return 'false' } - else { - switch (typeof(obj)) { - case 'number': return 'number' - case 'function': return 'function' - case 'string': return obj[0] == '\u029e' ? 'keyword' : 'string' - default: throw new Error(`Unknown type '${typeof(obj)}'`) - } - } -} - export function _equal_Q (a, b) { - let ota = _obj_type(a), otb = _obj_type(b) - if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) { - return false - } - switch (ota) { - case 'list': - case 'vector': + if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) { return false } for (let i=0; i f.ast ? true : false Function.prototype.clone = function() { - let that = this - // New function instance - let f = function (...args) { return that.apply(this, args) } - // Copy properties - for (let k of Object.keys(this)) { f[k] = this[k] } - return f + let f = (...a) => this.apply(f, a) // new function instance + return Object.assign(f, this) // copy original properties } -// Symbols -export const _symbol = name => Symbol.for(name) -export const _symbol_Q = obj => typeof obj === 'symbol' - // Keywords export const _keyword = obj => _keyword_Q(obj) ? obj : '\u029e' + obj export const _keyword_Q = obj => typeof obj === 'string' && obj[0] === '\u029e' -// Lists -export const _list_Q = obj => Array.isArray(obj) && !obj.__isvector__ +// Sequence collections +export const _list_Q = obj => Array.isArray(obj) && !(obj instanceof Vector) -// Vectors -// TODO: Extend Array when supported -export function _vector(...args) { - let v = args.slice(0) - v.__isvector__ = true - return v -} -export const _vector_Q = obj => Array.isArray(obj) && !!obj.__isvector__ +export class Vector extends Array {} -// Hash Maps -export const _hash_map = (...args) => _assoc_BANG(new Map(), ...args) -export const _hash_map_Q = hm => hm instanceof Map export function _assoc_BANG(hm, ...args) { - if (args % 2 === 1) { + if (args.length % 2 === 1) { throw new Error('Odd number of assoc arguments') } // Use iterator/Array.from when it works - for (let i=0; i