https://github.com/akkartik/mu/blob/master/030container.cc
1
2
3 :(before "End Mu Types Initialization")
4
5 type_ordinal point = put(Type_ordinal, "point", Next_type_ordinal++);
6 get_or_insert(Type, point);
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
13
14
15
16
17
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
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
37
38 type_ordinal point_number = put(Type_ordinal, "point-number", Next_type_ordinal++);
39 get_or_insert(Type, point_number);
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
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
69
70
71 :(scenario compare_multiple_locations)
72 def main [
73 1:num <- copy 34
74 2:num <- copy 35
75 3:num <- copy 36
76 4:num <- copy 34
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
86 2:num <- copy 35
87 3:num <- copy 36
88 4:num <- copy 34
89 5:num <- copy 35
90 6:num <- copy 37
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;
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
105 int result = 0;
106 for (int i = 0; i < SIZE(t.elements); ++i) {
107
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
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
127
128
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
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 base = inst.ingredients.at(0);
149
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
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
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 product = inst.products.at(0);
176
177
178 const reagent 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 base = current_instruction().ingredients.at(0);
188
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
196 int offset = ingredients.at(1).at(0);
197 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;
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
203 reagent 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
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
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();
221 reagent element = info.elements.at(offset_value);
222
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
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
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
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
272 ]
273
274
275
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 base = inst.ingredients.at(0);
298
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
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 offset = inst.ingredients.at(1);
310
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
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
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;
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
340 break;
341 }
342 :(before "End Primitive Recipe Implementations")
343 case PUT: {
344 reagent base = current_instruction().ingredients.at(0);
345
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
353 int offset = ingredients.at(1).at(0);
354 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break;
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
360
361
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
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
405
406 +parse: element: {y: "bar"}
407
408 +parse: --- defining container bar
409 +parse: type number: 1001
410 +parse: element: {x: "number"}
411 +parse: element: {y: "number"}
412
413
414 :(scenarios run)
415 :(scenario container_extend)
416 container foo [
417 x:num
418 ]
419
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
438
439
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
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
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
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
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);
494 replace_unknown_types_with_unique_ordinals(info.elements.back().type, info);
495 trace(9993, "parse") << " element: " << to_string(info.elements.back()) << end();
496
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
512 else if (type->name != "->") {
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
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);
546 :(code)
547
548
549 void expand_type_abbreviations_in_containers(const recipe_ordinal ) {
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
557
558 :(before "End Reset")
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
566 run("container foo [\n"
567 " a:num\n"
568 "]\n");
569
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
580
581
582 :(scenario run_complains_on_unknown_types)
583 % Hide_errors = true;
584 def main [
585
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);
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
614 }
615
616 void check_or_set_invalid_types(reagent& r, const recipe& caller, const instruction& inst) {
617
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
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
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
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;
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) {
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 }