fix other mandelbrot variants
[mu.git] / archive / 1.vm / 030container.cc
blobe20af025bafadd6705b2d3388b77ddb7fa182353
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.
19 :(code)
20 void test_copy_multiple_locations() {
21 run(
22 "def main [\n"
23 " 1:num <- copy 34\n"
24 " 2:num <- copy 35\n"
25 " 3:point <- copy 1:point/unsafe\n"
26 "]\n"
28 CHECK_TRACE_CONTENTS(
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() {
36 Hide_errors = true;
37 run(
38 "def main [\n"
39 " 2:point <- copy 1:num\n"
40 "]\n"
42 CHECK_TRACE_CONTENTS(
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
49 // its elements.
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"));
57 :(code)
58 void test_copy_handles_nested_container_elements() {
59 run(
60 "def main [\n"
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"
65 "]\n"
67 CHECK_TRACE_CONTENTS(
68 "mem: storing 36 in location 17\n"
72 //: products of recipes can include containers
73 void test_return_container() {
74 run(
75 "def main [\n"
76 " 3:point <- f 2\n"
77 "]\n"
78 "def f [\n"
79 " 12:num <- next-ingredient\n"
80 " 13:num <- copy 35\n"
81 " return 12:point/raw\n"
82 "]\n"
84 CHECK_TRACE_CONTENTS(
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() {
95 run(
96 "def main [\n"
97 " 1:num <- copy 34\n" // first
98 " 2:num <- copy 35\n"
99 " 3:num <- copy 36\n"
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"
104 "]\n"
106 CHECK_TRACE_CONTENTS(
107 "mem: storing 1 in location 7\n"
111 void test_compare_multiple_locations_2() {
112 run(
113 "def main [\n"
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"
121 "]\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();
133 return 0;
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
138 int result = 0;
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();
143 return 0;
145 result += size_of(element_type(type, i));
147 return result;
150 :(code)
151 void test_stash_container() {
152 run(
153 "def main [\n"
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"
158 "]\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.
169 void test_get() {
170 run(
171 "def main [\n"
172 " 12:num <- copy 34\n"
173 " 13:num <- copy 35\n"
174 " 15:num <- get 12:point/raw, 1:offset\n" // unsafe
175 "]\n"
177 CHECK_TRACE_CONTENTS(
178 "mem: storing 35 in location 15\n"
182 :(before "End Primitive Recipe Declarations")
183 GET,
184 :(before "End Primitive Recipe Numbers")
185 put(Recipe_ordinal, "get", GET);
186 :(before "End Primitive Recipe Checks")
187 case GET: {
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();
190 break;
192 reagent/*copy*/ base = inst.ingredients.at(0); // new copy for every invocation
193 // Update GET base in Check
194 if (!base.type) {
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();
196 break;
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();
202 break;
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();
207 break;
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();
216 break;
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();
225 break;
227 break;
229 :(before "End Primitive Recipe Implementations")
230 case GET: {
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();
236 break;
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();
250 // Read element
251 products.push_back(read_memory(element));
252 break;
255 :(code)
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
267 return element;
270 void test_get_handles_nested_container_elements() {
271 run(
272 "def main [\n"
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
277 "]\n"
279 CHECK_TRACE_CONTENTS(
280 "mem: storing 36 in location 15\n"
284 void test_get_out_of_bounds() {
285 Hide_errors = true;
286 run(
287 "def main [\n"
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
292 "]\n"
294 CHECK_TRACE_CONTENTS(
295 "error: main: invalid offset '2' for 'point-number'\n"
299 void test_get_out_of_bounds_2() {
300 Hide_errors = true;
301 run(
302 "def main [\n"
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"
307 "]\n"
309 CHECK_TRACE_CONTENTS(
310 "error: main: invalid offset '-1' for 'point-number'\n"
314 void test_get_product_type_mismatch() {
315 Hide_errors = true;
316 run(
317 "def main [\n"
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"
322 "]\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() {
332 run(
333 "def main [\n"
334 " 12:num <- copy 34\n"
335 " 13:num <- copy 35\n"
336 " get 12:point/raw, 1:offset\n" // unsafe
337 "]\n"
339 // just don't die
342 //:: To write to elements of containers, use 'put'.
344 void test_put() {
345 run(
346 "def main [\n"
347 " 12:num <- copy 34\n"
348 " 13:num <- copy 35\n"
349 " $clear-trace\n"
350 " 12:point <- put 12:point, 1:offset, 36\n"
351 "]\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")
360 PUT,
361 :(before "End Primitive Recipe Numbers")
362 put(Recipe_ordinal, "put", PUT);
363 :(before "End Primitive Recipe Checks")
364 case PUT: {
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();
367 break;
369 reagent/*copy*/ base = inst.ingredients.at(0);
370 // Update PUT base in Check
371 if (!base.type) {
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();
373 break;
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();
379 break;
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();
385 break;
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();
393 break;
396 else {
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();
404 break;
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();
409 break;
411 // End PUT Product Checks
412 break;
414 :(before "End Primitive Recipe Implementations")
415 case PUT: {
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();
421 break;
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));
439 break;
442 :(code)
443 void test_put_product_error() {
444 Hide_errors = true;
445 run(
446 "def main [\n"
447 " local-scope\n"
448 " load-ingredients\n"
449 " 1:point <- merge 34, 35\n"
450 " 3:point <- put 1:point, x:offset, 36\n"
451 "]\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() {
461 load(
462 "container foo [\n"
463 " x:num\n"
464 " y:num\n"
465 "]\n"
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() {
475 load(
476 "container foo [\n"
477 " x:num\n"
478 " y:bar\n"
479 "]\n"
480 "container bar [\n"
481 " x:num\n"
482 " y:num\n"
483 "]\n"
485 CHECK_TRACE_CONTENTS(
486 "parse: --- defining container foo\n"
487 "parse: type number: 1000\n"
488 "parse: element: {x: \"number\"}\n"
489 // todo: brittle
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() {
502 run(
503 "container foo [\n"
504 " x:num\n"
505 "]\n"
506 "container foo [\n" // add to previous definition
507 " y:num\n"
508 "]\n"
509 "def main [\n"
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"
514 "]\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;
535 :(code)
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);
539 if (name.empty()) {
540 assert(!has_data(in));
541 raise << "incomplete container definition at end of file (0)\n" << end();
542 return;
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();
560 return;
562 info.name = name;
563 info.kind = kind;
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();
570 return;
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;
580 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) {
591 if (!type) return;
592 if (!type->atom) {
593 replace_unknown_types_with_unique_ordinals(type->left, info);
594 replace_unknown_types_with_unique_ordinals(type->right, info);
595 return;
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);
610 if (in.get() != '[')
611 raise << message << '\n' << end();
614 void test_multi_word_line_in_container_declaration() {
615 Hide_errors = true;
616 run(
617 "container foo [\n"
618 " x:num y:num\n"
619 "]\n"
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() {
629 run(
630 "type foo = number\n"
631 "container bar [\n"
632 " x:foo\n"
633 "]\n"
634 "def main [\n"
635 " 1:num <- copy 34\n"
636 " 2:foo <- get 1:bar/unsafe, 0:offset\n"
637 "]\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
646 :(code)
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);
663 :(code)
664 void test_error_on_transform_all_between_container_definition_and_extension() {
665 // define a container
666 run("container foo [\n"
667 " a:num\n"
668 "]\n");
669 // try to extend the container after transform
670 transform_all();
671 CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
672 Hide_errors = true;
673 run("container foo [\n"
674 " b:num\n"
675 "]\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() {
683 Hide_errors = true;
684 run(
685 "def main [\n"
686 " 1:integer <- copy 0\n"
687 "]\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() {
695 run(
696 "def main [\n"
697 " 1:bar <- copy 0/unsafe\n"
698 "]\n"
699 "container bar [\n"
700 " x:num\n"
701 "]\n"
703 CHECK_TRACE_COUNT("error", 0);
706 :(before "End Type Modifying Transforms")
707 Transform.push_back(check_or_set_invalid_types); // idempotent
709 :(code)
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) {
729 if (!type) return;
730 // End Container Type Checks
731 if (!type->atom) {
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);
734 return;
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);
741 else
742 raise << location_for_error_messages << "unknown type " << type->name << " in " << name_for_error_messages << '\n' << end();
746 void test_container_unknown_field() {
747 Hide_errors = true;
748 run(
749 "container foo [\n"
750 " x:num\n"
751 " y:bar\n"
752 "]\n"
754 CHECK_TRACE_CONTENTS(
755 "error: foo: unknown type in y\n"
759 void test_read_container_with_bracket_in_comment() {
760 run(
761 "container foo [\n"
762 " x:num\n"
763 " # ']' in comment\n"
764 " y:num\n"
765 "]\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() {
774 run(
775 "container foo [\n"
776 " {x: (address array (address array character))}\n"
777 "]\n"
779 CHECK_TRACE_COUNT("error", 0);
782 :(before "End transform_all")
783 check_container_field_types();
785 :(code)
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
797 if (!type->atom) {
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);
800 return;
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) {
809 ostringstream out;
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";
817 out << "]\n";
818 return out.str();