1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-20 01:57:09 +03:00

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.
This commit is contained in:
Joel Martin 2017-02-10 22:06:09 -06:00
parent 577e643bfa
commit a05c086f05
19 changed files with 452 additions and 597 deletions

View File

@ -1,4 +1,4 @@
FROM ubuntu:vivid
FROM ubuntu:xenial
MAINTAINER Joel Martin <github@martintribe.org>
##########################################################
@ -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

View File

@ -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:

View File

@ -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<b],
['<=', (a,b) => a<=b],
['>' , (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<b],
['<=', (a,b) => a<=b],
['>' , (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))]
])

View File

@ -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

View File

@ -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": "*"
}
}

View File

@ -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"

View File

@ -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))
}

View File

@ -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)) }
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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}`) }
}
}

View File

@ -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<a.length; i++) {
if (! _equal_Q(a[i], b[i])) { return false }
}
return true
case 'hash-map':
} else if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) { return false }
for (let k of a.keys()) {
if (! _equal_Q(a.get(k), b.get(k))) { return false }
}
return true
default:
} else {
return a === b
}
}
export function _clone(obj, new_meta) {
let new_obj = null
if (_list_Q(obj)) {
new_obj = obj.slice(0)
} else if (_vector_Q(obj)) {
new_obj = _vector(...obj.slice(0))
} else if (_hash_map_Q(obj)) {
new_obj = new Map(obj.entries())
} else if (obj instanceof Function) {
new_obj = obj.clone()
} else {
throw Error('Unsupported type for clone')
}
if (typeof new_meta !== 'undefined') { new_obj.meta = new_meta }
if (_list_Q(obj)) { new_obj = obj.slice(0) }
else if (obj instanceof Vector) { new_obj = Vector.from(obj.slice(0)) }
else if (obj instanceof Map) { new_obj = new Map(obj.entries()) }
else if (obj instanceof Function) { new_obj = obj.clone() }
else { throw Error("Invalid clone") }
if (new_meta !== undefined) { new_obj.meta = new_meta }
return new_obj
}
// Functions
export function _malfunc(f, ast, env, params) {
f.ast = ast
f.env = env
f.params = params
f.meta = null
f.ismacro = false
return f
export function _malfunc(f, ast, env, params, meta=null, ismacro=false) {
return Object.assign(f, {ast, env, params, meta, ismacro})
}
export const _malfunc_Q = f => 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<args.length; i+=2) {
if (typeof args[i] !== 'string') {
throw new Error(`expected hash-map key string, got: ${typeof args[i]}`)
}
hm.set(args[i], args[i+1])
}
for (let i=0; i<args.length; i+=2) { hm.set(args[i], args[i+1]) }
return hm
}
export function _dissoc_BANG(hm, ...args) {