From 97eb971b7574b3f283d7111a567a301faec9912d Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 27 Dec 2016 22:20:43 -0800 Subject: 3725 More improvements to cross-linking example programs. Include their own functions as well in the tags for each program, even as you share the core .mu files everywhere. --- html/030container.cc.html | 1037 ++++++++++++++++++++++----------------------- 1 file changed, 518 insertions(+), 519 deletions(-) (limited to 'html/030container.cc.html') diff --git a/html/030container.cc.html b/html/030container.cc.html index c7144cb7..b7fea808 100644 --- a/html/030container.cc.html +++ b/html/030container.cc.html @@ -447,525 +447,524 @@ if ('onhashchange' in window) { 382 break; 383 } 384 int offset_value = 0; -385 //: later layers will permit non-integer offsets -386 if (is_integer(offset.name)) -387 offset_value = to_integer(offset.name); -388 else -389 offset_value = offset.value; -390 if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { -391 raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); -392 break; -393 } -394 if (inst.products.empty()) break; -395 reagent/*copy*/ product = inst.products.at(0); -396 // Update GET product in Check -397 //: use base.type rather than base_type because later layers will introduce compound types -398 const reagent/*copy*/ element = element_type(base.type, offset_value); -399 if (!types_coercible(product, element)) { -400 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(); -401 break; -402 } -403 break; -404 } -405 :(before "End Primitive Recipe Implementations") -406 case GET: { -407 reagent/*copy*/ base = current_instruction().ingredients.at(0); -408 // Update GET base in Run -409 int base_address = base.value; -410 if (base_address == 0) { -411 raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); -412 break; -413 } -414 const type_tree* base_type = base.type; -415 // Update GET base_type in Run -416 int offset = ingredients.at(1).at(0); -417 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above -418 assert(base.metadata.size); -419 int src = base_address + base.metadata.offset.at(offset); -420 trace(9998, "run") << "address to copy is " << src << end(); -421 //: use base.type rather than base_type because later layers will introduce compound types -422 reagent/*copy*/ element = element_type(base.type, offset); -423 element.set_value(src); -424 trace(9998, "run") << "its type is " << names_to_string(element.type) << end(); -425 // Read element -426 products.push_back(read_memory(element)); -427 break; -428 } -429 -430 :(code) -431 const reagent element_type(const type_tree* type, int offset_value) { -432 assert(offset_value >= 0); -433 const type_tree* base_type = type; -434 // Update base_type in element_type -435 assert(contains_key(Type, base_type->value)); -436 assert(!get(Type, base_type->value).name.empty()); -437 const type_info& info = get(Type, base_type->value); -438 assert(info.kind == CONTAINER); -439 if (offset_value >= SIZE(info.elements)) return reagent(); // error handled elsewhere -440 reagent/*copy*/ element = info.elements.at(offset_value); -441 // End element_type Special-cases -442 return element; -443 } -444 -445 :(scenario get_handles_nested_container_elements) -446 def main [ -447 12:num <- copy 34 -448 13:num <- copy 35 -449 14:num <- copy 36 -450 15:num <- get 12:point-number/raw, 1:offset # unsafe -451 ] -452 +mem: storing 36 in location 15 -453 -454 :(scenario get_out_of_bounds) -455 % Hide_errors = true; -456 def main [ -457 12:num <- copy 34 -458 13:num <- copy 35 -459 14:num <- copy 36 -460 get 12:point-number/raw, 2:offset # point-number occupies 3 locations but has only 2 fields; out of bounds -461 ] -462 +error: main: invalid offset '2' for 'point-number' -463 -464 :(scenario get_out_of_bounds_2) -465 % Hide_errors = true; -466 def main [ -467 12:num <- copy 34 -468 13:num <- copy 35 -469 14:num <- copy 36 -470 get 12:point-number/raw, -1:offset -471 ] -472 +error: main: invalid offset '-1' for 'point-number' -473 -474 :(scenario get_product_type_mismatch) -475 % Hide_errors = true; -476 def main [ -477 12:num <- copy 34 -478 13:num <- copy 35 -479 14:num <- copy 36 -480 15:address:num <- get 12:point-number/raw, 1:offset -481 ] -482 +error: main: 'get 12:point-number/raw, 1:offset' should write to number but '15' has type (address number) -483 -484 //: we might want to call 'get' without saving the results, say in a sandbox -485 -486 :(scenario get_without_product) -487 def main [ -488 12:num <- copy 34 -489 13:num <- copy 35 -490 get 12:point/raw, 1:offset # unsafe -491 ] -492 # just don't die -493 -494 //:: To write to elements of containers, use 'put'. -495 -496 :(scenario put) -497 def main [ -498 12:num <- copy 34 -499 13:num <- copy 35 -500 $clear-trace -501 12:point <- put 12:point, 1:offset, 36 -502 ] -503 +mem: storing 36 in location 13 -504 -mem: storing 34 in location 12 -505 -506 :(before "End Primitive Recipe Declarations") -507 PUT, -508 :(before "End Primitive Recipe Numbers") -509 put(Recipe_ordinal, "put", PUT); -510 :(before "End Primitive Recipe Checks") -511 case PUT: { -512 if (SIZE(inst.ingredients) != 3) { -513 raise << maybe(get(Recipe, r).name) << "'put' expects exactly 3 ingredients in '" << inst.original_string << "'\n" << end(); -514 break; -515 } -516 reagent/*copy*/ base = inst.ingredients.at(0); -517 // Update PUT base in Check -518 if (!base.type) { -519 raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -520 break; -521 } -522 const type_tree* base_type = base.type; -523 // Update PUT base_type in Check -524 if (!base_type->atom || base_type->value == 0 || !contains_key(Type, base_type->value) || get(Type, base_type->value).kind != CONTAINER) { -525 raise << maybe(get(Recipe, r).name) << "first ingredient of 'put' should be a container, but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); -526 break; -527 } -528 reagent/*copy*/ offset = inst.ingredients.at(1); -529 // Update PUT offset in Check -530 if (!is_literal(offset) || !is_mu_scalar(offset)) { -531 raise << maybe(get(Recipe, r).name) << "second ingredient of 'put' should have type 'offset', but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); -532 break; -533 } -534 int offset_value = 0; -535 //: later layers will permit non-integer offsets -536 if (is_integer(offset.name)) { -537 offset_value = to_integer(offset.name); -538 if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type->value).elements)) { -539 raise << maybe(get(Recipe, r).name) << "invalid offset '" << offset_value << "' for '" << get(Type, base_type->value).name << "'\n" << end(); -540 break; -541 } -542 } -543 else { -544 offset_value = offset.value; -545 } -546 const reagent& value = inst.ingredients.at(2); -547 //: use base.type rather than base_type because later layers will introduce compound types -548 const reagent& element = element_type(base.type, offset_value); -549 if (!types_coercible(element, value)) { -550 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(); -551 break; -552 } -553 if (inst.products.empty()) break; // no more checks necessary -554 if (inst.products.at(0).name != inst.ingredients.at(0).name) { -555 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(); -556 break; -557 } -558 // End PUT Product Checks -559 break; -560 } -561 :(before "End Primitive Recipe Implementations") -562 case PUT: { -563 reagent/*copy*/ base = current_instruction().ingredients.at(0); -564 // Update PUT base in Run -565 int base_address = base.value; -566 if (base_address == 0) { -567 raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_original_string(current_instruction()) << "'\n" << end(); -568 break; -569 } -570 const type_tree* base_type = base.type; -571 // Update PUT base_type in Run -572 int offset = ingredients.at(1).at(0); -573 if (offset < 0 || offset >= SIZE(get(Type, base_type->value).elements)) break; // copied from Check above -574 int address = base_address + base.metadata.offset.at(offset); -575 trace(9998, "run") << "address to copy to is " << address << end(); -576 // optimization: directly write the element rather than updating 'product' -577 // and writing the entire container -578 // Write Memory in PUT in Run -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 goto finish_instruction; -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), "'"+inst.original_string+"'"); -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 } +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 '" << inst.original_string << "'\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 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 goto finish_instruction; +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), "'"+inst.original_string+"'"); +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 } -- cgit 1.4.1-2-gfad0