All: remove slurp-do, use str around slurp instead.
[jackhill/mal.git] / js / step8_macros.js
CommitLineData
31690700
JM
1var types = require('./types');
2var reader = require('./reader');
3if (typeof module !== 'undefined') {
4 var readline = require('./node_readline');
5}
6
7// read
8function READ(str) {
9 return reader.read_str(str);
10}
11
12// eval
13function is_pair(x) {
14 return types.sequential_Q(x) && x.length > 0;
15}
16
17function quasiquote(ast) {
18 if (!is_pair(ast)) {
19 return [types.symbol("quote"), ast];
20 } else if (ast[0].value === 'unquote') {
21 return ast[1];
22 } else if (is_pair(ast[0]) && ast[0][0].value === 'splice-unquote') {
23 return [types.symbol("concat"), ast[0][1], quasiquote(ast.slice(1))];
24 } else {
25 return [types.symbol("cons"), quasiquote(ast[0]), quasiquote(ast.slice(1))];
26 }
27}
28
29function is_macro_call(ast, env) {
30 return types.list_Q(ast) &&
31 types.symbol_Q(ast[0]) &&
32 env.find(ast[0].value) &&
33 env.get(ast[0].value)._ismacro_;
34}
35
36function macroexpand(ast, env) {
37 while (is_macro_call(ast, env)) {
38 var mac = env.get(ast[0]);
39 ast = mac.apply(mac, ast.slice(1));
40 }
41 return ast;
42}
43
44function eval_ast(ast, env) {
45 if (types.symbol_Q(ast)) {
46 return env.get(ast);
47 } else if (types.list_Q(ast)) {
48 return ast.map(function(a) { return EVAL(a, env); });
49 } else if (types.vector_Q(ast)) {
50 var v = ast.map(function(a) { return EVAL(a, env); });
51 v.__isvector__ = true;
52 return v;
53 } else if (types.hash_map_Q(ast)) {
54 var new_hm = {};
55 for (k in ast) {
56 new_hm[EVAL(k, env)] = EVAL(ast[k], env);
57 }
58 return new_hm;
59 } else {
60 return ast;
61 }
62}
63
64function _EVAL(ast, env) {
65 while (true) {
66 //console.log("EVAL:", types._pr_str(ast, true));
67 if (!types.list_Q(ast)) {
68 return eval_ast(ast, env);
69 }
70
71 // apply list
72 ast = macroexpand(ast, env);
73 if (!types.list_Q(ast)) { return ast; }
74
75 var a0 = ast[0], a1 = ast[1], a2 = ast[2], a3 = ast[3];
76 switch (a0.value) {
77 case "def!":
78 var res = EVAL(a2, env);
79 return env.set(a1, res);
80 case "let*":
81 var let_env = new types.Env(env);
82 for (var i=0; i < a1.length; i+=2) {
83 let_env.set(a1[i].value, EVAL(a1[i+1], let_env));
84 }
85 return EVAL(a2, let_env);
86 case "quote":
87 return a1;
88 case "quasiquote":
89 return EVAL(quasiquote(a1), env);
90 case 'defmacro!':
91 var func = EVAL(a2, env);
92 func._ismacro_ = true;
93 return env.set(a1, func);
94 case 'macroexpand':
95 return macroexpand(a1, env);
96 case "do":
97 eval_ast(ast.slice(1, -1), env);
98 ast = ast[ast.length-1];
99 break;
100 case "if":
101 var cond = EVAL(a1, env);
102 if (cond === null || cond === false) {
103 ast = (typeof a3 !== "undefined") ? a3 : null;
104 } else {
105 ast = a2;
106 }
107 break;
108 case "fn*":
109 return types.new_function(EVAL, a2, env, a1);
110 default:
111 var el = eval_ast(ast, env), f = el[0], meta = f.__meta__;
112 if (meta && meta.exp) {
113 ast = meta.exp;
114 env = new types.Env(meta.env, meta.params, el.slice(1));
115 } else {
116 return f.apply(f, el.slice(1));
117 }
118 }
119 }
120}
121
122function EVAL(ast, env) {
123 var result = _EVAL(ast, env);
124 return (typeof result !== "undefined") ? result : null;
125}
126
127// print
128function PRINT(exp) {
129 return types._pr_str(exp, true);
130}
131
132// repl
133var repl_env = new types.Env();
134var rep = function(str) { return PRINT(EVAL(READ(str), repl_env)); };
135_ref = function (k,v) { repl_env.set(k, v); }
136
137// Import types functions
138for (var n in types.ns) { repl_env.set(n, types.ns[n]); }
139
140_ref('read-string', reader.read_str);
141_ref('eval', function(ast) { return EVAL(ast, repl_env); });
142_ref('slurp', function(f) {
143 return require('fs').readFileSync(f, 'utf-8');
144});
31690700
JM
145
146// Defined using the language itself
147rep("(def! not (fn* (a) (if a false true)))");
1617910a 148rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
31690700
JM
149
150if (typeof process !== 'undefined' && process.argv.length > 2) {
151 for (var i=2; i < process.argv.length; i++) {
152 rep('(load-file "' + process.argv[i] + '")');
153 }
154} else if (typeof require === 'undefined') {
155 // Asynchronous browser mode
156 readline.rlwrap(function(line) { return rep(line); },
157 function(exc) {
158 if (exc instanceof reader.BlankException) { return; }
159 if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
160 });
161} else if (require.main === module) {
162 // Synchronous node.js commandline mode
163 while (true) {
164 var line = readline.readline("user> ");
165 if (line === null) { break; }
166 try {
167 if (line) { console.log(rep(line)); }
168 } catch (exc) {
169 if (exc instanceof reader.BlankException) { continue; }
170 if (exc.stack) { console.log(exc.stack); } else { console.log(exc); }
171 }
172 }
173} else {
174 exports.rep = rep;
175}