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