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