DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / vhdl / step8_macros.vhdl
1 entity step8_macros is
2 end entity step8_macros;
3
4 library STD;
5 use STD.textio.all;
6 library WORK;
7 use WORK.pkg_readline.all;
8 use WORK.types.all;
9 use WORK.printer.all;
10 use WORK.reader.all;
11 use WORK.env.all;
12 use WORK.core.all;
13
14 architecture test of step8_macros is
15
16 shared variable repl_env: env_ptr;
17
18 procedure mal_READ(str: in string; ast: out mal_val_ptr; err: out mal_val_ptr) is
19 begin
20 read_str(str, ast, err);
21 end procedure mal_READ;
22
23 procedure is_pair(ast: inout mal_val_ptr; pair: out boolean) is
24 begin
25 pair := is_sequential_type(ast.val_type) and ast.seq_val'length > 0;
26 end procedure is_pair;
27
28 procedure quasiquote(ast: inout mal_val_ptr; result: out mal_val_ptr) is
29 variable ast_pair, a0_pair: boolean;
30 variable seq: mal_seq_ptr;
31 variable a0, rest: mal_val_ptr;
32 begin
33 is_pair(ast, ast_pair);
34 if not ast_pair then
35 seq := new mal_seq(0 to 1);
36 new_symbol("quote", seq(0));
37 seq(1) := ast;
38 new_seq_obj(mal_list, seq, result);
39 return;
40 end if;
41 a0 := ast.seq_val(0);
42 if a0.val_type = mal_symbol and a0.string_val.all = "unquote" then
43 result := ast.seq_val(1);
44 else
45 is_pair(a0, a0_pair);
46 if a0_pair and a0.seq_val(0).val_type = mal_symbol and a0.seq_val(0).string_val.all = "splice-unquote" then
47 seq := new mal_seq(0 to 2);
48 new_symbol("concat", seq(0));
49 seq(1) := a0.seq_val(1);
50 seq_drop_prefix(ast, 1, rest);
51 quasiquote(rest, seq(2));
52 new_seq_obj(mal_list, seq, result);
53 else
54 seq := new mal_seq(0 to 2);
55 new_symbol("cons", seq(0));
56 quasiquote(a0, seq(1));
57 seq_drop_prefix(ast, 1, rest);
58 quasiquote(rest, seq(2));
59 new_seq_obj(mal_list, seq, result);
60 end if;
61 end if;
62 end procedure quasiquote;
63
64 -- Forward declaration
65 procedure EVAL(in_ast: inout mal_val_ptr; in_env: inout env_ptr; result: out mal_val_ptr; err: out mal_val_ptr);
66
67 procedure apply_func(fn: inout mal_val_ptr; args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr);
68
69 procedure is_macro_call(ast: inout mal_val_ptr; env: inout env_ptr; is_macro: out boolean) is
70 variable f, env_err: mal_val_ptr;
71 begin
72 is_macro := false;
73 if ast.val_type = mal_list and
74 ast.seq_val'length > 0 and
75 ast.seq_val(0).val_type = mal_symbol then
76 env_get(env, ast.seq_val(0), f, env_err);
77 if env_err = null and f /= null and
78 f.val_type = mal_fn and f.func_val.f_is_macro then
79 is_macro := true;
80 end if;
81 end if;
82 end procedure is_macro_call;
83
84 procedure macroexpand(in_ast: inout mal_val_ptr; env: inout env_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
85 variable ast, macro_fn, call_args, macro_err: mal_val_ptr;
86 variable is_macro: boolean;
87 begin
88 ast := in_ast;
89 is_macro_call(ast, env, is_macro);
90 while is_macro loop
91 env_get(env, ast.seq_val(0), macro_fn, macro_err);
92 seq_drop_prefix(ast, 1, call_args);
93 apply_func(macro_fn, call_args, ast, macro_err);
94 if macro_err /= null then
95 err := macro_err;
96 return;
97 end if;
98 is_macro_call(ast, env, is_macro);
99 end loop;
100 result := ast;
101 end procedure macroexpand;
102
103 procedure fn_eval(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
104 begin
105 EVAL(args.seq_val(0), repl_env, result, err);
106 end procedure fn_eval;
107
108 procedure fn_swap(args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
109 variable atom: mal_val_ptr := args.seq_val(0);
110 variable fn: mal_val_ptr := args.seq_val(1);
111 variable call_args_seq: mal_seq_ptr;
112 variable call_args, eval_res, sub_err: mal_val_ptr;
113 begin
114 call_args_seq := new mal_seq(0 to args.seq_val'length - 2);
115 call_args_seq(0) := atom.seq_val(0);
116 call_args_seq(1 to call_args_seq'length - 1) := args.seq_val(2 to args.seq_val'length - 1);
117 new_seq_obj(mal_list, call_args_seq, call_args);
118 apply_func(fn, call_args, eval_res, sub_err);
119 if sub_err /= null then
120 err := sub_err;
121 return;
122 end if;
123 atom.seq_val(0) := eval_res;
124 result := eval_res;
125 end procedure fn_swap;
126
127 procedure apply_native_func(func_sym: inout mal_val_ptr; args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
128 begin
129 if func_sym.string_val.all = "eval" then
130 fn_eval(args, result, err);
131 elsif func_sym.string_val.all = "swap!" then
132 fn_swap(args, result, err);
133 else
134 eval_native_func(func_sym, args, result, err);
135 end if;
136 end procedure apply_native_func;
137
138 procedure apply_func(fn: inout mal_val_ptr; args: inout mal_val_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
139 variable fn_env: env_ptr;
140 begin
141 case fn.val_type is
142 when mal_nativefn =>
143 apply_native_func(fn, args, result, err);
144 when mal_fn =>
145 new_env(fn_env, fn.func_val.f_env, fn.func_val.f_args, args);
146 EVAL(fn.func_val.f_body, fn_env, result, err);
147 when others =>
148 new_string("not a function", err);
149 return;
150 end case;
151 end procedure apply_func;
152
153 procedure eval_ast_seq(ast_seq: inout mal_seq_ptr; env: inout env_ptr; result: inout mal_seq_ptr; err: out mal_val_ptr) is
154 variable eval_err: mal_val_ptr;
155 begin
156 result := new mal_seq(0 to ast_seq'length - 1);
157 for i in result'range loop
158 EVAL(ast_seq(i), env, result(i), eval_err);
159 if eval_err /= null then
160 err := eval_err;
161 return;
162 end if;
163 end loop;
164 end procedure eval_ast_seq;
165
166 procedure eval_ast(ast: inout mal_val_ptr; env: inout env_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
167 variable key, val, eval_err, env_err: mal_val_ptr;
168 variable new_seq: mal_seq_ptr;
169 variable i: integer;
170 begin
171 case ast.val_type is
172 when mal_symbol =>
173 env_get(env, ast, val, env_err);
174 if env_err /= null then
175 err := env_err;
176 return;
177 end if;
178 result := val;
179 return;
180 when mal_list | mal_vector | mal_hashmap =>
181 eval_ast_seq(ast.seq_val, env, new_seq, eval_err);
182 if eval_err /= null then
183 err := eval_err;
184 return;
185 end if;
186 new_seq_obj(ast.val_type, new_seq, result);
187 return;
188 when others =>
189 result := ast;
190 return;
191 end case;
192 end procedure eval_ast;
193
194 procedure EVAL(in_ast: inout mal_val_ptr; in_env: inout env_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
195 variable i: integer;
196 variable ast, evaled_ast, a0, call_args, val, vars, sub_err, fn: mal_val_ptr;
197 variable env, let_env, fn_env: env_ptr;
198 begin
199 ast := in_ast;
200 env := in_env;
201 loop
202 if ast.val_type /= mal_list then
203 eval_ast(ast, env, result, err);
204 return;
205 end if;
206
207 macroexpand(ast, env, ast, sub_err);
208 if sub_err /= null then
209 err := sub_err;
210 return;
211 end if;
212 if ast.val_type /= mal_list then
213 eval_ast(ast, env, result, err);
214 return;
215 end if;
216 if ast.seq_val'length = 0 then
217 result := ast;
218 return;
219 end if;
220
221 a0 := ast.seq_val(0);
222 if a0.val_type = mal_symbol then
223 if a0.string_val.all = "def!" then
224 EVAL(ast.seq_val(2), env, val, sub_err);
225 if sub_err /= null then
226 err := sub_err;
227 return;
228 end if;
229 env_set(env, ast.seq_val(1), val);
230 result := val;
231 return;
232
233 elsif a0.string_val.all = "let*" then
234 vars := ast.seq_val(1);
235 new_env(let_env, env);
236 i := 0;
237 while i < vars.seq_val'length loop
238 EVAL(vars.seq_val(i + 1), let_env, val, sub_err);
239 if sub_err /= null then
240 err := sub_err;
241 return;
242 end if;
243 env_set(let_env, vars.seq_val(i), val);
244 i := i + 2;
245 end loop;
246 env := let_env;
247 ast := ast.seq_val(2);
248 next; -- TCO
249
250 elsif a0.string_val.all = "quote" then
251 result := ast.seq_val(1);
252 return;
253
254 elsif a0.string_val.all = "quasiquote" then
255 quasiquote(ast.seq_val(1), ast);
256 next; -- TCO
257
258 elsif a0.string_val.all = "defmacro!" then
259 EVAL(ast.seq_val(2), env, val, sub_err);
260 if sub_err /= null then
261 err := sub_err;
262 return;
263 end if;
264 val.func_val.f_is_macro := true;
265 env_set(env, ast.seq_val(1), val);
266 result := val;
267 return;
268
269 elsif a0.string_val.all = "macroexpand" then
270 macroexpand(ast.seq_val(1), env, result, err);
271 return;
272
273 elsif a0.string_val.all = "do" then
274 for i in 1 to ast.seq_val'high - 1 loop
275 EVAL(ast.seq_val(i), env, result, sub_err);
276 if sub_err /= null then
277 err := sub_err;
278 return;
279 end if;
280 end loop;
281 ast := ast.seq_val(ast.seq_val'high);
282 next; -- TCO
283
284 elsif a0.string_val.all = "if" then
285 EVAL(ast.seq_val(1), env, val, sub_err);
286 if sub_err /= null then
287 err := sub_err;
288 return;
289 end if;
290 if val.val_type = mal_nil or val.val_type = mal_false then
291 if ast.seq_val'length > 3 then
292 ast := ast.seq_val(3);
293 else
294 new_nil(result);
295 return;
296 end if;
297 else
298 ast := ast.seq_val(2);
299 end if;
300 next; -- TCO
301
302 elsif a0.string_val.all = "fn*" then
303 new_fn(ast.seq_val(2), ast.seq_val(1), env, result);
304 return;
305
306 end if;
307 end if;
308
309 eval_ast(ast, env, evaled_ast, sub_err);
310 if sub_err /= null then
311 err := sub_err;
312 return;
313 end if;
314 seq_drop_prefix(evaled_ast, 1, call_args);
315 fn := evaled_ast.seq_val(0);
316 case fn.val_type is
317 when mal_nativefn =>
318 apply_native_func(fn, call_args, result, err);
319 return;
320 when mal_fn =>
321 new_env(fn_env, fn.func_val.f_env, fn.func_val.f_args, call_args);
322 env := fn_env;
323 ast := fn.func_val.f_body;
324 next; -- TCO
325 when others =>
326 new_string("not a function", err);
327 return;
328 end case;
329 end loop;
330 end procedure EVAL;
331
332 procedure mal_PRINT(exp: inout mal_val_ptr; result: out line) is
333 begin
334 pr_str(exp, true, result);
335 end procedure mal_PRINT;
336
337 procedure RE(str: in string; env: inout env_ptr; result: out mal_val_ptr; err: out mal_val_ptr) is
338 variable ast, read_err: mal_val_ptr;
339 begin
340 mal_READ(str, ast, read_err);
341 if read_err /= null then
342 err := read_err;
343 result := null;
344 return;
345 end if;
346 if ast = null then
347 result := null;
348 return;
349 end if;
350 EVAL(ast, env, result, err);
351 end procedure RE;
352
353 procedure REP(str: in string; env: inout env_ptr; result: out line; err: out mal_val_ptr) is
354 variable eval_res, eval_err: mal_val_ptr;
355 begin
356 RE(str, env, eval_res, eval_err);
357 if eval_err /= null then
358 err := eval_err;
359 result := null;
360 return;
361 end if;
362 mal_PRINT(eval_res, result);
363 end procedure REP;
364
365 procedure set_argv(e: inout env_ptr; program_file: inout line) is
366 variable argv_var_name: string(1 to 6) := "*ARGV*";
367 variable argv_sym, argv_list: mal_val_ptr;
368 file f: text;
369 variable status: file_open_status;
370 variable one_line: line;
371 variable seq: mal_seq_ptr;
372 variable element: mal_val_ptr;
373 begin
374 program_file := null;
375 seq := new mal_seq(0 to -1);
376 file_open(status, f, external_name => "vhdl_argv.tmp", open_kind => read_mode);
377 if status = open_ok then
378 if not endfile(f) then
379 readline(f, program_file);
380 while not endfile(f) loop
381 readline(f, one_line);
382 new_string(one_line.all, element);
383 seq := new mal_seq'(seq.all & element);
384 end loop;
385 end if;
386 file_close(f);
387 end if;
388 new_seq_obj(mal_list, seq, argv_list);
389 new_symbol(argv_var_name, argv_sym);
390 env_set(e, argv_sym, argv_list);
391 end procedure set_argv;
392
393 procedure repl is
394 variable is_eof: boolean;
395 variable program_file, input_line, result: line;
396 variable eval_sym, eval_fn, dummy_val, err: mal_val_ptr;
397 variable outer: env_ptr;
398 variable eval_func_name: string(1 to 4) := "eval";
399 begin
400 outer := null;
401 new_env(repl_env, outer);
402
403 -- core.EXT: defined using VHDL (see core.vhdl)
404 define_core_functions(repl_env);
405 new_symbol(eval_func_name, eval_sym);
406 new_nativefn(eval_func_name, eval_fn);
407 env_set(repl_env, eval_sym, eval_fn);
408 set_argv(repl_env, program_file);
409
410 -- core.mal: defined using the language itself
411 RE("(def! not (fn* (a) (if a false true)))", repl_env, dummy_val, err);
412 RE("(def! load-file (fn* (f) (eval (read-string (str " & '"' & "(do " & '"' & " (slurp f) " & '"' & "\nnil)" & '"' & ")))))", repl_env, dummy_val, err);
413 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, dummy_val, err);
414
415 if program_file /= null then
416 REP("(load-file " & '"' & program_file.all & '"' & ")", repl_env, result, err);
417 return;
418 end if;
419
420 loop
421 mal_readline("user> ", is_eof, input_line);
422 exit when is_eof;
423 next when input_line'length = 0;
424 REP(input_line.all, repl_env, result, err);
425 if err /= null then
426 pr_str(err, false, result);
427 result := new string'("Error: " & result.all);
428 end if;
429 if result /= null then
430 mal_printline(result.all);
431 end if;
432 deallocate(result);
433 deallocate(err);
434 end loop;
435 mal_printline("");
436 end procedure repl;
437
438 begin
439 repl;
440 end architecture test;