# Mu - 062convert_ingredients_to_text.cc

      1 //: make some recipes more friendly by trying to auto-convert their ingredients to text
      2
      3 :(scenarios transform)
      4 :(scenario rewrite_stashes_to_text)
      5 def main [
      6   local-scope
      7   n:num <- copy 34
      8   stash n
      9 ]
     10 +transform: {stash_2_0: ("address" "array" "character")} <- to-text-line {n: "number"}
     11 +transform: stash {stash_2_0: ("address" "array" "character")}
     12
     13 :(scenario rewrite_traces_to_text)
     14 def main [
     15   local-scope
     16   n:num <- copy 34
     17   trace 2, [app], n
     18 ]
     19 +transform: {trace_2_2: ("address" "array" "character")} <- to-text-line {n: "number"}
     20 +transform: trace {2: "literal"}, {"app": "literal-string"}, {trace_2_2: ("address" "array" "character")}
     21
     22 //: special case: rewrite attempts to stash contents of most arrays to avoid
     23 //: passing addresses around
     24
     25 :(scenario rewrite_stashes_of_arrays)
     26 def main [
     27   local-scope
     28   n:&:@:num <- new number:type, 3
     29   stash *n
     30 ]
     31 +transform: {stash_2_0: ("address" "array" "character")} <- array-to-text-line {n: ("address" "array" "number")}
     32 +transform: stash {stash_2_0: ("address" "array" "character")}
     33
     34 :(scenario ignore_stashes_of_static_arrays)
     35 def main [
     36   local-scope
     37   n:@:num:3 <- create-array
     38   stash n
     39 ]
     40 +transform: stash {n: ("array" "number" "3")}
     41
     42 :(scenario rewrite_stashes_of_recipe_header_products)
     43 container foo [
     44   x:num
     45 ]
     46 def bar -> x:foo [
     47   local-scope
     48   load-ingredients
     49   x <- merge 34
     50   stash x
     51 ]
     52 +transform: stash {stash_2_0: ("address" "array" "character")}
     53
     54 //: misplaced; should be in instruction inserting/deleting transforms, but has
     55 //: prerequisites: deduce_types_from_header and check_or_set_types_by_name
     56 :(after "Transform.push_back(deduce_types_from_header)")
     57 Transform.push_back(convert_ingredients_to_text);  // idempotent
     58
     59 :(code)
     60 void convert_ingredients_to_text(const recipe_ordinal r) {
     61   recipe& caller = get(Recipe, r);
     62   trace(9991, "transform") << "--- convert some ingredients to text in recipe " << caller.name << end();
     63   // in recipes without named locations, 'stash' is still not extensible
     64   if (contains_numeric_locations(caller)) return;
     65   convert_ingredients_to_text(caller);
     66 }
     67
     68 void convert_ingredients_to_text(recipe& caller) {
     69   vector<instruction> new_instructions;
     70   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
     71     instruction& inst = caller.steps.at(i);
     72     // all these cases are getting hairy. how can we make this extensible?
     73     if (inst.name == "stash") {
     74       for (int j = 0;  j < SIZE(inst.ingredients);  ++j) {
     75         if (is_literal_text(inst.ingredients.at(j))) continue;
     76         ostringstream ingredient_name;
     77         ingredient_name << "stash_" << i << '_' << j << ":address:array:character";
     78         convert_ingredient_to_text(inst.ingredients.at(j), new_instructions, ingredient_name.str());
     79       }
     80     }
     81     else if (inst.name == "trace") {
     82       for (int j = /*skip*/2;  j < SIZE(inst.ingredients);  ++j) {
     83         if (is_literal_text(inst.ingredients.at(j))) continue;
     84         ostringstream ingredient_name;
     85         ingredient_name << "trace_" << i << '_' << j << ":address:array:character";
     86         convert_ingredient_to_text(inst.ingredients.at(j), new_instructions, ingredient_name.str());
     87       }
     88     }
     89     else if (inst.name_before_rewrite == "append") {
     90       // override only variants that try to append to a string
     91       // Beware: this hack restricts how much 'append' can be overridden. Any
     92       // new variants that match:
     93       //   append _:text, ___
     94       // will never ever get used.
     95       if (is_literal_text(inst.ingredients.at(0)) || is_mu_text(inst.ingredients.at(0))) {
     96         for (int j = /*skip base*/1;  j < SIZE(inst.ingredients);  ++j) {
     97           ostringstream ingredient_name;
     98           ingredient_name << "append_" << i << '_' << j << ":address:array:character";
     99           convert_ingredient_to_text(inst.ingredients.at(j), new_instructions, ingredient_name.str());
    100         }
    101       }
    102     }
    103     trace(9993, "transform") << to_string(inst) << end();
    104     new_instructions.push_back(inst);
    105   }
    106   caller.steps.swap(new_instructions);
    107 }
    108
    109 // add an instruction to convert reagent 'r' to text in list 'out', then
    110 // replace r with converted text
    111 void convert_ingredient_to_text(reagent& r, vector<instruction>& out, const string& tmp_var) {
    112   if (!r.type) return;  // error; will be handled elsewhere
    113   if (is_mu_text(r)) return;
    114   // don't try to extend static arrays
    115   if (is_static_array(r)) return;
    116   instruction def;
    117   if (is_lookup_of_address_of_array(r)) {
    118     def.name = "array-to-text-line";
    119     reagent/*copy*/ tmp = r;
    120     drop_one_lookup(tmp);
    121     def.ingredients.push_back(tmp);
    122   }
    123   else {
    124     def.name = "to-text-line";
    125     def.ingredients.push_back(r);
    126   }
    127   def.products.push_back(reagent(tmp_var));
    128   trace(9993, "transform") << to_string(def) << end();
    129   out.push_back(def);
    130   r.clear();  // reclaim old memory
    131   r = reagent(tmp_var);
    132 }
    133
    134 bool is_lookup_of_address_of_array(reagent/*copy*/ x) {
    135   if (x.type->atom) return false;
    136   if (x.type->left->name != "address") return false;
    137   if (!canonize_type(x)) return false;
    138   return is_mu_array(x);
    139 }
    140
    141 bool is_static_array(const reagent& x) {
    142   // no canonize_type()
    143   return !x.type->atom && x.type->left->atom && x.type->left->name == "array";
    144 }
    145
    146 //: Supporting 'append' above requires remembering what name an instruction
    147 //: had before any rewrites or transforms.
    148 :(before "End instruction Fields")
    149 string name_before_rewrite;
    150 :(before "End instruction Clear")
    151 name_before_rewrite.clear();
    152 :(before "End next_instruction(curr)")
    153 curr->name_before_rewrite = curr->name;
    154
    155 :(scenarios run)
    156 :(scenario append_other_types_to_text)
    157 def main [
    158   local-scope
    159   n:num <- copy 11
    160   c:char <- copy 111/o
    161   a:text <- append [abc], 10, n, c
    162   expected:text <- new [abc1011o]
    163   10:bool/raw <- equal a, expected
    164 ]
    165
    166 //: Make sure that the new system is strictly better than just the 'stash'
    167 //: primitive by itself.
    168
    169 :(scenario rewrite_stash_continues_to_fall_back_to_default_implementation)
    170 # type without a to-text implementation
    171 container foo [
    172   x:num
    173   y:num
    174 ]
    175 def main [
    176   local-scope
    177   x:foo <- merge 34, 35
    178   stash x
    179 ]
    180 +app: 34 35