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:
parent
577e643bfa
commit
a05c086f05
@ -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
|
||||
|
35
es6/Makefile
35
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:
|
||||
|
90
es6/core.js
90
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,78 +14,45 @@ 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],
|
||||
['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)],
|
||||
['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('')],
|
||||
@ -110,33 +74,33 @@ export const core_ns = new Map([
|
||||
|
||||
['list', (...a) => a],
|
||||
['list?', _list_Q],
|
||||
['vector', _vector],
|
||||
['vector?', _vector_Q],
|
||||
['hash-map', _hash_map],
|
||||
['map?', _hash_map_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', keys],
|
||||
['vals', vals],
|
||||
['keys', a => Array.from(a.keys())],
|
||||
['vals', a => Array.from(a.values())],
|
||||
|
||||
['sequential?', _sequential_Q],
|
||||
['sequential?', a => Array.isArray(a)],
|
||||
['cons', (a,b) => [a].concat(b)],
|
||||
['concat', (...a) => a.reduce((x,y) => x.concat(y), [])],
|
||||
['nth', nth],
|
||||
['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 ? [] : a.slice(1)],
|
||||
['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) => a.map(x => f(x))],
|
||||
['map', (f,a) => Array.from(a.map(x => f(x)))],
|
||||
|
||||
['conj', conj],
|
||||
['seq', seq],
|
||||
|
||||
['meta', a => 'meta' in a ? a['meta'] : null],
|
||||
['with-meta', with_meta],
|
||||
['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],
|
||||
|
@ -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 {
|
||||
if (sym in env) { return env[sym] }
|
||||
throw Error(`'${Symbol.keyFor(sym)}' not found`)
|
||||
}
|
||||
}
|
||||
export const env_set = (env, sym, val) => env[sym] = val
|
||||
|
@ -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": "*"
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
@ -21,11 +19,11 @@ export function pr_str(obj, print_readably) {
|
||||
} else if (_r) {
|
||||
return '"' + obj.replace(/\\/g, "\\\\")
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, "\\n") + '"' // string
|
||||
.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"
|
||||
|
110
es6/reader.js
110
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))
|
||||
}
|
||||
|
||||
|
@ -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)) }
|
||||
}
|
||||
|
@ -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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +28,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +29,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +30,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +52,7 @@ 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)
|
||||
@ -67,7 +61,7 @@ const EVAL = (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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +30,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +52,7 @@ 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)
|
||||
@ -67,7 +61,7 @@ const EVAL = (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))
|
||||
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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +48,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +75,7 @@ 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)
|
||||
@ -86,7 +84,7 @@ const EVAL = (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))
|
||||
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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +61,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +94,7 @@ 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)
|
||||
@ -111,7 +103,7 @@ const EVAL = (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)))')
|
||||
@ -138,7 +130,7 @@ REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if
|
||||
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))
|
||||
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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +61,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +105,7 @@ 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)
|
||||
@ -122,7 +114,7 @@ const EVAL = (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)))')
|
||||
@ -149,7 +141,7 @@ REP('(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list \'if (first xs) (if
|
||||
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))
|
||||
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}`) }
|
||||
}
|
||||
}
|
||||
|
@ -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,8 +61,7 @@ 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) {
|
||||
switch (typeof a0 === 'symbol' ? Symbol.keyFor(a0) : Symbol(':default')) {
|
||||
case 'def!':
|
||||
return env_set(env, a1, EVAL(a2, env))
|
||||
case 'let*':
|
||||
@ -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,7 +105,7 @@ 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)
|
||||
@ -122,7 +114,7 @@ const EVAL = (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")')
|
||||
@ -152,7 +144,7 @@ REP('(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [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))
|
||||
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}`) }
|
||||
}
|
||||
}
|
||||
|
97
es6/types.js
97
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<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) {
|
||||
|
Loading…
Reference in New Issue
Block a user