From 1c2d788b454670bf8fa1cb65c6251a8ff6ddcaf7 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 19 Jun 2017 11:29:20 -0700 Subject: 3927 --- html/030container.cc.html | 1332 +++++++++++++++++++++++---------------------- 1 file changed, 672 insertions(+), 660 deletions(-) (limited to 'html/030container.cc.html') diff --git a/html/030container.cc.html b/html/030container.cc.html index b5e82041..f2d68105 100644 --- a/html/030container.cc.html +++ b/html/030container.cc.html @@ -218,14 +218,14 @@ if ('onhashchange' in window) { 151 const type_tree* base_type = type; 152 // Update base_type in size_of(type) 153 if (!contains_key(Type, base_type->value)) { -154 raise << "no such type " << base_type->value << '\n' << end(); +154 raise << "no such type " << base_type->value << '\n' << end(); 155 return 0; 156 } 157 type_info t = get(Type, base_type->value); 158 if (t.kind == CONTAINER) { 159 // Compute size_of Container 160 if (!contains_key(Container_metadata, type)) { -161 ¦ raise << "unknown size for container type '" << to_string(type) << "'\n" << end(); +161 ¦ raise << "unknown size for container type '" << to_string(type) << "'\n" << end(); 162 //? DUMP(""); 163 ¦ return 0; 164 } @@ -240,10 +240,10 @@ if ('onhashchange' in window) { 173 :(code) 174 void compute_container_sizes(const recipe_ordinal r) { 175 recipe& caller = get(Recipe, r); -176 trace(9992, "transform") << "--- compute container sizes for " << caller.name << end(); +176 trace(9992, "transform") << "--- compute container sizes for " << caller.name << end(); 177 for (int i = 0; i < SIZE(caller.steps); ++i) { 178 ¦ instruction& inst = caller.steps.at(i); -179 ¦ trace(9993, "transform") << "- compute container sizes for " << to_string(inst) << end(); +179 ¦ trace(9993, "transform") << "- compute container sizes for " << to_string(inst) << end(); 180 ¦ for (int i = 0; i < SIZE(inst.ingredients); ++i) 181 ¦ ¦ compute_container_sizes(inst.ingredients.at(i), " in '"+to_original_string(inst)+"'"); 182 ¦ for (int i = 0; i < SIZE(inst.products); ++i) @@ -264,13 +264,13 @@ if ('onhashchange' in window) { 197 198 void compute_container_sizes(const type_tree* type, set<type_tree>& pending_metadata, const string& location_for_error_messages) { 199 if (!type) return; -200 trace(9993, "transform") << "compute container sizes for " << to_string(type) << end(); +200 trace(9993, "transform") << "compute container sizes for " << to_string(type) << end(); 201 if (contains_key(Container_metadata, type)) return; 202 if (contains_key(pending_metadata, *type)) return; 203 pending_metadata.insert(*type); 204 if (!type->atom) { 205 ¦ if (!type->left->atom) { -206 ¦ ¦ raise << "invalid type " << to_string(type) << location_for_error_messages << '\n' << end(); +206 ¦ ¦ raise << "invalid type " << to_string(type) << location_for_error_messages << '\n' << end(); 207 ¦ ¦ return; 208 ¦ } 209 ¦ if (type->left->name == "address") @@ -312,662 +312,674 @@ if ('onhashchange' in window) { 245 246 container_metadata& get(vector<pair<type_tree*, container_metadata> >& all, const type_tree* key) { 247 for (int i = 0; i < SIZE(all); ++i) { -248 ¦ if (matches(all.at(i).first, key)) +248 ¦ if (matches(all.at(i).first, key)) 249 ¦ ¦ return all.at(i).second; 250 } -251 tb_shutdown(); -252 raise << "unknown size for type '" << to_string(key) << "'\n" << end(); -253 assert(false); -254 } -255 -256 bool contains_key(const vector<pair<type_tree*, container_metadata> >& all, const type_tree* key) { -257 for (int i = 0; i < SIZE(all); ++i) { -258 ¦ if (matches(all.at(i).first, key)) -259 ¦ ¦ return true; -260 } -261 return false; -262 } -263 -264 bool matches(const type_tree* a, const type_tree* b) { -265 if (a == b) return true; -266 if (!a || !b) return false; -267 if (a->atom != b->atom) return false; -268 if (a->atom) return a->value == b->value; -269 return matches(a->left, b->left) && matches(a->right, b->right); -270 } -271 -272 :(scenario stash_container) -273 def main [ -274 1:num <- copy 34 # first -275 2:num <- copy 35 -276 3:num <- copy 36 -277 stash [foo:], 1:point-number/raw -278 ] -279 +app: foo: 34 35 36 -280 -281 //: for the following unit tests we'll do the work of the transform by hand -282 -283 :(before "End Unit Tests") -284 void test_container_sizes() { -285 // a container we don't have the size for -286 reagent r("x:point"); -287 CHECK(!contains_key(Container_metadata, r.type)); -288 // scan -289 compute_container_sizes(r, ""); -290 // the reagent we scanned knows its size -291 CHECK_EQ(r.metadata.size, 2); -292 // the global table also knows its size -293 CHECK(contains_key(Container_metadata, r.type)); -294 CHECK_EQ(get(Container_metadata, r.type).size, 2); -295 } -296 -297 void test_container_sizes_through_aliases() { -298 // a new alias for a container -299 put(Type_abbreviations, "pt", new_type_tree("point")); -300 reagent r("x:pt"); -301 // scan -302 compute_container_sizes(r, ""); -303 // the reagent we scanned knows its size -304 CHECK_EQ(r.metadata.size, 2); -305 // the global table also knows its size -306 CHECK(contains_key(Container_metadata, r.type)); -307 CHECK_EQ(get(Container_metadata, r.type).size, 2); -308 } -309 -310 void test_container_sizes_nested() { -311 // a container we don't have the size for -312 reagent r("x:point-number"); -313 CHECK(!contains_key(Container_metadata, r.type)); -314 // scan -315 compute_container_sizes(r, ""); -316 // the reagent we scanned knows its size -317 CHECK_EQ(r.metadata.size, 3); -318 // the global table also knows its size -319 CHECK(contains_key(Container_metadata, r.type)); -320 CHECK_EQ(get(Container_metadata, r.type).size, 3); -321 } -322 -323 void test_container_sizes_recursive() { -324 // define a container containing an address to itself -325 run("container foo [\n" -326 ¦ ¦ " x:num\n" -327 ¦ ¦ " y:address:foo\n" -328 ¦ ¦ "]\n"); -329 reagent r("x:foo"); -330 compute_container_sizes(r, ""); -331 CHECK_EQ(r.metadata.size, 2); -332 } -333 -334 void test_container_sizes_from_address() { -335 // a container we don't have the size for -336 reagent container("x:point"); -337 CHECK(!contains_key(Container_metadata, container.type)); -338 // scanning an address to the container precomputes the size of the container -339 reagent r("x:address:point"); -340 compute_container_sizes(r, ""); -341 CHECK(contains_key(Container_metadata, container.type)); -342 CHECK_EQ(get(Container_metadata, container.type).size, 2); -343 } -344 -345 //:: To access elements of a container, use 'get' -346 //: 'get' takes a 'base' container and an 'offset' into it and returns the -347 //: appropriate element of the container value. -348 -349 :(scenario get) -350 def main [ -351 12:num <- copy 34 -352 13:num <- copy 35 -353 15:num <- get 12:point/raw, 1:offset # unsafe -354 ] -355 +mem: storing 35 in location 15 -356 -357 :(before "End Primitive Recipe Declarations") -358 GET, -359 :(before "End Primitive Recipe Numbers") -360 put(Recipe_ordinal, "get", GET); -361 :(before "End Primitive Recipe Checks") -362 case GET: { -363 if (SIZE(inst.ingredients) != 2) { -364 ¦ raise << maybe(get(Recipe, r).name) << "'get' expects exactly 2 ingredients in '" << to_original_string(inst) << "'\n" << end(); -365 ¦ break; -366 } -367 reagent/*copy*/ base = inst.ingredients.at(0); // new copy for every invocation -368 // Update GET base in Check -369 if (!base.type) { -370 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -371 ¦ break; -372 } -373 const type_tree* base_type = base.type; -374 // Update GET base_type in Check -375 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) { -376 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -377 ¦ break; -378 } -379 const reagent& offset = inst.ingredients.at(1); -380 if (!is_literal(offset) || !is_mu_scalar(offset)) { -381 ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); -382 ¦ break; -383 } -384 int offset_value = 0; -385 if (is_integer(offset.name)) { -386 ¦ offset_value = to_integer(offset.name); -387 } -388 // End update GET offset_value in Check -389 if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { -390 ¦ raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); -391 ¦ break; -392 } -393 if (inst.products.empty()) break; -394 reagent/*copy*/ product = inst.products.at(0); -395 // Update GET product in Check -396 //: use base.type rather than base_type because later layers will introduce compound types -397 const reagent/*copy*/ element = element_type(base.type, offset_value); -398 if (!types_coercible(product, element)) { -399 ¦ 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(); -400 ¦ break; -401 } -402 break; -403 } -404 :(before "End Primitive Recipe Implementations") -405 case GET: { -406 reagent/*copy*/ base = current_instruction().ingredients.at(0); -407 // Update GET base in Run -408 int base_address = base.value; -409 if (base_address == 0) { -410 ¦ raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); -411 ¦ break; -412 } -413 const type_tree* base_type = base.type; -414 // Update GET base_type in Run -415 int offset = ingredients.at(1).at(0); -416 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above -417 assert(base.metadata.size); -418 int src = base_address + base.metadata.offset.at(offset); -419 trace(9998, "run") << "address to copy is " << src << end(); -420 //: use base.type rather than base_type because later layers will introduce compound types -421 reagent/*copy*/ element = element_type(base.type, offset); -422 element.set_value(src); -423 trace(9998, "run") << "its type is " << names_to_string(element.type) << end(); -424 // Read element -425 products.push_back(read_memory(element)); -426 break; -427 } -428 -429 :(code) -430 const reagent element_type(const type_tree* type, int offset_value) { -431 assert(offset_value >= 0); -432 const type_tree* base_type = type; -433 // Update base_type in element_type -434 assert(contains_key(Type, base_type->value)); -435 assert(!get(Type, base_type->value).name.empty()); -436 const type_info& info = get(Type, base_type->value); -437 assert(info.kind == CONTAINER); -438 if (offset_value >= SIZE(info.elements)) return reagent(); // error handled elsewhere -439 reagent/*copy*/ element = info.elements.at(offset_value); -440 // End element_type Special-cases -441 return element; -442 } -443 -444 :(scenario get_handles_nested_container_elements) -445 def main [ -446 12:num <- copy 34 -447 13:num <- copy 35 -448 14:num <- copy 36 -449 15:num <- get 12:point-number/raw, 1:offset # unsafe -450 ] -451 +mem: storing 36 in location 15 -452 -453 :(scenario get_out_of_bounds) -454 % Hide_errors = true; -455 def main [ -456 12:num <- copy 34 -457 13:num <- copy 35 -458 14:num <- copy 36 -459 get 12:point-number/raw, 2:offset # point-number occupies 3 locations but has only 2 fields; out of bounds -460 ] -461 +error: main: invalid offset '2' for 'point-number' -462 -463 :(scenario get_out_of_bounds_2) -464 % Hide_errors = true; -465 def main [ -466 12:num <- copy 34 -467 13:num <- copy 35 -468 14:num <- copy 36 -469 get 12:point-number/raw, -1:offset -470 ] -471 +error: main: invalid offset '-1' for 'point-number' -472 -473 :(scenario get_product_type_mismatch) -474 % Hide_errors = true; -475 def main [ -476 12:num <- copy 34 -477 13:num <- copy 35 -478 14:num <- copy 36 -479 15:address:num <- get 12:point-number/raw, 1:offset -480 ] -481 +error: main: 'get 12:point-number/raw, 1:offset' should write to number but '15' has type (address number) -482 -483 //: we might want to call 'get' without saving the results, say in a sandbox -484 -485 :(scenario get_without_product) -486 def main [ -487 12:num <- copy 34 -488 13:num <- copy 35 -489 get 12:point/raw, 1:offset # unsafe -490 ] -491 # just don't die -492 -493 //:: To write to elements of containers, use 'put'. -494 -495 :(scenario put) -496 def main [ -497 12:num <- copy 34 -498 13:num <- copy 35 -499 $clear-trace -500 12:point <- put 12:point, 1:offset, 36 -501 ] -502 +mem: storing 36 in location 13 -503 -mem: storing 34 in location 12 -504 -505 :(before "End Primitive Recipe Declarations") -506 PUT, -507 :(before "End Primitive Recipe Numbers") -508 put(Recipe_ordinal, "put", PUT); -509 :(before "End Primitive Recipe Checks") -510 case PUT: { -511 if (SIZE(inst.ingredients) != 3) { -512 ¦ raise << maybe(get(Recipe, r).name) << "'put' expects exactly 3 ingredients in '" << to_original_string(inst) << "'\n" << end(); -513 ¦ break; -514 } -515 reagent/*copy*/ base = inst.ingredients.at(0); -516 // Update PUT base in Check -517 if (!base.type) { -518 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -519 ¦ break; -520 } -521 const type_tree* base_type = base.type; -522 // Update PUT base_type in Check -523 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) { -524 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -525 ¦ break; -526 } -527 reagent/*copy*/ offset = inst.ingredients.at(1); -528 // Update PUT offset in Check -529 if (!is_literal(offset) || !is_mu_scalar(offset)) { -530 ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'put' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); -531 ¦ break; -532 } -533 int offset_value = 0; -534 //: later layers will permit non-integer offsets -535 if (is_integer(offset.name)) { -536 ¦ offset_value = to_integer(offset.name); -537 ¦ if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { -538 ¦ ¦ raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); -539 ¦ ¦ break; -540 ¦ } -541 } -542 else { -543 ¦ offset_value = offset.value; -544 } -545 const reagent& value = inst.ingredients.at(2); -546 //: use base.type rather than base_type because later layers will introduce compound types -547 const reagent& element = element_type(base.type, offset_value); -548 if (!types_coercible(element, value)) { -549 ¦ 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(); -550 ¦ break; -551 } -552 if (inst.products.empty()) break; // no more checks necessary -553 if (inst.products.at(0).name != inst.ingredients.at(0).name) { -554 ¦ 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(); -555 ¦ break; -556 } -557 // End PUT Product Checks -558 break; -559 } -560 :(before "End Primitive Recipe Implementations") -561 case PUT: { -562 reagent/*copy*/ base = current_instruction().ingredients.at(0); -563 // Update PUT base in Run -564 int base_address = base.value; -565 if (base_address == 0) { -566 ¦ raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); -567 ¦ break; -568 } -569 const type_tree* base_type = base.type; -570 // Update PUT base_type in Run -571 int offset = ingredients.at(1).at(0); -572 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above -573 int address = base_address + base.metadata.offset.at(offset); -574 trace(9998, "run") << "address to copy to is " << address << end(); -575 // optimization: directly write the element rather than updating 'product' -576 // and writing the entire container -577 // Write Memory in PUT in Run -578 write_products = false; -579 for (int i = 0; i < SIZE(ingredients.at(2)); ++i) { -580 ¦ trace(9999, "mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end(); -581 ¦ put(Memory, address+i, ingredients.at(2).at(i)); -582 } -583 break; -584 } -585 -586 :(scenario put_product_error) -587 % Hide_errors = true; -588 def main [ -589 local-scope -590 load-ingredients -591 1:point <- merge 34, 35 -592 3:point <- put 1:point, x:offset, 36 -593 ] -594 +error: main: product of 'put' must be first ingredient '1:point', but got '3:point' -595 -596 //:: Allow containers to be defined in Mu code. -597 -598 :(scenarios load) -599 :(scenario container) -600 container foo [ -601 x:num -602 y:num -603 ] -604 +parse: --- defining container foo -605 +parse: element: {x: "number"} -606 +parse: element: {y: "number"} -607 -608 :(scenario container_use_before_definition) -609 container foo [ -610 x:num -611 y:bar -612 ] -613 container bar [ -614 x:num -615 y:num -616 ] -617 +parse: --- defining container foo -618 +parse: type number: 1000 -619 +parse: element: {x: "number"} -620 # todo: brittle -621 # type bar is unknown at this point, but we assign it a number -622 +parse: element: {y: "bar"} -623 # later type bar geon -624 +parse: --- defining container bar -625 +parse: type number: 1001 -626 +parse: element: {x: "number"} -627 +parse: element: {y: "number"} -628 -629 //: if a container is defined again, the new fields add to the original definition -630 :(scenarios run) -631 :(scenario container_extend) -632 container foo [ -633 x:num -634 ] -635 # add to previous definition -636 container foo [ -637 y:num -638 ] -639 def main [ -640 1:num <- copy 34 -641 2:num <- copy 35 -642 3:num <- get 1:foo, 0:offset -643 4:num <- get 1:foo, 1:offset -644 ] -645 +mem: storing 34 in location 3 -646 +mem: storing 35 in location 4 -647 -648 :(before "End Command Handlers") -649 else if (command == "container") { -650 insert_container(command, CONTAINER, in); -651 } -652 -653 //: Even though we allow containers to be extended, we don't allow this after -654 //: a call to transform_all. But we do want to detect this situation and raise -655 //: an error. This field will help us raise such errors. -656 :(before "End type_info Fields") -657 int Num_calls_to_transform_all_at_first_definition; -658 :(before "End type_info Constructor") -659 Num_calls_to_transform_all_at_first_definition = -1; -660 -661 :(code) -662 void insert_container(const string& command, kind_of_type kind, istream& in) { -663 skip_whitespace_but_not_newline(in); -664 string name = next_word(in); -665 if (name.empty()) { -666 ¦ assert(!has_data(in)); -667 ¦ raise << "incomplete container definition at end of file (0)\n" << end(); -668 ¦ return; -669 } -670 // End container Name Refinements -671 trace(9991, "parse") << "--- defining " << command << ' ' << name << end(); -672 if (!contains_key(Type_ordinal, name) -673 ¦ ¦ || get(Type_ordinal, name) == 0) { -674 ¦ put(Type_ordinal, name, Next_type_ordinal++); -675 } -676 trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end(); -677 skip_bracket(in, "'"+command+"' must begin with '['"); -678 type_info& info = get_or_insert(Type, get(Type_ordinal, name)); -679 if (info.Num_calls_to_transform_all_at_first_definition == -1) { -680 ¦ // initial definition of this container -681 ¦ info.Num_calls_to_transform_all_at_first_definition = Num_calls_to_transform_all; -682 } -683 else if (info.Num_calls_to_transform_all_at_first_definition != Num_calls_to_transform_all) { -684 ¦ // extension after transform_all -685 ¦ 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(); -686 ¦ return; -687 } -688 info.name = name; -689 info.kind = kind; -690 while (has_data(in)) { -691 ¦ skip_whitespace_and_comments(in); -692 ¦ string element = next_word(in); -693 ¦ if (element.empty()) { -694 ¦ ¦ assert(!has_data(in)); -695 ¦ ¦ raise << "incomplete container definition at end of file (1)\n" << end(); -696 ¦ ¦ return; -697 ¦ } -698 ¦ if (element == "]") break; -699 ¦ if (in.peek() != '\n') { -700 ¦ ¦ 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(); -701 ¦ ¦ // skip rest of container declaration -702 ¦ ¦ while (has_data(in)) { -703 ¦ ¦ ¦ skip_whitespace_and_comments(in); -704 ¦ ¦ ¦ if (next_word(in) == "]") break; -705 ¦ ¦ } -706 ¦ ¦ break; -707 ¦ } -708 ¦ info.elements.push_back(reagent(element)); -709 ¦ expand_type_abbreviations(info.elements.back().type); // todo: use abbreviation before declaration -710 ¦ replace_unknown_types_with_unique_ordinals(info.elements.back().type, info); -711 ¦ trace(9993, "parse") << " element: " << to_string(info.elements.back()) << end(); -712 ¦ // End Load Container Element Definition -713 } -714 } -715 -716 void replace_unknown_types_with_unique_ordinals(type_tree* type, const type_info& info) { -717 if (!type) return; -718 if (!type->atom) { -719 ¦ replace_unknown_types_with_unique_ordinals(type->left, info); -720 ¦ replace_unknown_types_with_unique_ordinals(type->right, info); -721 ¦ return; -722 } -723 assert(!type->name.empty()); -724 if (contains_key(Type_ordinal, type->name)) { -725 ¦ type->value = get(Type_ordinal, type->name); -726 } -727 // End insert_container Special-cases -728 else if (type->name != "->") { // used in recipe types -729 ¦ put(Type_ordinal, type->name, Next_type_ordinal++); -730 ¦ type->value = get(Type_ordinal, type->name); -731 } -732 } -733 -734 void skip_bracket(istream& in, string message) { -735 skip_whitespace_and_comments(in); -736 if (in.get() != '[') -737 ¦ raise << message << '\n' << end(); -738 } -739 -740 :(scenario multi_word_line_in_container_declaration) -741 % Hide_errors = true; -742 container foo [ -743 x:num y:num -744 ] -745 +error: container 'foo' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code. -746 -747 //: support type abbreviations in container definitions -748 -749 :(scenario type_abbreviations_in_containers) -750 type foo = number -751 container bar [ -752 x:foo -753 ] -754 def main [ -755 1:num <- copy 34 -756 2:foo <- get 1:bar/unsafe, 0:offset -757 ] -758 +mem: storing 34 in location 2 -759 -760 :(after "Transform.push_back(expand_type_abbreviations)") -761 Transform.push_back(expand_type_abbreviations_in_containers); // idempotent -762 :(code) -763 // extremely inefficient; we process all types over and over again, once for every single recipe -764 // but it doesn't seem to cause any noticeable slowdown -765 void expand_type_abbreviations_in_containers(unused const recipe_ordinal r) { -766 for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { -767 ¦ for (int i = 0; i < SIZE(p->second.elements); ++i) -768 ¦ ¦ expand_type_abbreviations(p->second.elements.at(i).type); -769 } -770 } -771 -772 //: ensure scenarios are consistent by always starting new container -773 //: declarations at the same type number -774 :(before "End Setup") //: for tests -775 Next_type_ordinal = 1000; -776 :(before "End Test Run Initialization") -777 assert(Next_type_ordinal < 1000); -778 -779 :(code) -780 void test_error_on_transform_all_between_container_definition_and_extension() { -781 // define a container -782 run("container foo [\n" -783 ¦ ¦ " a:num\n" -784 ¦ ¦ "]\n"); -785 // try to extend the container after transform -786 transform_all(); -787 CHECK_TRACE_DOESNT_CONTAIN_ERRORS(); -788 Hide_errors = true; -789 run("container foo [\n" -790 ¦ ¦ " b:num\n" -791 ¦ ¦ "]\n"); -792 CHECK_TRACE_CONTAINS_ERRORS(); -793 } -794 -795 //:: Allow container definitions anywhere in the codebase, but complain if you -796 //:: can't find a definition at the end. -797 -798 :(scenario run_complains_on_unknown_types) -799 % Hide_errors = true; -800 def main [ -801 # integer is not a type -802 1:integer <- copy 0 -803 ] -804 +error: main: unknown type integer in '1:integer <- copy 0' -805 -806 :(scenario run_allows_type_definition_after_use) -807 def main [ -808 1:bar <- copy 0/unsafe -809 ] -810 container bar [ -811 x:num -812 ] -813 $error: 0 -814 -815 :(before "End Type Modifying Transforms") -816 Transform.push_back(check_or_set_invalid_types); // idempotent -817 -818 :(code) -819 void check_or_set_invalid_types(const recipe_ordinal r) { -820 recipe& caller = get(Recipe, r); -821 trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end(); -822 for (int index = 0; index < SIZE(caller.steps); ++index) { -823 ¦ instruction& inst = caller.steps.at(index); -824 ¦ for (int i = 0; i < SIZE(inst.ingredients); ++i) -825 ¦ ¦ check_or_set_invalid_types(inst.ingredients.at(i), caller, inst); -826 ¦ for (int i = 0; i < SIZE(inst.products); ++i) -827 ¦ ¦ check_or_set_invalid_types(inst.products.at(i), caller, inst); -828 } -829 // End check_or_set_invalid_types -830 } -831 -832 void check_or_set_invalid_types(reagent& r, const recipe& caller, const instruction& inst) { -833 // Begin check_or_set_invalid_types(r) -834 check_or_set_invalid_types(r.type, maybe(caller.name), "'"+to_original_string(inst)+"'"); -835 } -836 -837 void check_or_set_invalid_types(type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) { -838 if (!type) return; -839 // End Container Type Checks -840 if (!type->atom) { -841 ¦ check_or_set_invalid_types(type->left, location_for_error_messages, name_for_error_messages); -842 ¦ check_or_set_invalid_types(type->right, location_for_error_messages, name_for_error_messages); -843 ¦ return; -844 } -845 if (type->value == 0) return; -846 if (!contains_key(Type, type->value)) { -847 ¦ assert(!type->name.empty()); -848 ¦ if (contains_key(Type_ordinal, type->name)) -849 ¦ ¦ type->value = get(Type_ordinal, type->name); -850 ¦ else -851 ¦ ¦ raise << location_for_error_messages << "unknown type " << type->name << " in " << name_for_error_messages << '\n' << end(); -852 } -853 } -854 -855 :(scenario container_unknown_field) -856 % Hide_errors = true; -857 container foo [ -858 x:num -859 y:bar -860 ] -861 +error: foo: unknown type in y -862 -863 :(scenario read_container_with_bracket_in_comment) -864 container foo [ -865 x:num -866 # ']' in comment -867 y:num -868 ] -869 +parse: --- defining container foo -870 +parse: element: {x: "number"} -871 +parse: element: {y: "number"} -872 -873 :(scenario container_with_compound_field_type) -874 container foo [ -875 {x: (address array (address array character))} -876 ] -877 $error: 0 -878 -879 :(before "End transform_all") -880 check_container_field_types(); -881 -882 :(code) -883 void check_container_field_types() { -884 for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { -885 ¦ const type_info& info = p->second; -886 ¦ // Check Container Field Types(info) -887 ¦ for (int i = 0; i < SIZE(info.elements); ++i) -888 ¦ ¦ check_invalid_types(info.elements.at(i).type, maybe(info.name), info.elements.at(i).name); -889 } -890 } -891 -892 void check_invalid_types(const type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) { -893 if (!type) return; // will throw a more precise error elsewhere -894 if (!type->atom) { -895 ¦ check_invalid_types(type->left, location_for_error_messages, name_for_error_messages); -896 ¦ check_invalid_types(type->right, location_for_error_messages, name_for_error_messages); -897 ¦ return; -898 } -899 if (type->value != 0) { // value 0 = compound types (layer parse_tree) or type ingredients (layer shape_shifting_container) -900 ¦ if (!contains_key(Type, type->value)) -901 ¦ ¦ raise << location_for_error_messages << "unknown type in " << name_for_error_messages << '\n' << end(); -902 } -903 } +251 raise << "unknown size for type '" << to_string(key) << "'\n" << end(); +252 exit(1); +253 } +254 +255 bool contains_key(const vector<pair<type_tree*, container_metadata> >& all, const type_tree* key) { +256 for (int i = 0; i < SIZE(all); ++i) { +257 ¦ if (matches(all.at(i).first, key)) +258 ¦ ¦ return true; +259 } +260 return false; +261 } +262 +263 bool matches(const type_tree* a, const type_tree* b) { +264 if (a == b) return true; +265 if (!a || !b) return false; +266 if (a->atom != b->atom) return false; +267 if (a->atom) return a->value == b->value; +268 return matches(a->left, b->left) && matches(a->right, b->right); +269 } +270 +271 :(scenario stash_container) +272 def main [ +273 1:num <- copy 34 # first +274 2:num <- copy 35 +275 3:num <- copy 36 +276 stash [foo:], 1:point-number/raw +277 ] +278 +app: foo: 34 35 36 +279 +280 //: for the following unit tests we'll do the work of the transform by hand +281 +282 :(before "End Unit Tests") +283 void test_container_sizes() { +284 // a container we don't have the size for +285 reagent r("x:point"); +286 CHECK(!contains_key(Container_metadata, r.type)); +287 // scan +288 compute_container_sizes(r, ""); +289 // the reagent we scanned knows its size +290 CHECK_EQ(r.metadata.size, 2); +291 // the global table also knows its size +292 CHECK(contains_key(Container_metadata, r.type)); +293 CHECK_EQ(get(Container_metadata, r.type).size, 2); +294 } +295 +296 void test_container_sizes_through_aliases() { +297 // a new alias for a container +298 put(Type_abbreviations, "pt", new_type_tree("point")); +299 reagent r("x:pt"); +300 // scan +301 compute_container_sizes(r, ""); +302 // the reagent we scanned knows its size +303 CHECK_EQ(r.metadata.size, 2); +304 // the global table also knows its size +305 CHECK(contains_key(Container_metadata, r.type)); +306 CHECK_EQ(get(Container_metadata, r.type).size, 2); +307 } +308 +309 void test_container_sizes_nested() { +310 // a container we don't have the size for +311 reagent r("x:point-number"); +312 CHECK(!contains_key(Container_metadata, r.type)); +313 // scan +314 compute_container_sizes(r, ""); +315 // the reagent we scanned knows its size +316 CHECK_EQ(r.metadata.size, 3); +317 // the global table also knows its size +318 CHECK(contains_key(Container_metadata, r.type)); +319 CHECK_EQ(get(Container_metadata, r.type).size, 3); +320 } +321 +322 void test_container_sizes_recursive() { +323 // define a container containing an address to itself +324 run("container foo [\n" +325 ¦ ¦ " x:num\n" +326 ¦ ¦ " y:address:foo\n" +327 ¦ ¦ "]\n"); +328 reagent r("x:foo"); +329 compute_container_sizes(r, ""); +330 CHECK_EQ(r.metadata.size, 2); +331 } +332 +333 void test_container_sizes_from_address() { +334 // a container we don't have the size for +335 reagent container("x:point"); +336 CHECK(!contains_key(Container_metadata, container.type)); +337 // scanning an address to the container precomputes the size of the container +338 reagent r("x:address:point"); +339 compute_container_sizes(r, ""); +340 CHECK(contains_key(Container_metadata, container.type)); +341 CHECK_EQ(get(Container_metadata, container.type).size, 2); +342 } +343 +344 //:: To access elements of a container, use 'get' +345 //: 'get' takes a 'base' container and an 'offset' into it and returns the +346 //: appropriate element of the container value. +347 +348 :(scenario get) +349 def main [ +350 12:num <- copy 34 +351 13:num <- copy 35 +352 15:num <- get 12:point/raw, 1:offset # unsafe +353 ] +354 +mem: storing 35 in location 15 +355 +356 :(before "End Primitive Recipe Declarations") +357 GET, +358 :(before "End Primitive Recipe Numbers") +359 put(Recipe_ordinal, "get", GET); +360 :(before "End Primitive Recipe Checks") +361 case GET: { +362 if (SIZE(inst.ingredients) != 2) { +363 ¦ raise << maybe(get(Recipe, r).name) << "'get' expects exactly 2 ingredients in '" << to_original_string(inst) << "'\n" << end(); +364 ¦ break; +365 } +366 reagent/*copy*/ base = inst.ingredients.at(0); // new copy for every invocation +367 // Update GET base in Check +368 if (!base.type) { +369 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +370 ¦ break; +371 } +372 const type_tree* base_type = base.type; +373 // Update GET base_type in Check +374 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) { +375 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +376 ¦ break; +377 } +378 const reagent& offset = inst.ingredients.at(1); +379 if (!is_literal(offset) || !is_mu_scalar(offset)) { +380 ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); +381 ¦ break; +382 } +383 int offset_value = 0; +384 if (is_integer(offset.name)) { +385 ¦ offset_value = to_integer(offset.name); +386 } +387 // End update GET offset_value in Check +388 if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { +389 ¦ raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); +390 ¦ break; +391 } +392 if (inst.products.empty()) break; +393 reagent/*copy*/ product = inst.products.at(0); +394 // Update GET product in Check +395 //: use base.type rather than base_type because later layers will introduce compound types +396 const reagent/*copy*/ element = element_type(base.type, offset_value); +397 if (!types_coercible(product, element)) { +398 ¦ 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(); +399 ¦ break; +400 } +401 break; +402 } +403 :(before "End Primitive Recipe Implementations") +404 case GET: { +405 reagent/*copy*/ base = current_instruction().ingredients.at(0); +406 // Update GET base in Run +407 int base_address = base.value; +408 if (base_address == 0) { +409 ¦ raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); +410 ¦ break; +411 } +412 const type_tree* base_type = base.type; +413 // Update GET base_type in Run +414 int offset = ingredients.at(1).at(0); +415 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above +416 assert(base.metadata.size); +417 int src = base_address + base.metadata.offset.at(offset); +418 trace(9998, "run") << "address to copy is " << src << end(); +419 //: use base.type rather than base_type because later layers will introduce compound types +420 reagent/*copy*/ element = element_type(base.type, offset); +421 element.set_value(src); +422 trace(9998, "run") << "its type is " << names_to_string(element.type) << end(); +423 // Read element +424 products.push_back(read_memory(element)); +425 break; +426 } +427 +428 :(code) +429 const reagent element_type(const type_tree* type, int offset_value) { +430 assert(offset_value >= 0); +431 const type_tree* base_type = type; +432 // Update base_type in element_type +433 assert(contains_key(Type, base_type->value)); +434 assert(!get(Type, base_type->value).name.empty()); +435 const type_info& info = get(Type, base_type->value); +436 assert(info.kind == CONTAINER); +437 if (offset_value >= SIZE(info.elements)) return reagent(); // error handled elsewhere +438 reagent/*copy*/ element = info.elements.at(offset_value); +439 // End element_type Special-cases +440 return element; +441 } +442 +443 :(scenario get_handles_nested_container_elements) +444 def main [ +445 12:num <- copy 34 +446 13:num <- copy 35 +447 14:num <- copy 36 +448 15:num <- get 12:point-number/raw, 1:offset # unsafe +449 ] +450 +mem: storing 36 in location 15 +451 +452 :(scenario get_out_of_bounds) +453 % Hide_errors = true; +454 def main [ +455 12:num <- copy 34 +456 13:num <- copy 35 +457 14:num <- copy 36 +458 get 12:point-number/raw, 2:offset # point-number occupies 3 locations but has only 2 fields; out of bounds +459 ] +460 +error: main: invalid offset '2' for 'point-number' +461 +462 :(scenario get_out_of_bounds_2) +463 % Hide_errors = true; +464 def main [ +465 12:num <- copy 34 +466 13:num <- copy 35 +467 14:num <- copy 36 +468 get 12:point-number/raw, -1:offset +469 ] +470 +error: main: invalid offset '-1' for 'point-number' +471 +472 :(scenario get_product_type_mismatch) +473 % Hide_errors = true; +474 def main [ +475 12:num <- copy 34 +476 13:num <- copy 35 +477 14:num <- copy 36 +478 15:address:num <- get 12:point-number/raw, 1:offset +479 ] +480 +error: main: 'get 12:point-number/raw, 1:offset' should write to number but '15' has type (address number) +481 +482 //: we might want to call 'get' without saving the results, say in a sandbox +483 +484 :(scenario get_without_product) +485 def main [ +486 12:num <- copy 34 +487 13:num <- copy 35 +488 get 12:point/raw, 1:offset # unsafe +489 ] +490 # just don't die +491 +492 //:: To write to elements of containers, use 'put'. +493 +494 :(scenario put) +495 def main [ +496 12:num <- copy 34 +497 13:num <- copy 35 +498 $clear-trace +499 12:point <- put 12:point, 1:offset, 36 +500 ] +501 +mem: storing 36 in location 13 +502 -mem: storing 34 in location 12 +503 +504 :(before "End Primitive Recipe Declarations") +505 PUT, +506 :(before "End Primitive Recipe Numbers") +507 put(Recipe_ordinal, "put", PUT); +508 :(before "End Primitive Recipe Checks") +509 case PUT: { +510 if (SIZE(inst.ingredients) != 3) { +511 ¦ raise << maybe(get(Recipe, r).name) << "'put' expects exactly 3 ingredients in '" << to_original_string(inst) << "'\n" << end(); +512 ¦ break; +513 } +514 reagent/*copy*/ base = inst.ingredients.at(0); +515 // Update PUT base in Check +516 if (!base.type) { +517 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +518 ¦ break; +519 } +520 const type_tree* base_type = base.type; +521 // Update PUT base_type in Check +522 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) { +523 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); +524 ¦ break; +525 } +526 reagent/*copy*/ offset = inst.ingredients.at(1); +527 // Update PUT offset in Check +528 if (!is_literal(offset) || !is_mu_scalar(offset)) { +529 ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'put' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); +530 ¦ break; +531 } +532 int offset_value = 0; +533 //: later layers will permit non-integer offsets +534 if (is_integer(offset.name)) { +535 ¦ offset_value = to_integer(offset.name); +536 ¦ if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { +537 ¦ ¦ raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); +538 ¦ ¦ break; +539 ¦ } +540 } +541 else { +542 ¦ offset_value = offset.value; +543 } +544 const reagent& value = inst.ingredients.at(2); +545 //: use base.type rather than base_type because later layers will introduce compound types +546 const reagent& element = element_type(base.type, offset_value); +547 if (!types_coercible(element, value)) { +548 ¦ 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(); +549 ¦ break; +550 } +551 if (inst.products.empty()) break; // no more checks necessary +552 if (inst.products.at(0).name != inst.ingredients.at(0).name) { +553 ¦ 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(); +554 ¦ break; +555 } +556 // End PUT Product Checks +557 break; +558 } +559 :(before "End Primitive Recipe Implementations") +560 case PUT: { +561 reagent/*copy*/ base = current_instruction().ingredients.at(0); +562 // Update PUT base in Run +563 int base_address = base.value; +564 if (base_address == 0) { +565 ¦ raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); +566 ¦ break; +567 } +568 const type_tree* base_type = base.type; +569 // Update PUT base_type in Run +570 int offset = ingredients.at(1).at(0); +571 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above +572 int address = base_address + base.metadata.offset.at(offset); +573 trace(9998, "run") << "address to copy to is " << address << end(); +574 // optimization: directly write the element rather than updating 'product' +575 // and writing the entire container +576 // Write Memory in PUT in Run +577 write_products = false; +578 for (int i = 0; i < SIZE(ingredients.at(2)); ++i) { +579 ¦ trace(9999, "mem") << "storing " << no_scientific(ingredients.at(2).at(i)) << " in location " << address+i << end(); +580 ¦ put(Memory, address+i, ingredients.at(2).at(i)); +581 } +582 break; +583 } +584 +585 :(scenario put_product_error) +586 % Hide_errors = true; +587 def main [ +588 local-scope +589 load-ingredients +590 1:point <- merge 34, 35 +591 3:point <- put 1:point, x:offset, 36 +592 ] +593 +error: main: product of 'put' must be first ingredient '1:point', but got '3:point' +594 +595 //:: Allow containers to be defined in Mu code. +596 +597 :(scenarios load) +598 :(scenario container) +599 container foo [ +600 x:num +601 y:num +602 ] +603 +parse: --- defining container foo +604 +parse: element: {x: "number"} +605 +parse: element: {y: "number"} +606 +607 :(scenario container_use_before_definition) +608 container foo [ +609 x:num +610 y:bar +611 ] +612 container bar [ +613 x:num +614 y:num +615 ] +616 +parse: --- defining container foo +617 +parse: type number: 1000 +618 +parse: element: {x: "number"} +619 # todo: brittle +620 # type bar is unknown at this point, but we assign it a number +621 +parse: element: {y: "bar"} +622 # later type bar geon +623 +parse: --- defining container bar +624 +parse: type number: 1001 +625 +parse: element: {x: "number"} +626 +parse: element: {y: "number"} +627 +628 //: if a container is defined again, the new fields add to the original definition +629 :(scenarios run) +630 :(scenario container_extend) +631 container foo [ +632 x:num +633 ] +634 # add to previous definition +635 container foo [ +636 y:num +637 ] +638 def main [ +639 1:num <- copy 34 +640 2:num <- copy 35 +641 3:num <- get 1:foo, 0:offset +642 4:num <- get 1:foo, 1:offset +643 ] +644 +mem: storing 34 in location 3 +645 +mem: storing 35 in location 4 +646 +647 :(before "End Command Handlers") +648 else if (command == "container") { +649 insert_container(command, CONTAINER, in); +650 } +651 +652 //: Even though we allow containers to be extended, we don't allow this after +653 //: a call to transform_all. But we do want to detect this situation and raise +654 //: an error. This field will help us raise such errors. +655 :(before "End type_info Fields") +656 int Num_calls_to_transform_all_at_first_definition; +657 :(before "End type_info Constructor") +658 Num_calls_to_transform_all_at_first_definition = -1; +659 +660 :(code) +661 void insert_container(const string& command, kind_of_type kind, istream& in) { +662 skip_whitespace_but_not_newline(in); +663 string name = next_word(in); +664 if (name.empty()) { +665 ¦ assert(!has_data(in)); +666 ¦ raise << "incomplete container definition at end of file (0)\n" << end(); +667 ¦ return; +668 } +669 // End container Name Refinements +670 trace(9991, "parse") << "--- defining " << command << ' ' << name << end(); +671 if (!contains_key(Type_ordinal, name) +672 ¦ ¦ || get(Type_ordinal, name) == 0) { +673 ¦ put(Type_ordinal, name, Next_type_ordinal++); +674 } +675 trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end(); +676 skip_bracket(in, "'"+command+"' must begin with '['"); +677 type_info& info = get_or_insert(Type, get(Type_ordinal, name)); +678 if (info.Num_calls_to_transform_all_at_first_definition == -1) { +679 ¦ // initial definition of this container +680 ¦ info.Num_calls_to_transform_all_at_first_definition = Num_calls_to_transform_all; +681 } +682 else if (info.Num_calls_to_transform_all_at_first_definition != Num_calls_to_transform_all) { +683 ¦ // extension after transform_all +684 ¦ 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(); +685 ¦ return; +686 } +687 info.name = name; +688 info.kind = kind; +689 while (has_data(in)) { +690 ¦ skip_whitespace_and_comments(in); +691 ¦ string element = next_word(in); +692 ¦ if (element.empty()) { +693 ¦ ¦ assert(!has_data(in)); +694 ¦ ¦ raise << "incomplete container definition at end of file (1)\n" << end(); +695 ¦ ¦ return; +696 ¦ } +697 ¦ if (element == "]") break; +698 ¦ if (in.peek() != '\n') { +699 ¦ ¦ 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(); +700 ¦ ¦ // skip rest of container declaration +701 ¦ ¦ while (has_data(in)) { +702 ¦ ¦ ¦ skip_whitespace_and_comments(in); +703 ¦ ¦ ¦ if (next_word(in) == "]") break; +704 ¦ ¦ } +705 ¦ ¦ break; +706 ¦ } +707 ¦ info.elements.push_back(reagent(element)); +708 ¦ expand_type_abbreviations(info.elements.back().type); // todo: use abbreviation before declaration +709 ¦ replace_unknown_types_with_unique_ordinals(info.elements.back().type, info); +710 ¦ trace(9993, "parse") << " element: " << to_string(info.elements.back()) << end(); +711 ¦ // End Load Container Element Definition +712 } +713 } +714 +715 void replace_unknown_types_with_unique_ordinals(type_tree* type, const type_info& info) { +716 if (!type) return; +717 if (!type->atom) { +718 ¦ replace_unknown_types_with_unique_ordinals(type->left, info); +719 ¦ replace_unknown_types_with_unique_ordinals(type->right, info); +720 ¦ return; +721 } +722 assert(!type->name.empty()); +723 if (contains_key(Type_ordinal, type->name)) { +724 ¦ type->value = get(Type_ordinal, type->name); +725 } +726 // End insert_container Special-cases +727 else if (type->name != "->") { // used in recipe types +728 ¦ put(Type_ordinal, type->name, Next_type_ordinal++); +729 ¦ type->value = get(Type_ordinal, type->name); +730 } +731 } +732 +733 void skip_bracket(istream& in, string message) { +734 skip_whitespace_and_comments(in); +735 if (in.get() != '[') +736 ¦ raise << message << '\n' << end(); +737 } +738 +739 :(scenario multi_word_line_in_container_declaration) +740 % Hide_errors = true; +741 container foo [ +742 x:num y:num +743 ] +744 +error: container 'foo' contains multiple elements on a single line. Containers and exclusive containers must only contain elements, one to a line, no code. +745 +746 //: support type abbreviations in container definitions +747 +748 :(scenario type_abbreviations_in_containers) +749 type foo = number +750 container bar [ +751 x:foo +752 ] +753 def main [ +754 1:num <- copy 34 +755 2:foo <- get 1:bar/unsafe, 0:offset +756 ] +757 +mem: storing 34 in location 2 +758 +759 :(after "Transform.push_back(expand_type_abbreviations)") +760 Transform.push_back(expand_type_abbreviations_in_containers); // idempotent +761 :(code) +762 // extremely inefficient; we process all types over and over again, once for every single recipe +763 // but it doesn't seem to cause any noticeable slowdown +764 void expand_type_abbreviations_in_containers(unused const recipe_ordinal r) { +765 for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { +766 ¦ for (int i = 0; i < SIZE(p->second.elements); ++i) +767 ¦ ¦ expand_type_abbreviations(p->second.elements.at(i).type); +768 } +769 } +770 +771 //: ensure scenarios are consistent by always starting new container +772 //: declarations at the same type number +773 :(before "End Setup") //: for tests +774 Next_type_ordinal = 1000; +775 :(before "End Test Run Initialization") +776 assert(Next_type_ordinal < 1000); +777 +778 :(code) +779 void test_error_on_transform_all_between_container_definition_and_extension() { +780 // define a container +781 run("container foo [\n" +782 ¦ ¦ " a:num\n" +783 ¦ ¦ "]\n"); +784 // try to extend the container after transform +785 transform_all(); +786 CHECK_TRACE_DOESNT_CONTAIN_ERRORS(); +787 Hide_errors = true; +788 run("container foo [\n" +789 ¦ ¦ " b:num\n" +790 ¦ ¦ "]\n"); +791 CHECK_TRACE_CONTAINS_ERRORS(); +792 } +793 +794 //:: Allow container definitions anywhere in the codebase, but complain if you +795 //:: can't find a definition at the end. +796 +797 :(scenario run_complains_on_unknown_types) +798 % Hide_errors = true; +799 def main [ +800 # integer is not a type +801 1:integer <- copy 0 +802 ] +803 +error: main: unknown type integer in '1:integer <- copy 0' +804 +805 :(scenario run_allows_type_definition_after_use) +806 def main [ +807 1:bar <- copy 0/unsafe +808 ] +809 container bar [ +810 x:num +811 ] +812 $error: 0 +813 +814 :(before "End Type Modifying Transforms") +815 Transform.push_back(check_or_set_invalid_types); // idempotent +816 +817 :(code) +818 void check_or_set_invalid_types(const recipe_ordinal r) { +819 recipe& caller = get(Recipe, r); +820 trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end(); +821 for (int index = 0; index < SIZE(caller.steps); ++index) { +822 ¦ instruction& inst = caller.steps.at(index); +823 ¦ for (int i = 0; i < SIZE(inst.ingredients); ++i) +824 ¦ ¦ check_or_set_invalid_types(inst.ingredients.at(i), caller, inst); +825 ¦ for (int i = 0; i < SIZE(inst.products); ++i) +826 ¦ ¦ check_or_set_invalid_types(inst.products.at(i), caller, inst); +827 } +828 // End check_or_set_invalid_types +829 } +830 +831 void check_or_set_invalid_types(reagent& r, const recipe& caller, const instruction& inst) { +832 // Begin check_or_set_invalid_types(r) +833 check_or_set_invalid_types(r.type, maybe(caller.name), "'"+to_original_string(inst)+"'"); +834 } +835 +836 void check_or_set_invalid_types(type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) { +837 if (!type) return; +838 // End Container Type Checks +839 if (!type->atom) { +840 ¦ check_or_set_invalid_types(type->left, location_for_error_messages, name_for_error_messages); +841 ¦ check_or_set_invalid_types(type->right, location_for_error_messages, name_for_error_messages); +842 ¦ return; +843 } +844 if (type->value == 0) return; +845 if (!contains_key(Type, type->value)) { +846 ¦ assert(!type->name.empty()); +847 ¦ if (contains_key(Type_ordinal, type->name)) +848 ¦ ¦ type->value = get(Type_ordinal, type->name); +849 ¦ else +850 ¦ ¦ raise << location_for_error_messages << "unknown type " << type->name << " in " << name_for_error_messages << '\n' << end(); +851 } +852 } +853 +854 :(scenario container_unknown_field) +855 % Hide_errors = true; +856 container foo [ +857 x:num +858 y:bar +859 ] +860 +error: foo: unknown type in y +861 +862 :(scenario read_container_with_bracket_in_comment) +863 container foo [ +864 x:num +865 # ']' in comment +866 y:num +867 ] +868 +parse: --- defining container foo +869 +parse: element: {x: "number"} +870 +parse: element: {y: "number"} +871 +872 :(scenario container_with_compound_field_type) +873 container foo [ +874 {x: (address array (address array character))} +875 ] +876 $error: 0 +877 +878 :(before "End transform_all") +879 check_container_field_types(); +880 +881 :(code) +882 void check_container_field_types() { +883 for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) { +884 ¦ const type_info& info = p->second; +885 ¦ // Check Container Field Types(info) +886 ¦ for (int i = 0; i < SIZE(info.elements); ++i) +887 ¦ ¦ check_invalid_types(info.elements.at(i).type, maybe(info.name), info.elements.at(i).name); +888 } +889 } +890 +891 void check_invalid_types(const type_tree* type, const string& location_for_error_messages, const string& name_for_error_messages) { +892 if (!type) return; // will throw a more precise error elsewhere +893 if (!type->atom) { +894 ¦ check_invalid_types(type->left, location_for_error_messages, name_for_error_messages); +895 ¦ check_invalid_types(type->right, location_for_error_messages, name_for_error_messages); +896 ¦ return; +897 } +898 if (type->value != 0) { // value 0 = compound types (layer parse_tree) or type ingredients (layer shape_shifting_container) +899 ¦ if (!contains_key(Type, type->value)) +900 ¦ ¦ raise << location_for_error_messages << "unknown type in " << name_for_error_messages << '\n' << end(); +901 } +902 } +903 +904 string to_original_string(const type_ordinal t) { +905 ostringstream out; +906 if (!contains_key(Type, t)) return out.str(); +907 const type_info& info = get(Type, t); +908 if (info.kind == PRIMITIVE) return out.str(); +909 out << (info.kind == CONTAINER ? "container" : "exclusive-container") << " " << info.name << " [\n"; +910 for (int i = 0; i < SIZE(info.elements); ++i) { +911 ¦ out << " " << info.elements.at(i).original_string << "\n"; +912 } +913 out << "]\n"; +914 return out.str(); +915 } -- cgit 1.4.1-2-gfad0