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