https://github.com/akkartik/mu/blob/master/027call_ingredient.cc
  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 //: another primitive: 'ingredient' for random access
128 
129 :(scenario ingredient)
130 def main [
131   f 1, 2
132 ]
133 def f [
134   12:num <- ingredient 1  # consume second ingredient first
135   13:num, 1:bool <- next-ingredient  # next-ingredient tries to scan past that
136 ]
137 +mem: storing 2 in location 12
138 +mem: storing 0 in location 1
139 
140 :(before "End Primitive Recipe Declarations")
141 INGREDIENT,
142 :(before "End Primitive Recipe Numbers")
143 put(Recipe_ordinal, "ingredient", INGREDIENT);
144 put(Recipe_ordinal, "input", INGREDIENT);
145 :(before "End Primitive Recipe Checks")
146 case INGREDIENT: {
147   if (SIZE(inst.ingredients) != 1) {
148     raise << maybe(get(Recipe, r).name) << "'ingredient' expects exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
149     break;
150   }
151   if (!is_literal(inst.ingredients.at(0)) && !is_mu_number(inst.ingredients.at(0))) {
152     raise << maybe(get(Recipe, r).name) << "'ingredient' expects a literal ingredient, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
153     break;
154   }
155   break;
156 }
157 :(before "End Primitive Recipe Implementations")
158 case INGREDIENT: {
159   if (static_cast<int>(ingredients.at(0).at(0)) < SIZE(current_call().ingredient_atoms)) {
160     current_call().next_ingredient_to_process = ingredients.at(0).at(0);
161     products.push_back(
162         current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
163     assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
164     products.at(1).push_back(1);
165     ++current_call().next_ingredient_to_process;
166   }
167   else {
168     if (SIZE(current_instruction().products) > 1) {
169       products.resize(2);
170       products.at(0).push_back(0);  // todo: will fail noisily if we try to read a compound value
171       products.at(1).push_back(0);
172     }
173   }
174   break;
175 }
176 
177 //: a particularly common array type is the text, or address:array:character
178 :(code)
179 bool is_mu_text(reagent/*copy*/ x) {
180   // End Preprocess is_mu_text(reagent x)
181   return x.type
182       && !x.type->atom
183       && x.type->left->atom
184       && x.type->left->value == Address_type_ordinal
185       && x.type->right
186       && !x.type->right->atom
187       && x.type->right->left->atom
188       && x.type->right->left->value == Array_type_ordinal
189       && x.type->right->right
190       && !x.type->right->right->atom
191       && x.type->right->right->left->value == Character_type_ordinal
192       && x.type->right->right->right == NULL;
193 }