https://github.com/akkartik/mu/blob/master/033exclusive_container.cc

      1 //: Exclusive containers contain exactly one of a fixed number of 'variants'
      2 //: of different types.
      3 //:
      4 //: They also implicitly contain a tag describing precisely which variant is
      5 //: currently stored in them.
      6
      7 :(before "End Mu Types Initialization")
      8 //: We'll use this container as a running example, with two number elements.
      9 {
     10 type_ordinal tmp = put(Type_ordinal, "number-or-point", Next_type_ordinal++);
     11 get_or_insert(Type, tmp);  // initialize
     12 get(Type, tmp).kind = EXCLUSIVE_CONTAINER;
     13 get(Type, tmp).name = "number-or-point";
     14 get(Type, tmp).elements.push_back(reagent("i:number"));
     15 get(Type, tmp).elements.push_back(reagent("p:point"));
     16 }
     17
     18 //: Tests in this layer often explicitly set up memory before reading it as a
     19 //: container. Don't do this in general. I'm tagging such cases with /unsafe;
     20 //: they'll be exceptions to later checks.
     21 :(scenario copy_exclusive_container)
     22 # Copying exclusive containers copies all their contents and an extra location for the tag.
     23 def main [
     24   1:num <- copy 1  # 'point' variant
     25   2:num <- copy 34
     26   3:num <- copy 35
     27   4:number-or-point <- copy 1:number-or-point/unsafe
     28 ]
     29 +mem: storing 1 in location 4
     30 +mem: storing 34 in location 5
     31 +mem: storing 35 in location 6
     32
     33 :(before "End size_of(type) Special-cases")
     34 if (t.kind == EXCLUSIVE_CONTAINER) {
     35   // size of an exclusive container is the size of its largest variant
     36   // (So like containers, it can't contain arrays.)
     37   int result = 0;
     38   for (int i = 0; i < SIZE(t.elements); ++i) {
     39     reagent tmp;
     40     tmp.type = new type_tree(*type);
     41     int size = size_of(variant_type(tmp, i));
     42     if (size > result) result = size;
     43   }
     44   // ...+1 for its tag.
     45   return result+1;
     46 }
     47
     48 //:: To access variants of an exclusive container, use 'maybe-convert'.
     49 //: It always returns an address (so that you can modify it) or null (to
     50 //: signal that the conversion failed (because the container contains a
     51 //: different variant).
     52
     53 //: 'maybe-convert' requires a literal in ingredient 1. We'll use a synonym
     54 //: called 'variant'.
     55 :(before "End Mu Types Initialization")
     56 put(Type_ordinal, "variant", 0);
     57
     58 :(scenario maybe_convert)
     59 def main [
     60   12:num <- copy 1
     61   13:num <- copy 35
     62   14:num <- copy 36
     63   20:point, 22:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant
     64 ]
     65 # boolean
     66 +mem: storing 1 in location 22
     67 # point
     68 +mem: storing 35 in location 20
     69 +mem: storing 36 in location 21
     70
     71 :(scenario maybe_convert_fail)
     72 def main [
     73   12:num <- copy 1
     74   13:num <- copy 35
     75   14:num <- copy 36
     76   20:num, 21:bool <- maybe-convert 12:number-or-point/unsafe, 0:variant
     77 ]
     78 # boolean
     79 +mem: storing 0 in location 21
     80 # number: no write
     81
     82 :(before "End Primitive Recipe Declarations")
     83 MAYBE_CONVERT,
     84 :(before "End Primitive Recipe Numbers")
     85 put(Recipe_ordinal, "maybe-convert", MAYBE_CONVERT);
     86 :(before "End Primitive Recipe Checks")
     87 case MAYBE_CONVERT: {
     88   const recipe& caller = get(Recipe, r);
     89   if (SIZE(inst.ingredients) != 2) {
     90     raise << maybe(caller.name) << "'maybe-convert' expects exactly 2 ingredients in '" << to_original_string(inst) << "'\n" << end();
     91     break;
     92   }
     93   reagent/*copy*/ base = inst.ingredients.at(0);
     94   // Update MAYBE_CONVERT base in Check
     95   if (!base.type) {
     96     raise << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got '" << base.original_string << "'\n" << end();
     97     break;
     98   }
     99   const type_tree* base_type = base.type;
    100   // Update MAYBE_CONVERT base_type in Check
    101   if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != EXCLUSIVE_CONTAINER) {
    102     raise << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got '" << base.original_string << "'\n" << end();
    103     break;
    104   }
    105   if (!is_literal(inst.ingredients.at(1))) {
    106     raise << maybe(caller.name) << "second ingredient of 'maybe-convert' should have type 'variant', but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
    107     break;
    108   }
    109   if (inst.products.empty()) break;
    110   if (SIZE(inst.products) != 2) {
    111     raise << maybe(caller.name) << "'maybe-convert' expects exactly 2 products in '" << to_original_string(inst) << "'\n" << end();
    112     break;
    113   }
    114   reagent/*copy*/ product = inst.products.at(0);
    115   // Update MAYBE_CONVERT product in Check
    116   reagent& offset = inst.ingredients.at(1);
    117   populate_value(offset);
    118   if (offset.value >= SIZE(get(Type, base_type->value).elements)) {
    119     raise << maybe(caller.name) << "invalid tag " << offset.value << " in '" << to_original_string(inst) << '\n' << end();
    120     break;
    121   }
    122   const reagent& variant = variant_type(base, offset.value);
    123   if (!types_coercible(product, variant)) {
    124     raise << maybe(caller.name) << "'maybe-convert " << base.original_string << ", " << inst.ingredients.at(1).original_string << "' should write to " << to_string(variant.type) << " but '" << product.name << "' has type " << to_string(product.type) << '\n' << end();
    125     break;
    126   }
    127   reagent/*copy*/ status = inst.products.at(1);
    128   // Update MAYBE_CONVERT status in Check
    129   if (!is_mu_boolean(status)) {
    130     raise << maybe(get(Recipe, r).name) << "second product yielded by 'maybe-convert' should be a boolean, but tried to write to '" << inst.products.at(1).original_string << "'\n" << end();
    131     break;
    132   }
    133   break;
    134 }
    135 :(before "End Primitive Recipe Implementations")
    136 case MAYBE_CONVERT: {
    137   reagent/*copy*/ base = current_instruction().ingredients.at(0);
    138   // Update MAYBE_CONVERT base in Run
    139   int base_address = base.value;
    140   if (base_address == 0) {
    141     raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
    142     break;
    143   }
    144   int tag = current_instruction().ingredients.at(1).value;
    145   reagent/*copy*/ product = current_instruction().products.at(0);
    146   // Update MAYBE_CONVERT product in Run
    147   reagent/*copy*/ status = current_instruction().products.at(1);
    148   // Update MAYBE_CONVERT status in Run
    149   // optimization: directly write results to only update first product when necessary
    150   write_products = false;
    151   if (tag == static_cast<int>(get_or_insert(Memory, base_address))) {
    152     const reagent& variant = variant_type(base, tag);
    153     trace("mem") << "storing 1 in location " << status.value << end();
    154     put(Memory, status.value, 1);
    155     if (!is_dummy(product)) {
    156       // Write Memory in Successful MAYBE_CONVERT in Run
    157       for (int i = 0;  i < size_of(variant);  ++i) {
    158         double val = get_or_insert(Memory, base_address+/*skip tag*/1+i);
    159         trace("mem") << "storing " << no_scientific(val) << " in location " << product.value+i << end();
    160         put(Memory, product.value+i, val);
    161       }
    162     }
    163   }
    164   else {
    165     trace("mem") << "storing 0 in location " << status.value << end();
    166     put(Memory, status.value, 0);
    167   }
    168   break;
    169 }
    170
    171 :(code)
    172 const reagent variant_type(const reagent& base, int tag) {
    173   return variant_type(base.type, tag);
    174 }
    175
    176 const reagent variant_type(const type_tree* type, int tag) {
    177   assert(tag >= 0);
    178   const type_tree* root_type = type->atom ? type : type->left;
    179   assert(contains_key(Type, root_type->value));
    180   assert(!get(Type, root_type->value).name.empty());
    181   const type_info& info = get(Type, root_type->value);
    182   assert(info.kind == EXCLUSIVE_CONTAINER);
    183   reagent/*copy*/ element = info.elements.at(tag);
    184   // End variant_type Special-cases
    185   return element;
    186 }
    187
    188 :(scenario maybe_convert_product_type_mismatch)
    189 % Hide_errors = true;
    190 def main [
    191   12:num <- copy 1
    192   13:num <- copy 35
    193   14:num <- copy 36
    194   20:num, 21:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant
    195 ]
    196 +error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to point but '20' has type number
    197
    198 :(scenario maybe_convert_dummy_product)
    199 def main [
    200   12:num <- copy 1
    201   13:num <- copy 35
    202   14:num <- copy 36
    203   _, 21:bool <- maybe-convert 12:number-or-point/unsafe, 1:variant
    204 ]
    205 $error: 0
    206
    207 //:: Allow exclusive containers to be defined in Mu code.
    208
    209 :(scenario exclusive_container)
    210 exclusive-container foo [
    211   x:num
    212   y:num
    213 ]
    214 +parse: --- defining exclusive-container foo
    215 +parse: element: {x: "number"}
    216 +parse: element: {y: "number"}
    217
    218 :(before "End Command Handlers")
    219 else if (command == "exclusive-container") {
    220   insert_container(command, EXCLUSIVE_CONTAINER, in);
    221 }
    222
    223 //: arrays are disallowed inside exclusive containers unless their length is
    224 //: fixed in advance
    225
    226 :(scenario exclusive_container_contains_array)
    227 exclusive-container foo [
    228   x:@:num:3
    229 ]
    230 $error: 0
    231
    232 :(scenario exclusive_container_disallows_dynamic_array_element)
    233 % Hide_errors = true;
    234 exclusive-container foo [
    235   x:@:num
    236 ]
    237 +error: container 'foo' cannot determine size of element 'x'
    238
    239 //:: To construct exclusive containers out of variant types, use 'merge'.
    240 :(scenario lift_to_exclusive_container)
    241 exclusive-container foo [
    242   x:num
    243   y:num
    244 ]
    245 def main [
    246   1:num <- copy 34
    247   2:foo <- merge 0/x, 1:num  # tag must be a literal when merging exclusive containers
    248   4:foo <- merge 1/y, 1:num
    249 ]
    250 +mem: storing 0 in location 2
    251 +mem: storing 34 in location 3
    252 +mem: storing 1 in location 4
    253 +mem: storing 34 in location 5
    254
    255 //: type-checking for 'merge' on exclusive containers
    256
    257 :(scenario merge_handles_exclusive_container)
    258 exclusive-container foo [
    259   x:num
    260   y:bar
    261 ]
    262 container bar [
    263   z:num
    264 ]
    265 def main [
    266   1:foo <- merge 0/x, 34
    267 ]
    268 +mem: storing 0 in location 1
    269 +mem: storing 34 in location 2
    270 $error: 0
    271
    272 :(scenario merge_requires_literal_tag_for_exclusive_container)
    273 % Hide_errors = true;
    274 exclusive-container foo [
    275   x:num
    276   y:bar
    277 ]
    278 container bar [
    279   z:num
    280 ]
    281 def main [
    282   1:num <- copy 0
    283   2:foo <- merge 1:num, 34
    284 ]
    285 +error: main: ingredient 0 of 'merge' should be a literal, for the tag of exclusive-container 'foo' in '2:foo <- merge 1:num, 34'
    286
    287 :(scenario merge_handles_exclusive_container_inside_exclusive_container)
    288 exclusive-container foo [
    289   x:num
    290   y:bar
    291 ]
    292 exclusive-container bar [
    293   a:num
    294   b:num
    295 ]
    296 def main [
    297   1:num <- copy 0
    298   2:bar <- merge 0/a, 34
    299   4:foo <- merge 1/y, 2:bar
    300 ]
    301 +mem: storing 0 in location 5
    302 +mem: storing 34 in location 6
    303 $error: 0
    304
    305 :(before "End check_merge_call Special-cases")
    306 case EXCLUSIVE_CONTAINER: {
    307   assert(state.data.top().container_element_index == 0);
    308   trace("transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end();
    309   // easy case: exact match
    310   if (types_strictly_match(container, inst.ingredients.at(ingredient_index)))
    311     return;
    312   if (!is_literal(ingredients.at(ingredient_index))) {
    313     raise << maybe(caller.name) << "ingredient " << ingredient_index << " of 'merge' should be a literal, for the tag of exclusive-container '" << container_info.name << "' in '" << to_original_string(inst) << "'\n" << end();
    314     return;
    315   }
    316   reagent/*copy*/ ingredient = ingredients.at(ingredient_index);  // unnecessary copy just to keep this function from modifying caller
    317   populate_value(ingredient);
    318   if (ingredient.value >= SIZE(container_info.elements)) {
    319     raise << maybe(caller.name) << "invalid tag at " << ingredient_index << " for '" << container_info.name << "' in '" << to_original_string(inst) << "'\n" << end();
    320     return;
    321   }
    322   const reagent& variant = variant_type(container, ingredient.value);
    323   trace("transform") << "tag: " << ingredient.value << end();
    324   // replace union with its variant
    325   state.data.pop();
    326   state.data.push(merge_check_point(variant, 0));
    327   ++ingredient_index;
    328   break;
    329 }
    330
    331 :(scenario merge_check_container_containing_exclusive_container)
    332 container foo [
    333   x:num
    334   y:bar
    335 ]
    336 exclusive-container bar [
    337   x:num
    338   y:num
    339 ]
    340 def main [
    341   1:foo <- merge 23, 1/y, 34
    342 ]
    343 +mem: storing 23 in location 1
    344 +mem: storing 1 in location 2
    345 +mem: storing 34 in location 3
    346 $error: 0
    347
    348 :(scenario merge_check_container_containing_exclusive_container_2)
    349 % Hide_errors = true;
    350 container foo [
    351   x:num
    352   y:bar
    353 ]
    354 exclusive-container bar [
    355   x:num
    356   y:num
    357 ]
    358 def main [
    359   1:foo <- merge 23, 1/y, 34, 35
    360 ]
    361 +error: main: too many ingredients in '1:foo <- merge 23, 1/y, 34, 35'
    362
    363 :(scenario merge_check_exclusive_container_containing_container)
    364 exclusive-container foo [
    365   x:num
    366   y:bar
    367 ]
    368 container bar [
    369   x:num
    370   y:num
    371 ]
    372 def main [
    373   1:foo <- merge 1/y, 23, 34
    374 ]
    375 +mem: storing 1 in location 1
    376 +mem: storing 23 in location 2
    377 +mem: storing 34 in location 3
    378 $error: 0
    379
    380 :(scenario merge_check_exclusive_container_containing_container_2)
    381 exclusive-container foo [
    382   x:num
    383   y:bar
    384 ]
    385 container bar [
    386   x:num
    387   y:num
    388 ]
    389 def main [
    390   1:foo <- merge 0/x, 23
    391 ]
    392 $error: 0
    393
    394 :(scenario merge_check_exclusive_container_containing_container_3)
    395 % Hide_errors = true;
    396 exclusive-container foo [
    397   x:num
    398   y:bar
    399 ]
    400 container bar [
    401   x:num
    402   y:num
    403 ]
    404 def main [
    405   1:foo <- merge 1/y, 23
    406 ]
    407 +error: main: too few ingredients in '1:foo <- merge 1/y, 23'
    408
    409 :(scenario merge_check_exclusive_container_containing_container_4)
    410 exclusive-container foo [
    411   x:num
    412   y:bar
    413 ]
    414 container bar [
    415   a:num
    416   b:num
    417 ]
    418 def main [
    419   1:bar <- merge 23, 24
    420   3:foo <- merge 1/y, 1:bar
    421 ]
    422 $error: 0
    423
    424 //: Since the different variants of an exclusive-container might have
    425 //: different sizes, relax the size mismatch check for 'merge' instructions.
    426 :(before "End size_mismatch(x) Special-cases")
    427 if (current_step_index() < SIZE(Current_routine->steps())
    428     && current_instruction().operation == MERGE
    429     && !current_instruction().products.empty()
    430     && current_instruction().products.at(0).type) {
    431   reagent/*copy*/ x = current_instruction().products.at(0);
    432   // Update size_mismatch Check for MERGE(x)
    433   const type_tree* root_type = x.type->atom ? x.type : x.type->left;
    434   assert(root_type->atom);
    435   if (get(Type, root_type->value).kind == EXCLUSIVE_CONTAINER)
    436     return size_of(x) < SIZE(data);
    437 }
    438
    439 :(scenario merge_exclusive_container_with_mismatched_sizes)
    440 container foo [
    441   x:num
    442   y:num
    443 ]
    444 exclusive-container bar [
    445   x:num
    446   y:foo
    447 ]
    448 def main [
    449   1:num <- copy 34
    450   2:num <- copy 35
    451   3:bar <- merge 0/x, 1:num
    452   6:bar <- merge 1/foo, 1:num, 2:num
    453 ]
    454 +mem: storing 0 in location 3
    455 +mem: storing 34 in location 4
    456 # bar is always 3 large so location 5 is skipped
    457 +mem: storing 1 in location 6
    458 +mem: storing 34 in location 7
    459 +mem: storing 35 in location 8