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.
  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 // 'recipe' variables can store recipe-literal
 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 //: It's confusing to use variable names that are also recipe names. Always
 53 //: assume variable types override recipe literals.
 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   // Begin Call
 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);  // 9998-101 plus cushion
 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/*copy*/ 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());  // drop the callee
 97   finish_call_housekeeping(call_instruction, ingredients);
 98   // not done with caller
 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 //:: check types for 'call' instructions
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 = /*skip recipe*/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);  // idempotent
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;  // error raised above
233     const reagent& callee = inst.ingredients.at(0);
234     if (!is_mu_recipe(callee)) continue;  // error raised above
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 = /*skip callee*/1;  i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1);  ++i) {
239         if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i)))
240           raise << maybe(caller.name) << "ingredient " << i-/*skip callee*/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   // End is_indirect_call_with_ingredients Special-cases
260   return false;
261 }
262 bool is_indirect_call_with_products(const recipe_ordinal r) {
263   if (r == CALL) return true;
264   // End is_indirect_call_with_products Special-cases
265   return false;
266 }
267 
268 recipe from_reagent(const reagent& r) {
269   assert(r.type);
270   recipe result_header;  // will contain only ingredients and products, nothing else
271   result_header.has_header = true;
272   // Begin Reagent->Recipe(r, recipe_header)
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 (/*nada*/;  curr && !curr->atom;  curr = curr->right) {
282     if (curr->left->atom && curr->left->name == "->") {
283       curr = curr->right;  // skip delimiter
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;  // no products
292   }
293   read_products:
294   for (/*nada*/;  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     // End is_mu_recipe Atom Cases(r)
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  # abc def
348   {1: (recipe number -> number)} <- copy f  # store literal in a matching variable
349   {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}  # mismatch between recipe variables
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  # mismatch with a recipe literal
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 0
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 <- copy x
399 ]
400 $error: 0
401 
402 //: make sure we don't accidentally break on a recipe literal
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 # error should be as if foo is not a recipe
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 //: copying functions to variables
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 # variant f
466 def f x:bool -> y:bool [
467   local-scope
468   load-ingredients
469   y <- copy x
470 ]
471 # variant f_2
472 def f x:num -> y:num [
473   local-scope
474   load-ingredients
475   y <- copy x
476 ]
477 # x contains f_2
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     // potentially overloaded recipe
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 "";  // error raised elsewhere
498   if (is_recipe_literal(call_types)) return "";  // error raised elsewhere
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 (/*skip '->'*/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 //: passing function literals to (higher-order) functions
527 
528 :(scenario pass_overloaded_recipe_literal_to_ingredient)
529 # like copy_overloaded_recipe_to_variable except we bind 'x' in the course of
530 # a 'call' rather than 'copy'
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 # variant f
540 def f x:bool -> y:bool [
541   local-scope
542   load-ingredients
543   y <- copy x
544 ]
545 # variant f_2
546 def f x:num -> y:num [
547   local-scope
548   load-ingredients
549   y <- copy x
550 ]
551 # x contains f_2
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 # variant f
586 def f x:bool -> y:bool [
587   local-scope
588   load-ingredients
589   y <- copy x
590 ]
591 # variant f_2
592 def f x:num -> y:num [
593   local-scope
594   load-ingredients
595   y <- copy x
596 ]
597 # x contains f_2
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     // potentially overloaded recipe
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 }