https://github.com/akkartik/mu1/blob/master/072recipe.cc
1
2
3
4
5 :(scenario call_literal_recipe)
6 def main [
7 1:num <- call f, 34
8 ]
9 def f x:num -> y:num [
10 local-scope
11 load-ingredients
12 y <- copy x
13 ]
14 +mem: storing 34 in location 1
15
16 :(before "End Mu Types Initialization")
17 put(Type_ordinal, "recipe-literal", 0);
18
19 type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++);
20 get_or_insert(Type, recipe).name = "recipe";
21
22 :(after "Deduce Missing Type(x, caller)")
23 if (!x.type)
24 try_initialize_recipe_literal(x, caller);
25 :(before "Type Check in Type-ingredient-aware check_or_set_types_by_name")
26 if (!x.type)
27 try_initialize_recipe_literal(x, variant);
28 :(code)
29 void try_initialize_recipe_literal(reagent& x, const recipe& caller) {
30 if (x.type) return;
31 if (!contains_key(Recipe_ordinal, x.name)) return;
32 if (contains_reagent_with_non_recipe_literal_type(caller, x.name)) return;
33 x.type = new type_tree("recipe-literal");
34 x.set_value(get(Recipe_ordinal, x.name));
35 }
36 bool contains_reagent_with_non_recipe_literal_type(const recipe& caller, const string& name) {
37 for (int i = 0; i < SIZE(caller.steps); ++i) {
38 const instruction& inst = caller.steps.at(i);
39 for (int i = 0; i < SIZE(inst.ingredients); ++i)
40 if (is_matching_non_recipe_literal(inst.ingredients.at(i), name)) return true;
41 for (int i = 0; i < SIZE(inst.products); ++i)
42 if (is_matching_non_recipe_literal(inst.products.at(i), name)) return true;
43 }
44 return false;
45 }
46 bool is_matching_non_recipe_literal(const reagent& x, const string& name) {
47 if (x.name != name) return false;
48 if (!x.type) return false;
49 return !x.type->atom || x.type->name != "recipe-literal";
50 }
51
52
53
54 :(scenario error_on_recipe_literal_used_as_a_variable)
55 % Hide_errors = true;
56 def main [
57 local-scope
58 a:bool <- equal break 0
59 break:bool <- copy 0
60 ]
61 +error: main: missing type for 'break' in 'a:bool <- equal break, 0'
62
63 :(before "End Primitive Recipe Declarations")
64 CALL,
65 :(before "End Primitive Recipe Numbers")
66 put(Recipe_ordinal, "call", CALL);
67 :(before "End Primitive Recipe Checks")
68 case CALL: {
69 if (inst.ingredients.empty()) {
70 raise << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
71 break;
72 }
73 if (!is_mu_recipe(inst.ingredients.at(0))) {
74 raise << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
75 break;
76 }
77 break;
78 }
79 :(before "End Primitive Recipe Implementations")
80 case CALL: {
81
82 if (Trace_stream) {
83 ++Trace_stream->callstack_depth;
84 trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end();
85 assert(Trace_stream->callstack_depth < 9000);
86 }
87 if (!ingredients.at(0).at(0)) {
88 raise << maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end();
89 break;
90 }
91 const call& caller_frame = current_call();
92 instruction call_instruction = to_instruction(caller_frame);
93 call_instruction.operation = ingredients.at(0).at(0);
94 call_instruction.ingredients.erase(call_instruction.ingredients.begin());
95 Current_routine->calls.push_front(call(ingredients.at(0).at(0)));
96 ingredients.erase(ingredients.begin());
97 finish_call_housekeeping(call_instruction, ingredients);
98
99 write_products = false;
100 fall_through_to_next_instruction = false;
101 break;
102 }
103
104 :(scenario call_variable)
105 def main [
106 {1: (recipe number -> number)} <- copy f
107 2:num <- call {1: (recipe number -> number)}, 34
108 ]
109 def f x:num -> y:num [
110 local-scope
111 load-ingredients
112 y <- copy x
113 ]
114 +mem: storing 34 in location 2
115
116 :(scenario call_literal_recipe_repeatedly)
117 def main [
118 1:num <- call f, 34
119 1:num <- call f, 35
120 ]
121 def f x:num -> y:num [
122 local-scope
123 load-ingredients
124 y <- copy x
125 ]
126 +mem: storing 34 in location 1
127 +mem: storing 35 in location 1
128
129 :(scenario call_shape_shifting_recipe)
130 def main [
131 1:num <- call f, 34
132 ]
133 def f x:_elem -> y:_elem [
134 local-scope
135 load-ingredients
136 y <- copy x
137 ]
138 +mem: storing 34 in location 1
139
140 :(scenario call_shape_shifting_recipe_inside_shape_shifting_recipe)
141 def main [
142 1:num <- f 34
143 ]
144 def f x:_elem -> y:_elem [
145 local-scope
146 load-ingredients
147 y <- call g x
148 ]
149 def g x:_elem -> y:_elem [
150 local-scope
151 load-ingredients
152 y <- copy x
153 ]
154 +mem: storing 34 in location 1
155
156 :(scenario call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe)
157 def main [
158 1:num <- f 34
159 ]
160 def f x:_elem -> y:_elem [
161 local-scope
162 load-ingredients
163 y <- call g x
164 y <- call g x
165 ]
166 def g x:_elem -> y:_elem [
167 local-scope
168 load-ingredients
169 y <- copy x
170 ]
171 +mem: storing 34 in location 1
172
173
174
175 :(scenario call_check_literal_recipe)
176 % Hide_errors = true;
177 def main [
178 1:num <- call f, 34
179 ]
180 def f x:point -> y:point [
181 local-scope
182 load-ingredients
183 y <- copy x
184 ]
185 +error: main: ingredient 0 has the wrong type at '1:num <- call f, 34'
186 +error: main: product 0 has the wrong type at '1:num <- call f, 34'
187
188 :(scenario call_check_variable_recipe)
189 % Hide_errors = true;
190 def main [
191 {1: (recipe point -> point)} <- copy f
192 2:num <- call {1: (recipe point -> point)}, 34
193 ]
194 def f x:point -> y:point [
195 local-scope
196 load-ingredients
197 y <- copy x
198 ]
199 +error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
200 +error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'
201
202 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
203 if (inst.name == "call" && !inst.ingredients.empty() && is_recipe_literal(inst.ingredients.at(0))) {
204 resolve_indirect_ambiguous_call(r, index, inst, caller_recipe);
205 return;
206 }
207 :(code)
208 bool is_recipe_literal(const reagent& x) {
209 return x.type && x.type->atom && x.type->name == "recipe-literal";
210 }
211 void resolve_indirect_ambiguous_call(const recipe_ordinal r, int index, instruction& inst, const recipe& caller_recipe) {
212 instruction inst2;
213 inst2.name = inst.ingredients.at(0).name;
214 for (int i = 1; i < SIZE(inst.ingredients); ++i)
215 inst2.ingredients.push_back(inst.ingredients.at(i));
216 for (int i = 0; i < SIZE(inst.products); ++i)
217 inst2.products.push_back(inst.products.at(i));
218 resolve_ambiguous_call(r, index, inst2, caller_recipe);
219 inst.ingredients.at(0).name = inst2.name;
220 inst.ingredients.at(0).set_value(get(Recipe_ordinal, inst2.name));
221 }
222
223 :(after "Transform.push_back(check_instruction)")
224 Transform.push_back(check_indirect_calls_against_header);
225 :(code)
226 void check_indirect_calls_against_header(const recipe_ordinal r) {
227 trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end();
228 const recipe& caller = get(Recipe, r);
229 for (int i = 0; i < SIZE(caller.steps); ++i) {
230 const instruction& inst = caller.steps.at(i);
231 if (!is_indirect_call(inst.operation)) continue;
232 if (inst.ingredients.empty()) continue;
233 const reagent& callee = inst.ingredients.at(0);
234 if (!is_mu_recipe(callee)) continue;
235 const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0));
236 if (!callee_header.has_header) continue;
237 if (is_indirect_call_with_ingredients(inst.operation)) {
238 for (long int i = 1; i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+1); ++i) {
239 if (!types_coercible(callee_header.ingredients.at(i-1), inst.ingredients.at(i)))
240 raise << maybe(caller.name) << "ingredient " << i-1 << " has the wrong type at '" << to_original_string(inst) << "'\n" << end();
241 }
242 }
243 if (is_indirect_call_with_products(inst.operation)) {
244 for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee_header.products)); ++i) {
245 if (is_dummy(inst.products.at(i))) continue;
246 if (!types_coercible(callee_header.products.at(i), inst.products.at(i)))
247 raise << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_original_string(inst) << "'\n" << end();
248 }
249 }
250 }
251 }
252
253 bool is_indirect_call(const recipe_ordinal r) {
254 return is_indirect_call_with_ingredients(r) || is_indirect_call_with_products(r);
255 }
256
257 bool is_indirect_call_with_ingredients(const recipe_ordinal r) {
258 if (r == CALL) return true;
259
260 return false;
261 }
262 bool is_indirect_call_with_products(const recipe_ordinal r) {
263 if (r == CALL) return true;
264
265 return false;
266 }
267
268 recipe from_reagent(const reagent& r) {
269 assert(r.type);
270 recipe result_header;
271 result_header.has_header = true;
272
273 if (r.type->atom) {
274 assert(r.type->name == "recipe");
275 return result_header;
276 }
277 const type_tree* root_type = r.type->atom ? r.type : r.type->left;
278 assert(root_type->atom);
279 assert(root_type->name == "recipe");
280 const type_tree* curr = r.type->right;
281 for (; curr && !curr->atom; curr = curr->right) {
282 if (curr->left->atom && curr->left->name == "->") {
283 curr = curr->right;
284 goto read_products;
285 }
286 result_header.ingredients.push_back(next_recipe_reagent(curr->left));
287 }
288 if (curr) {
289 assert(curr->atom);
290 result_header.ingredients.push_back(next_recipe_reagent(curr));
291 return result_header;
292 }
293 read_products:
294 for (; curr && !curr->atom; curr = curr->right)
295 result_header.products.push_back(next_recipe_reagent(curr->left));
296 if (curr) {
297 assert(curr->atom);
298 result_header.products.push_back(next_recipe_reagent(curr));
299 }
300 return result_header;
301 }
302
303 :(before "End Unit Tests")
304 void test_from_reagent_atomic() {
305 reagent a("{f: recipe}");
306 recipe r_header = from_reagent(a);
307 CHECK(r_header.ingredients.empty());
308 CHECK(r_header.products.empty());
309 }
310 void test_from_reagent_non_atomic() {
311 reagent a("{f: (recipe number -> number)}");
312 recipe r_header = from_reagent(a);
313 CHECK_EQ(SIZE(r_header.ingredients), 1);
314 CHECK_EQ(SIZE(r_header.products), 1);
315 }
316 void test_from_reagent_reads_ingredient_at_end() {
317 reagent a("{f: (recipe number number)}");
318 recipe r_header = from_reagent(a);
319 CHECK_EQ(SIZE(r_header.ingredients), 2);
320 CHECK(r_header.products.empty());
321 }
322 void test_from_reagent_reads_sole_ingredient_at_end() {
323 reagent a("{f: (recipe number)}");
324 recipe r_header = from_reagent(a);
325 CHECK_EQ(SIZE(r_header.ingredients), 1);
326 CHECK(r_header.products.empty());
327 }
328
329 :(code)
330 reagent next_recipe_reagent(const type_tree* curr) {
331 if (!curr->left) return reagent("recipe:"+curr->name);
332 return reagent(new type_tree(*curr));
333 }
334
335 bool is_mu_recipe(const reagent& r) {
336 if (!r.type) return false;
337 if (r.type->atom) {
338
339 return r.type->name == "recipe-literal";
340 }
341 return r.type->left->atom && r.type->left->name == "recipe";
342 }
343
344 :(scenario copy_typecheck_recipe_variable)
345 % Hide_errors = true;
346 def main [
347 3:num <- copy 34
348 {1: (recipe number -> number)} <- copy f
349 {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}
350 ]
351 def f x:num -> y:num [
352 local-scope
353 load-ingredients
354 y <- copy x
355 ]
356 +error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match
357
358 :(scenario copy_typecheck_recipe_variable_2)
359 % Hide_errors = true;
360 def main [
361 {1: (recipe number -> number)} <- copy f
362 ]
363 def f x:bool -> y:bool [
364 local-scope
365 load-ingredients
366 y <- copy x
367 ]
368 +error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match
369
370 :(before "End Matching Types For Literal(to)")
371 if (is_mu_recipe(to)) {
372 if (!contains_key(Recipe, from.value)) {
373 raise << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end();
374 return false;
375 }
376 const recipe& rrhs = get(Recipe, from.value);
377 const recipe& rlhs = from_reagent(to);
378 for (long int i = 0; i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients)); ++i) {
379 if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i)))
380 return false;
381 }
382 for (long int i = 0; i < min(SIZE(rlhs.products), SIZE(rrhs.products)); ++i) {
383 if (!types_match(rlhs.products.at(i), rrhs.products.at(i)))
384 return false;
385 }
386 return true;
387 }
388
389 :(scenario call_variable_compound_ingredient)
390 def main [
391 {1: (recipe (address number) -> number)} <- copy f
392 2:&:num <- copy null
393 3:num <- call {1: (recipe (address number) -> number)}, 2:&:num
394 ]
395 def f x:&:num -> y:num [
396 local-scope
397 load-ingredients
398 y <- deaddress x
399 ]
400 $error: 0
401
402
403 :(scenario jump_forbidden_on_recipe_literals)
404 % Hide_errors = true;
405 def foo [
406 local-scope
407 ]
408 def main [
409 local-scope
410 {
411 break-if foo
412 }
413 ]
414
415 +error: main: missing type for 'foo' in 'break-if foo'
416
417 :(before "End JUMP_IF Checks")
418 check_for_recipe_literals(inst, get(Recipe, r));
419 :(before "End JUMP_UNLESS Checks")
420 check_for_recipe_literals(inst, get(Recipe, r));
421 :(code)
422 void check_for_recipe_literals(const instruction& inst, const recipe& caller) {
423 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
424 if (is_mu_recipe(inst.ingredients.at(i))) {
425 raise << maybe(caller.name) << "missing type for '" << inst.ingredients.at(i).original_string << "' in '" << to_original_string(inst) << "'\n" << end();
426 if (is_present_in_ingredients(caller, inst.ingredients.at(i).name))
427 raise << " did you forget 'load-ingredients'?\n" << end();
428 }
429 }
430 }
431
432 :(scenario load_ingredients_missing_error_3)
433 % Hide_errors = true;
434 def foo {f: (recipe num -> num)} [
435 local-scope
436 b:num <- call f, 1
437 ]
438 +error: foo: missing type for 'f' in 'b:num <- call f, 1'
439 +error: did you forget 'load-ingredients'?
440
441 :(before "End Mu Types Initialization")
442 put(Type_abbreviations, "function", new_type_tree("recipe"));
443 put(Type_abbreviations, "fn", new_type_tree("recipe"));
444
445
446
447 :(scenario copy_recipe_to_variable)
448 def main [
449 {1: (fn number -> number)} <- copy f
450 2:num <- call {1: (function number -> number)}, 34
451 ]
452 def f x:num -> y:num [
453 local-scope
454 load-ingredients
455 y <- copy x
456 ]
457 +mem: storing 34 in location 2
458
459 :(scenario copy_overloaded_recipe_to_variable)
460 def main [
461 local-scope
462 {x: (fn num -> num)} <- copy f
463 1:num/raw <- call x, 34
464 ]
465
466 def f x:bool -> y:bool [
467 local-scope
468 load-ingredients
469 y <- copy x
470 ]
471
472 def f x:num -> y:num [
473 local-scope
474 load-ingredients
475 y <- copy x
476 ]
477
478 +mem: storing 34 in location 1
479
480 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
481 if (inst.name == "copy") {
482 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
483 if (!is_recipe_literal(inst.ingredients.at(i))) continue;
484 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
485
486 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, inst.products.at(i), r, index, caller_recipe);
487 if (new_name == "") continue;
488 inst.ingredients.at(i).name = new_name;
489 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
490 }
491 return;
492 }
493 :(code)
494 string resolve_ambiguous_call(const string& recipe_name, const reagent& call_types, const recipe_ordinal r, int index, const recipe& caller_recipe) {
495 instruction inst;
496 inst.name = recipe_name;
497 if (!is_mu_recipe(call_types)) return "";
498 if (is_recipe_literal(call_types)) return "";
499 construct_fake_call(call_types, inst);
500 resolve_ambiguous_call(r, index, inst, caller_recipe);
501 return inst.name;
502 }
503 void construct_fake_call(const reagent& recipe_var, instruction& out) {
504 assert(recipe_var.type->left->name == "recipe");
505 type_tree* stem = NULL;
506 for (stem = recipe_var.type->right; stem && stem->left->name != "->"; stem = stem->right)
507 out.ingredients.push_back(copy(stem->left));
508 if (stem == NULL) return;
509 for (stem = stem->right; stem; stem = stem->right)
510 out.products.push_back(copy(stem->left));
511 }
512
513 :(scenario copy_shape_shifting_recipe_to_variable)
514 def main [
515 local-scope
516 {x: (fn num -> num)} <- copy f
517 1:num/raw <- call x, 34
518 ]
519 def f x:_elem -> y:_elem [
520 local-scope
521 load-inputs
522 y <- copy x
523 ]
524 +mem: storing 34 in location 1
525
526
527
528 :(scenario pass_overloaded_recipe_literal_to_ingredient)
529
530
531 def main [
532 1:num <- g f
533 ]
534 def g {x: (fn num -> num)} -> result:num [
535 local-scope
536 load-ingredients
537 result <- call x, 34
538 ]
539
540 def f x:bool -> y:bool [
541 local-scope
542 load-ingredients
543 y <- copy x
544 ]
545
546 def f x:num -> y:num [
547 local-scope
548 load-ingredients
549 y <- copy x
550 ]
551
552 +mem: storing 34 in location 1
553
554 :(after "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
555 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
556 if (!is_mu_recipe(inst.ingredients.at(i))) continue;
557 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
558 if (get(Recipe_ordinal, inst.name) < MAX_PRIMITIVE_RECIPES) continue;
559 if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) > 1) {
560 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to simultaneously guess which overloads you want for '" << inst.name << "' and '" << inst.ingredients.at(i).name << "'\n" << end();
561 return;
562 }
563 const recipe& callee = get(Recipe, get(Recipe_ordinal, inst.name));
564 if (!callee.has_header) {
565 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, when the caller '" << inst.name << "' doesn't have a header\n" << end();
566 return;
567 }
568 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, callee.ingredients.at(i), r, index, caller_recipe);
569 if (new_name != "") {
570 inst.ingredients.at(i).name = new_name;
571 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
572 }
573 }
574
575 :(scenario return_overloaded_recipe_literal_to_caller)
576 def main [
577 local-scope
578 {x: (fn num -> num)} <- g
579 1:num/raw <- call x, 34
580 ]
581 def g -> {x: (fn num -> num)} [
582 local-scope
583 return f
584 ]
585
586 def f x:bool -> y:bool [
587 local-scope
588 load-ingredients
589 y <- copy x
590 ]
591
592 def f x:num -> y:num [
593 local-scope
594 load-ingredients
595 y <- copy x
596 ]
597
598 +mem: storing 34 in location 1
599
600 :(before "End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
601 if (inst.name == "return" || inst.name == "reply") {
602 for (int i = 0; i < SIZE(inst.ingredients); ++i) {
603 if (!is_recipe_literal(inst.ingredients.at(i))) continue;
604 if (non_ghost_size(get_or_insert(Recipe_variants, inst.ingredients.at(i).name)) < 1) continue;
605
606 if (!caller_recipe.has_header) {
607 raise << maybe(caller_recipe.name) << "sorry, we're not yet smart enough to guess which variant of '" << inst.ingredients.at(i).name << "' you want, without a recipe header\n" << end();
608 return;
609 }
610 string new_name = resolve_ambiguous_call(inst.ingredients.at(i).name, caller_recipe.products.at(i), r, index, caller_recipe);
611 if (new_name == "") continue;
612 inst.ingredients.at(i).name = new_name;
613 inst.ingredients.at(i).value = get(Recipe_ordinal, new_name);
614 }
615 return;
616 }