# Mu - 031merge.cc

      1 //: Construct types out of their constituent fields.
      2
      3 :(scenario merge)
      4 container foo [
      5   x:num
      6   y:num
      7 ]
      8 def main [
      9   1:foo <- merge 3, 4
     10 ]
     11 +mem: storing 3 in location 1
     12 +mem: storing 4 in location 2
     13
     14 :(before "End Primitive Recipe Declarations")
     15 MERGE,
     16 :(before "End Primitive Recipe Numbers")
     17 put(Recipe_ordinal, "merge", MERGE);
     18 :(before "End Primitive Recipe Checks")
     19 case MERGE: {
     20   // type-checking in a separate transform below
     21   break;
     22 }
     23 :(before "End Primitive Recipe Implementations")
     24 case MERGE: {
     25   products.resize(1);
     26   for (int i = 0;  i < SIZE(ingredients);  ++i)
     27     for (int j = 0;  j < SIZE(ingredients.at(i));  ++j)
     28       products.at(0).push_back(ingredients.at(i).at(j));
     29   break;
     30 }
     31
     32 //: type-check 'merge' to avoid interpreting numbers as addresses
     33
     34 :(scenario merge_check)
     35 def main [
     36   1:point <- merge 3, 4
     37 ]
     38 $error: 0
     39
     40 :(scenario merge_check_missing_element)
     41 % Hide_errors = true;
     42 def main [
     43   1:point <- merge 3
     44 ]
     45 +error: main: too few ingredients in '1:point <- merge 3'
     46
     47 :(scenario merge_check_extra_element)
     48 % Hide_errors = true;
     49 def main [
     50   1:point <- merge 3, 4, 5
     51 ]
     52 +error: main: too many ingredients in '1:point <- merge 3, 4, 5'
     53
     54 //: We want to avoid causing memory corruption, but other than that we want to
     55 //: be flexible in how we construct containers of containers. It should be
     56 //: equally easy to define a container out of primitives or intermediate
     57 //: container fields.
     58
     59 :(scenario merge_check_recursive_containers)
     60 def main [
     61   1:point <- merge 3, 4
     62   1:point-number <- merge 1:point, 5
     63 ]
     64 $error: 0
     65
     66 :(scenario merge_check_recursive_containers_2)
     67 % Hide_errors = true;
     68 def main [
     69   1:point <- merge 3, 4
     70   2:point-number <- merge 1:point
     71 ]
     72 +error: main: too few ingredients in '2:point-number <- merge 1:point'
     73
     74 :(scenario merge_check_recursive_containers_3)
     75 def main [
     76   1:point-number <- merge 3, 4, 5
     77 ]
     78 $error: 0
     79
     80 :(scenario merge_check_recursive_containers_4)
     81 % Hide_errors = true;
     82 def main [
     83   1:point-number <- merge 3, 4
     84 ]
     85 +error: main: too few ingredients in '1:point-number <- merge 3, 4'
     86
     87 :(scenario merge_check_reflexive)
     88 % Hide_errors = true;
     89 def main [
     90   1:point <- merge 3, 4
     91   2:point <- merge 1:point
     92 ]
     93 $error: 0
     94
     95 //: Since a container can be merged in several ways, we need to be able to
     96 //: backtrack through different possibilities. Later we'll allow creating
     97 //: exclusive containers which contain just one of rather than all of their
     98 //: elements. That will also require backtracking capabilities. Here's the
     99 //: state we need to maintain for backtracking:
    100
    101 :(before "End Types")
    102 struct merge_check_point {
    103   reagent container;
    104   int container_element_index;
    105   merge_check_point(const reagent& c, int i) :container(c), container_element_index(i) {}
    106 };
    107
    108 struct merge_check_state {
    109   stack<merge_check_point> data;
    110 };
    111
    112 :(before "End Checks")
    113 Transform.push_back(check_merge_calls);  // idempotent
    114 :(code)
    115 void check_merge_calls(const recipe_ordinal r) {
    116   const recipe& caller = get(Recipe, r);
    117   trace(9991, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
    118   for (int i = 0;  i < SIZE(caller.steps);  ++i) {
    119     const instruction& inst = caller.steps.at(i);
    120     if (inst.name != "merge") continue;
    121     if (SIZE(inst.products) != 1) {
    122       raise << maybe(caller.name) << "'merge' should yield a single product in '" << to_original_string(inst) << "'\n" << end();
    123       continue;
    124     }
    125     reagent/*copy*/ product = inst.products.at(0);
    126     // Update product While Type-checking Merge
    127     const type_tree* product_base_type = product.type->atom ? product.type : product.type->left;
    128     assert(product_base_type->atom);
    129     if (product_base_type->value == 0 || !contains_key(Type, product_base_type->value)) {
    130       raise << maybe(caller.name) << "'merge' should yield a container in '" << to_original_string(inst) << "'\n" << end();
    131       continue;
    132     }
    133     const type_info& info = get(Type, product_base_type->value);
    134     if (info.kind != CONTAINER && info.kind != EXCLUSIVE_CONTAINER) {
    135       raise << maybe(caller.name) << "'merge' should yield a container in '" << to_original_string(inst) << "'\n" << end();
    136       continue;
    137     }
    138     check_merge_call(inst.ingredients, product, caller, inst);
    139   }
    140 }
    141
    142 void check_merge_call(const vector<reagent>& ingredients, const reagent& product, const recipe& caller, const instruction& inst) {
    143   int ingredient_index = 0;
    144   merge_check_state state;
    145   state.data.push(merge_check_point(product, 0));
    146   while (true) {
    147     assert(!state.data.empty());
    148     trace("transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
    149     if (ingredient_index >= SIZE(ingredients)) {
    150       raise << maybe(caller.name) << "too few ingredients in '" << to_original_string(inst) << "'\n" << end();
    151       return;
    152     }
    153     reagent& container = state.data.top().container;
    154     if (!container.type) return;  // error handled elsewhere
    155     const type_tree* top_root_type = container.type->atom ? container.type : container.type->left;
    156     assert(top_root_type->atom);
    157     type_info& container_info = get(Type, top_root_type->value);
    158     switch (container_info.kind) {
    159       case CONTAINER: {
    160         // degenerate case: merge with the same type always succeeds
    161         if (state.data.top().container_element_index == 0 && types_coercible(container, inst.ingredients.at(ingredient_index)))
    162           return;
    163         const reagent& expected_ingredient = element_type(container.type, state.data.top().container_element_index);
    164         trace("transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
    165         // if the current element is the ingredient we expect, move on to the next element/ingredient
    166         if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) {
    167           ++ingredient_index;
    168           ++state.data.top().container_element_index;
    169           while (state.data.top().container_element_index >= SIZE(get(Type, get_base_type(state.data.top().container.type)->value).elements)) {
    170             state.data.pop();
    171             if (state.data.empty()) {
    172               if (ingredient_index < SIZE(ingredients))
    173                 raise << maybe(caller.name) << "too many ingredients in '" << to_original_string(inst) << "'\n" << end();
    174               return;
    175             }
    176             ++state.data.top().container_element_index;
    177           }
    178         }
    179         // if not, maybe it's a field of the current element
    180         else {
    181           // no change to ingredient_index
    182           state.data.push(merge_check_point(expected_ingredient, 0));
    183         }
    184         break;
    185       }
    186       // End check_merge_call Special-cases
    187       default: {
    188         if (!types_coercible(container, ingredients.at(ingredient_index))) {
    189           raise << maybe(caller.name) << "incorrect type of ingredient " << ingredient_index << " in '" << to_original_string(inst) << "'\n" << end();
    190           raise << "  (expected '" << debug_string(container) << "')\n" << end();
    191           raise << "  (got '" << debug_string(ingredients.at(ingredient_index)) << "')\n" << end();
    192           return;
    193         }
    194         ++ingredient_index;
    195         // ++state.data.top().container_element_index;  // unnecessary, but wouldn't do any harm
    196         do {
    197           state.data.pop();
    198           if (state.data.empty()) {
    199             if (ingredient_index < SIZE(ingredients))
    200               raise << maybe(caller.name) << "too many ingredients in '" << to_original_string(inst) << "'\n" << end();
    201             return;
    202           }
    203           ++state.data.top().container_element_index;
    204         } while (state.data.top().container_element_index >= SIZE(get(Type, get_base_type(state.data.top().container.type)->value).elements));
    205       }
    206     }
    207   }
    208   // never gets here
    209   assert(false);
    210 }
    211
    212 //: replaced in a later layer
    213 //: todo: find some clean way to take this call completely out of this layer
    214 const type_tree* get_base_type(const type_tree* t) {
    215   return t;
    216 }
    217
    218 :(scenario merge_check_product)
    219 % Hide_errors = true;
    220 def main [
    221   1:num <- merge 3
    222 ]
    223 +error: main: 'merge' should yield a container in '1:num <- merge 3'
    224
    225 :(before "End Includes")
    226 #include <stack>
    227 using std::stack;