1
1
mirror of https://github.com/kanaka/mal.git synced 2024-11-10 12:47:45 +03:00

ES6: all optionals and self-hosting.

This commit is contained in:
Joel Martin 2015-08-01 22:29:44 -05:00
parent e5c4e656ba
commit afa79313d8
13 changed files with 241 additions and 58 deletions

View File

@ -1,4 +1,7 @@
import { _equal_Q, _list_Q, _clone, Sym, Atom } from './types';
import { _equal_Q, _clone, _list_Q, _sequential_Q,
_keyword, _keyword_Q, _vector, _vector_Q,
_hash_map, _hash_map_Q, _assoc_BANG, _dissoc_BANG,
Sym, Atom } from './types';
import { pr_str } from './printer';
import { readline } from './node_readline';
import { read_str } from './reader';
@ -47,7 +50,23 @@ function nth(lst, idx) {
}
function conj(lst, ...args) {
return b.slice(1).reverse().concat(lst);
if (_list_Q(lst)) {
return args.reverse().concat(lst);
} else {
return _vector(...lst.concat(args));
}
}
function keys(hm) {
let ks = [];
for (let k of hm.keys()) { ks.push(k) };
return ks;
}
function vals(hm) {
let vs = [];
for (let v of hm.values()) { vs.push(v) };
return vs;
}
// Metadata functions
@ -65,11 +84,14 @@ function with_meta(obj, m) {
export const core_ns = new Map([
['=', _equal_Q],
['throw', mal_throw],
['nil?', a => a === null],
['true?', a => a === true],
['false?', a => a === false],
['symbol', a => new Sym(a)],
['symbol?', a => a instanceof Sym],
['keyword', a => _keyword(a)],
['keyword?', a => _keyword_Q(a)],
['pr-str', do_pr_str],
['str', str],
@ -91,7 +113,18 @@ 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],
['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],
['sequential?', _sequential_Q],
['cons', (a,b) => [a].concat(b)],
['concat', (...a) => a.reduce((x,y) => x.concat(y), [])],
['nth', nth],
@ -110,5 +143,5 @@ export const core_ns = new Map([
['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))]
['swap!', (atm,f,...args) => atm.val = f(...[atm.val].concat(args))]
]);

View File

@ -1,11 +1,20 @@
import { Sym, Atom } from './types';
import { Sym, _list_Q, _vector_Q, _hash_map_Q, Atom } from './types';
export function pr_str(obj, print_readably) {
if (typeof print_readably === 'undefined') { print_readably = true; }
var _r = print_readably;
if (obj instanceof Array) {
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)) {
var ret = [];
for (let [k,v] of obj) {
ret.push(pr_str(k,_r), pr_str(v,_r));
}
return "{" + ret.join(' ') + "}";
} else if (typeof obj === "string") {
if (obj[0] === '\u029e') {
return ':' + obj.slice(1);
@ -19,7 +28,7 @@ export function pr_str(obj, print_readably) {
} else if (obj === null) {
return "nil";
} else if (obj instanceof Atom) {
return "(atom " + obj.val + ")";
return "(atom " + pr_str(obj.val,_r) + ")";
} else {
return obj.toString();
}

View File

@ -1,4 +1,4 @@
import * as types from './types';
import { _symbol, _keyword, _vector, _hash_map } from './types';
export class BlankException extends Error {}
@ -34,7 +34,7 @@ function read_atom (reader) {
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n"); // string
} else if (token[0] === ":") {
return types._keyword(token.slice(1));
return _keyword(token.slice(1));
} else if (token === "nil") {
return null;
} else if (token === "true") {
@ -42,7 +42,7 @@ function read_atom (reader) {
} else if (token === "false") {
return false;
} else {
return types._symbol(token); // symbol
return _symbol(token); // symbol
}
}
@ -65,29 +65,47 @@ function read_list(reader, start, end) {
return ast;
}
// read vector of tokens
function read_vector(reader) {
return _vector(...read_list(reader, '[', ']'));
}
// read hash-map key/value pairs
function read_hash_map(reader) {
return _hash_map(...read_list(reader, '{', '}'));
}
function read_form(reader) {
var token = reader.peek();
switch (token) {
// reader macros/transforms
case ';': return null; // Ignore comments
case '\'': reader.next();
return [types._symbol('quote'), read_form(reader)];
return [_symbol('quote'), read_form(reader)];
case '`': reader.next();
return [types._symbol('quasiquote'), read_form(reader)];
return [_symbol('quasiquote'), read_form(reader)];
case '~': reader.next();
return [types._symbol('unquote'), read_form(reader)];
return [_symbol('unquote'), read_form(reader)];
case '~@': reader.next();
return [types._symbol('splice-unquote'), read_form(reader)];
return [_symbol('splice-unquote'), read_form(reader)];
case '^': reader.next();
var meta = read_form(reader);
return [types._symbol('with-meta'), read_form(reader), meta];
return [_symbol('with-meta'), read_form(reader), meta];
case '@': reader.next();
return [types._symbol('deref'), read_form(reader)];
return [_symbol('deref'), read_form(reader)];
// list
case ')': throw new Error("unexpected ')'");
case '(': return read_list(reader);
// vector
case ']': throw new Error("unexpected ']'");
case '[': return read_vector(reader);
// hash-map
case '}': throw new Error("unexpected '}'");
case '{': return read_hash_map(reader);
// atom
default: return read_atom(reader);
}

View File

@ -1,5 +1,5 @@
import { readline } from './node_readline';
import { Sym, _list_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q } from './types';
import { BlankException, read_str } from './reader';
import { pr_str } from './printer';
@ -16,6 +16,14 @@ const eval_ast = (ast, env) => {
}
} 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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}

View File

@ -1,5 +1,5 @@
import { readline } from './node_readline';
import { Sym, _list_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q } from './types';
import { BlankException, read_str } from './reader';
import { pr_str } from './printer';
import { new_env, env_set, env_get } from './env';
@ -13,6 +13,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}

View File

@ -1,5 +1,5 @@
import { readline } from './node_readline';
import { Sym, _list_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q } from './types';
import { BlankException, read_str } from './reader';
import { pr_str } from './printer';
import { new_env, env_set, env_get } from './env';
@ -14,12 +14,21 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
}
const EVAL = (ast, env) => {
//console.log("EVAL:", pr_str(ast, true));
if (!_list_Q(ast)) { return eval_ast(ast, env) }
let [{ name: a0sym }, a1, a2, a3] = ast;

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_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';
@ -14,6 +15,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_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';
@ -14,6 +15,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
@ -82,7 +91,7 @@ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\"))
if (process.argv.length > 2) {
env_set(repl_env, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
REP('(load-file "' + process.argv[2] + '")');
process.exit(0);
}

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q, _sequential_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';
@ -28,6 +29,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
@ -101,7 +110,7 @@ REP("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\"))
if (process.argv.length > 2) {
env_set(repl_env, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
REP('(load-file "' + process.argv[2] + '")');
process.exit(0);
}

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q, _sequential_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';
@ -44,6 +45,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
@ -128,7 +137,7 @@ REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first x
if (process.argv.length > 2) {
env_set(repl_env, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
REP('(load-file "' + process.argv[2] + '")');
process.exit(0);
}

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q, _sequential_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';
@ -44,6 +45,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
@ -139,7 +148,7 @@ REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first x
if (process.argv.length > 2) {
env_set(repl_env, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
REP('(load-file "' + process.argv[2] + '")');
process.exit(0);
}

View File

@ -1,5 +1,6 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types';
import { Sym, _list_Q, _vector, _vector_Q, _hash_map_Q, _sequential_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';
@ -44,6 +45,14 @@ const eval_ast = (ast, env) => {
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)) {
let new_hm = new Map();
for (let [k, v] of ast) {
new_hm.set(EVAL(k, env), EVAL(v, env));
}
return new_hm;
} else {
return ast;
}
@ -140,7 +149,7 @@ REP("(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first x
if (process.argv.length > 2) {
env_set(repl_env, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
REP('(load-file "' + process.argv[2] + '")');
process.exit(0);
}

View File

@ -1,11 +1,12 @@
// General functions
//function _sequential_Q(lst) { return _list_Q(lst) || _vector_Q(lst); }
export const _sequential_Q = (lst) => _list_Q(lst)
export const _sequential_Q = lst => _list_Q(lst) || _vector_Q(lst)
export function _obj_type(obj) {
if (obj instanceof Sym) { 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'; }
@ -19,21 +20,8 @@ export function _obj_type(obj) {
}
}
export function _clone(obj) {
if (obj instanceof Sym) {
return new Sym(obj.name);
} else if (_list_Q(obj)) {
return obj.slice(0);
} else if (obj instanceof Function) {
return obj.clone();
} else {
throw Error("Unsupported type for clone");
}
}
export function _equal_Q (a, b) {
var ota = _obj_type(a), otb = _obj_type(b);
let ota = _obj_type(a), otb = _obj_type(b);
if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) {
return false;
}
@ -42,24 +30,40 @@ export function _equal_Q (a, b) {
case 'list':
case 'vector':
if (a.length !== b.length) { return false; }
for (var i=0; i<a.length; i++) {
for (let i=0; i<a.length; i++) {
if (! _equal_Q(a[i], b[i])) { return false; }
}
return true;
// case 'hash-map':
// var akeys = Object.keys(a).sort(),
// bkeys = Object.keys(b).sort();
// if (akeys.length !== bkeys.length) { return false; }
// for (var i=0; i<akeys.length; i++) {
// if (akeys[i] !== bkeys[i]) { return false; }
// if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { return false; }
// }
// return true;
case 'hash-map':
let akeys = Object.keys(a).sort(),
bkeys = Object.keys(b).sort();
if (akeys.length !== bkeys.length) { return false; }
for (let i=0; i<akeys.length; i++) {
if (akeys[i] !== bkeys[i]) { return false; }
if (! _equal_Q(a.get(akeys[i]), b.get(bkeys[i]))) { return false; }
}
return true;
default:
return a === b;
}
}
export function _clone(obj) {
if (obj instanceof Sym) {
return new Sym(obj.name);
} else if (_list_Q(obj)) {
return obj.slice(0);
} else if (_vector_Q(obj)) {
return _vector(...obj.slice(0));
} else if (_hash_map_Q(obj)) {
return new Map(obj.entries());
} else if (obj instanceof Function) {
return obj.clone();
} else {
throw Error("Unsupported type for clone");
}
}
// Functions
export function _malfunc(f, ast, env, params) {
f.ast = ast;
@ -72,9 +76,9 @@ export function _malfunc(f, ast, env, params) {
}
export const _malfunc_Q = f => f.ast ? true : false;
Function.prototype.clone = function() {
var that = this;
var temp = function () { return that.apply(this, arguments); };
for( let key in this ) {
let that = this;
let temp = function () { return that.apply(this, arguments); };
for (let key in this ) {
if (this.hasOwnProperty(key)) {
temp[key] = this[key];
}
@ -92,9 +96,49 @@ export class Sym {
export const _symbol = name => new Sym(name);
export const _symbol_Q = obj => obj instanceof Sym;
// 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__;
// Vectors
export function _vector(...args) {
let v = args.slice(0);
v.__isvector__ = true;
return v;
}
export const _vector_Q = obj => Array.isArray(obj) && !!obj.__isvector__
// Hash Maps
export function _hash_map(...args) {
if (args % 2 === 1) {
throw new Error("Odd number of hash map arguments");
}
return _assoc_BANG(new Map(), ...args);
}
export const _hash_map_Q = hm => hm instanceof Map;
export function _assoc_BANG(hm, ...args) {
if (args % 2 === 1) {
throw new Error("Odd number of assoc arguments");
}
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]);
}
return hm;
}
export function _dissoc_BANG(hm, ...args) {
for (let i=0; i<args.length; i++) {
hm.delete(args[i]);
}
return hm;
}
// Atoms
export class Atom {
constructor(val) { this.val = val; }