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 << "'\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 }