1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
|
#include <lcq/pit/runtime/compile.h>
/* helper functions for manipulating the compiler state */
static void c_now(pit_runtime *rt, pit_value v) {
/* push a literal value to the traversal */
pit_traversal_push_value(rt, rt->traversal, v);
}
static void c_compile(pit_runtime *rt, pit_value e) {
/* schedule an expression to be compiled */
/* the resulting entries will be pushed to the traversal subsequently */
pit_compilation_push_value(rt, rt->compilation_stack, e);
}
static void c_drop(pit_runtime *rt) {
/* schedule a drop to be compiled */
pit_compilation_push_drop(rt, rt->compilation_stack);
}
static void c_code(pit_runtime *rt, pit_value e) {
/* schedule an expression to be compiled, and then captured as code */
/* this will result in a literal code value being included in the program, */
/* rather than the code itself (as in c_compile) */
pit_compilation_push_end_code(rt, rt->compilation_stack);
pit_compilation_push_value(rt, rt->compilation_stack, e);
pit_compilation_push_begin_code(rt, rt->compilation_stack);
}
/* given an expression and some initially bound variables (e.g. arguments), */
/* compute the free variables in the expression */
static pit_value free_vars(pit_runtime *rt, pit_value initial_bound, pit_value body) {
i64 expr_stack_reset = rt->expr_stack->next;
pit_value ret = PIT_NIL;
if (pit_vec_push(pit_value)(rt->expr_stack, pit_value_cons(rt, initial_bound, body)) < 0) {
pit_error(rt, "free variable search stack overflow");
return PIT_NIL;
}
while (rt->expr_stack->next > expr_stack_reset) {
pit_value boundscur, bound, cur;
if (pit_vec_pop(pit_value)(rt->expr_stack, &boundscur) < 0) {
pit_error(rt, "free variable search stack underflow");
return PIT_NIL;
}
bound = pit_value_cons_car(rt, boundscur);
cur = pit_value_cons_cdr(rt, boundscur);
if (pit_value_is_cons(rt, cur)) {
/* if the expression is a list, determine if it is a normal application */
pit_value fsym = pit_value_cons_car(rt, cur);
bool is_symbol = pit_value_is_symbol(rt, fsym);
bool is_special_form = is_symbol && pit_symtab_is_symbol_special_form(rt, fsym);
pit_value fargs = pit_value_cons_cdr(rt, cur);
/* first, consider special forms that have special behavior with regard to binding */
if (is_special_form && pit_symtab_symbol_name_match_cstr(rt, fsym, "quote")) {
/* don't look inside quote!
NOTICE if we add other special forms, make sure to consider them here if necessary! */
} else if (is_special_form && pit_symtab_symbol_name_match_cstr(rt, fsym, "lambda")) {
/* the variables from the lambda argument list are now bound */
pit_value new_bound = pit_value_list_append(rt, pit_value_cons_car(rt, fargs), bound);
fargs = pit_value_cons_cdr(rt, fargs);
/* consider all body forms within the lambda with these new bindings in mind */
while (fargs != PIT_NIL) {
pit_value bindings_expr_pair = pit_value_cons(rt, new_bound, pit_value_cons_car(rt, fargs));
if (pit_vec_push(pit_value)(rt->expr_stack, bindings_expr_pair) < 0) {
pit_error(rt, "free variable search stack overflow");
return PIT_NIL;
}
fargs = pit_value_cons_cdr(rt, fargs);
}
} else {
/* otherwise, this is a normal application form */
/* first consider all arguments to the application */
while (fargs != PIT_NIL) {
pit_value bindings_expr_pair = pit_value_cons(rt, bound, pit_value_cons_car(rt, fargs));
if (pit_vec_push(pit_value)(rt->expr_stack, bindings_expr_pair) < 0) {
pit_error(rt, "free variable search stack overflow");
return PIT_NIL;
}
fargs = pit_value_cons_cdr(rt, fargs);
}
if (!is_symbol) {
/* ... and then consider the function being applied, if it is not a symbol */
pit_value bindings_expr_pair = pit_value_cons(rt, bound, fsym);
if (pit_vec_push(pit_value)(rt->expr_stack, bindings_expr_pair) < 0) {
pit_error(rt, "free variable search stack overflow");
return PIT_NIL;
}
}
}
} else if (pit_value_is_symbol(rt, cur)) {
/* if the expression is a symbol, check if it's free! */
if (pit_value_list_contains_eq(rt, cur, bound) == PIT_NIL) {
ret = pit_value_cons(rt, cur, ret);
}
}
}
rt->expr_stack->next = expr_stack_reset;
return ret;
}
static void compile_special_form(pit_runtime *rt, pit_value f, pit_value args) {
char buf[256] = {0};
if (pit_symtab_symbol_name_match_cstr(rt, f, "quote")) {
/* quote compiles to a literal instruction */
c_now(rt,
pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), pit_value_cons_car(rt, args))
);
} else if (pit_symtab_symbol_name_match_cstr(rt, f, "if")) {
/* if compiles the condition, and then code for the branches, and then the if instruction */
pit_value c = pit_value_cons_car(rt, args);
args = pit_value_cons_cdr(rt, args);
pit_value t = pit_value_cons_car(rt, args);
args = pit_value_cons_cdr(rt, args);
pit_value e = pit_value_cons_car(rt, args);
c_now(rt, pit_value_list(rt, 1, pit_symtab_intern_cstr(rt, "if")));
c_code(rt, e);
c_code(rt, t);
c_compile(rt, c);
} else if (pit_symtab_symbol_name_match_cstr(rt, f, "progn")) {
/* progn simply compiles each of its argument forms in order */
/* after every form but the final form, it inserts a drop to ignore the return value */
while (args != PIT_NIL) {
pit_value form = pit_value_cons_car(rt, args);
args = pit_value_cons_cdr(rt, args);
c_compile(rt, form);
if (args != PIT_NIL) {
c_drop(rt);
}
}
} else if (pit_symtab_symbol_name_match_cstr(rt, f, "lambda")) {
pit_value arglist = pit_value_cons_car(rt, args);
pit_value body = pit_value_cons(rt, pit_symtab_intern_cstr(rt, "progn"), pit_value_cons_cdr(rt, args));
c_now(rt, pit_value_list(rt, 1, pit_symtab_intern_cstr(rt, "lambda")));
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), arglist));
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), free_vars(rt, arglist, body)));
c_code(rt, body);
} else {
i64 end = pit_dump(rt, buf, sizeof(buf) - 1, f, true);
buf[end] = 0;
pit_error(rt, "unknown special form: %s", buf);
}
}
static void compile_output_instruction(pit_runtime *rt, pit_value ins) {
/* write an instruction to the code block at the top of the compilation stack */
pit_compilation_entry b;
if (pit_vec_pop(pit_compilation_entry)(rt->compilation_stack, &b) < 0) {
pit_error(rt, "compilation block stack underflow");
return;
}
pit_value new = pit_value_cons(rt, ins, b.in.value);
pit_compilation_push_value(rt, rt->compilation_stack, new);
}
static void compile_interpret_traversal_entry(pit_runtime *rt, pit_traversal_entry *ent) {
if (ent == NULL) {
pit_error(rt, "evaluation traversal invalid");
return;
}
switch (ent->sort) {
case PIT_TRAVERSAL_ENTRY_VALUE: {
/* normal traversal entries just cons values to the code block at the top of the compilation stack */
compile_output_instruction(rt, ent->in.value);
return;
}
case PIT_TRAVERSAL_ENTRY_BEGIN_CODE: {
/* starting a new code block pushes a new list to the top of the compilation stack */
pit_compilation_push_value(rt, rt->compilation_stack, PIT_NIL);
return;
}
case PIT_TRAVERSAL_ENTRY_END_CODE: {
/* ending a code block pops a code block, and pushes it as a literal in the frame below */
pit_compilation_entry b;
if (pit_vec_pop(pit_compilation_entry)(rt->compilation_stack, &b) < 0) {
pit_error(rt, "compilation block stack underflow");
return;
}
compile_output_instruction(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), b.in.value));
return;
}
default:
pit_error(rt, "unknown traversal entry");
return;
}
}
pit_value pit_compile(pit_runtime *rt, pit_value top) {
/* compilation proceeds in a somewhat unintuitive manner */
/* typically, we would simply recursively traverse the tree, accumulating the output code */
/* (this also has the advantage of allowing the results of the recursive compilation call to */
/* be used in nonstandard contexts, such as compiling the body of lambdas. */
/* however, in pit we would like to avoid this sort of recursion, as we'd like to be able to */
/* make claims about the interpreter's C stack usage (and also, in general, it seems quite */
/* nice to avoid C stack usage growing with expression size!) */
/* as such, we must explicitly represent the control/data flow that would otherwise be handled */
/* implicitly by the recursion. we do this by breaking the compilation step into several steps. */
/* first, we traverse the expression using a stack (rt->compilation_stack). during this step, we */
/* push subexpression to be compiled to the stack, and append "generated code" to an intermediate */
/* structure (rt->traversal). this intermediate structure contains "operations" to later perform */
/* - think of it as the "continuation" of the code that produced it. finally, these intermediate */
/* operations are "interpreted" to generate the real compiled code, which is a Lisp list of instructions */
/* each instruction is itself a list of opcode and operands (see eval.c for more information on this) */
char buf[256] = {0};
i64 compilation_stack_reset = rt->compilation_stack->next;
i64 traversal_reset = rt->traversal->next;
/* before we do anything else, we fully macroexpand the input */
top = pit_macroexpand(rt, top);
c_compile(rt, top);
while (rt->compilation_stack->next > compilation_stack_reset) {
pit_compilation_entry curent;
if (rt->error != PIT_NIL) goto err;
if (pit_vec_pop(pit_compilation_entry)(rt->compilation_stack, &curent) < 0) {
pit_error(rt, "compilation stack underflow");
goto err;
}
switch (curent.sort) {
case PIT_COMPILATION_ENTRY_VALUE: {
pit_value cur = curent.in.value;
if (pit_value_is_cons(rt, cur)) {
pit_value fsym = pit_value_cons_car(rt, cur);
bool is_symbol = pit_value_is_symbol(rt, fsym);
if (is_symbol && pit_symtab_is_symbol_special_form(rt, fsym)) { /* special forms */
pit_value args = pit_value_cons_cdr(rt, cur);
compile_special_form(rt, fsym, args);
} else if (is_symbol && pit_symtab_is_symbol_macro(rt, fsym)) { /* macros */
i64 end = pit_dump(rt, buf, sizeof(buf) - 1, fsym, true);
buf[end] = 0;
pit_error(rt, "encountered an unexpanded macro while compiling: %s", buf);
} else { /* normal functions */
pit_value args = pit_value_cons_cdr(rt, cur);
i64 argcount = 0;
while (args != PIT_NIL) {
c_compile(rt, pit_value_cons_car(rt, args));
args = pit_value_cons_cdr(rt, args);
argcount += 1;
}
if (!is_symbol) {
c_compile(rt, fsym);
}
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "apply"), pit_value_integer_new(rt, argcount)));
if (is_symbol) {
c_now(rt, pit_value_list(rt, 1, pit_symtab_intern_cstr(rt, "fget")));
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), fsym));
}
}
} else if (pit_value_is_symbol(rt, cur)) { /* unquoted symbols: variable lookup */
pit_symtab_entry *ent = pit_symtab_lookup(rt, cur);
if (ent->is_keyword) {
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), cur));
} else {
c_now(rt, pit_value_list(rt, 1, pit_symtab_intern_cstr(rt, "get")));
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), cur));
}
} else { /* other expressions evaluate to themselves! */
c_now(rt, pit_value_list(rt, 2, pit_symtab_intern_cstr(rt, "literal"), cur));
}
break;
}
case PIT_COMPILATION_ENTRY_DROP: {
/* a BEGIN_CODE is a marker indicating that a new "block" of code has started */
c_now(rt, pit_value_list(rt, 1, pit_symtab_intern_cstr(rt, "drop")));
break;
}
case PIT_COMPILATION_ENTRY_BEGIN_CODE: {
/* a BEGIN_CODE is a marker indicating that a new "block" of code has started */
pit_traversal_push_begin_code(rt, rt->traversal);
break;
}
case PIT_COMPILATION_ENTRY_END_CODE: {
/* an END_CODE is a marker indicating that the current "block" of code has finished */
pit_traversal_push_end_code(rt, rt->traversal);
break;
}
}
}
/* now that we've built up the traversal, we iterate over it to build up the final program */
/* the "current" code is at the top of the compilation stack (which we now repurpose to this end) */
/* (note that this usage of the compilation state is entirely separate from the previous usage!) */
rt->compilation_stack->next = compilation_stack_reset;
pit_compilation_push_value(rt, rt->compilation_stack, PIT_NIL);
for (i64 idx = traversal_reset; idx < rt->traversal->next; idx++) {
pit_traversal_entry *ent = pit_vec_get(pit_traversal_entry)(rt->traversal, idx);
if (rt->error != PIT_NIL) goto end;
compile_interpret_traversal_entry(rt, ent);
}
goto end;
err: {
/* in case of error, we should always consider the stack empty, so we don't return something spurious */
rt->compilation_stack->next = compilation_stack_reset;
}
end: {
/* finally, we are done (possibly due to an error) */
pit_value ret = PIT_NIL;
if (rt->compilation_stack->next > compilation_stack_reset) {
/* if the compilation stack is not empty, the top is our compiled code! */
pit_compilation_entry ent;
pit_vec_pop(pit_compilation_entry)(rt->compilation_stack, &ent);
ret = ent.in.value;
}
if (rt->compilation_stack->next != compilation_stack_reset) {
/* if there was more than one entry on the compilation stack, this is a bug */
pit_error(rt, "compilation did not reduce stack to single code! this is a bug!");
}
rt->compilation_stack->next = compilation_stack_reset;
rt->traversal->next = traversal_reset;
return ret;
}
}
/* mark the symbols for the special forms as such */
void pit_compile_install_special_forms(pit_runtime *rt) {
pit_symtab_symbol_mark_special_form(rt, pit_symtab_intern_cstr(rt, "quote"));
pit_symtab_symbol_mark_special_form(rt, pit_symtab_intern_cstr(rt, "if"));
pit_symtab_symbol_mark_special_form(rt, pit_symtab_intern_cstr(rt, "progn"));
pit_symtab_symbol_mark_special_form(rt, pit_symtab_intern_cstr(rt, "lambda"));
}
|