https://github.com/akkartik/mu1/blob/master/030container.cc
  1 //: Containers contain a fixed number of elements of different types.
  2 
  3 :(before "End Mu Types Initialization")
  4 //: We'll use this container as a running example in scenarios below.
  5 type_ordinal point = put(Type_ordinal, "point", Next_type_ordinal++);
  6 get_or_insert(Type, point);  // initialize
  7 get(Type, point).kind = CONTAINER;
  8 get(Type, point).name = "point";
  9 get(Type, point).elements.push_back(reagent("x:number"));
 10 get(Type, point).elements.push_back(reagent("y:number"));
 11 
 12 //: Containers can be copied around with a single instruction just like
 13 //: numbers, no matter how large they are.
 14 
 15 //: Tests in this layer often explicitly set up memory before reading it as a
 16 //: container. Don't do this in general. I'm tagging such cases with /unsafe;
 17 //: they'll be exceptions to later checks.
 18 :(scenario copy_multiple_locations)
 19 def main [
 20   1:num <- copy 34
 21   2:num <- copy 35
 22   3:point <- copy 1:point/unsafe
 23 ]
 24 +mem: storing 34 in location 3
 25 +mem: storing 35 in location 4
 26 
 27 //: trying to copy to a differently-typed destination will fail
 28 :(scenario copy_checks_size)
 29 % Hide_errors = true;
 30 def main [
 31   2:point <- copy 1:num
 32 ]
 33 +error: main: can't copy '1:num' to '2:point'; types don't match
 34 
 35 :(before "End Mu Types Initialization")
 36 // A more complex example container, containing another container as one of
 37 // its elements.
 38 type_ordinal point_number = put(Type_ordinal, "point-number", Next_type_ordinal++);
 39 get_or_insert(Type, point_number);  // initialize
 40 get(Type, point_number).kind = CONTAINER;
 41 get(Type, point_number).name = "point-number";
 42 get(Type, point_number).elements.push_back(reagent("xy:point"));
 43 get(Type, point_number).elements.push_back(reagent("z:number"));
 44 
 45 :(scenario copy_handles_nested_container_elements)
 46 def main [
 47   12:num <- copy 34
 48   13:num <- copy 35
 49   14:num <- copy 36
 50   15:point-number <- copy 12:point-number/unsafe
 51 ]
 52 +mem: storing 36 in location 17
 53 
 54 //: products of recipes can include containers
 55 :(scenario return_container)
 56 def main [
 57   3:point <- f 2
 58 ]
 59 def f [
 60   12:num <- next-ingredient
 61   13:num <- copy 35
 62   return 12:point/raw
 63 ]
 64 +run: result 0 is [2, 35]
 65 +mem: storing 2 in location 3
 66 +mem: storing 35 in location 4
 67 
 68 //: Containers can be checked for equality with a single instruction just like
 69 //: numbers, no matter how large they are.
 70 
 71 :(scenario compare_multiple_locations)
 72 def main [
 73   1:num <- copy 34  # first
 74   2:num <- copy 35
 75   3:num <- copy 36
 76   4:num <- copy 34  # second
 77   5:num <- copy 35
 78   6:num <- copy 36
 79   7:bool <- equal 1:point-number/raw, 4:point-number/unsafe
 80 ]
 81 +mem: storing 1 in location 7
 82 
 83 :(scenario compare_multiple_locations_2)
 84 def main [
 85   1:num <- copy 34  # first
 86   2:num <- copy 35
 87   3:num <- copy 36
 88   4:num <- copy 34  # second
 89   5:num <- copy 35
 90   6:num <- copy 37  # different
 91   7:bool <- equal 1:point-number/raw, 4:point-number/unsafe
 92 ]
 93 +mem: storing 0 in location 7
 94 
 95 :(before "End size_of(type) Special-cases")
 96 if (type->value == -1) return 1;  // error value, but we'll raise it elsewhere
 97 if (type->value == 0) return 1;
 98 if (!contains_key(Type, type->value)) {
 99   raise << "no such type " << type->value << '\n' << end();
100   return 0;
101 }
102 type_info t = get(Type, type->value);
103 if (t.kind == CONTAINER) {
104   // size of a container is the sum of the sizes of its elements
105   int result = 0;
106   for (int i = 0; i < SIZE(t.elements); ++i) {
107     // todo: strengthen assertion to disallow mutual type recursion
108     if (t.elements.at(i).type->value == type->value) {
109       raise << "container " << t.name << " can't include itself as a member\n" << end();
110       return 0;
111     }
112     result += size_of(element_type(type, i));
113   }
114   return result;
115 }
116 
117 :(scenario stash_container)
118 def main [
119   1:num <- copy 34  # first
120   2:num <- copy 35
121   3:num <- copy 36
122   stash [foo:], 1:point-number/raw
123 ]
124 +app: foo: 34 35 36
125 
126 //:: To access elements of a container, use 'get'
127 //: 'get' takes a 'base' container and an 'offset' into it and returns the
128 //: appropriate element of the container value.
129 
130 :(scenario get)
131 def main [
132   12:num <- copy 34
133   13:num <- copy 35
134   15:num <- get 12:point/raw, 1:offset  # unsafe
135 ]
136 +mem: storing 35 in location 15
137 
138 :(before "End Primitive Recipe Declarations")
139 GET,
140 :(before "End Primitive Recipe Numbers")
141 put(Recipe_ordinal, "get", GET);
142 :(before "End Primitive Recipe Checks")
143 case GET: {
144   if (SIZE(inst.ingredients) != 2) {
145     raise << maybe(get(Recipe, r).name) << "'get' expects exactly 2 ingredients in '" << to_original_string(inst) << "'\n" << end();
146     break;
147   }
148   reagent/*copy*/ base = inst.ingredients.at(0);  // new copy for every invocation
149   // Update GET base in Check
150   if (!base.type) {
151     raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
152     break;
153   }
154   const type_tree* base_type = base.type;
155   // Update GET base_type in Check
156   if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) {
157     raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
158     break;
159   }
160   const reagent& offset = inst.ingredients.at(1);
161   if (!is_literal(offset) || !is_mu_scalar(offset)) {
162     raise << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
163     break;
164   }
165   int offset_value = 0;
166   if (is_integer(offset.name)) {
167     offset_value = to_integer(offset.name);
168   }
169   // End update GET offset_value in Check
170   if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) {
171     raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end();
172     break;
173   }
174   if (inst.products.empty()) break;
175   reagent/*copy*/ product = inst.products.at(0);
176   // Update GET product in Check
177   //: use base.type rather than base_type because later layers will introduce compound types
178   const reagent/*copy*/ element = element_type(base.type, offset_value);
179   if (!types_coercible(product, element)) {
180     raise << maybe(get(Recipe, r).name) << "'get " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but '" << product.name << "' has type " << names_to_string_without_quotes(product.type) << '\n' << end();
181     break;
182   }
183   break;
184 }
185 :(before "End Primitive Recipe Implementations")
186 case GET: {
187   reagent/*copy*/ base = current_instruction().ingredients.at(0);
188   // Update GET base in Run
189   int base_address = base.value;
190   if (base_address == 0) {
191     raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
192     break;
193   }
194   const type_tree* base_type = base.type;
195   // Update GET base_type in Run
196   int offset = ingredients.at(1).at(0);
197   if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;  // copied from Check above
198   int src = base_address;
199   for (int i = 0; i < offset; ++i)
200     src += size_of(element_type(base.type, i));
201   trace(9998, "run") << "address to copy is " << src << end();
202   //: use base.type rather than base_type because later layers will introduce compound types
203   reagent/*copy*/ element = element_type(base.type, offset);
204   element.set_value(src);
205   trace(9998, "run") << "its type is " << names_to_string(element.type) << end();
206   // Read element
207   products.push_back(read_memory(element));
208   break;
209 }
210 
211 :(code)
212 const reagent element_type(const type_tree* type, int offset_value) {
213   assert(offset_value >= 0);
214   const type_tree* base_type = type;
215   // Update base_type in element_type
216   assert(contains_key(Type, base_type->value));
217   assert(!get(Type, base_type->value).name.empty());
218   const type_info& info = get(Type, base_type->value);
219   assert(info.kind == CONTAINER);
220   if (offset_value >= SIZE(info.elements)) return reagent();  // error handled elsewhere
221   reagent/*copy*/ element = info.elements.at(offset_value);
222   // End element_type Special-cases
223   return element;
224 }
225 
226 :(scenario get_handles_nested_container_elements)
227 def main [
228   12:num <- copy 34
229   13:num <- copy 35
230   14:num <- copy 36
231   15:num <- get 12:point-number/raw, 1:offset  # unsafe
232 ]
233 +mem: storing 36 in location 15
234 
235 :(scenario get_out_of_bounds)
236 % Hide_errors = true;
237 def main [
238   12:num <- copy 34
239   13:num <- copy 35
240   14:num <- copy 36
241   get 12:point-number/raw, 2:offset  # point-number occupies 3 locations but has only 2 fields; out of bounds
242 ]
243 +error: main: invalid offset '2' for 'point-number'
244 
245 :(scenario get_out_of_bounds_2)
246 % Hide_errors = true;
247 def main [
248   12:num <- copy 34
249   13:num <- copy 35
250   14:num <- copy 36
251   get 12:point-number/raw, -1:offset
252 ]
253 +error: main: invalid offset '-1' for 'point-number'
254 
255 :(scenario get_product_type_mismatch)
256 % Hide_errors = true;
257 def main [
258   12:num <- copy 34
259   13:num <- copy 35
260   14:num <- copy 36
261   15:&:num <- get 12:point-number/raw, 1:offset
262 ]
263 +error: main: 'get 12:point-number/raw, 1:offset' should write to number but '15' has type (address number)
264 
265 //: we might want to call 'get' without saving the results, say in a sandbox
266 
267 :(scenario get_without_product)
268 def main [
269   12:num <- copy 34
270   13:num <- copy 35
271   get 12:point/raw, 1:offset  # unsafe
272 ]
273 # just don't die
274 
275 //:: To write to elements of containers, use 'put'.
276 
277 :(scenario put)
278 def main [
279   12:num <- copy 34
280   13:num <- copy 35
281   $clear-trace
282   12:point <- put 12:point, 1:offset, 36
283 ]
284 +mem: storing 36 in location 13
285 -mem: storing 34 in location 12
286 
287 :(before "End Primitive Recipe Declarations")
288 PUT,
289 :(before "End Primitive Recipe Numbers")
290 put(Recipe_ordinal, "put", PUT);
291 :(before "End Primitive Recipe Checks")
292 case PUT: {
293   if (SIZE(inst.ingredients) != 3) {
294     raise << maybe(get(Recipe, r).name) << "'put' expects exactly 3 ingredients in '" << to_original_string(inst) << "'\n" << end();
295     break;
296   }
297   reagent/*copy*/ base = inst.ingredients.at(0);
298   // Update PUT base in Check
299   if (!base.type) {
300     raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
301     break;
302   }
303   const type_tree* base_type = base.type;
304   // Update PUT base_type in Check
305   if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) {
306     raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
307     break;
308   }
309   reagent/*copy*/ offset = inst.ingredients.at(1);
310   // Update PUT offset in Check
311   if (!is_literal(offset) || !is_mu_scalar(offset)) {
312     raise << maybe(get(Recipe, r).name) << "second ingredient of 'put' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
313     break;
314   }
315   int offset_value = 0;
316   //: later layers will permit non-integer offsets
317   if (is_integer(offset.name)) {
318     offset_value = to_integer(offset.name);
319     if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) {
320       raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end();
321       break;
322     }
323   }
324   else {
325     offset_value = offset.value;
326   }
327   const reagent& value = inst.ingredients.at(2);
328   //: use base.type rather than base_type because later layers will introduce compound types
329   const reagent& element = element_type(base.type, offset_value);
330   if (!types_coercible(element, value)) {
331     raise << maybe(get(Recipe, r).name) << "'put " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but '" << value.name << "' has type " << names_to_string_without_quotes(value.type) << '\n' << end();
332     break;
333   }
334   if (inst.products.empty()) break;  // no more checks necessary
335   if (inst.products.at(0).name != inst.ingredients.at(0).name) {
336     raise << maybe(get(Recipe, r).name) << "product of 'put' must be first ingredient '" << inst.ingredients.at(0).original_string << "', but got '" << inst.products.at(0).original_string << "'\n" << end();
337     break;
338   }
339   // End PUT Product Checks
340   break;
341 }
342 :(before "End Primitive Recipe Implementations")
343 case PUT: {
344   reagent/*copy*/ base = current_instruction().ingredients.at(0);
345   // Update PUT base in Run
346   int base_address = base.value;
347   if (base_address == 0) {
348     raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
349     break;
350   }
351   const type_tree* base_type = base.type;
352   // Update PUT base_type in Run
353   int offset = ingredients.at(1).at(0);
354   if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;  // copied from Check above
355   int address = base_address;
356   for (int i = 0; i < offset; ++i)
357     address += size_of(element_type(base.type, i));
358   trace(9998, "run") << "address to copy to is " << address << end();
359   // optimization: directly write the element rather than updating 'product'
360   // and writing the entire container
361   // Write Memory in PUT in Run
362   write_products = false;
363   for (int i = 0;  i < SIZE(ingredients.at(2));  ++i) {
364     trace("mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end();
365     put(Memory, address+i, ingredients.at(2).at(i));
366   }
367   break;
368 }
369 
370 :(scenario put_product_error)
371 % Hide_errors = true;
372 def main [
373   local-scope
374   load-ingredients
375   1:point <- merge 34, 35
376   3:point <- put 1:point, x:offset, 36
377 ]
378 +error: main: product of 'put' must be first ingredient '1:point', but got '3:point'
379 
380 //:: Allow containers to be defined in Mu code.
381 
382 :(scenarios load)
383 :(scenario container)
384 container foo [
385   x:num
386   y:num
387 ]
388 +parse: --- defining container foo
389 +parse: element: {x: "number"}
390 +parse: element: {y: "number"}
391 
392 :(scenario container_use_before_definition)
393 container foo [
394   x:num
395   y:bar
396 ]
397 container bar [
398   x:num
399   y:num
400 ]
401 +parse: --- defining container foo
402 +parse: type number: 1000
403 +parse:   element: {x: "number"}
404 # todo: brittle
405 # type bar is unknown at this point, but we assign it a number
406 +parse:   element: {y: "bar"}
407 # later type bar geon
408 +parse: --- defining container bar
409 +parse: type number: 1001
410 +parse:   element: {x: "number"}
411 +parse:   element: {y: "number"}
412 
413 //: if a container is defined again, the new fields add to the original definition
414 :(scenarios run)
415 :(scenario container_extend)
416 container foo [
417   x:num
418 ]
419 # add to previous definition
420 container foo [
421   y:num
422 ]
423 def main [
424   1:num <- copy 34
425   2:num <- copy 35
426   3:num <- get 1:foo, 0:offset
427   4:num <- get 1:foo, 1:offset
428 ]
429 +mem: storing 34 in location 3
430 +mem: storing 35 in location 4
431 
432 :(before "End Command Handlers")
433 else if (command == "container") {
434   insert_container(command, CONTAINER, in);
435 }
436 
437 //: Even though we allow containers to be extended, we don't allow this after
438 //: a call to transform_all. But we do want to detect this situation and raise
439 //: an error. This field will help us raise such errors.
440 :(before "End type_info Fields")
441 int Num_calls_to_transform_all_at_first_definition;
442 :(before "End type_info Constructor")
443 Num_calls_to_transform_all_at_first_definition = -1;
444 
445 :(code)
446 void insert_container(const string& command, kind_of_type kind, istream& in) {
447   skip_whitespace_but_not_newline(in);
448   string name = next_word(in);
449   if (name.empty()) {
450     assert(!has_data(in));
451     raise << "incomplete container definition at end of file (0)\n" << end();
452     return;
453   }
454   // End container Name Refinements
455   trace(9991, "parse") << "--- defining " << command << ' ' << name << end();
456   if (!contains_key(Type_ordinal, name)
457       || get(Type_ordinal, name) == 0) {
458     put(Type_ordinal, name, Next_type_ordinal++);
459   }
460   trace("parse") << "type number: " << get(Type_ordinal, name) << end();
461   skip_bracket(in, "'"+command+"' must begin with '['");
462   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
463   if (info.Num_calls_to_transform_all_at_first_definition == -1) {
464     // initial definition of this container
465     info.Num_calls_to_transform_all_at_first_definition = Num_calls_to_transform_all;
466   }
467   else if (info.Num_calls_to_transform_all_at_first_definition != Num_calls_to_transform_all) {
468     // extension after transform_all
469     raise << "there was a call to transform_all() between the definition of container '" << name << "' and a subsequent extension. This is not supported, since any recipes that used '" << name << "' values have already been transformed and \"frozen\".\n" << end();
470     return;
471   }
472   info.name = name;
473   info.kind = kind;
474   while (has_data(in)) {
475     skip_whitespace_and_comments(in);
476     string element = next_word(in);
477     if (element.empty()) {
478       assert(!has_data(in));
479       raise << "incomplete container definition at end of file (1)\n" << end();
480       return;
481     }
482     if (element == "]") break;
483     if (in.peek() != '\n') {
484       raise << command << " '" << name << "' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code.\n" << end();
485       // skip rest of container declaration
486       while (has_data(in)) {
487         skip_whitespace_and_comments(in);
488         if (next_word(in) == "]") break;
489       }
490       break;
491     }
492     info.elements.push_back(reagent(element));
493     expand_type_abbreviations(info.elements.back().type);  // todo: use abbreviation before declaration
494     replace_unknown_types_with_unique_ordinals(info.elements.back().type, info);
495     trace(9993, "parse") << "  element: " << to_string(info.elements.back()) << end();
496     // End Load Container Element Definition
497   }
498 }
499 
500 void replace_unknown_types_with_unique_ordinals(type_tree* type, const type_info& info) {
501   if (!type) return;
502   if (!type->atom) {
503     replace_unknown_types_with_unique_ordinals(type->left, info);
504     replace_unknown_types_with_unique_ordinals(type->right, info);
505     return;
506   }
507   assert(!type->name.empty());
508   if (contains_key(Type_ordinal, type->name)) {
509     type->value = get(Type_ordinal, type->name);
510   }
511   // End insert_container Special-cases
512   else if (type->name != "->") {  // used in recipe types
513     put(Type_ordinal, type->name, Next_type_ordinal++);
514     type->value = get(Type_ordinal, type->name);
515   }
516 }
517 
518 void skip_bracket(istream& in, string message) {
519   skip_whitespace_and_comments(in);
520   if (in.get() != '[')
521     raise << message << '\n' << end();
522 }
523 
524 :(scenario multi_word_line_in_container_declaration)
525 % Hide_errors = true;
526 container foo [
527   x:num y:num
528 ]
529 +error: container 'foo' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code.
530 
531 //: support type abbreviations in container definitions
532 
533 :(scenario type_abbreviations_in_containers)
534 type foo = number
535 container bar [
536   x:foo
537 ]
538 def main [
539   1:num <- copy 34
540   2:foo <- get 1:bar/unsafe, 0:offset
541 ]
542 +mem: storing 34 in location 2
543 
544 :(after "Transform.push_back(expand_type_abbreviations)")
545 Transform.push_back(expand_type_abbreviations_in_containers);  // idempotent
546 :(code)
547 // extremely inefficient; we process all types over and over again, once for every single recipe
548 // but it doesn't seem to cause any noticeable slowdown
549 void expand_type_abbreviations_in_containers(const recipe_ordinal /*unused*/) {
550   for (map<type_ordinal, type_info>::iterator p = Type.begin();  p != Type.end();  ++p) {
551     for (int i = 0;  i < SIZE(p->second.elements);  ++i)
552       expand_type_abbreviations(p->second.elements.at(i).type);
553   }
554 }
555 
556 //: ensure scenarios are consistent by always starting new container
557 //: declarations at the same type number
558 :(before "End Reset")  //: for tests
559 Next_type_ordinal = 1000;
560 :(before "End Test Run Initialization")
561 assert(Next_type_ordinal < 1000);
562 
563 :(code)
564 void test_error_on_transform_all_between_container_definition_and_extension() {
565   // define a container
566   run("container foo [\n"
567       "  a:num\n"
568       "]\n");
569   // try to extend the container after transform
570   transform_all();
571   CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
572   Hide_errors = true;
573   run("container foo [\n"
574       "  b:num\n"
575       "]\n");
576   CHECK_TRACE_CONTAINS_ERRORS();
577 }
578 
579 //:: Allow container definitions anywhere in the codebase, but complain if you
580 //:: can't find a definition at the end.
581 
582 :(scenario run_complains_on_unknown_types)
583 % Hide_errors = true;
584 def main [
585   # integer is not a type
586   1:integer <- copy 0
587 ]
588 +error: main: unknown type integer in '1:integer <- copy 0'
589 
590 :(scenario run_allows_type_definition_after_use)
591 def main [
592   1:bar <- copy 0/unsafe
593 ]
594 container bar [
595   x:num
596 ]
597 $error: 0
598 
599 :(before "End Type Modifying Transforms")
600 Transform.push_back(check_or_set_invalid_types);  // idempotent
601 
602 :(code)
603 void check_or_set_invalid_types(const recipe_ordinal r) {
604   recipe& caller = get(Recipe, r);
605   trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end();
606   for (int index = 0;  index < SIZE(caller.steps);  ++index) {
607     instruction& inst = caller.steps.at(index);
608     for (int i = 0;  i < SIZE(inst.ingredients);  ++i)
609       check_or_set_invalid_types(inst.ingredients.at(i), caller, inst);
610     for (int i = 0;  i < SIZE(inst.products);  ++i)
611       check_or_set_invalid_types(inst.products.at(i), caller, inst);
612   }
613   // End check_or_set_invalid_types
614 }
615 
616 void check_or_set_invalid_types(reagent& r, const recipe& caller, const instruction& inst) {
617   // Begin check_or_set_invalid_types(r)
618   check_or_set_invalid_types(r.type, maybe(caller.name), "'"+to_original_string(inst)+"'");
619 }
620 
621 void check_or_set_invalid_types(type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) {
622   if (!type) return;
623   // End Container Type Checks
624   if (!type->atom) {
625     check_or_set_invalid_types(type->left, location_for_error_messages, name_for_error_messages);
626     check_or_set_invalid_types(type->right, location_for_error_messages, name_for_error_messages);
627     return;
628   }
629   if (type->value == 0) return;
630   if (!contains_key(Type, type->value)) {
631     assert(!type->name.empty());
632     if (contains_key(Type_ordinal, type->name))
633       type->value = get(Type_ordinal, type->name);
634     else
635       raise << location_for_error_messages << "unknown type " << type->name << " in " << name_for_error_messages << '\n' << end();
636   }
637 }
638 
639 :(scenario container_unknown_field)
640 % Hide_errors = true;
641 container foo [
642   x:num
643   y:bar
644 ]
645 +error: foo: unknown type in y
646 
647 :(scenario read_container_with_bracket_in_comment)
648 container foo [
649   x:num
650   # ']' in comment
651   y:num
652 ]
653 +parse: --- defining container foo
654 +parse: element: {x: "number"}
655 +parse: element: {y: "number"}
656 
657 :(scenario container_with_compound_field_type)
658 container foo [
659   {x: (address array (address array character))}
660 ]
661 $error: 0
662 
663 :(before "End transform_all")
664 check_container_field_types();
665 
666 :(code)
667 void check_container_field_types() {
668   for (map<type_ordinal, type_info>::iterator p = Type.begin();  p != Type.end();  ++p) {
669     const type_info& info = p->second;
670     // Check Container Field Types(info)
671     for (int i = 0;  i < SIZE(info.elements);  ++i)
672       check_invalid_types(info.elements.at(i).type, maybe(info.name), info.elements.at(i).name);
673   }
674 }
675 
676 void check_invalid_types(const type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) {
677   if (!type) return;  // will throw a more precise error elsewhere
678   if (!type->atom) {
679     check_invalid_types(type->left, location_for_error_messages, name_for_error_messages);
680     check_invalid_types(type->right, location_for_error_messages, name_for_error_messages);
681     return;
682   }
683   if (type->value != 0) {  // value 0 = compound types (layer parse_tree) or type ingredients (layer shape_shifting_container)
684     if (!contains_key(Type, type->value))
685       raise << location_for_error_messages << "unknown type in " << name_for_error_messages << '\n' << end();
686   }
687 }
688 
689 string to_original_string(const type_ordinal t) {
690   ostringstream out;
691   if (!contains_key(Type, t)) return out.str();
692   const type_info& info = get(Type, t);
693   if (info.kind == PRIMITIVE) return out.str();
694   out << (info.kind == CONTAINER ? "container" : "exclusive-container") << " " << info.name << " [\n";
695   for (int i = 0;  i < SIZE(info.elements);  ++i) {
696     out << "  " << info.elements.at(i).original_string << "\n";
697   }
698   out << "]\n";
699   return out.str();
700 }