Merge pull request #256 from vvakame/impl-ts
[jackhill/mal.git] / cpp / stepA_mal.cpp
CommitLineData
cb252845
ST
1#include "MAL.h"
2
3#include "Environment.h"
4#include "ReadLine.h"
5#include "Types.h"
6
7#include <iostream>
8#include <memory>
9
10malValuePtr READ(const String& input);
11String PRINT(malValuePtr ast);
12static void installFunctions(malEnvPtr env);
13
14static void makeArgv(malEnvPtr env, int argc, char* argv[]);
2f8f48e1 15static String safeRep(const String& input, malEnvPtr env);
cb252845
ST
16static malValuePtr quasiquote(malValuePtr obj);
17static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env);
18static void installMacros(malEnvPtr env);
19
20static ReadLine s_readLine("~/.mal-history");
21
494c1608
ST
22static malEnvPtr replEnv(new malEnv);
23
cb252845
ST
24int main(int argc, char* argv[])
25{
26 String prompt = "user> ";
27 String input;
cb252845
ST
28 installCore(replEnv);
29 installFunctions(replEnv);
30 installMacros(replEnv);
31 makeArgv(replEnv, argc - 2, argv + 2);
32 if (argc > 1) {
33 String filename = escape(argv[1]);
34 safeRep(STRF("(load-file %s)", filename.c_str()), replEnv);
35 return 0;
36 }
b6dc3e37 37 rep("(println (str \"Mal [\" *host-language* \"]\"))", replEnv);
cb252845 38 while (s_readLine.get(prompt, input)) {
2f8f48e1
DM
39 String out = safeRep(input, replEnv);
40 if (out.length() > 0)
41 std::cout << out << "\n";
cb252845
ST
42 }
43 return 0;
44}
45
2f8f48e1 46static String safeRep(const String& input, malEnvPtr env)
cb252845 47{
cb252845 48 try {
2f8f48e1 49 return rep(input, env);
cb252845
ST
50 }
51 catch (malEmptyInputException&) {
2f8f48e1 52 return String();
cb252845
ST
53 }
54 catch (String& s) {
2f8f48e1 55 return s;
cb252845 56 };
cb252845
ST
57}
58
59static void makeArgv(malEnvPtr env, int argc, char* argv[])
60{
61 malValueVec* args = new malValueVec();
62 for (int i = 0; i < argc; i++) {
63 args->push_back(mal::string(argv[i]));
64 }
65 env->set("*ARGV*", mal::list(args));
66}
67
68String rep(const String& input, malEnvPtr env)
69{
70 return PRINT(EVAL(READ(input), env));
71}
72
73malValuePtr READ(const String& input)
74{
75 return readStr(input);
76}
77
78malValuePtr EVAL(malValuePtr ast, malEnvPtr env)
79{
494c1608
ST
80 if (!env) {
81 env = replEnv;
82 }
cb252845
ST
83 while (1) {
84 const malList* list = DYNAMIC_CAST(malList, ast);
85 if (!list || (list->count() == 0)) {
86 return ast->eval(env);
87 }
88
89 ast = macroExpand(ast, env);
90 list = DYNAMIC_CAST(malList, ast);
91 if (!list || (list->count() == 0)) {
92 return ast->eval(env);
93 }
94
95 // From here on down we are evaluating a non-empty list.
96 // First handle the special forms.
97 if (const malSymbol* symbol = DYNAMIC_CAST(malSymbol, list->item(0))) {
98 String special = symbol->value();
99 int argCount = list->count() - 1;
100
101 if (special == "def!") {
102 checkArgsIs("def!", 2, argCount);
103 const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
104 return env->set(id->value(), EVAL(list->item(2), env));
105 }
106
107 if (special == "defmacro!") {
108 checkArgsIs("defmacro!", 2, argCount);
109
110 const malSymbol* id = VALUE_CAST(malSymbol, list->item(1));
111 malValuePtr body = EVAL(list->item(2), env);
112 const malLambda* lambda = VALUE_CAST(malLambda, body);
113 return env->set(id->value(), mal::macro(*lambda));
114 }
115
116 if (special == "do") {
117 checkArgsAtLeast("do", 1, argCount);
118
119 for (int i = 1; i < argCount; i++) {
120 EVAL(list->item(i), env);
121 }
122 ast = list->item(argCount);
123 continue; // TCO
124 }
125
126 if (special == "fn*") {
127 checkArgsIs("fn*", 2, argCount);
128
129 const malSequence* bindings =
130 VALUE_CAST(malSequence, list->item(1));
131 StringVec params;
132 for (int i = 0; i < bindings->count(); i++) {
133 const malSymbol* sym =
134 VALUE_CAST(malSymbol, bindings->item(i));
135 params.push_back(sym->value());
136 }
137
138 return mal::lambda(params, list->item(2), env);
139 }
140
141 if (special == "if") {
142 checkArgsBetween("if", 2, 3, argCount);
143
144 bool isTrue = EVAL(list->item(1), env)->isTrue();
145 if (!isTrue && (argCount == 2)) {
146 return mal::nilValue();
147 }
148 ast = list->item(isTrue ? 2 : 3);
149 continue; // TCO
150 }
151
152 if (special == "let*") {
153 checkArgsIs("let*", 2, argCount);
154 const malSequence* bindings =
155 VALUE_CAST(malSequence, list->item(1));
156 int count = checkArgsEven("let*", bindings->count());
157 malEnvPtr inner(new malEnv(env));
158 for (int i = 0; i < count; i += 2) {
159 const malSymbol* var =
160 VALUE_CAST(malSymbol, bindings->item(i));
161 inner->set(var->value(), EVAL(bindings->item(i+1), inner));
162 }
163 ast = list->item(2);
164 env = inner;
165 continue; // TCO
166 }
167
168 if (special == "macroexpand") {
169 checkArgsIs("macroexpand", 1, argCount);
170 return macroExpand(list->item(1), env);
171 }
172
173 if (special == "quasiquote") {
174 checkArgsIs("quasiquote", 1, argCount);
175 ast = quasiquote(list->item(1));
176 continue; // TCO
177 }
178
179 if (special == "quote") {
180 checkArgsIs("quote", 1, argCount);
181 return list->item(1);
182 }
183
184 if (special == "try*") {
185 checkArgsIs("try*", 2, argCount);
186 malValuePtr tryBody = list->item(1);
187 const malList* catchBlock = VALUE_CAST(malList, list->item(2));
188
189 checkArgsIs("catch*", 2, catchBlock->count() - 1);
0997015d 190 MAL_CHECK(VALUE_CAST(malSymbol,
cb252845
ST
191 catchBlock->item(0))->value() == "catch*",
192 "catch block must begin with catch*");
193
194 // We don't need excSym at this scope, but we want to check
195 // that the catch block is valid always, not just in case of
196 // an exception.
197 const malSymbol* excSym =
198 VALUE_CAST(malSymbol, catchBlock->item(1));
199
200 malValuePtr excVal;
201
202 try {
203 ast = EVAL(tryBody, env);
204 }
205 catch(String& s) {
206 excVal = mal::string(s);
207 }
208 catch (malEmptyInputException&) {
209 // Not an error, continue as if we got nil
210 ast = mal::nilValue();
211 }
212 catch(malValuePtr& o) {
213 excVal = o;
214 };
215
216 if (excVal) {
217 // we got some exception
218 env = malEnvPtr(new malEnv(env));
219 env->set(excSym->value(), excVal);
220 ast = catchBlock->item(2);
221 }
222 continue; // TCO
223 }
224 }
225
226 // Now we're left with the case of a regular list to be evaluated.
227 std::unique_ptr<malValueVec> items(list->evalItems(env));
228 malValuePtr op = items->at(0);
229 if (const malLambda* lambda = DYNAMIC_CAST(malLambda, op)) {
230 ast = lambda->getBody();
231 env = lambda->makeEnv(items->begin()+1, items->end());
232 continue; // TCO
233 }
234 else {
494c1608 235 return APPLY(op, items->begin()+1, items->end());
cb252845
ST
236 }
237 }
238}
239
240String PRINT(malValuePtr ast)
241{
242 return ast->print(true);
243}
244
494c1608 245malValuePtr APPLY(malValuePtr op, malValueIter argsBegin, malValueIter argsEnd)
cb252845
ST
246{
247 const malApplicable* handler = DYNAMIC_CAST(malApplicable, op);
0997015d
ST
248 MAL_CHECK(handler != NULL,
249 "\"%s\" is not applicable", op->print(true).c_str());
cb252845 250
494c1608 251 return handler->apply(argsBegin, argsEnd);
cb252845
ST
252}
253
254static bool isSymbol(malValuePtr obj, const String& text)
255{
256 const malSymbol* sym = DYNAMIC_CAST(malSymbol, obj);
257 return sym && (sym->value() == text);
258}
259
260static const malSequence* isPair(malValuePtr obj)
261{
262 const malSequence* list = DYNAMIC_CAST(malSequence, obj);
263 return list && !list->isEmpty() ? list : NULL;
264}
265
266static malValuePtr quasiquote(malValuePtr obj)
267{
268 const malSequence* seq = isPair(obj);
269 if (!seq) {
270 return mal::list(mal::symbol("quote"), obj);
271 }
272
273 if (isSymbol(seq->item(0), "unquote")) {
274 // (qq (uq form)) -> form
275 checkArgsIs("unquote", 1, seq->count() - 1);
276 return seq->item(1);
277 }
278
279 const malSequence* innerSeq = isPair(seq->item(0));
280 if (innerSeq && isSymbol(innerSeq->item(0), "splice-unquote")) {
281 checkArgsIs("splice-unquote", 1, innerSeq->count() - 1);
282 // (qq (sq '(a b c))) -> a b c
283 return mal::list(
284 mal::symbol("concat"),
285 innerSeq->item(1),
286 quasiquote(seq->rest())
287 );
288 }
289 else {
290 // (qq (a b c)) -> (list (qq a) (qq b) (qq c))
291 // (qq xs ) -> (cons (qq (car xs)) (qq (cdr xs)))
292 return mal::list(
293 mal::symbol("cons"),
294 quasiquote(seq->first()),
295 quasiquote(seq->rest())
296 );
297 }
298}
299
300static const malLambda* isMacroApplication(malValuePtr obj, malEnvPtr env)
301{
302 if (const malSequence* seq = isPair(obj)) {
303 if (malSymbol* sym = DYNAMIC_CAST(malSymbol, seq->first())) {
304 if (malEnvPtr symEnv = env->find(sym->value())) {
305 malValuePtr value = sym->eval(symEnv);
306 if (malLambda* lambda = DYNAMIC_CAST(malLambda, value)) {
307 return lambda->isMacro() ? lambda : NULL;
308 }
309 }
310 }
311 }
312 return NULL;
313}
314
315static malValuePtr macroExpand(malValuePtr obj, malEnvPtr env)
316{
317 while (const malLambda* macro = isMacroApplication(obj, env)) {
318 const malSequence* seq = STATIC_CAST(malSequence, obj);
494c1608 319 obj = macro->apply(seq->begin() + 1, seq->end());
cb252845
ST
320 }
321 return obj;
322}
323
324static const char* macroTable[] = {
325 "(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)))))))",
29ba1fb6 326 "(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)))))))))",
cb252845
ST
327};
328
329static void installMacros(malEnvPtr env)
330{
f01c2683
ST
331 for (auto &macro : macroTable) {
332 rep(macro, env);
cb252845
ST
333 }
334}
335
336malValuePtr readline(const String& prompt)
337{
338 String input;
339 if (s_readLine.get(prompt, input)) {
340 return mal::string(input);
341 }
342 return mal::nilValue();
343}
344
345static const char* malFunctionTable[] = {
346 "(def! list (fn* (& items) items))",
347 "(def! not (fn* (cond) (if cond false true)))",
348 "(def! >= (fn* (a b) (<= b a)))",
349 "(def! < (fn* (a b) (not (<= b a))))",
350 "(def! > (fn* (a b) (not (<= a b))))",
351 "(def! load-file (fn* (filename) \
352 (eval (read-string (str \"(do \" (slurp filename) \")\")))))",
353 "(def! map (fn* (f xs) (if (empty? xs) xs \
354 (cons (f (first xs)) (map f (rest xs))))))",
29ba1fb6
DM
355 "(def! *gensym-counter* (atom 0))",
356 "(def! gensym (fn* [] (symbol (str \"G__\" (swap! *gensym-counter* (fn* [x] (+ 1 x)))))))",
b6dc3e37 357 "(def! *host-language* \"C++\")",
cb252845
ST
358};
359
360static void installFunctions(malEnvPtr env) {
f01c2683
ST
361 for (auto &function : malFunctionTable) {
362 rep(function, env);
cb252845
ST
363 }
364}