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:
parent
1db28cde9e
commit
e5c4e656ba
@ -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/*
|
||||
|
33
es6/core.js
33
es6/core.js
@ -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))]
|
||||
]);
|
||||
|
@ -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
158
es6/stepA_mal.js
Normal 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); }
|
||||
}
|
||||
}
|
29
es6/types.js
29
es6/types.js
@ -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; }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user