# Mu - 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 }