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

ES6: add stepA basics.

This commit is contained in:
Joel Martin 2015-07-31 23:08:33 -05:00
parent 1db28cde9e
commit e5c4e656ba
5 changed files with 222 additions and 5 deletions

View File

@ -1,7 +1,7 @@
SOURCES = step0_repl.js step1_read_print.js step2_eval.js step3_env.js \
step4_if_fn_do.js step5_tco.js step6_file.js step7_quote.js \
step8_macros.js step9_try.js
step8_macros.js step9_try.js stepA_mal.js
all: $(foreach s,$(SOURCES),build/$(s))
@ -18,6 +18,7 @@ build/step6_file.js: step6_file.js build/node_readline.js build/types.js build/r
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
clean:
rm -f build/*

View File

@ -1,5 +1,6 @@
import { _equal_Q, _list_Q, Sym } from './types';
import { _equal_Q, _list_Q, _clone, Sym, Atom } from './types';
import { pr_str } from './printer';
import { readline } from './node_readline';
import { read_str } from './reader';
// Errors/Exceptions
@ -45,6 +46,21 @@ function nth(lst, idx) {
else { throw new Error("nth: index out of range"); }
}
function conj(lst, ...args) {
return b.slice(1).reverse().concat(lst);
}
// Metadata functions
function meta(obj) {
return 'meta' in obj ? obj['meta'] : null;
}
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],
@ -59,7 +75,8 @@ export const core_ns = new Map([
['str', str],
['prn', prn],
['println', println],
['read-string',read_str],
['read-string', read_str],
['readline', readline],
['slurp', slurp],
['<' , (a,b) => a<b],
@ -83,5 +100,15 @@ export const core_ns = new Map([
['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) => a.map(x => f(x))],
['conj', conj],
['meta', meta],
['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))]
]);

View File

@ -1,4 +1,4 @@
import { Sym } from './types';
import { Sym, Atom } from './types';
export function pr_str(obj, print_readably) {
if (typeof print_readably === 'undefined') { print_readably = true; }
@ -18,6 +18,8 @@ export function pr_str(obj, print_readably) {
}
} else if (obj === null) {
return "nil";
} else if (obj instanceof Atom) {
return "(atom " + obj.val + ")";
} else {
return obj.toString();
}

158
es6/stepA_mal.js Normal file
View File

@ -0,0 +1,158 @@
import { readline } from './node_readline';
import { Sym, _list_Q, _malfunc, _malfunc_Q, _sequential_Q } from './types';
import { BlankException, read_str } from './reader';
import { pr_str } from './printer';
import { new_env, env_set, env_get } from './env';
import { core_ns } from './core';
// read
const READ = (str) => read_str(str);
// eval
const is_pair = x => _sequential_Q(x) && x.length > 0
const quasiquote = ast => {
if (!is_pair(ast)) {
return [new Sym("quote"), ast];
} else if (ast[0].name === 'unquote') {
return ast[1];
} else if (is_pair(ast[0]) && ast[0][0].name === 'splice-unquote') {
return [new Sym("concat"), ast[0][1], quasiquote(ast.slice(1))];
} else {
return [new Sym("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
}
}
function is_macro_call(ast, env) {
return _list_Q(ast) &&
ast[0] instanceof Sym &&
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));
}
return ast;
}
const eval_ast = (ast, env) => {
if (ast instanceof Sym) {
return env_get(env, ast)
} else if (_list_Q(ast)) {
return ast.map((x) => EVAL(x, env));
} else {
return ast;
}
}
const EVAL = (ast, env) => {
while (true) {
//console.log("EVAL:", pr_str(ast, true));
if (!_list_Q(ast)) { return eval_ast(ast, env) }
ast = macroexpand(ast, env);
if (!_list_Q(ast)) { return ast; }
let [{ name: a0sym }, a1, a2, a3] = ast;
switch (a0sym) {
case 'def!':
return env_set(env, a1, EVAL(a2, env));
case 'let*':
let let_env = new_env(env);
for (let i=0; i < a1.length; i+=2) {
env_set(let_env, a1[i], EVAL(a1[i+1], let_env));
}
env = let_env;
ast = a2;
break; // continue TCO loop
case "quote":
return a1;
case "quasiquote":
ast = quasiquote(a1);
break; // continue TCO loop
case "defmacro!":
let func = EVAL(a2, env);
func.ismacro = true;
return env_set(env, a1, func);
case "macroexpand":
return macroexpand(a1, env);
case "try*":
try {
return EVAL(a1, env);
} catch (exc) {
if (a2 && a2[0].name === "catch*") {
if (exc instanceof Error) { exc = exc.message; }
return EVAL(a2[2], new_env(env, [a2[1]], [exc]));
} else {
throw exc;
}
}
case "do":
eval_ast(ast.slice(1,ast.length-1), env);
ast = ast[ast.length-1];
break; // continue TCO loop
case "if":
let cond = EVAL(a1, env);
if (cond === null || cond === false) {
ast = (typeof a3 !== "undefined") ? a3 : null;
} else {
ast = a2;
}
break; // continue TCO loop
case "fn*":
return _malfunc((...args) => EVAL(a2, new_env(env, a1, args)),
a2, env, a1);
default:
let [f, ...args] = eval_ast(ast, env);
if (_malfunc_Q(f)) {
env = new_env(f.env, f.params, args);
ast = f.ast;
break; // continue TCO loop
} else {
return f(...args);
}
}
}
}
// print
const PRINT = (exp) => pr_str(exp, true);
// repl
let repl_env = new_env();
const REP = (str) => PRINT(EVAL(READ(str), repl_env));
// core.EXT: defined using ES6
for (let [k, v] of core_ns) { env_set(repl_env, new Sym(k), v) }
env_set(repl_env, new Sym('eval'), a => EVAL(a, repl_env));
env_set(repl_env, new Sym('*ARGV*'), []);
// core.mal: defined using language itself
REP("(def! *host-language* \"ecmascript6\")")
REP("(def! not (fn* (a) (if a false true)))")
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, '*ARGV*', process.argv.slice(3));
REPL('(load-file "' + process.argv[2] + '")');
process.exit(0);
}
REP("(println (str \"Mal [\" *host-language* \"]\"))")
while (true) {
let line = readline("user> ");
if (line == null) break;
try {
if (line) { console.log(REP(line)); }
} catch (exc) {
if (exc instanceof BlankException) { continue; }
if (exc.stack) { console.log(exc.stack); }
else { console.log("Error: " + exc); }
}
}

View File

@ -19,6 +19,18 @@ 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);
@ -53,11 +65,23 @@ export function _malfunc(f, ast, env, params) {
f.ast = ast;
f.env = env;
f.params = params;
f.gen_env = args => new Env(env, params, args);
f.meta = null;
f.ismacro = false;
return f;
}
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 ) {
if (this.hasOwnProperty(key)) {
temp[key] = this[key];
}
}
return temp;
};
// Symbols
export class Sym {
@ -70,3 +94,8 @@ export const _symbol_Q = obj => obj instanceof Sym;
// Lists
export const _list_Q = obj => Array.isArray(obj) && !obj.__isvector__;
// Atoms
export class Atom {
constructor(val) { this.val = val; }
}