1 //: So far we've been calling a fixed recipe in each instruction, but we'd
2 //: also like to make the recipe a variable, pass recipes to "higher-order"
3 //: recipes, return recipes from recipes and so on.
5 void test_call_literal_recipe() {
8 " 1:num <- call f, 34\n"
10 "def f x:num -> y:num [\n"
17 "mem: storing 34 in location 1\n"
21 :(before
"End Mu Types Initialization")
22 put(Type_ordinal
, "recipe-literal", 0);
23 // 'recipe' variables can store recipe-literal
24 type_ordinal recipe
= put(Type_ordinal
, "recipe", Next_type_ordinal
++);
25 get_or_insert(Type
, recipe
).name
= "recipe";
27 :(after
"Deduce Missing Type(x, caller)")
29 try_initialize_recipe_literal(x
, caller
);
30 :(before
"Type Check in Type-ingredient-aware check_or_set_types_by_name")
32 try_initialize_recipe_literal(x
, variant
);
34 void try_initialize_recipe_literal(reagent
& x
, const recipe
& caller
) {
36 if (!contains_key(Recipe_ordinal
, x
.name
)) return;
37 if (contains_reagent_with_non_recipe_literal_type(caller
, x
.name
)) return;
38 x
.type
= new type_tree("recipe-literal");
39 x
.set_value(get(Recipe_ordinal
, x
.name
));
41 bool contains_reagent_with_non_recipe_literal_type(const recipe
& caller
, const string
& name
) {
42 for (int i
= 0; i
< SIZE(caller
.steps
); ++i
) {
43 const instruction
& inst
= caller
.steps
.at(i
);
44 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
)
45 if (is_matching_non_recipe_literal(inst
.ingredients
.at(i
), name
)) return true;
46 for (int i
= 0; i
< SIZE(inst
.products
); ++i
)
47 if (is_matching_non_recipe_literal(inst
.products
.at(i
), name
)) return true;
51 bool is_matching_non_recipe_literal(const reagent
& x
, const string
& name
) {
52 if (x
.name
!= name
) return false;
53 if (!x
.type
) return false;
54 return !x
.type
->atom
|| x
.type
->name
!= "recipe-literal";
57 //: It's confusing to use variable names that are also recipe names. Always
58 //: assume variable types override recipe literals.
59 void test_error_on_recipe_literal_used_as_a_variable() {
64 " a:bool <- equal break 0\n"
65 " break:bool <- copy 0\n"
69 "error: main: missing type for 'break' in 'a:bool <- equal break, 0'\n"
73 :(before
"End Primitive Recipe Declarations")
75 :(before
"End Primitive Recipe Numbers")
76 put(Recipe_ordinal
, "call", CALL
);
77 :(before
"End Primitive Recipe Checks")
79 if (inst
.ingredients
.empty()) {
80 raise
<< maybe(get(Recipe
, r
).name
) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
83 if (!is_mu_recipe(inst
.ingredients
.at(0))) {
84 raise
<< maybe(get(Recipe
, r
).name
) << "first ingredient of 'call' should be a recipe, but got '" << inst
.ingredients
.at(0).original_string
<< "'\n" << end();
89 :(before
"End Primitive Recipe Implementations")
92 trace(Callstack_depth
+1, "trace") << "indirect 'call': incrementing callstack depth to " << Callstack_depth
<< end();
94 assert(Callstack_depth
< Max_depth
);
95 if (!ingredients
.at(0).at(0)) {
96 raise
<< maybe(current_recipe_name()) << "tried to call empty recipe in '" << to_string(current_instruction()) << "'" << end();
99 const call
& caller_frame
= current_call();
100 instruction
/*copy*/ call_instruction
= to_instruction(caller_frame
);
101 call_instruction
.operation
= ingredients
.at(0).at(0);
102 call_instruction
.ingredients
.erase(call_instruction
.ingredients
.begin());
103 Current_routine
->calls
.push_front(call(ingredients
.at(0).at(0)));
104 ingredients
.erase(ingredients
.begin()); // drop the callee
105 finish_call_housekeeping(call_instruction
, ingredients
);
106 // not done with caller
107 write_products
= false;
108 fall_through_to_next_instruction
= false;
113 void test_call_variable() {
116 " {1: (recipe number -> number)} <- copy f\n"
117 " 2:num <- call {1: (recipe number -> number)}, 34\n"
119 "def f x:num -> y:num [\n"
121 " load-ingredients\n"
125 CHECK_TRACE_CONTENTS(
126 "mem: storing 34 in location 2\n"
130 void test_call_literal_recipe_repeatedly() {
133 " 1:num <- call f, 34\n"
134 " 1:num <- call f, 35\n"
136 "def f x:num -> y:num [\n"
138 " load-ingredients\n"
142 CHECK_TRACE_CONTENTS(
143 "mem: storing 34 in location 1\n"
144 "mem: storing 35 in location 1\n"
148 void test_call_shape_shifting_recipe() {
151 " 1:num <- call f, 34\n"
153 "def f x:_elem -> y:_elem [\n"
155 " load-ingredients\n"
159 CHECK_TRACE_CONTENTS(
160 "mem: storing 34 in location 1\n"
164 void test_call_shape_shifting_recipe_inside_shape_shifting_recipe() {
169 "def f x:_elem -> y:_elem [\n"
171 " load-ingredients\n"
174 "def g x:_elem -> y:_elem [\n"
176 " load-ingredients\n"
180 CHECK_TRACE_CONTENTS(
181 "mem: storing 34 in location 1\n"
185 void test_call_shape_shifting_recipe_repeatedly_inside_shape_shifting_recipe() {
190 "def f x:_elem -> y:_elem [\n"
192 " load-ingredients\n"
196 "def g x:_elem -> y:_elem [\n"
198 " load-ingredients\n"
202 CHECK_TRACE_CONTENTS(
203 "mem: storing 34 in location 1\n"
207 //:: check types for 'call' instructions
209 void test_call_check_literal_recipe() {
213 " 1:num <- call f, 34\n"
215 "def f x:point -> y:point [\n"
217 " load-ingredients\n"
221 CHECK_TRACE_CONTENTS(
222 "error: main: ingredient 0 has the wrong type at '1:num <- call f, 34'\n"
223 "error: main: product 0 has the wrong type at '1:num <- call f, 34'\n"
227 void test_call_check_variable_recipe() {
231 " {1: (recipe point -> point)} <- copy f\n"
232 " 2:num <- call {1: (recipe point -> point)}, 34\n"
234 "def f x:point -> y:point [\n"
236 " load-ingredients\n"
240 CHECK_TRACE_CONTENTS(
241 "error: main: ingredient 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'\n"
242 "error: main: product 0 has the wrong type at '2:num <- call {1: (recipe point -> point)}, 34'\n"
246 :(before
"End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
247 if (inst
.name
== "call" && !inst
.ingredients
.empty() && is_recipe_literal(inst
.ingredients
.at(0))) {
248 resolve_indirect_ambiguous_call(r
, index
, inst
, caller_recipe
);
252 bool is_recipe_literal(const reagent
& x
) {
253 return x
.type
&& x
.type
->atom
&& x
.type
->name
== "recipe-literal";
255 void resolve_indirect_ambiguous_call(const recipe_ordinal r
, int index
, instruction
& inst
, const recipe
& caller_recipe
) {
257 inst2
.name
= inst
.ingredients
.at(0).name
;
258 for (int i
= /*skip recipe*/1; i
< SIZE(inst
.ingredients
); ++i
)
259 inst2
.ingredients
.push_back(inst
.ingredients
.at(i
));
260 for (int i
= 0; i
< SIZE(inst
.products
); ++i
)
261 inst2
.products
.push_back(inst
.products
.at(i
));
262 resolve_ambiguous_call(r
, index
, inst2
, caller_recipe
);
263 inst
.ingredients
.at(0).name
= inst2
.name
;
264 inst
.ingredients
.at(0).set_value(get(Recipe_ordinal
, inst2
.name
));
267 :(after
"Transform.push_back(check_instruction)")
268 Transform
.push_back(check_indirect_calls_against_header
); // idempotent
270 void check_indirect_calls_against_header(const recipe_ordinal r
) {
271 trace(101, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe
, r
).name
<< end();
272 const recipe
& caller
= get(Recipe
, r
);
273 for (int i
= 0; i
< SIZE(caller
.steps
); ++i
) {
274 const instruction
& inst
= caller
.steps
.at(i
);
275 if (!is_indirect_call(inst
.operation
)) continue;
276 if (inst
.ingredients
.empty()) continue; // error raised above
277 const reagent
& callee
= inst
.ingredients
.at(0);
278 if (!is_mu_recipe(callee
)) continue; // error raised above
279 const recipe callee_header
= is_literal(callee
) ? get(Recipe
, callee
.value
) : from_reagent(inst
.ingredients
.at(0));
280 if (!callee_header
.has_header
) continue;
281 if (is_indirect_call_with_ingredients(inst
.operation
)) {
282 for (long int i
= /*skip callee*/1; i
< min(SIZE(inst
.ingredients
), SIZE(callee_header
.ingredients
)+/*skip callee*/1); ++i
) {
283 if (!types_coercible(callee_header
.ingredients
.at(i
-/*skip callee*/1), inst
.ingredients
.at(i
)))
284 raise
<< maybe(caller
.name
) << "ingredient " << i
-/*skip callee*/1 << " has the wrong type at '" << to_original_string(inst
) << "'\n" << end();
287 if (is_indirect_call_with_products(inst
.operation
)) {
288 for (long int i
= 0; i
< min(SIZE(inst
.products
), SIZE(callee_header
.products
)); ++i
) {
289 if (is_dummy(inst
.products
.at(i
))) continue;
290 if (!types_coercible(callee_header
.products
.at(i
), inst
.products
.at(i
)))
291 raise
<< maybe(caller
.name
) << "product " << i
<< " has the wrong type at '" << to_original_string(inst
) << "'\n" << end();
297 bool is_indirect_call(const recipe_ordinal r
) {
298 return is_indirect_call_with_ingredients(r
) || is_indirect_call_with_products(r
);
301 bool is_indirect_call_with_ingredients(const recipe_ordinal r
) {
302 if (r
== CALL
) return true;
303 // End is_indirect_call_with_ingredients Special-cases
306 bool is_indirect_call_with_products(const recipe_ordinal r
) {
307 if (r
== CALL
) return true;
308 // End is_indirect_call_with_products Special-cases
312 recipe
from_reagent(const reagent
& r
) {
314 recipe result_header
; // will contain only ingredients and products, nothing else
315 result_header
.has_header
= true;
316 // Begin Reagent->Recipe(r, recipe_header)
318 assert(r
.type
->name
== "recipe");
319 return result_header
;
321 const type_tree
* root_type
= r
.type
->atom
? r
.type
: r
.type
->left
;
322 assert(root_type
->atom
);
323 assert(root_type
->name
== "recipe");
324 const type_tree
* curr
= r
.type
->right
;
325 for (/*nada*/; curr
&& !curr
->atom
; curr
= curr
->right
) {
326 if (curr
->left
->atom
&& curr
->left
->name
== "->") {
327 curr
= curr
->right
; // skip delimiter
330 result_header
.ingredients
.push_back(next_recipe_reagent(curr
->left
));
334 result_header
.ingredients
.push_back(next_recipe_reagent(curr
));
335 return result_header
; // no products
338 for (/*nada*/; curr
&& !curr
->atom
; curr
= curr
->right
)
339 result_header
.products
.push_back(next_recipe_reagent(curr
->left
));
342 result_header
.products
.push_back(next_recipe_reagent(curr
));
344 return result_header
;
347 :(before
"End Unit Tests")
348 void test_from_reagent_atomic() {
349 reagent
a("{f: recipe}");
350 recipe r_header
= from_reagent(a
);
351 CHECK(r_header
.ingredients
.empty());
352 CHECK(r_header
.products
.empty());
354 void test_from_reagent_non_atomic() {
355 reagent
a("{f: (recipe number -> number)}");
356 recipe r_header
= from_reagent(a
);
357 CHECK_EQ(SIZE(r_header
.ingredients
), 1);
358 CHECK_EQ(SIZE(r_header
.products
), 1);
360 void test_from_reagent_reads_ingredient_at_end() {
361 reagent
a("{f: (recipe number number)}");
362 recipe r_header
= from_reagent(a
);
363 CHECK_EQ(SIZE(r_header
.ingredients
), 2);
364 CHECK(r_header
.products
.empty());
366 void test_from_reagent_reads_sole_ingredient_at_end() {
367 reagent
a("{f: (recipe number)}");
368 recipe r_header
= from_reagent(a
);
369 CHECK_EQ(SIZE(r_header
.ingredients
), 1);
370 CHECK(r_header
.products
.empty());
374 reagent
next_recipe_reagent(const type_tree
* curr
) {
375 if (!curr
->left
) return reagent("recipe:"+curr
->name
);
376 return reagent(new type_tree(*curr
));
379 bool is_mu_recipe(const reagent
& r
) {
380 if (!r
.type
) return false;
382 // End is_mu_recipe Atom Cases(r)
383 return r
.type
->name
== "recipe-literal";
385 return r
.type
->left
->atom
&& r
.type
->left
->name
== "recipe";
388 void test_copy_typecheck_recipe_variable() {
392 " 3:num <- copy 34\n"
393 " {1: (recipe number -> number)} <- copy f\n" // store literal in a matching variable
394 " {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}\n" // mismatch between recipe variables
396 "def f x:num -> y:num [\n"
398 " load-ingredients\n"
402 CHECK_TRACE_CONTENTS(
403 "error: main: can't copy '{1: (recipe number -> number)}' to '{2: (recipe boolean -> boolean)}'; types don't match\n"
407 void test_copy_typecheck_recipe_variable_2() {
411 " {1: (recipe number -> number)} <- copy f\n" // mismatch with a recipe literal
413 "def f x:bool -> y:bool [\n"
415 " load-ingredients\n"
419 CHECK_TRACE_CONTENTS(
420 "error: main: can't copy 'f' to '{1: (recipe number -> number)}'; types don't match\n"
424 :(before
"End Matching Types For Literal(to)")
425 if (is_mu_recipe(to
)) {
426 if (!contains_key(Recipe
, from
.value
)) {
427 raise
<< "trying to store recipe " << from
.name
<< " into " << to_string(to
) << " but there's no such recipe\n" << end();
430 const recipe
& rrhs
= get(Recipe
, from
.value
);
431 const recipe
& rlhs
= from_reagent(to
);
432 for (long int i
= 0; i
< min(SIZE(rlhs
.ingredients
), SIZE(rrhs
.ingredients
)); ++i
) {
433 if (!types_match(rlhs
.ingredients
.at(i
), rrhs
.ingredients
.at(i
)))
436 for (long int i
= 0; i
< min(SIZE(rlhs
.products
), SIZE(rrhs
.products
)); ++i
) {
437 if (!types_match(rlhs
.products
.at(i
), rrhs
.products
.at(i
)))
444 void test_call_variable_compound_ingredient() {
447 " {1: (recipe (address number) -> number)} <- copy f\n"
448 " 2:&:num <- copy null\n"
449 " 3:num <- call {1: (recipe (address number) -> number)}, 2:&:num\n"
451 "def f x:&:num -> y:num [\n"
453 " load-ingredients\n"
454 " y <- deaddress x\n"
457 CHECK_TRACE_COUNT("error", 0);
460 //: make sure we don't accidentally break on a recipe literal
461 void test_jump_forbidden_on_recipe_literals() {
474 // error should be as if foo is not a recipe
475 CHECK_TRACE_CONTENTS(
476 "error: main: missing type for 'foo' in 'break-if foo'\n"
480 :(before
"End JUMP_IF Checks")
481 check_for_recipe_literals(inst
, get(Recipe
, r
));
482 :(before
"End JUMP_UNLESS Checks")
483 check_for_recipe_literals(inst
, get(Recipe
, r
));
485 void check_for_recipe_literals(const instruction
& inst
, const recipe
& caller
) {
486 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
) {
487 if (is_mu_recipe(inst
.ingredients
.at(i
))) {
488 raise
<< maybe(caller
.name
) << "missing type for '" << inst
.ingredients
.at(i
).original_string
<< "' in '" << to_original_string(inst
) << "'\n" << end();
489 if (is_present_in_ingredients(caller
, inst
.ingredients
.at(i
).name
))
490 raise
<< " did you forget 'load-ingredients'?\n" << end();
495 void test_load_ingredients_missing_error_3() {
498 "def foo {f: (recipe num -> num)} [\n"
500 " b:num <- call f, 1\n"
503 CHECK_TRACE_CONTENTS(
504 "error: foo: missing type for 'f' in 'b:num <- call f, 1'\n"
505 "error: did you forget 'load-ingredients'?\n"
509 :(before
"End Mu Types Initialization")
510 put(Type_abbreviations
, "function", new_type_tree("recipe"));
511 put(Type_abbreviations
, "fn", new_type_tree("recipe"));
513 //: copying functions to variables
516 void test_copy_recipe_to_variable() {
519 " {1: (fn number -> number)} <- copy f\n"
520 " 2:num <- call {1: (function number -> number)}, 34\n"
522 "def f x:num -> y:num [\n"
524 " load-ingredients\n"
528 CHECK_TRACE_CONTENTS(
529 "mem: storing 34 in location 2\n"
533 void test_copy_overloaded_recipe_to_variable() {
537 " {x: (fn num -> num)} <- copy f\n"
538 " 1:num/raw <- call x, 34\n"
541 "def f x:bool -> y:bool [\n"
543 " load-ingredients\n"
547 "def f x:num -> y:num [\n"
549 " load-ingredients\n"
554 CHECK_TRACE_CONTENTS(
555 "mem: storing 34 in location 1\n"
559 :(before
"End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
560 if (inst
.name
== "copy") {
561 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
) {
562 if (!is_recipe_literal(inst
.ingredients
.at(i
))) continue;
563 if (non_ghost_size(get_or_insert(Recipe_variants
, inst
.ingredients
.at(i
).name
)) < 1) continue;
564 // potentially overloaded recipe
565 string new_name
= resolve_ambiguous_call(inst
.ingredients
.at(i
).name
, inst
.products
.at(i
), r
, index
, caller_recipe
);
566 if (new_name
== "") continue;
567 inst
.ingredients
.at(i
).name
= new_name
;
568 inst
.ingredients
.at(i
).value
= get(Recipe_ordinal
, new_name
);
573 string
resolve_ambiguous_call(const string
& recipe_name
, const reagent
& call_types
, const recipe_ordinal r
, int index
, const recipe
& caller_recipe
) {
575 inst
.name
= recipe_name
;
576 if (!is_mu_recipe(call_types
)) return ""; // error raised elsewhere
577 if (is_recipe_literal(call_types
)) return ""; // error raised elsewhere
578 construct_fake_call(call_types
, inst
);
579 resolve_ambiguous_call(r
, index
, inst
, caller_recipe
);
582 void construct_fake_call(const reagent
& recipe_var
, instruction
& out
) {
583 assert(recipe_var
.type
->left
->name
== "recipe");
584 type_tree
* stem
= NULL
;
585 for (stem
= recipe_var
.type
->right
; stem
&& stem
->left
->name
!= "->"; stem
= stem
->right
)
586 out
.ingredients
.push_back(copy(stem
->left
));
587 if (stem
== NULL
) return;
588 for (/*skip '->'*/stem
= stem
->right
; stem
; stem
= stem
->right
)
589 out
.products
.push_back(copy(stem
->left
));
592 void test_copy_shape_shifting_recipe_to_variable() {
596 " {x: (fn num -> num)} <- copy f\n"
597 " 1:num/raw <- call x, 34\n"
599 "def f x:_elem -> y:_elem [\n"
605 CHECK_TRACE_CONTENTS(
606 "mem: storing 34 in location 1\n"
610 //: passing function literals to (higher-order) functions
612 void test_pass_overloaded_recipe_literal_to_ingredient() {
614 // like test_copy_overloaded_recipe_to_variable, except we bind 'x' in
615 // the course of a 'call' rather than 'copy'
619 "def g {x: (fn num -> num)} -> result:num [\n"
621 " load-ingredients\n"
622 " result <- call x, 34\n"
625 "def f x:bool -> y:bool [\n"
627 " load-ingredients\n"
631 "def f x:num -> y:num [\n"
633 " load-ingredients\n"
638 CHECK_TRACE_CONTENTS(
639 "mem: storing 34 in location 1\n"
643 :(after
"End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
644 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
) {
645 if (!is_mu_recipe(inst
.ingredients
.at(i
))) continue;
646 if (non_ghost_size(get_or_insert(Recipe_variants
, inst
.ingredients
.at(i
).name
)) < 1) continue;
647 if (get(Recipe_ordinal
, inst
.name
) < MAX_PRIMITIVE_RECIPES
) continue;
648 if (non_ghost_size(get_or_insert(Recipe_variants
, inst
.name
)) > 1) {
649 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();
652 const recipe
& callee
= get(Recipe
, get(Recipe_ordinal
, inst
.name
));
653 if (!callee
.has_header
) {
654 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();
657 string new_name
= resolve_ambiguous_call(inst
.ingredients
.at(i
).name
, callee
.ingredients
.at(i
), r
, index
, caller_recipe
);
658 if (new_name
!= "") {
659 inst
.ingredients
.at(i
).name
= new_name
;
660 inst
.ingredients
.at(i
).value
= get(Recipe_ordinal
, new_name
);
665 void test_return_overloaded_recipe_literal_to_caller() {
669 " {x: (fn num -> num)} <- g\n"
670 " 1:num/raw <- call x, 34\n"
672 "def g -> {x: (fn num -> num)} [\n"
677 "def f x:bool -> y:bool [\n"
679 " load-ingredients\n"
683 "def f x:num -> y:num [\n"
685 " load-ingredients\n"
690 CHECK_TRACE_CONTENTS(
691 "mem: storing 34 in location 1\n"
695 :(before
"End resolve_ambiguous_call(r, index, inst, caller_recipe) Special-cases")
696 if (inst
.name
== "return" || inst
.name
== "reply") {
697 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
) {
698 if (!is_recipe_literal(inst
.ingredients
.at(i
))) continue;
699 if (non_ghost_size(get_or_insert(Recipe_variants
, inst
.ingredients
.at(i
).name
)) < 1) continue;
700 // potentially overloaded recipe
701 if (!caller_recipe
.has_header
) {
702 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();
705 string new_name
= resolve_ambiguous_call(inst
.ingredients
.at(i
).name
, caller_recipe
.products
.at(i
), r
, index
, caller_recipe
);
706 if (new_name
== "") continue;
707 inst
.ingredients
.at(i
).name
= new_name
;
708 inst
.ingredients
.at(i
).value
= get(Recipe_ordinal
, new_name
);