1 //: Calls can take ingredients just like primitives. To access a recipe's
  2 //: ingredients, use 'next-ingredient'.
  3 
  4 :(scenario next_ingredient)
  5 def main [
  6   f 2
  7 ]
  8 def f [
  9   12:num <- next-ingredient
 10   13:num <- add 1, 12:num
 11 ]
 12 +mem: storing 3 in location 13
 13 
 14 :(scenario next_ingredient_missing)
 15 def main [
 16   f
 17 ]
 18 def f [
 19   _, 12:num <- next-ingredient
 20 ]
 21 +mem: storing 0 in location 12
 22 
 23 :(before "End call Fields")
 24 vector<vector<double> > ingredient_atoms;
 25 vector<reagent> ingredients;
 26 int next_ingredient_to_process;
 27 :(before "End call Constructor")
 28 next_ingredient_to_process = 0;
 29 
 30 :(before "End Call Housekeeping")
 31 for (int i = 0;  i < SIZE(ingredients);  ++i) {
 32   current_call().ingredient_atoms.push_back(ingredients.at(i));
 33   reagent/*copy*/ ingredient = call_instruction.ingredients.at(i);
 34   // End Compute Call Ingredient
 35   current_call().ingredients.push_back(ingredient);
 36   // End Populate Call Ingredient
 37 }
 38 
 39 :(before "End Primitive Recipe Declarations")
 40 NEXT_INGREDIENT,
 41 :(before "End Primitive Recipe Numbers")
 42 put(Recipe_ordinal, "next-ingredient", NEXT_INGREDIENT);
 43 :(before "End Primitive Recipe Checks")
 44 case NEXT_INGREDIENT: {
 45   if (!inst.ingredients.empty()) {
 46     raise << maybe(get(Recipe, r).name) << "'next-ingredient' didn't expect any ingredients in '" << inst.original_string << "'\n" << end();
 47     break;
 48   }
 49   break;
 50 }
 51 :(before "End Primitive Recipe Implementations")
 52 case NEXT_INGREDIENT: {
 53   assert(!Current_routine->calls.empty());
 54   if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) {
 55     reagent/*copy*/ product = current_instruction().products.at(0);
 56     // End Preprocess NEXT_INGREDIENT product
 57     if (current_recipe_name() == "main") {
 58       // no ingredient types since the call might be implicit; assume ingredients are always strings
 59       // todo: how to test this?
 60       if (!is_mu_text(product))
 61         raise << "main: wrong type for ingredient '" << product.original_string << "'\n" << end();
 62     }
 63     else if (!types_coercible(product,
 64                               current_call().ingredients.at(current_call().next_ingredient_to_process))) {
 65       raise << maybe(current_recipe_name()) << "wrong type for ingredient '" << product.original_string << "'\n" << end();
 66       // End next-ingredient Type Mismatch Error
 67     }
 68     products.push_back(
 69         current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
 70     assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
 71     products.at(1).push_back(1);
 72     ++current_call().next_ingredient_to_process;
 73   }
 74   else {
 75     if (SIZE(current_instruction().products) < 2)
 76       raise << maybe(current_recipe_name()) << "no ingredient to save in '" << current_instruction().products.at(0).original_string << "'\n" << end();
 77     if (current_instruction().products.empty()) break;
 78     products.resize(2);
 79     // pad the first product with sufficient zeros to match its type
 80     products.at(0).resize(size_of(current_instruction().products.at(0)));
 81     products.at(1).push_back(0);
 82   }
 83   break;
 84 }
 85 
 86 :(scenario next_ingredient_fail_on_missing)
 87 % Hide_errors = true;
 88 def main [
 89   f
 90 ]
 91 def f [
 92   11:num <- next-ingredient
 93 ]
 94 +error: f: no ingredient to save in '11:num'
 95 
 96 :(scenario rewind_ingredients)
 97 def main [
 98   f 2
 99 ]
100 def f [
101   12:num <- next-ingredient  # consume ingredient
102   _, 1:bool <- next-ingredient  # will not find any ingredients
103   rewind-ingredients
104   13:num, 2:bool <- next-ingredient  # will find ingredient again
105 ]
106 +mem: storing 2 in location 12
107 +mem: storing 0 in location 1
108 +mem: storing 2 in location 13
109 +mem: storing 1 in location 2
110 
111 :(before "End Primitive Recipe Declarations")
112 REWIND_INGREDIENTS,
113 :(before "End Primitive Recipe Numbers")
114 put(Recipe_ordinal, "rewind-ingredients", REWIND_INGREDIENTS);
115 :(before "End Primitive Recipe Checks")
116 case REWIND_INGREDIENTS: {
117   break;
118 }
119 :(before "End Primitive Recipe Implementations")
120 case REWIND_INGREDIENTS: {
121   current_call().next_ingredient_to_process = 0;
122   break;
123 }
124 
125 :(scenario ingredient)
126 def main [
127   f 1, 2
128 ]
129 def f [
130   12:num <- ingredient 1  # consume second ingredient first
131   13:num, 1:bool <- next-ingredient  # next-ingredient tries to scan past that
132 ]
133 +mem: storing 2 in location 12
134 +mem: storing 0 in location 1
135 
136 :(before "End Primitive Recipe Declarations")
137 INGREDIENT,
138 :(before "End Primitive Recipe Numbers")
139 put(Recipe_ordinal, "ingredient", INGREDIENT);
140 :(before "End Primitive Recipe Checks")
141 case INGREDIENT: {
142   if (SIZE(inst.ingredients) != 1) {
143     raise << maybe(get(Recipe, r).name) << "'ingredient' expects exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
144     break;
145   }
146   if (!is_literal(inst.ingredients.at(0)) && !is_mu_number(inst.ingredients.at(0))) {
147     raise << maybe(get(Recipe, r).name) << "'ingredient' expects a literal ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
148     break;
149   }
150   break;
151 }
152 :(before "End Primitive Recipe Implementations")
153 case INGREDIENT: {
154   if (static_cast<int>(ingredients.at(0).at(0)) < SIZE(current_call().ingredient_atoms)) {
155     current_call().next_ingredient_to_process = ingredients.at(0).at(0);
156     products.push_back(
157         current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
158     assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
159     products.at(1).push_back(1);
160     ++current_call().next_ingredient_to_process;
161   }
162   else {
163     if (SIZE(current_instruction().products) > 1) {
164       products.resize(2);
165       products.at(0).push_back(0);  // todo: will fail noisily if we try to read a compound value
166       products.at(1).push_back(0);
167     }
168   }
169   break;
170 }
171 
172 //: a particularly common array type is the text, or address:array:character
173 :(code)
174 bool is_mu_text(reagent/*copy*/ x) {
175   // End Preprocess is_mu_text(reagent x)
176   return x.type
177       && !x.type->atom
178       && x.type->left->atom
179       && x.type->left->value == get(Type_ordinal, "address")
180       && x.type->right
181       && !x.type->right->atom
182       && x.type->right->left->atom
183       && x.type->right->left->value == get(Type_ordinal, "array")
184       && x.type->right->right
185       && !x.type->right->right->atom
186       && x.type->right->right->left->value == get(Type_ordinal, "character")
187       && x.type->right->right->right == NULL;
188 }