8 import core
.stdc
.stdlib
;
16 bool starts_with(MalType ast
, MalSymbol sym
)
18 auto lst
= cast(MalList
) ast
;
19 if (lst
is null) return false;
20 auto lste
= lst
.elements
;
21 return lste
.length
> 0 && lste
[0] == sym
;
24 MalType
quasiquote(MalType ast
)
26 if (cast(MalSymbol
)ast ||
cast(MalHashmap
)ast
)
27 return new MalList([sym_quote
, ast
]);
29 auto ast_seq
= cast(MalSequential
) ast
;
33 auto aste
= ast_seq
.elements
;
34 if (starts_with(ast
, sym_unquote
))
37 MalType res
= new MalList([]);;
38 foreach_reverse (elt
; ast_seq
.elements
)
39 if (starts_with(elt
, sym_splice_unquote
))
40 res
= new MalList([new MalSymbol("concat"), (cast(MalList
) elt
).elements
[1], res
]);
42 res
= new MalList([new MalSymbol("cons"), quasiquote(elt
), res
]);
43 if (cast(MalVector
) ast
)
44 res
= new MalList([new MalSymbol("vec"), res
]);
48 bool is_macro_call(MalType ast
, Env env
)
50 auto lst
= cast(MalList
) ast
;
51 if (lst
is null) return false;
52 if (lst
.elements
.length
== 0) return false;
53 auto sym0
= cast(MalSymbol
) lst
.elements
[0];
54 if (sym0
is null) return false;
55 if (env
.find(sym0
) is null) return false;
56 auto val
= env
.get(sym0
);
57 auto val_func
= cast(MalFunc
) val
;
58 if (val_func
is null) return false;
59 return val_func
.is_macro
;
62 MalType
macroexpand(MalType ast
, Env env
)
64 while (is_macro_call(ast
, env
))
66 auto ast_list
= verify_cast
!MalList(ast
);
67 auto sym0
= verify_cast
!MalSymbol(ast_list
.elements
[0]);
68 auto macrofunc
= verify_cast
!MalFunc(env
.get(sym0
));
69 auto rest
= ast_list
.elements
[1..$];
70 auto callenv
= new Env(macrofunc
.def_env
, macrofunc
.arg_names
, rest
);
71 ast
= EVAL(macrofunc
.func_body
, callenv
);
76 MalType
READ(string
str)
81 MalType
eval_ast(MalType ast
, Env env
)
83 if (auto sym
= cast(MalSymbol
)ast
)
87 else if (auto lst
= cast(MalList
)ast
)
89 auto el
= array(lst
.elements
.map
!(e
=> EVAL(e
, env
)));
90 return new MalList(el
);
92 else if (auto lst
= cast(MalVector
)ast
)
94 auto el
= array(lst
.elements
.map
!(e
=> EVAL(e
, env
)));
95 return new MalVector(el
);
97 else if (auto hm
= cast(MalHashmap
)ast
)
99 typeof(hm
.data
) new_data
;
100 foreach (string k
, MalType v
; hm
.data
)
102 new_data
[k
] = EVAL(v
, env
);
104 return new MalHashmap(new_data
);
112 MalType
EVAL(MalType ast
, Env env
)
116 MalList ast_list
= cast(MalList
) ast
;
117 if (ast_list
is null)
119 return eval_ast(ast
, env
);
122 ast
= macroexpand(ast
, env
);
123 ast_list
= cast(MalList
) ast
;
124 if (ast_list
is null)
126 return eval_ast(ast
, env
);
129 auto aste
= ast_list
.elements
;
130 if (aste
.length
== 0)
134 auto a0_sym
= cast(MalSymbol
) aste
[0];
135 auto sym_name
= a0_sym
is null ?
"" : a0_sym
.name
;
139 auto a1
= verify_cast
!MalSymbol(aste
[1]);
140 return env
.set(a1
, EVAL(aste
[2], env
));
143 auto a1
= verify_cast
!MalSequential(aste
[1]);
144 auto let_env
= new Env(env
);
145 foreach (kv
; chunks(a1
.elements
, 2))
147 if (kv
.length
< 2) throw new Exception("let* requires even number of elements");
148 auto var_name
= verify_cast
!MalSymbol(kv
[0]);
149 let_env
.set(var_name
, EVAL(kv
[1], let_env
));
158 case "quasiquoteexpand":
159 return quasiquote(aste
[1]);
162 ast
= quasiquote(aste
[1]);
166 auto a1
= verify_cast
!MalSymbol(aste
[1]);
167 auto mac
= verify_cast
!MalFunc(EVAL(aste
[2], env
));
169 return env
.set(a1
, mac
);
172 return macroexpand(aste
[1], env
);
175 if (aste
.length
< 2) return mal_nil
;
184 // d seems to do erroneous tco all by itself without this
185 // little distraction
187 return EVAL(aste
[1], env
);
189 catch (MalException e
)
195 exc
= new MalString(e
.msg
);
197 auto catch_clause
= verify_cast
!MalList(aste
[2]);
198 auto catch_env
= new Env(env
, [catch_clause
.elements
[1]], [exc
]);
199 ast
= catch_clause
.elements
[2];
204 auto all_but_last
= new MalList(aste
[1..$-1]);
205 eval_ast(all_but_last
, env
);
210 auto cond
= EVAL(aste
[1], env
);
211 if (cond
.is_truthy())
228 auto args_list
= verify_cast
!MalSequential(aste
[1]);
229 return new MalFunc(args_list
.elements
, aste
[2], env
);
232 auto el
= verify_cast
!MalList(eval_ast(ast
, env
));
233 if (el
.elements
.length
== 0)
235 throw new Exception("Expected a non-empty list");
237 auto first
= el
.elements
[0];
238 auto rest
= el
.elements
[1..$];
239 if (auto funcobj
= cast(MalFunc
)first
)
241 auto callenv
= new Env(funcobj
.def_env
, funcobj
.arg_names
, rest
);
242 ast
= funcobj
.func_body
;
246 else if (auto builtinfuncobj
= cast(MalBuiltinFunc
)first
)
248 return builtinfuncobj
.fn(rest
);
252 throw new Exception("Expected a function");
258 string
PRINT(MalType ast
)
263 MalType
re(string
str, Env env
)
265 return EVAL(READ(str), env
);
268 string
rep(string
str, Env env
)
270 return PRINT(re(str, env
));
273 static MalList
create_argv_list(string
[] args
)
275 if (args
.length
<= 2) return new MalList([]);
276 return new MalList(array(args
[2..$].map
!(s
=> cast(MalType
)(new MalString(s
)))));
279 void main(string
[] args
)
281 Env repl_env
= new Env(null);
282 foreach (string sym_name
, BuiltinStaticFuncType f
; core_ns
)
284 repl_env
.set(new MalSymbol(sym_name
), new MalBuiltinFunc(f
, sym_name
));
287 BuiltinFuncType eval_func
= (a
...) {
288 verify_args_count(a
, 1);
289 return EVAL(a
[0], repl_env
);
291 repl_env
.set(new MalSymbol("eval"), new MalBuiltinFunc(eval_func
, "eval"));
292 repl_env
.set(new MalSymbol("*ARGV*"), create_argv_list(args
));
294 // core.mal: defined using the language itself
295 re("(def! not (fn* (a) (if a false true)))", repl_env
);
296 re("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\")))))", repl_env
);
297 re("(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)))))))", repl_env
);
303 rep("(load-file \"" ~ args
[1] ~ "\")", repl_env
);
308 writeln("Error: ", e
.msg
);
315 string line
= _readline("user> ");
316 if (line
is null) break;
317 if (line
.length
== 0) continue;
320 writeln(rep(line
, repl_env
));
322 catch (MalException e
)
324 writeln("Error: ", pr_str(e
.data
));
328 writeln("Error: ", e
.msg
);