1 //: Containers contain a fixed number of elements of different types.
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"));
12 //: Containers can be copied around with a single instruction just like
13 //: numbers, no matter how large they are.
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.
20 void test_copy_multiple_locations() {
25 " 3:point <- copy 1:point/unsafe\n"
29 "mem: storing 34 in location 3\n"
30 "mem: storing 35 in location 4\n"
34 //: trying to copy to a differently-typed destination will fail
35 void test_copy_checks_size() {
39 " 2:point <- copy 1:num\n"
43 "error: main: can't copy '1:num' to '2:point'; types don't match\n"
47 :(before
"End Mu Types Initialization")
48 // A more complex example container, containing another container as one of
50 type_ordinal point_number
= put(Type_ordinal
, "point-number", Next_type_ordinal
++);
51 get_or_insert(Type
, point_number
); // initialize
52 get(Type
, point_number
).kind
= CONTAINER
;
53 get(Type
, point_number
).name
= "point-number";
54 get(Type
, point_number
).elements
.push_back(reagent("xy:point"));
55 get(Type
, point_number
).elements
.push_back(reagent("z:number"));
58 void test_copy_handles_nested_container_elements() {
61 " 12:num <- copy 34\n"
62 " 13:num <- copy 35\n"
63 " 14:num <- copy 36\n"
64 " 15:point-number <- copy 12:point-number/unsafe\n"
68 "mem: storing 36 in location 17\n"
72 //: products of recipes can include containers
73 void test_return_container() {
79 " 12:num <- next-ingredient\n"
80 " 13:num <- copy 35\n"
81 " return 12:point/raw\n"
85 "run: result 0 is [2, 35]\n"
86 "mem: storing 2 in location 3\n"
87 "mem: storing 35 in location 4\n"
91 //: Containers can be checked for equality with a single instruction just like
92 //: numbers, no matter how large they are.
94 void test_compare_multiple_locations() {
97 " 1:num <- copy 34\n" // first
100 " 4:num <- copy 34\n" // second
101 " 5:num <- copy 35\n"
102 " 6:num <- copy 36\n"
103 " 7:bool <- equal 1:point-number/raw, 4:point-number/unsafe\n"
106 CHECK_TRACE_CONTENTS(
107 "mem: storing 1 in location 7\n"
111 void test_compare_multiple_locations_2() {
114 " 1:num <- copy 34\n" // first
115 " 2:num <- copy 35\n"
116 " 3:num <- copy 36\n"
117 " 4:num <- copy 34\n" // second
118 " 5:num <- copy 35\n"
119 " 6:num <- copy 37\n" // different
120 " 7:bool <- equal 1:point-number/raw, 4:point-number/unsafe\n"
123 CHECK_TRACE_CONTENTS(
124 "mem: storing 0 in location 7\n"
128 :(before
"End size_of(type) Special-cases")
129 if (type
->value
== -1) return 1; // error value, but we'll raise it elsewhere
130 if (type
->value
== 0) return 1;
131 if (!contains_key(Type
, type
->value
)) {
132 raise
<< "no such type " << type
->value
<< '\n' << end();
135 type_info t
= get(Type
, type
->value
);
136 if (t
.kind
== CONTAINER
) {
137 // size of a container is the sum of the sizes of its elements
139 for (int i
= 0; i
< SIZE(t
.elements
); ++i
) {
140 // todo: strengthen assertion to disallow mutual type recursion
141 if (t
.elements
.at(i
).type
->value
== type
->value
) {
142 raise
<< "container " << t
.name
<< " can't include itself as a member\n" << end();
145 result
+= size_of(element_type(type
, i
));
151 void test_stash_container() {
154 " 1:num <- copy 34\n" // first
155 " 2:num <- copy 35\n"
156 " 3:num <- copy 36\n"
157 " stash [foo:], 1:point-number/raw\n"
160 CHECK_TRACE_CONTENTS(
161 "app: foo: 34 35 36\n"
165 //:: To access elements of a container, use 'get'
166 //: 'get' takes a 'base' container and an 'offset' into it and returns the
167 //: appropriate element of the container value.
172 " 12:num <- copy 34\n"
173 " 13:num <- copy 35\n"
174 " 15:num <- get 12:point/raw, 1:offset\n" // unsafe
177 CHECK_TRACE_CONTENTS(
178 "mem: storing 35 in location 15\n"
182 :(before
"End Primitive Recipe Declarations")
184 :(before
"End Primitive Recipe Numbers")
185 put(Recipe_ordinal
, "get", GET
);
186 :(before
"End Primitive Recipe Checks")
188 if (SIZE(inst
.ingredients
) != 2) {
189 raise
<< maybe(get(Recipe
, r
).name
) << "'get' expects exactly 2 ingredients in '" << to_original_string(inst
) << "'\n" << end();
192 reagent
/*copy*/ base
= inst
.ingredients
.at(0); // new copy for every invocation
193 // Update GET base in Check
195 raise
<< maybe(get(Recipe
, r
).name
) << "first ingredient of 'get' should be a container, but got '" << inst
.ingredients
.at(0).original_string
<< "'\n" << end();
198 const type_tree
* base_type
= base
.type
;
199 // Update GET base_type in Check
200 if (!base_type
->atom
|| base_type
->value
== 0 || !contains_key(Type
, base_type
->value
) || get(Type
, base_type
->value
).kind
!= CONTAINER
) {
201 raise
<< maybe(get(Recipe
, r
).name
) << "first ingredient of 'get' should be a container, but got '" << inst
.ingredients
.at(0).original_string
<< "'\n" << end();
204 const reagent
& offset
= inst
.ingredients
.at(1);
205 if (!is_literal(offset
) || !is_mu_scalar(offset
)) {
206 raise
<< maybe(get(Recipe
, r
).name
) << "second ingredient of 'get' should have type 'offset', but got '" << inst
.ingredients
.at(1).original_string
<< "'\n" << end();
209 int offset_value
= 0;
210 if (is_integer(offset
.name
)) {
211 offset_value
= to_integer(offset
.name
);
213 // End update GET offset_value in Check
214 if (offset_value
< 0 || offset_value
>= SIZE(get(Type
, base_type
->value
).elements
)) {
215 raise
<< maybe(get(Recipe
, r
).name
) << "invalid offset '" << offset_value
<< "' for '" << get(Type
, base_type
->value
).name
<< "'\n" << end();
218 if (inst
.products
.empty()) break;
219 reagent
/*copy*/ product
= inst
.products
.at(0);
220 // Update GET product in Check
221 //: use base.type rather than base_type because later layers will introduce compound types
222 const reagent
/*copy*/ element
= element_type(base
.type
, offset_value
);
223 if (!types_coercible(product
, element
)) {
224 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();
229 :(before
"End Primitive Recipe Implementations")
231 reagent
/*copy*/ base
= current_instruction().ingredients
.at(0);
232 // Update GET base in Run
233 int base_address
= base
.value
;
234 if (base_address
== 0) {
235 raise
<< maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
238 const type_tree
* base_type
= base
.type
;
239 // Update GET base_type in Run
240 int offset
= ingredients
.at(1).at(0);
241 if (offset
< 0 || offset
>= SIZE(get(Type
, base_type
->value
).elements
)) break; // copied from Check above
242 int src
= base_address
;
243 for (int i
= 0; i
< offset
; ++i
)
244 src
+= size_of(element_type(base
.type
, i
));
245 trace(Callstack_depth
+1, "run") << "address to copy is " << src
<< end();
246 //: use base.type rather than base_type because later layers will introduce compound types
247 reagent
/*copy*/ element
= element_type(base
.type
, offset
);
248 element
.set_value(src
);
249 trace(Callstack_depth
+1, "run") << "its type is " << names_to_string(element
.type
) << end();
251 products
.push_back(read_memory(element
));
256 const reagent
element_type(const type_tree
* type
, int offset_value
) {
257 assert(offset_value
>= 0);
258 const type_tree
* base_type
= type
;
259 // Update base_type in element_type
260 assert(contains_key(Type
, base_type
->value
));
261 assert(!get(Type
, base_type
->value
).name
.empty());
262 const type_info
& info
= get(Type
, base_type
->value
);
263 assert(info
.kind
== CONTAINER
);
264 if (offset_value
>= SIZE(info
.elements
)) return reagent(); // error handled elsewhere
265 reagent
/*copy*/ element
= info
.elements
.at(offset_value
);
266 // End element_type Special-cases
270 void test_get_handles_nested_container_elements() {
273 " 12:num <- copy 34\n"
274 " 13:num <- copy 35\n"
275 " 14:num <- copy 36\n"
276 " 15:num <- get 12:point-number/raw, 1:offset\n" // unsafe
279 CHECK_TRACE_CONTENTS(
280 "mem: storing 36 in location 15\n"
284 void test_get_out_of_bounds() {
288 " 12:num <- copy 34\n"
289 " 13:num <- copy 35\n"
290 " 14:num <- copy 36\n"
291 " get 12:point-number/raw, 2:offset\n" // point-number occupies 3 locations but has only 2 fields; out of bounds
294 CHECK_TRACE_CONTENTS(
295 "error: main: invalid offset '2' for 'point-number'\n"
299 void test_get_out_of_bounds_2() {
303 " 12:num <- copy 34\n"
304 " 13:num <- copy 35\n"
305 " 14:num <- copy 36\n"
306 " get 12:point-number/raw, -1:offset\n"
309 CHECK_TRACE_CONTENTS(
310 "error: main: invalid offset '-1' for 'point-number'\n"
314 void test_get_product_type_mismatch() {
318 " 12:num <- copy 34\n"
319 " 13:num <- copy 35\n"
320 " 14:num <- copy 36\n"
321 " 15:&:num <- get 12:point-number/raw, 1:offset\n"
324 CHECK_TRACE_CONTENTS(
325 "error: main: 'get 12:point-number/raw, 1:offset' should write to number but '15' has type (address number)\n"
329 //: we might want to call 'get' without saving the results, say in a sandbox
331 void test_get_without_product() {
334 " 12:num <- copy 34\n"
335 " 13:num <- copy 35\n"
336 " get 12:point/raw, 1:offset\n" // unsafe
342 //:: To write to elements of containers, use 'put'.
347 " 12:num <- copy 34\n"
348 " 13:num <- copy 35\n"
350 " 12:point <- put 12:point, 1:offset, 36\n"
353 CHECK_TRACE_CONTENTS(
354 "mem: storing 36 in location 13"
356 CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 12");
359 :(before
"End Primitive Recipe Declarations")
361 :(before
"End Primitive Recipe Numbers")
362 put(Recipe_ordinal
, "put", PUT
);
363 :(before
"End Primitive Recipe Checks")
365 if (SIZE(inst
.ingredients
) != 3) {
366 raise
<< maybe(get(Recipe
, r
).name
) << "'put' expects exactly 3 ingredients in '" << to_original_string(inst
) << "'\n" << end();
369 reagent
/*copy*/ base
= inst
.ingredients
.at(0);
370 // Update PUT base in Check
372 raise
<< maybe(get(Recipe
, r
).name
) << "first ingredient of 'put' should be a container, but got '" << inst
.ingredients
.at(0).original_string
<< "'\n" << end();
375 const type_tree
* base_type
= base
.type
;
376 // Update PUT base_type in Check
377 if (!base_type
->atom
|| base_type
->value
== 0 || !contains_key(Type
, base_type
->value
) || get(Type
, base_type
->value
).kind
!= CONTAINER
) {
378 raise
<< maybe(get(Recipe
, r
).name
) << "first ingredient of 'put' should be a container, but got '" << inst
.ingredients
.at(0).original_string
<< "'\n" << end();
381 reagent
/*copy*/ offset
= inst
.ingredients
.at(1);
382 // Update PUT offset in Check
383 if (!is_literal(offset
) || !is_mu_scalar(offset
)) {
384 raise
<< maybe(get(Recipe
, r
).name
) << "second ingredient of 'put' should have type 'offset', but got '" << inst
.ingredients
.at(1).original_string
<< "'\n" << end();
387 int offset_value
= 0;
388 //: later layers will permit non-integer offsets
389 if (is_integer(offset
.name
)) {
390 offset_value
= to_integer(offset
.name
);
391 if (offset_value
< 0 || offset_value
>= SIZE(get(Type
, base_type
->value
).elements
)) {
392 raise
<< maybe(get(Recipe
, r
).name
) << "invalid offset '" << offset_value
<< "' for '" << get(Type
, base_type
->value
).name
<< "'\n" << end();
397 offset_value
= offset
.value
;
399 const reagent
& value
= inst
.ingredients
.at(2);
400 //: use base.type rather than base_type because later layers will introduce compound types
401 const reagent
& element
= element_type(base
.type
, offset_value
);
402 if (!types_coercible(element
, value
)) {
403 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();
406 if (inst
.products
.empty()) break; // no more checks necessary
407 if (inst
.products
.at(0).name
!= inst
.ingredients
.at(0).name
) {
408 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();
411 // End PUT Product Checks
414 :(before
"End Primitive Recipe Implementations")
416 reagent
/*copy*/ base
= current_instruction().ingredients
.at(0);
417 // Update PUT base in Run
418 int base_address
= base
.value
;
419 if (base_address
== 0) {
420 raise
<< maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end();
423 const type_tree
* base_type
= base
.type
;
424 // Update PUT base_type in Run
425 int offset
= ingredients
.at(1).at(0);
426 if (offset
< 0 || offset
>= SIZE(get(Type
, base_type
->value
).elements
)) break; // copied from Check above
427 int address
= base_address
;
428 for (int i
= 0; i
< offset
; ++i
)
429 address
+= size_of(element_type(base
.type
, i
));
430 trace(Callstack_depth
+1, "run") << "address to copy to is " << address
<< end();
431 // optimization: directly write the element rather than updating 'product'
432 // and writing the entire container
433 // Write Memory in PUT in Run
434 write_products
= false;
435 for (int i
= 0; i
< SIZE(ingredients
.at(2)); ++i
) {
436 trace(Callstack_depth
+1, "mem") << "storing " << no_scientific(ingredients
.at(2).at(i
)) << " in location " << address
+i
<< end();
437 put(Memory
, address
+i
, ingredients
.at(2).at(i
));
443 void test_put_product_error() {
448 " load-ingredients\n"
449 " 1:point <- merge 34, 35\n"
450 " 3:point <- put 1:point, x:offset, 36\n"
453 CHECK_TRACE_CONTENTS(
454 "error: main: product of 'put' must be first ingredient '1:point', but got '3:point'\n"
458 //:: Allow containers to be defined in Mu code.
460 void test_container() {
467 CHECK_TRACE_CONTENTS(
468 "parse: --- defining container foo\n"
469 "parse: element: {x: \"number\"}\n"
470 "parse: element: {y: \"number\"}\n"
474 void test_container_use_before_definition() {
485 CHECK_TRACE_CONTENTS(
486 "parse: --- defining container foo\n"
487 "parse: type number: 1000\n"
488 "parse: element: {x: \"number\"}\n"
490 // type bar is unknown at this point, but we assign it a number
491 "parse: element: {y: \"bar\"}\n"
492 // later type bar gets a definition
493 "parse: --- defining container bar\n"
494 "parse: type number: 1001\n"
495 "parse: element: {x: \"number\"}\n"
496 "parse: element: {y: \"number\"}\n"
500 //: if a container is defined again, the new fields add to the original definition
501 void test_container_extend() {
506 "container foo [\n" // add to previous definition
510 " 1:num <- copy 34\n"
511 " 2:num <- copy 35\n"
512 " 3:num <- get 1:foo, 0:offset\n"
513 " 4:num <- get 1:foo, 1:offset\n"
516 CHECK_TRACE_CONTENTS(
517 "mem: storing 34 in location 3\n"
518 "mem: storing 35 in location 4\n"
522 :(before
"End Command Handlers")
523 else if (command
== "container") {
524 insert_container(command
, CONTAINER
, in
);
527 //: Even though we allow containers to be extended, we don't allow this after
528 //: a call to transform_all. But we do want to detect this situation and raise
529 //: an error. This field will help us raise such errors.
530 :(before
"End type_info Fields")
531 int Num_calls_to_transform_all_at_first_definition
;
532 :(before
"End type_info Constructor")
533 Num_calls_to_transform_all_at_first_definition
= -1;
536 void insert_container(const string
& command
, kind_of_type kind
, istream
& in
) {
537 skip_whitespace_but_not_newline(in
);
538 string name
= next_word(in
);
540 assert(!has_data(in
));
541 raise
<< "incomplete container definition at end of file (0)\n" << end();
544 // End container Name Refinements
545 trace(101, "parse") << "--- defining " << command
<< ' ' << name
<< end();
546 if (!contains_key(Type_ordinal
, name
)
547 || get(Type_ordinal
, name
) == 0) {
548 put(Type_ordinal
, name
, Next_type_ordinal
++);
550 trace(102, "parse") << "type number: " << get(Type_ordinal
, name
) << end();
551 skip_bracket(in
, "'"+command
+"' must begin with '['");
552 type_info
& info
= get_or_insert(Type
, get(Type_ordinal
, name
));
553 if (info
.Num_calls_to_transform_all_at_first_definition
== -1) {
554 // initial definition of this container
555 info
.Num_calls_to_transform_all_at_first_definition
= Num_calls_to_transform_all
;
557 else if (info
.Num_calls_to_transform_all_at_first_definition
!= Num_calls_to_transform_all
) {
558 // extension after transform_all
559 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();
564 while (has_data(in
)) {
565 skip_whitespace_and_comments(in
);
566 string element
= next_word(in
);
567 if (element
.empty()) {
568 assert(!has_data(in
));
569 raise
<< "incomplete container definition at end of file (1)\n" << end();
572 if (element
== "]") break;
573 if (in
.peek() != '\n') {
574 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();
575 // skip rest of container declaration
576 while (has_data(in
)) {
577 skip_whitespace_and_comments(in
);
578 if (next_word(in
) == "]") break;
582 info
.elements
.push_back(reagent(element
));
583 expand_type_abbreviations(info
.elements
.back().type
); // todo: use abbreviation before declaration
584 replace_unknown_types_with_unique_ordinals(info
.elements
.back().type
, info
);
585 trace(103, "parse") << " element: " << to_string(info
.elements
.back()) << end();
586 // End Load Container Element Definition
590 void replace_unknown_types_with_unique_ordinals(type_tree
* type
, const type_info
& info
) {
593 replace_unknown_types_with_unique_ordinals(type
->left
, info
);
594 replace_unknown_types_with_unique_ordinals(type
->right
, info
);
597 assert(!type
->name
.empty());
598 if (contains_key(Type_ordinal
, type
->name
)) {
599 type
->value
= get(Type_ordinal
, type
->name
);
601 // End insert_container Special-cases
602 else if (type
->name
!= "->") { // used in recipe types
603 put(Type_ordinal
, type
->name
, Next_type_ordinal
++);
604 type
->value
= get(Type_ordinal
, type
->name
);
608 void skip_bracket(istream
& in
, string message
) {
609 skip_whitespace_and_comments(in
);
611 raise
<< message
<< '\n' << end();
614 void test_multi_word_line_in_container_declaration() {
621 CHECK_TRACE_CONTENTS(
622 "error: container 'foo' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code.\n"
626 //: support type abbreviations in container definitions
628 void test_type_abbreviations_in_containers() {
630 "type foo = number\n"
635 " 1:num <- copy 34\n"
636 " 2:foo <- get 1:bar/unsafe, 0:offset\n"
639 CHECK_TRACE_CONTENTS(
640 "mem: storing 34 in location 2\n"
644 :(after
"Transform.push_back(expand_type_abbreviations)")
645 Transform
.push_back(expand_type_abbreviations_in_containers
); // idempotent
647 // extremely inefficient; we process all types over and over again, once for every single recipe
648 // but it doesn't seem to cause any noticeable slowdown
649 void expand_type_abbreviations_in_containers(const recipe_ordinal
/*unused*/) {
650 for (map
<type_ordinal
, type_info
>::iterator p
= Type
.begin(); p
!= Type
.end(); ++p
) {
651 for (int i
= 0; i
< SIZE(p
->second
.elements
); ++i
)
652 expand_type_abbreviations(p
->second
.elements
.at(i
).type
);
656 //: ensure scenarios are consistent by always starting new container
657 //: declarations at the same type number
658 :(before
"End Reset") //: for tests
659 Next_type_ordinal
= 1000;
660 :(before
"End Test Run Initialization")
661 assert(Next_type_ordinal
< 1000);
664 void test_error_on_transform_all_between_container_definition_and_extension() {
665 // define a container
666 run("container foo [\n"
669 // try to extend the container after transform
671 CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
673 run("container foo [\n"
676 CHECK_TRACE_CONTAINS_ERRORS();
679 //:: Allow container definitions anywhere in the codebase, but complain if you
680 //:: can't find a definition at the end.
682 void test_run_complains_on_unknown_types() {
686 " 1:integer <- copy 0\n"
689 CHECK_TRACE_CONTENTS(
690 "error: main: unknown type integer in '1:integer <- copy 0'\n"
694 void test_run_allows_type_definition_after_use() {
697 " 1:bar <- copy 0/unsafe\n"
703 CHECK_TRACE_COUNT("error", 0);
706 :(before
"End Type Modifying Transforms")
707 Transform
.push_back(check_or_set_invalid_types
); // idempotent
710 void check_or_set_invalid_types(const recipe_ordinal r
) {
711 recipe
& caller
= get(Recipe
, r
);
712 trace(101, "transform") << "--- check for invalid types in recipe " << caller
.name
<< end();
713 for (int index
= 0; index
< SIZE(caller
.steps
); ++index
) {
714 instruction
& inst
= caller
.steps
.at(index
);
715 for (int i
= 0; i
< SIZE(inst
.ingredients
); ++i
)
716 check_or_set_invalid_types(inst
.ingredients
.at(i
), caller
, inst
);
717 for (int i
= 0; i
< SIZE(inst
.products
); ++i
)
718 check_or_set_invalid_types(inst
.products
.at(i
), caller
, inst
);
720 // End check_or_set_invalid_types
723 void check_or_set_invalid_types(reagent
& r
, const recipe
& caller
, const instruction
& inst
) {
724 // Begin check_or_set_invalid_types(r)
725 check_or_set_invalid_types(r
.type
, maybe(caller
.name
), "'"+to_original_string(inst
)+"'");
728 void check_or_set_invalid_types(type_tree
* type
, const string
& location_for_error_messages
, const string
& name_for_error_messages
) {
730 // End Container Type Checks
732 check_or_set_invalid_types(type
->left
, location_for_error_messages
, name_for_error_messages
);
733 check_or_set_invalid_types(type
->right
, location_for_error_messages
, name_for_error_messages
);
736 if (type
->value
== 0) return;
737 if (!contains_key(Type
, type
->value
)) {
738 assert(!type
->name
.empty());
739 if (contains_key(Type_ordinal
, type
->name
))
740 type
->value
= get(Type_ordinal
, type
->name
);
742 raise
<< location_for_error_messages
<< "unknown type " << type
->name
<< " in " << name_for_error_messages
<< '\n' << end();
746 void test_container_unknown_field() {
754 CHECK_TRACE_CONTENTS(
755 "error: foo: unknown type in y\n"
759 void test_read_container_with_bracket_in_comment() {
763 " # ']' in comment\n"
767 CHECK_TRACE_CONTENTS(
768 "parse: --- defining container foo\n"
769 "parse: element: {x: \"number\"}\n"
770 "parse: element: {y: \"number\"}\n"
773 void test_container_with_compound_field_type() {
776 " {x: (address array (address array character))}\n"
779 CHECK_TRACE_COUNT("error", 0);
782 :(before
"End transform_all")
783 check_container_field_types();
786 void check_container_field_types() {
787 for (map
<type_ordinal
, type_info
>::iterator p
= Type
.begin(); p
!= Type
.end(); ++p
) {
788 const type_info
& info
= p
->second
;
789 // Check Container Field Types(info)
790 for (int i
= 0; i
< SIZE(info
.elements
); ++i
)
791 check_invalid_types(info
.elements
.at(i
).type
, maybe(info
.name
), info
.elements
.at(i
).name
);
795 void check_invalid_types(const type_tree
* type
, const string
& location_for_error_messages
, const string
& name_for_error_messages
) {
796 if (!type
) return; // will throw a more precise error elsewhere
798 check_invalid_types(type
->left
, location_for_error_messages
, name_for_error_messages
);
799 check_invalid_types(type
->right
, location_for_error_messages
, name_for_error_messages
);
802 if (type
->value
!= 0) { // value 0 = compound types (layer parse_tree) or type ingredients (layer shape_shifting_container)
803 if (!contains_key(Type
, type
->value
))
804 raise
<< location_for_error_messages
<< "unknown type in " << name_for_error_messages
<< '\n' << end();
808 string
to_original_string(const type_ordinal t
) {
810 if (!contains_key(Type
, t
)) return out
.str();
811 const type_info
& info
= get(Type
, t
);
812 if (info
.kind
== PRIMITIVE
) return out
.str();
813 out
<< (info
.kind
== CONTAINER
? "container" : "exclusive-container") << " " << info
.name
<< " [\n";
814 for (int i
= 0; i
< SIZE(info
.elements
); ++i
) {
815 out
<< " " << info
.elements
.at(i
).original_string
<< "\n";