# Mu - 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 }