From 059def11cb8c53d85f7eed2af98a0bca0120a9cc Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Sat, 12 May 2018 23:08:39 -0700 Subject: 4244 --- html/036refcount.cc.html | 91 -- html/037abandon.cc.html | 198 +++-- html/055shape_shifting_container.cc.html | 1396 +++++++++++++++--------------- html/057immutable.cc.html | 390 +++++---- 4 files changed, 1005 insertions(+), 1070 deletions(-) delete mode 100644 html/036refcount.cc.html (limited to 'html') diff --git a/html/036refcount.cc.html b/html/036refcount.cc.html deleted file mode 100644 index fcdca25d..00000000 --- a/html/036refcount.cc.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - -Mu - 036refcount.cc - - - - - - - - - - -
- 1 int payload_size(reagent/*copy*/ x) {
- 2   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
- 3   lookup_memory_core(x, /*check_for_null*/false);
- 4   return size_of(x);
- 5 }
- 6 
- 7 bool is_mu_container(const reagent& r) {
- 8   return is_mu_container(r.type);
- 9 }
-10 bool is_mu_container(const type_tree* type) {
-11   if (!type) return false;
-12   // End is_mu_container(type) Special-cases
-13   if (type->value == 0) return false;
-14   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
-15   type_info& info = get(Type, type->value);
-16   return info.kind == CONTAINER;
-17 }
-18 
-19 bool is_mu_exclusive_container(const reagent& r) {
-20   return is_mu_exclusive_container(r.type);
-21 }
-22 bool is_mu_exclusive_container(const type_tree* type) {
-23   if (!type) return false;
-24   // End is_mu_exclusive_container(type) Special-cases
-25   if (type->value == 0) return false;
-26   if (!contains_key(Type, type->value)) return false;  // error raised elsewhere
-27   type_info& info = get(Type, type->value);
-28   return info.kind == EXCLUSIVE_CONTAINER;
-29 }
-
- - - diff --git a/html/037abandon.cc.html b/html/037abandon.cc.html index 4c496c5b..b010c83a 100644 --- a/html/037abandon.cc.html +++ b/html/037abandon.cc.html @@ -60,102 +60,108 @@ if ('onhashchange' in window) {
- 1 //: Reclaiming memory when it's no longer used.
- 2 
- 3 :(scenario new_reclaim)
- 4 def main [
- 5   1:address:num <- new number:type
- 6   2:num <- copy 1:address:num  # because 1 will get reset during abandon below
- 7   abandon 1:address:num
- 8   3:address:num <- new number:type  # must be same size as abandoned memory to reuse
- 9   4:num <- copy 3:address:num
-10   5:bool <- equal 2:num, 4:num
-11 ]
-12 # both allocations should have returned the same address
-13 +mem: storing 1 in location 5
-14 
-15 //: When abandoning addresses we'll save them to a 'free list', segregated by size.
-16 
-17 :(before "End routine Fields")
-18 map<int, int> free_list;
-19 
-20 :(before "End Primitive Recipe Declarations")
-21 ABANDON,
-22 :(before "End Primitive Recipe Numbers")
-23 put(Recipe_ordinal, "abandon", ABANDON);
-24 :(before "End Primitive Recipe Checks")
-25 case ABANDON: {
-26   if (!inst.products.empty()) {
-27     raise << maybe(get(Recipe, r).name) << "'abandon' shouldn't write to any products in '" << to_original_string(inst) << "'\n" << end();
-28     break;
-29   }
-30   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
-31     if (!is_mu_address(inst.ingredients.at(i)))
-32       raise << maybe(get(Recipe, r).name) << "ingredients of 'abandon' should be addresses, but ingredient " << i << " is '" << to_string(inst.ingredients.at(i)) << '\n' << end();
-33     break;
-34   }
-35   break;
-36 }
-37 :(before "End Primitive Recipe Implementations")
-38 case ABANDON: {
-39   for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
-40     reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
-41     canonize(ingredient);
-42     abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient));
-43   }
-44   break;
-45 }
-46 
-47 :(code)
-48 void abandon(int address, int payload_size) {
-49   // clear memory
-50   for (int curr = address;  curr < address+payload_size;  ++curr)
-51     put(Memory, curr, 0);
-52   // append existing free list to address
-53   trace("abandon") << "saving " << address << " in free-list of size " << payload_size << end();
-54   put(Memory, address, get_or_insert(Current_routine->free_list, payload_size));
-55   put(Current_routine->free_list, payload_size, address);
-56 }
-57 
-58 :(after "Allocate Special-cases")
-59 if (get_or_insert(Current_routine->free_list, size)) {
-60   trace("abandon") << "picking up space from free-list of size " << size << end();
-61   int result = get_or_insert(Current_routine->free_list, size);
-62   trace("mem") << "new alloc from free list: " << result << end();
-63   put(Current_routine->free_list, size, get_or_insert(Memory, result));
-64   put(Memory, result, 0);
-65   for (int curr = result;  curr < result+size;  ++curr) {
-66     if (get_or_insert(Memory, curr) != 0) {
-67       raise << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
-68       break;  // always fatal
-69     }
-70   }
-71   return result;
-72 }
-73 
-74 :(scenario new_differing_size_no_reclaim)
-75 def main [
-76   1:address:num <- new number:type
-77   2:num <- copy 1:address:num
-78   abandon 1:address:num
-79   3:address:array:num <- new number:type, 2  # different size
-80   4:num <- copy 3:address:array:num
-81   5:bool <- equal 2:num, 4:num
-82 ]
-83 # no reuse
-84 +mem: storing 0 in location 5
-85 
-86 :(scenario new_reclaim_array)
-87 def main [
-88   1:address:array:num <- new number:type, 2
-89   2:num <- copy 1:address:array:num
-90   abandon 1:address:array:num
-91   3:address:array:num <- new number:type, 2  # same size
-92   4:num <- copy 3:address:array:num
-93   5:bool <- equal 2:num, 4:num
-94 ]
-95 # both calls to new returned identical addresses
-96 +mem: storing 1 in location 5
+  1 //: Reclaiming memory when it's no longer used.
+  2 
+  3 :(scenario new_reclaim)
+  4 def main [
+  5   1:address:num <- new number:type
+  6   2:num <- copy 1:address:num  # because 1 will get reset during abandon below
+  7   abandon 1:address:num
+  8   3:address:num <- new number:type  # must be same size as abandoned memory to reuse
+  9   4:num <- copy 3:address:num
+ 10   5:bool <- equal 2:num, 4:num
+ 11 ]
+ 12 # both allocations should have returned the same address
+ 13 +mem: storing 1 in location 5
+ 14 
+ 15 //: When abandoning addresses we'll save them to a 'free list', segregated by size.
+ 16 
+ 17 :(before "End routine Fields")
+ 18 map<int, int> free_list;
+ 19 
+ 20 :(before "End Primitive Recipe Declarations")
+ 21 ABANDON,
+ 22 :(before "End Primitive Recipe Numbers")
+ 23 put(Recipe_ordinal, "abandon", ABANDON);
+ 24 :(before "End Primitive Recipe Checks")
+ 25 case ABANDON: {
+ 26   if (!inst.products.empty()) {
+ 27     raise << maybe(get(Recipe, r).name) << "'abandon' shouldn't write to any products in '" << to_original_string(inst) << "'\n" << end();
+ 28     break;
+ 29   }
+ 30   for (int i = 0;  i < SIZE(inst.ingredients);  ++i) {
+ 31     if (!is_mu_address(inst.ingredients.at(i)))
+ 32       raise << maybe(get(Recipe, r).name) << "ingredients of 'abandon' should be addresses, but ingredient " << i << " is '" << to_string(inst.ingredients.at(i)) << '\n' << end();
+ 33     break;
+ 34   }
+ 35   break;
+ 36 }
+ 37 :(before "End Primitive Recipe Implementations")
+ 38 case ABANDON: {
+ 39   for (int i = 0;  i < SIZE(current_instruction().ingredients);  ++i) {
+ 40     reagent/*copy*/ ingredient = current_instruction().ingredients.at(i);
+ 41     canonize(ingredient);
+ 42     abandon(get_or_insert(Memory, ingredient.value), payload_size(ingredient));
+ 43   }
+ 44   break;
+ 45 }
+ 46 
+ 47 :(code)
+ 48 void abandon(int address, int payload_size) {
+ 49   // clear memory
+ 50   for (int curr = address;  curr < address+payload_size;  ++curr)
+ 51     put(Memory, curr, 0);
+ 52   // append existing free list to address
+ 53   trace("abandon") << "saving " << address << " in free-list of size " << payload_size << end();
+ 54   put(Memory, address, get_or_insert(Current_routine->free_list, payload_size));
+ 55   put(Current_routine->free_list, payload_size, address);
+ 56 }
+ 57 
+ 58 int payload_size(reagent/*copy*/ x) {
+ 59   x.properties.push_back(pair<string, string_tree*>("lookup", NULL));
+ 60   lookup_memory_core(x, /*check_for_null*/false);
+ 61   return size_of(x);
+ 62 }
+ 63 
+ 64 :(after "Allocate Special-cases")
+ 65 if (get_or_insert(Current_routine->free_list, size)) {
+ 66   trace("abandon") << "picking up space from free-list of size " << size << end();
+ 67   int result = get_or_insert(Current_routine->free_list, size);
+ 68   trace("mem") << "new alloc from free list: " << result << end();
+ 69   put(Current_routine->free_list, size, get_or_insert(Memory, result));
+ 70   put(Memory, result, 0);
+ 71   for (int curr = result;  curr < result+size;  ++curr) {
+ 72     if (get_or_insert(Memory, curr) != 0) {
+ 73       raise << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
+ 74       break;  // always fatal
+ 75     }
+ 76   }
+ 77   return result;
+ 78 }
+ 79 
+ 80 :(scenario new_differing_size_no_reclaim)
+ 81 def main [
+ 82   1:address:num <- new number:type
+ 83   2:num <- copy 1:address:num
+ 84   abandon 1:address:num
+ 85   3:address:array:num <- new number:type, 2  # different size
+ 86   4:num <- copy 3:address:array:num
+ 87   5:bool <- equal 2:num, 4:num
+ 88 ]
+ 89 # no reuse
+ 90 +mem: storing 0 in location 5
+ 91 
+ 92 :(scenario new_reclaim_array)
+ 93 def main [
+ 94   1:address:array:num <- new number:type, 2
+ 95   2:num <- copy 1:address:array:num
+ 96   abandon 1:address:array:num
+ 97   3:address:array:num <- new number:type, 2  # same size
+ 98   4:num <- copy 3:address:array:num
+ 99   5:bool <- equal 2:num, 4:num
+100 ]
+101 # both calls to new returned identical addresses
+102 +mem: storing 1 in location 5
 
diff --git a/html/055shape_shifting_container.cc.html b/html/055shape_shifting_container.cc.html index 08a7305b..1ab99309 100644 --- a/html/055shape_shifting_container.cc.html +++ b/html/055shape_shifting_container.cc.html @@ -67,717 +67,711 @@ if ('onhashchange' in window) { 2 3 //: pre-requisite: extend our notion of containers to not necessarily be 4 //: atomic types - 5 :(before "End is_mu_container(type) Special-cases") - 6 if (!type->atom) - 7 return is_mu_container(get_base_type(type)); - 8 :(before "End is_mu_exclusive_container(type) Special-cases") - 9 if (!type->atom) - 10 return is_mu_exclusive_container(get_base_type(type)); - 11 :(after "Update GET base_type in Check") + 5 :(after "Update GET base_type in Check") + 6 base_type = get_base_type(base_type); + 7 :(after "Update GET base_type in Run") + 8 base_type = get_base_type(base_type); + 9 :(after "Update PUT base_type in Check") + 10 base_type = get_base_type(base_type); + 11 :(after "Update PUT base_type in Run") 12 base_type = get_base_type(base_type); - 13 :(after "Update GET base_type in Run") + 13 :(after "Update MAYBE_CONVERT base_type in Check") 14 base_type = get_base_type(base_type); - 15 :(after "Update PUT base_type in Check") + 15 :(after "Update base_type in size_of(type)") 16 base_type = get_base_type(base_type); - 17 :(after "Update PUT base_type in Run") + 17 :(after "Update base_type in element_type") 18 base_type = get_base_type(base_type); - 19 :(after "Update MAYBE_CONVERT base_type in Check") - 20 base_type = get_base_type(base_type); - 21 :(after "Update base_type in size_of(type)") - 22 base_type = get_base_type(base_type); - 23 :(after "Update base_type in element_type") - 24 base_type = get_base_type(base_type); - 25 //? :(after "Update base_type in compute_container_address_offsets") - 26 //? base_type = get_base_type(base_type); - 27 //? :(after "Update base_type in append_container_address_offsets") - 28 //? base_type = get_base_type(base_type); - 29 //? :(after "Update element_base_type For Exclusive Container in append_addresses") - 30 //? element_base_type = get_base_type(element_base_type); - 31 :(after "Update base_type in skip_addresses") - 32 base_type = get_base_type(base_type); - 33 :(replace{} "const type_tree* get_base_type(const type_tree* t)") - 34 const type_tree* get_base_type(const type_tree* t) { - 35 const type_tree* result = t->atom ? t : t->left; - 36 if (!result->atom) - 37 raise << "invalid type " << to_string(t) << '\n' << end(); - 38 return result; - 39 } - 40 - 41 :(scenario ill_formed_container) - 42 % Hide_errors = true; - 43 def main [ - 44 {1: ((foo) num)} <- copy 0 - 45 ] - 46 # no crash - 47 - 48 :(scenario size_of_shape_shifting_container) - 49 container foo:_t [ - 50 x:_t - 51 y:num - 52 ] - 53 def main [ - 54 1:foo:num <- merge 12, 13 - 55 3:foo:point <- merge 14, 15, 16 - 56 ] - 57 +mem: storing 12 in location 1 - 58 +mem: storing 13 in location 2 - 59 +mem: storing 14 in location 3 - 60 +mem: storing 15 in location 4 - 61 +mem: storing 16 in location 5 - 62 - 63 :(scenario size_of_shape_shifting_container_2) - 64 # multiple type ingredients - 65 container foo:_a:_b [ - 66 x:_a - 67 y:_b - 68 ] - 69 def main [ - 70 1:foo:num:bool <- merge 34, 1/true - 71 ] - 72 $error: 0 - 73 - 74 :(scenario size_of_shape_shifting_container_3) - 75 container foo:_a:_b [ - 76 x:_a - 77 y:_b - 78 ] - 79 def main [ - 80 1:text <- new [abc] - 81 # compound types for type ingredients - 82 {2: (foo number (address array character))} <- merge 34/x, 1:text/y - 83 ] - 84 $error: 0 - 85 - 86 :(scenario size_of_shape_shifting_container_4) - 87 container foo:_a:_b [ - 88 x:_a - 89 y:_b - 90 ] - 91 container bar:_a:_b [ - 92 # dilated element - 93 {data: (foo _a (address _b))} - 94 ] - 95 def main [ - 96 1:text <- new [abc] - 97 2:bar:num:@:char <- merge 34/x, 1:text/y + 19 //? :(after "Update base_type in compute_container_address_offsets") + 20 //? base_type = get_base_type(base_type); + 21 //? :(after "Update base_type in append_container_address_offsets") + 22 //? base_type = get_base_type(base_type); + 23 //? :(after "Update element_base_type For Exclusive Container in append_addresses") + 24 //? element_base_type = get_base_type(element_base_type); + 25 :(after "Update base_type in skip_addresses") + 26 base_type = get_base_type(base_type); + 27 :(replace{} "const type_tree* get_base_type(const type_tree* t)") + 28 const type_tree* get_base_type(const type_tree* t) { + 29 const type_tree* result = t->atom ? t : t->left; + 30 if (!result->atom) + 31 raise << "invalid type " << to_string(t) << '\n' << end(); + 32 return result; + 33 } + 34 + 35 :(scenario ill_formed_container) + 36 % Hide_errors = true; + 37 def main [ + 38 {1: ((foo) num)} <- copy 0 + 39 ] + 40 # no crash + 41 + 42 :(scenario size_of_shape_shifting_container) + 43 container foo:_t [ + 44 x:_t + 45 y:num + 46 ] + 47 def main [ + 48 1:foo:num <- merge 12, 13 + 49 3:foo:point <- merge 14, 15, 16 + 50 ] + 51 +mem: storing 12 in location 1 + 52 +mem: storing 13 in location 2 + 53 +mem: storing 14 in location 3 + 54 +mem: storing 15 in location 4 + 55 +mem: storing 16 in location 5 + 56 + 57 :(scenario size_of_shape_shifting_container_2) + 58 # multiple type ingredients + 59 container foo:_a:_b [ + 60 x:_a + 61 y:_b + 62 ] + 63 def main [ + 64 1:foo:num:bool <- merge 34, 1/true + 65 ] + 66 $error: 0 + 67 + 68 :(scenario size_of_shape_shifting_container_3) + 69 container foo:_a:_b [ + 70 x:_a + 71 y:_b + 72 ] + 73 def main [ + 74 1:text <- new [abc] + 75 # compound types for type ingredients + 76 {2: (foo number (address array character))} <- merge 34/x, 1:text/y + 77 ] + 78 $error: 0 + 79 + 80 :(scenario size_of_shape_shifting_container_4) + 81 container foo:_a:_b [ + 82 x:_a + 83 y:_b + 84 ] + 85 container bar:_a:_b [ + 86 # dilated element + 87 {data: (foo _a (address _b))} + 88 ] + 89 def main [ + 90 1:text <- new [abc] + 91 2:bar:num:@:char <- merge 34/x, 1:text/y + 92 ] + 93 $error: 0 + 94 + 95 :(scenario shape_shifting_container_extend) + 96 container foo:_a [ + 97 x:_a 98 ] - 99 $error: 0 -100 -101 :(scenario shape_shifting_container_extend) -102 container foo:_a [ -103 x:_a -104 ] -105 container foo:_a [ -106 y:_a -107 ] -108 $error: 0 -109 -110 :(scenario shape_shifting_container_extend_error) -111 % Hide_errors = true; -112 container foo:_a [ -113 x:_a -114 ] -115 container foo:_b [ -116 y:_b -117 ] -118 +error: headers of container 'foo' must use identical type ingredients -119 -120 :(scenario type_ingredient_must_start_with_underscore) -121 % Hide_errors = true; -122 container foo:t [ -123 x:num -124 ] -125 +error: foo: type ingredient 't' must begin with an underscore -126 -127 :(before "End Globals") -128 // We'll use large type ordinals to mean "the following type of the variable". -129 // For example, if we have a generic type called foo:_elem, the type -130 // ingredient _elem in foo's type_info will have value START_TYPE_INGREDIENTS, -131 // and we'll handle it by looking in the current reagent for the next type -132 // that appears after foo. -133 extern const int START_TYPE_INGREDIENTS = 2000; -134 :(before "End Commandline Parsing") // after loading .mu files -135 assert(Next_type_ordinal < START_TYPE_INGREDIENTS); -136 -137 :(before "End type_info Fields") -138 map<string, type_ordinal> type_ingredient_names; -139 -140 //: Suppress unknown type checks in shape-shifting containers. -141 -142 :(before "Check Container Field Types(info)") -143 if (!info.type_ingredient_names.empty()) continue; -144 -145 :(before "End container Name Refinements") -146 if (name.find(':') != string::npos) { -147 trace("parse") << "container has type ingredients; parsing" << end(); -148 if (!read_type_ingredients(name, command)) { -149 // error; skip rest of the container definition and continue -150 slurp_balanced_bracket(in); -151 return; -152 } -153 } -154 -155 :(code) -156 bool read_type_ingredients(string& name, const string& command) { -157 string save_name = name; -158 istringstream in(save_name); -159 name = slurp_until(in, ':'); -160 map<string, type_ordinal> type_ingredient_names; -161 if (!slurp_type_ingredients(in, type_ingredient_names, name)) { -162 return false; -163 } -164 if (contains_key(Type_ordinal, name) -165 && contains_key(Type, get(Type_ordinal, name))) { -166 const type_info& previous_info = get(Type, get(Type_ordinal, name)); -167 // we've already seen this container; make sure type ingredients match -168 if (!type_ingredients_match(type_ingredient_names, previous_info.type_ingredient_names)) { -169 raise << "headers of " << command << " '" << name << "' must use identical type ingredients\n" << end(); -170 return false; -171 } -172 return true; -173 } -174 // we haven't seen this container before -175 if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0) -176 put(Type_ordinal, name, Next_type_ordinal++); -177 type_info& info = get_or_insert(Type, get(Type_ordinal, name)); -178 info.type_ingredient_names.swap(type_ingredient_names); -179 return true; -180 } -181 -182 bool slurp_type_ingredients(istream& in, map<string, type_ordinal>& out, const string& container_name) { -183 int next_type_ordinal = START_TYPE_INGREDIENTS; -184 while (has_data(in)) { -185 string curr = slurp_until(in, ':'); -186 if (curr.empty()) { -187 raise << container_name << ": empty type ingredients not permitted\n" << end(); -188 return false; -189 } -190 if (!starts_with(curr, "_")) { -191 raise << container_name << ": type ingredient '" << curr << "' must begin with an underscore\n" << end(); -192 return false; -193 } -194 if (out.find(curr) != out.end()) { -195 raise << container_name << ": can't repeat type ingredient name'" << curr << "' in a single container definition\n" << end(); -196 return false; -197 } -198 put(out, curr, next_type_ordinal++); -199 } -200 return true; -201 } -202 -203 bool type_ingredients_match(const map<string, type_ordinal>& a, const map<string, type_ordinal>& b) { -204 if (SIZE(a) != SIZE(b)) return false; -205 for (map<string, type_ordinal>::const_iterator p = a.begin(); p != a.end(); ++p) { -206 if (!contains_key(b, p->first)) return false; -207 if (p->second != get(b, p->first)) return false; -208 } -209 return true; + 99 container foo:_a [ +100 y:_a +101 ] +102 $error: 0 +103 +104 :(scenario shape_shifting_container_extend_error) +105 % Hide_errors = true; +106 container foo:_a [ +107 x:_a +108 ] +109 container foo:_b [ +110 y:_b +111 ] +112 +error: headers of container 'foo' must use identical type ingredients +113 +114 :(scenario type_ingredient_must_start_with_underscore) +115 % Hide_errors = true; +116 container foo:t [ +117 x:num +118 ] +119 +error: foo: type ingredient 't' must begin with an underscore +120 +121 :(before "End Globals") +122 // We'll use large type ordinals to mean "the following type of the variable". +123 // For example, if we have a generic type called foo:_elem, the type +124 // ingredient _elem in foo's type_info will have value START_TYPE_INGREDIENTS, +125 // and we'll handle it by looking in the current reagent for the next type +126 // that appears after foo. +127 extern const int START_TYPE_INGREDIENTS = 2000; +128 :(before "End Commandline Parsing") // after loading .mu files +129 assert(Next_type_ordinal < START_TYPE_INGREDIENTS); +130 +131 :(before "End type_info Fields") +132 map<string, type_ordinal> type_ingredient_names; +133 +134 //: Suppress unknown type checks in shape-shifting containers. +135 +136 :(before "Check Container Field Types(info)") +137 if (!info.type_ingredient_names.empty()) continue; +138 +139 :(before "End container Name Refinements") +140 if (name.find(':') != string::npos) { +141 trace("parse") << "container has type ingredients; parsing" << end(); +142 if (!read_type_ingredients(name, command)) { +143 // error; skip rest of the container definition and continue +144 slurp_balanced_bracket(in); +145 return; +146 } +147 } +148 +149 :(code) +150 bool read_type_ingredients(string& name, const string& command) { +151 string save_name = name; +152 istringstream in(save_name); +153 name = slurp_until(in, ':'); +154 map<string, type_ordinal> type_ingredient_names; +155 if (!slurp_type_ingredients(in, type_ingredient_names, name)) { +156 return false; +157 } +158 if (contains_key(Type_ordinal, name) +159 && contains_key(Type, get(Type_ordinal, name))) { +160 const type_info& previous_info = get(Type, get(Type_ordinal, name)); +161 // we've already seen this container; make sure type ingredients match +162 if (!type_ingredients_match(type_ingredient_names, previous_info.type_ingredient_names)) { +163 raise << "headers of " << command << " '" << name << "' must use identical type ingredients\n" << end(); +164 return false; +165 } +166 return true; +167 } +168 // we haven't seen this container before +169 if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0) +170 put(Type_ordinal, name, Next_type_ordinal++); +171 type_info& info = get_or_insert(Type, get(Type_ordinal, name)); +172 info.type_ingredient_names.swap(type_ingredient_names); +173 return true; +174 } +175 +176 bool slurp_type_ingredients(istream& in, map<string, type_ordinal>& out, const string& container_name) { +177 int next_type_ordinal = START_TYPE_INGREDIENTS; +178 while (has_data(in)) { +179 string curr = slurp_until(in, ':'); +180 if (curr.empty()) { +181 raise << container_name << ": empty type ingredients not permitted\n" << end(); +182 return false; +183 } +184 if (!starts_with(curr, "_")) { +185 raise << container_name << ": type ingredient '" << curr << "' must begin with an underscore\n" << end(); +186 return false; +187 } +188 if (out.find(curr) != out.end()) { +189 raise << container_name << ": can't repeat type ingredient name'" << curr << "' in a single container definition\n" << end(); +190 return false; +191 } +192 put(out, curr, next_type_ordinal++); +193 } +194 return true; +195 } +196 +197 bool type_ingredients_match(const map<string, type_ordinal>& a, const map<string, type_ordinal>& b) { +198 if (SIZE(a) != SIZE(b)) return false; +199 for (map<string, type_ordinal>::const_iterator p = a.begin(); p != a.end(); ++p) { +200 if (!contains_key(b, p->first)) return false; +201 if (p->second != get(b, p->first)) return false; +202 } +203 return true; +204 } +205 +206 :(before "End insert_container Special-cases") +207 // check for use of type ingredients +208 else if (is_type_ingredient_name(type->name)) { +209 type->value = get(info.type_ingredient_names, type->name); 210 } -211 -212 :(before "End insert_container Special-cases") -213 // check for use of type ingredients -214 else if (is_type_ingredient_name(type->name)) { -215 type->value = get(info.type_ingredient_names, type->name); -216 } -217 :(code) -218 bool is_type_ingredient_name(const string& type) { -219 return starts_with(type, "_"); -220 } -221 -222 :(before "End Container Type Checks") -223 if (type->value >= START_TYPE_INGREDIENTS -224 && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names)) -225 return; -226 -227 :(scenario size_of_shape_shifting_exclusive_container) -228 exclusive-container foo:_t [ -229 x:_t -230 y:num -231 ] -232 def main [ -233 1:foo:num <- merge 0/x, 34 -234 3:foo:point <- merge 0/x, 15, 16 -235 6:foo:point <- merge 1/y, 23 -236 ] -237 +run: {1: ("foo" "number")} <- merge {0: "literal", "x": ()}, {34: "literal"} -238 +mem: storing 0 in location 1 -239 +mem: storing 34 in location 2 -240 +run: {3: ("foo" "point")} <- merge {0: "literal", "x": ()}, {15: "literal"}, {16: "literal"} -241 +mem: storing 0 in location 3 -242 +mem: storing 15 in location 4 -243 +mem: storing 16 in location 5 -244 +run: {6: ("foo" "point")} <- merge {1: "literal", "y": ()}, {23: "literal"} -245 +mem: storing 1 in location 6 -246 +mem: storing 23 in location 7 -247 +run: return -248 # no other stores -249 % CHECK_EQ(trace_count_prefix("mem", "storing"), 7); -250 -251 :(before "End variant_type Special-cases") -252 if (contains_type_ingredient(element)) -253 replace_type_ingredients(element.type, type->right, info, " while computing variant type of exclusive-container"); -254 -255 :(scenario get_on_shape_shifting_container) -256 container foo:_t [ -257 x:_t -258 y:num -259 ] -260 def main [ -261 1:foo:point <- merge 14, 15, 16 -262 2:num <- get 1:foo:point, y:offset -263 ] -264 +mem: storing 16 in location 2 -265 -266 :(scenario get_on_shape_shifting_container_2) -267 container foo:_t [ -268 x:_t -269 y:num -270 ] -271 def main [ -272 1:foo:point <- merge 14, 15, 16 -273 2:point <- get 1:foo:point, x:offset -274 ] -275 +mem: storing 14 in location 2 -276 +mem: storing 15 in location 3 -277 -278 :(scenario get_on_shape_shifting_container_3) -279 container foo:_t [ -280 x:_t -281 y:num -282 ] -283 def main [ -284 1:foo:&:point <- merge 34/unsafe, 48 -285 3:&:point <- get 1:foo:&:point, x:offset -286 ] -287 +mem: storing 34 in location 3 -288 -289 :(scenario get_on_shape_shifting_container_inside_container) -290 container foo:_t [ -291 x:_t -292 y:num -293 ] -294 container bar [ -295 x:foo:point -296 y:num -297 ] -298 def main [ -299 1:bar <- merge 14, 15, 16, 17 -300 2:num <- get 1:bar, 1:offset -301 ] -302 +mem: storing 17 in location 2 -303 -304 :(scenario get_on_complex_shape_shifting_container) -305 container foo:_a:_b [ -306 x:_a -307 y:_b +211 :(code) +212 bool is_type_ingredient_name(const string& type) { +213 return starts_with(type, "_"); +214 } +215 +216 :(before "End Container Type Checks") +217 if (type->value >= START_TYPE_INGREDIENTS +218 && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names)) +219 return; +220 +221 :(scenario size_of_shape_shifting_exclusive_container) +222 exclusive-container foo:_t [ +223 x:_t +224 y:num +225 ] +226 def main [ +227 1:foo:num <- merge 0/x, 34 +228 3:foo:point <- merge 0/x, 15, 16 +229 6:foo:point <- merge 1/y, 23 +230 ] +231 +run: {1: ("foo" "number")} <- merge {0: "literal", "x": ()}, {34: "literal"} +232 +mem: storing 0 in location 1 +233 +mem: storing 34 in location 2 +234 +run: {3: ("foo" "point")} <- merge {0: "literal", "x": ()}, {15: "literal"}, {16: "literal"} +235 +mem: storing 0 in location 3 +236 +mem: storing 15 in location 4 +237 +mem: storing 16 in location 5 +238 +run: {6: ("foo" "point")} <- merge {1: "literal", "y": ()}, {23: "literal"} +239 +mem: storing 1 in location 6 +240 +mem: storing 23 in location 7 +241 +run: return +242 # no other stores +243 % CHECK_EQ(trace_count_prefix("mem", "storing"), 7); +244 +245 :(before "End variant_type Special-cases") +246 if (contains_type_ingredient(element)) +247 replace_type_ingredients(element.type, type->right, info, " while computing variant type of exclusive-container"); +248 +249 :(scenario get_on_shape_shifting_container) +250 container foo:_t [ +251 x:_t +252 y:num +253 ] +254 def main [ +255 1:foo:point <- merge 14, 15, 16 +256 2:num <- get 1:foo:point, y:offset +257 ] +258 +mem: storing 16 in location 2 +259 +260 :(scenario get_on_shape_shifting_container_2) +261 container foo:_t [ +262 x:_t +263 y:num +264 ] +265 def main [ +266 1:foo:point <- merge 14, 15, 16 +267 2:point <- get 1:foo:point, x:offset +268 ] +269 +mem: storing 14 in location 2 +270 +mem: storing 15 in location 3 +271 +272 :(scenario get_on_shape_shifting_container_3) +273 container foo:_t [ +274 x:_t +275 y:num +276 ] +277 def main [ +278 1:foo:&:point <- merge 34/unsafe, 48 +279 3:&:point <- get 1:foo:&:point, x:offset +280 ] +281 +mem: storing 34 in location 3 +282 +283 :(scenario get_on_shape_shifting_container_inside_container) +284 container foo:_t [ +285 x:_t +286 y:num +287 ] +288 container bar [ +289 x:foo:point +290 y:num +291 ] +292 def main [ +293 1:bar <- merge 14, 15, 16, 17 +294 2:num <- get 1:bar, 1:offset +295 ] +296 +mem: storing 17 in location 2 +297 +298 :(scenario get_on_complex_shape_shifting_container) +299 container foo:_a:_b [ +300 x:_a +301 y:_b +302 ] +303 def main [ +304 1:text <- new [abc] +305 {2: (foo number (address array character))} <- merge 34/x, 1:text/y +306 3:text <- get {2: (foo number (address array character))}, y:offset +307 4:bool <- equal 1:text, 3:text 308 ] -309 def main [ -310 1:text <- new [abc] -311 {2: (foo number (address array character))} <- merge 34/x, 1:text/y -312 3:text <- get {2: (foo number (address array character))}, y:offset -313 4:bool <- equal 1:text, 3:text -314 ] -315 +mem: storing 1 in location 4 -316 -317 :(before "End element_type Special-cases") -318 replace_type_ingredients(element, type, info, " while computing element type of container"); -319 :(before "Compute Container Size(element, full_type)") -320 replace_type_ingredients(element, full_type, container_info, location_for_error_messages); -321 :(before "Compute Exclusive Container Size(element, full_type)") -322 replace_type_ingredients(element, full_type, exclusive_container_info, location_for_error_messages); -323 //? :(before "Compute Container Address Offset(element)") -324 //? replace_type_ingredients(element, type, info, location_for_error_messages); -325 //? if (contains_type_ingredient(element)) return; // error raised elsewhere -326 -327 :(after "Compute size_of Container") -328 assert(!contains_type_ingredient(type)); -329 :(after "Compute size_of Exclusive Container") -330 assert(!contains_type_ingredient(type)); -331 -332 :(code) -333 bool contains_type_ingredient(const reagent& x) { -334 return contains_type_ingredient(x.type); +309 +mem: storing 1 in location 4 +310 +311 :(before "End element_type Special-cases") +312 replace_type_ingredients(element, type, info, " while computing element type of container"); +313 :(before "Compute Container Size(element, full_type)") +314 replace_type_ingredients(element, full_type, container_info, location_for_error_messages); +315 :(before "Compute Exclusive Container Size(element, full_type)") +316 replace_type_ingredients(element, full_type, exclusive_container_info, location_for_error_messages); +317 //? :(before "Compute Container Address Offset(element)") +318 //? replace_type_ingredients(element, type, info, location_for_error_messages); +319 //? if (contains_type_ingredient(element)) return; // error raised elsewhere +320 +321 :(after "Compute size_of Container") +322 assert(!contains_type_ingredient(type)); +323 :(after "Compute size_of Exclusive Container") +324 assert(!contains_type_ingredient(type)); +325 +326 :(code) +327 bool contains_type_ingredient(const reagent& x) { +328 return contains_type_ingredient(x.type); +329 } +330 +331 bool contains_type_ingredient(const type_tree* type) { +332 if (!type) return false; +333 if (type->atom) return type->value >= START_TYPE_INGREDIENTS; +334 return contains_type_ingredient(type->left) || contains_type_ingredient(type->right); 335 } 336 -337 bool contains_type_ingredient(const type_tree* type) { -338 if (!type) return false; -339 if (type->atom) return type->value >= START_TYPE_INGREDIENTS; -340 return contains_type_ingredient(type->left) || contains_type_ingredient(type->right); -341 } -342 -343 void replace_type_ingredients(reagent& element, const type_tree* caller_type, const type_info& info, const string& location_for_error_messages) { -344 if (contains_type_ingredient(element)) { -345 if (!caller_type->right) -346 raise << "illegal type " << names_to_string(caller_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end(); -347 replace_type_ingredients(element.type, caller_type->right, info, location_for_error_messages); -348 } -349 } -350 -351 // replace all type_ingredients in element_type with corresponding elements of callsite_type -352 void replace_type_ingredients(type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) { -353 if (!callsite_type) return; // error but it's already been raised above -354 if (!element_type) return; -355 if (!element_type->atom) { -356 if (element_type->right == NULL && is_type_ingredient(element_type->left)) { -357 int type_ingredient_index = to_type_ingredient_index(element_type->left); -358 if (corresponding(callsite_type, type_ingredient_index, is_final_type_ingredient(type_ingredient_index, container_info))->right) { -359 // replacing type ingredient at end of list, and replacement is a non-degenerate compound type -- (a b) but not (a) -360 replace_type_ingredient_at(type_ingredient_index, element_type, callsite_type, container_info, location_for_error_messages); -361 return; -362 } -363 } -364 replace_type_ingredients(element_type->left, callsite_type, container_info, location_for_error_messages); -365 replace_type_ingredients(element_type->right, callsite_type, container_info, location_for_error_messages); -366 return; -367 } -368 if (is_type_ingredient(element_type)) -369 replace_type_ingredient_at(to_type_ingredient_index(element_type), element_type, callsite_type, container_info, location_for_error_messages); -370 } -371 -372 const type_tree* corresponding(const type_tree* type, int index, bool final) { -373 for (const type_tree* curr = type; curr; curr = curr->right, --index) { -374 assert_for_now(!curr->atom); -375 if (index == 0) -376 return final ? curr : curr->left; -377 } -378 assert_for_now(false); -379 } -380 -381 bool is_type_ingredient(const type_tree* type) { -382 return type->atom && type->value >= START_TYPE_INGREDIENTS; -383 } -384 -385 int to_type_ingredient_index(const type_tree* type) { -386 assert(type->atom); -387 return type->value-START_TYPE_INGREDIENTS; -388 } -389 -390 void replace_type_ingredient_at(const int type_ingredient_index, type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) { -391 if (!has_nth_type(callsite_type, type_ingredient_index)) { -392 raise << "illegal type " << names_to_string(callsite_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end(); -393 return; -394 } -395 *element_type = *nth_type_ingredient(callsite_type, type_ingredient_index, container_info); -396 } -397 -398 const type_tree* nth_type_ingredient(const type_tree* callsite_type, int type_ingredient_index, const type_info& container_info) { -399 bool final = is_final_type_ingredient(type_ingredient_index, container_info); -400 const type_tree* curr = callsite_type; -401 for (int i = 0; i < type_ingredient_index; ++i) { -402 assert(curr); -403 assert(!curr->atom); -404 //? cerr << "type ingredient " << i << " is " << to_string(curr->left) << '\n'; -405 curr = curr->right; -406 } -407 assert(curr); -408 if (curr->atom) return curr; -409 if (!final) return curr->left; -410 if (!curr->right) return curr->left; -411 return curr; -412 } -413 -414 bool is_final_type_ingredient(int type_ingredient_index, const type_info& container_info) { -415 for (map<string, type_ordinal>::const_iterator p = container_info.type_ingredient_names.begin(); -416 p != container_info.type_ingredient_names.end(); -417 ++p) { -418 if (p->second > START_TYPE_INGREDIENTS+type_ingredient_index) return false; -419 } -420 return true; -421 } -422 -423 :(before "End Unit Tests") -424 void test_replace_type_ingredients_entire() { -425 run("container foo:_elem [\n" -426 " x:_elem\n" -427 " y:num\n" -428 "]\n"); -429 reagent callsite("x:foo:point"); -430 reagent element = element_type(callsite.type, 0); -431 CHECK_EQ(to_string(element), "{x: \"point\"}"); -432 } -433 -434 void test_replace_type_ingredients_tail() { -435 run("container foo:_elem [\n" -436 " x:_elem\n" -437 "]\n" -438 "container bar:_elem [\n" -439 " x:foo:_elem\n" -440 "]\n"); -441 reagent callsite("x:bar:point"); -442 reagent element = element_type(callsite.type, 0); -443 CHECK_EQ(to_string(element), "{x: (\"foo\" \"point\")}"); -444 } -445 -446 void test_replace_type_ingredients_head_tail_multiple() { -447 run("container foo:_elem [\n" -448 " x:_elem\n" -449 "]\n" -450 "container bar:_elem [\n" -451 " x:foo:_elem\n" -452 "]\n"); -453 reagent callsite("x:bar:address:array:character"); -454 reagent element = element_type(callsite.type, 0); -455 CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"array\" \"character\")}"); -456 } -457 -458 void test_replace_type_ingredients_head_middle() { -459 run("container foo:_elem [\n" -460 " x:_elem\n" -461 "]\n" -462 "container bar:_elem [\n" -463 " x:foo:_elem:num\n" -464 "]\n"); -465 reagent callsite("x:bar:address"); -466 reagent element = element_type(callsite.type, 0); -467 CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"number\")}"); -468 } -469 -470 void test_replace_last_type_ingredient_with_multiple() { -471 run("container foo:_a:_b [\n" -472 " x:_a\n" -473 " y:_b\n" -474 "]\n"); -475 reagent callsite("{f: (foo number (address array character))}"); -476 reagent element1 = element_type(callsite.type, 0); -477 CHECK_EQ(to_string(element1), "{x: \"number\"}"); -478 reagent element2 = element_type(callsite.type, 1); -479 CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}"); -480 } -481 -482 void test_replace_last_type_ingredient_inside_compound() { -483 run("container foo:_a:_b [\n" -484 " {x: (bar _a (address _b))}\n" -485 "]\n"); -486 reagent callsite("f:foo:number:array:character"); -487 reagent element = element_type(callsite.type, 0); -488 CHECK_EQ(names_to_string_without_quotes(element.type), "(bar number (address array character))"); -489 } -490 -491 void test_replace_middle_type_ingredient_with_multiple() { -492 run("container foo:_a:_b:_c [\n" -493 " x:_a\n" -494 " y:_b\n" -495 " z:_c\n" -496 "]\n"); -497 reagent callsite("{f: (foo number (address array character) boolean)}"); -498 reagent element1 = element_type(callsite.type, 0); -499 CHECK_EQ(to_string(element1), "{x: \"number\"}"); -500 reagent element2 = element_type(callsite.type, 1); -501 CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}"); -502 reagent element3 = element_type(callsite.type, 2); -503 CHECK_EQ(to_string(element3), "{z: \"boolean\"}"); -504 } -505 -506 void test_replace_middle_type_ingredient_with_multiple2() { -507 run("container foo:_key:_value [\n" -508 " key:_key\n" -509 " value:_value\n" -510 "]\n"); -511 reagent callsite("{f: (foo (address array character) number)}"); -512 reagent element = element_type(callsite.type, 0); -513 CHECK_EQ(to_string(element), "{key: (\"address\" \"array\" \"character\")}"); -514 } -515 -516 void test_replace_middle_type_ingredient_with_multiple3() { -517 run("container foo_table:_key:_value [\n" -518 " data:&:@:foo_table_row:_key:_value\n" -519 "]\n" -520 "\n" -521 "container foo_table_row:_key:_value [\n" -522 " key:_key\n" -523 " value:_value\n" -524 "]\n"); -525 reagent callsite("{f: (foo_table (address array character) number)}"); -526 reagent element = element_type(callsite.type, 0); -527 CHECK_EQ(to_string(element), "{data: (\"address\" \"array\" \"foo_table_row\" (\"address\" \"array\" \"character\") \"number\")}"); -528 } -529 -530 :(code) -531 bool has_nth_type(const type_tree* base, int n) { -532 assert(n >= 0); -533 if (!base) return false; -534 if (n == 0) return true; -535 return has_nth_type(base->right, n-1); -536 } -537 -538 :(scenario get_on_shape_shifting_container_error) -539 % Hide_errors = true; -540 container foo:_t [ -541 x:_t -542 y:num -543 ] -544 def main [ -545 10:foo:point <- merge 14, 15, 16 -546 1:num <- get 10:foo, 1:offset -547 ] -548 +error: illegal type "foo" seems to be missing a type ingredient or three in '1:num <- get 10:foo, 1:offset' +337 void replace_type_ingredients(reagent& element, const type_tree* caller_type, const type_info& info, const string& location_for_error_messages) { +338 if (contains_type_ingredient(element)) { +339 if (!caller_type->right) +340 raise << "illegal type " << names_to_string(caller_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end(); +341 replace_type_ingredients(element.type, caller_type->right, info, location_for_error_messages); +342 } +343 } +344 +345 // replace all type_ingredients in element_type with corresponding elements of callsite_type +346 void replace_type_ingredients(type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) { +347 if (!callsite_type) return; // error but it's already been raised above +348 if (!element_type) return; +349 if (!element_type->atom) { +350 if (element_type->right == NULL && is_type_ingredient(element_type->left)) { +351 int type_ingredient_index = to_type_ingredient_index(element_type->left); +352 if (corresponding(callsite_type, type_ingredient_index, is_final_type_ingredient(type_ingredient_index, container_info))->right) { +353 // replacing type ingredient at end of list, and replacement is a non-degenerate compound type -- (a b) but not (a) +354 replace_type_ingredient_at(type_ingredient_index, element_type, callsite_type, container_info, location_for_error_messages); +355 return; +356 } +357 } +358 replace_type_ingredients(element_type->left, callsite_type, container_info, location_for_error_messages); +359 replace_type_ingredients(element_type->right, callsite_type, container_info, location_for_error_messages); +360 return; +361 } +362 if (is_type_ingredient(element_type)) +363 replace_type_ingredient_at(to_type_ingredient_index(element_type), element_type, callsite_type, container_info, location_for_error_messages); +364 } +365 +366 const type_tree* corresponding(const type_tree* type, int index, bool final) { +367 for (const type_tree* curr = type; curr; curr = curr->right, --index) { +368 assert_for_now(!curr->atom); +369 if (index == 0) +370 return final ? curr : curr->left; +371 } +372 assert_for_now(false); +373 } +374 +375 bool is_type_ingredient(const type_tree* type) { +376 return type->atom && type->value >= START_TYPE_INGREDIENTS; +377 } +378 +379 int to_type_ingredient_index(const type_tree* type) { +380 assert(type->atom); +381 return type->value-START_TYPE_INGREDIENTS; +382 } +383 +384 void replace_type_ingredient_at(const int type_ingredient_index, type_tree* element_type, const type_tree* callsite_type, const type_info& container_info, const string& location_for_error_messages) { +385 if (!has_nth_type(callsite_type, type_ingredient_index)) { +386 raise << "illegal type " << names_to_string(callsite_type) << " seems to be missing a type ingredient or three" << location_for_error_messages << '\n' << end(); +387 return; +388 } +389 *element_type = *nth_type_ingredient(callsite_type, type_ingredient_index, container_info); +390 } +391 +392 const type_tree* nth_type_ingredient(const type_tree* callsite_type, int type_ingredient_index, const type_info& container_info) { +393 bool final = is_final_type_ingredient(type_ingredient_index, container_info); +394 const type_tree* curr = callsite_type; +395 for (int i = 0; i < type_ingredient_index; ++i) { +396 assert(curr); +397 assert(!curr->atom); +398 //? cerr << "type ingredient " << i << " is " << to_string(curr->left) << '\n'; +399 curr = curr->right; +400 } +401 assert(curr); +402 if (curr->atom) return curr; +403 if (!final) return curr->left; +404 if (!curr->right) return curr->left; +405 return curr; +406 } +407 +408 bool is_final_type_ingredient(int type_ingredient_index, const type_info& container_info) { +409 for (map<string, type_ordinal>::const_iterator p = container_info.type_ingredient_names.begin(); +410 p != container_info.type_ingredient_names.end(); +411 ++p) { +412 if (p->second > START_TYPE_INGREDIENTS+type_ingredient_index) return false; +413 } +414 return true; +415 } +416 +417 :(before "End Unit Tests") +418 void test_replace_type_ingredients_entire() { +419 run("container foo:_elem [\n" +420 " x:_elem\n" +421 " y:num\n" +422 "]\n"); +423 reagent callsite("x:foo:point"); +424 reagent element = element_type(callsite.type, 0); +425 CHECK_EQ(to_string(element), "{x: \"point\"}"); +426 } +427 +428 void test_replace_type_ingredients_tail() { +429 run("container foo:_elem [\n" +430 " x:_elem\n" +431 "]\n" +432 "container bar:_elem [\n" +433 " x:foo:_elem\n" +434 "]\n"); +435 reagent callsite("x:bar:point"); +436 reagent element = element_type(callsite.type, 0); +437 CHECK_EQ(to_string(element), "{x: (\"foo\" \"point\")}"); +438 } +439 +440 void test_replace_type_ingredients_head_tail_multiple() { +441 run("container foo:_elem [\n" +442 " x:_elem\n" +443 "]\n" +444 "container bar:_elem [\n" +445 " x:foo:_elem\n" +446 "]\n"); +447 reagent callsite("x:bar:address:array:character"); +448 reagent element = element_type(callsite.type, 0); +449 CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"array\" \"character\")}"); +450 } +451 +452 void test_replace_type_ingredients_head_middle() { +453 run("container foo:_elem [\n" +454 " x:_elem\n" +455 "]\n" +456 "container bar:_elem [\n" +457 " x:foo:_elem:num\n" +458 "]\n"); +459 reagent callsite("x:bar:address"); +460 reagent element = element_type(callsite.type, 0); +461 CHECK_EQ(to_string(element), "{x: (\"foo\" \"address\" \"number\")}"); +462 } +463 +464 void test_replace_last_type_ingredient_with_multiple() { +465 run("container foo:_a:_b [\n" +466 " x:_a\n" +467 " y:_b\n" +468 "]\n"); +469 reagent callsite("{f: (foo number (address array character))}"); +470 reagent element1 = element_type(callsite.type, 0); +471 CHECK_EQ(to_string(element1), "{x: \"number\"}"); +472 reagent element2 = element_type(callsite.type, 1); +473 CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}"); +474 } +475 +476 void test_replace_last_type_ingredient_inside_compound() { +477 run("container foo:_a:_b [\n" +478 " {x: (bar _a (address _b))}\n" +479 "]\n"); +480 reagent callsite("f:foo:number:array:character"); +481 reagent element = element_type(callsite.type, 0); +482 CHECK_EQ(names_to_string_without_quotes(element.type), "(bar number (address array character))"); +483 } +484 +485 void test_replace_middle_type_ingredient_with_multiple() { +486 run("container foo:_a:_b:_c [\n" +487 " x:_a\n" +488 " y:_b\n" +489 " z:_c\n" +490 "]\n"); +491 reagent callsite("{f: (foo number (address array character) boolean)}"); +492 reagent element1 = element_type(callsite.type, 0); +493 CHECK_EQ(to_string(element1), "{x: \"number\"}"); +494 reagent element2 = element_type(callsite.type, 1); +495 CHECK_EQ(to_string(element2), "{y: (\"address\" \"array\" \"character\")}"); +496 reagent element3 = element_type(callsite.type, 2); +497 CHECK_EQ(to_string(element3), "{z: \"boolean\"}"); +498 } +499 +500 void test_replace_middle_type_ingredient_with_multiple2() { +501 run("container foo:_key:_value [\n" +502 " key:_key\n" +503 " value:_value\n" +504 "]\n"); +505 reagent callsite("{f: (foo (address array character) number)}"); +506 reagent element = element_type(callsite.type, 0); +507 CHECK_EQ(to_string(element), "{key: (\"address\" \"array\" \"character\")}"); +508 } +509 +510 void test_replace_middle_type_ingredient_with_multiple3() { +511 run("container foo_table:_key:_value [\n" +512 " data:&:@:foo_table_row:_key:_value\n" +513 "]\n" +514 "\n" +515 "container foo_table_row:_key:_value [\n" +516 " key:_key\n" +517 " value:_value\n" +518 "]\n"); +519 reagent callsite("{f: (foo_table (address array character) number)}"); +520 reagent element = element_type(callsite.type, 0); +521 CHECK_EQ(to_string(element), "{data: (\"address\" \"array\" \"foo_table_row\" (\"address\" \"array\" \"character\") \"number\")}"); +522 } +523 +524 :(code) +525 bool has_nth_type(const type_tree* base, int n) { +526 assert(n >= 0); +527 if (!base) return false; +528 if (n == 0) return true; +529 return has_nth_type(base->right, n-1); +530 } +531 +532 :(scenario get_on_shape_shifting_container_error) +533 % Hide_errors = true; +534 container foo:_t [ +535 x:_t +536 y:num +537 ] +538 def main [ +539 10:foo:point <- merge 14, 15, 16 +540 1:num <- get 10:foo, 1:offset +541 ] +542 +error: illegal type "foo" seems to be missing a type ingredient or three in '1:num <- get 10:foo, 1:offset' +543 +544 //:: fix up previous layers +545 +546 //: We have two transforms in previous layers -- for computing sizes and +547 //: offsets containing addresses for containers and exclusive containers -- +548 //: that we need to teach about type ingredients. 549 -550 //:: fix up previous layers -551 -552 //: We have two transforms in previous layers -- for computing sizes and -553 //: offsets containing addresses for containers and exclusive containers -- -554 //: that we need to teach about type ingredients. -555 -556 :(before "End compute_container_sizes Non-atom Special-cases") -557 const type_tree* root = get_base_type(type); -558 if (contains_key(Type, root->value)) { -559 type_info& info = get(Type, root->value); -560 if (info.kind == CONTAINER) { -561 compute_container_sizes(info, type, pending_metadata, location_for_error_messages); -562 return; -563 } -564 if (info.kind == EXCLUSIVE_CONTAINER) { -565 compute_exclusive_container_sizes(info, type, pending_metadata, location_for_error_messages); -566 return; -567 } -568 } // otherwise error raised elsewhere -569 -570 :(before "End Unit Tests") -571 void test_container_sizes_shape_shifting_container() { -572 run("container foo:_t [\n" -573 " x:num\n" -574 " y:_t\n" -575 "]\n"); -576 reagent r("x:foo:point"); -577 compute_container_sizes(r, ""); -578 CHECK_EQ(r.metadata.size, 3); -579 } -580 -581 void test_container_sizes_shape_shifting_exclusive_container() { -582 run("exclusive-container foo:_t [\n" -583 " x:num\n" -584 " y:_t\n" -585 "]\n"); -586 reagent r("x:foo:point"); -587 compute_container_sizes(r, ""); -588 CHECK_EQ(r.metadata.size, 3); -589 reagent r2("x:foo:num"); -590 compute_container_sizes(r2, ""); -591 CHECK_EQ(r2.metadata.size, 2); -592 } -593 -594 void test_container_sizes_compound_type_ingredient() { -595 run("container foo:_t [\n" -596 " x:num\n" -597 " y:_t\n" -598 "]\n"); -599 reagent r("x:foo:&:point"); -600 compute_container_sizes(r, ""); -601 CHECK_EQ(r.metadata.size, 2); -602 // scan also pre-computes metadata for type ingredient -603 reagent point("x:point"); -604 CHECK(contains_key(Container_metadata, point.type)); -605 CHECK_EQ(get(Container_metadata, point.type).size, 2); -606 } -607 -608 void test_container_sizes_recursive_shape_shifting_container() { -609 run("container foo:_t [\n" -610 " x:num\n" -611 " y:&:foo:_t\n" -612 "]\n"); -613 reagent r2("x:foo:num"); -614 compute_container_sizes(r2, ""); -615 CHECK_EQ(r2.metadata.size, 2); -616 } -617 -618 :(scenario typos_in_container_definitions) -619 % Hide_errors = true; -620 container foo:_t [ -621 x:adress:_t # typo -622 ] -623 def main [ -624 local-scope -625 x:address:foo:num <- new {(foo num): type} -626 ] -627 # no crash -628 -629 :(scenario typos_in_recipes) -630 % Hide_errors = true; -631 def foo [ -632 local-scope -633 x:adress:array:number <- copy 0 # typo -634 ] -635 # shouldn't crash -636 -637 //:: 'merge' on shape-shifting containers -638 -639 :(scenario merge_check_shape_shifting_container_containing_exclusive_container) -640 container foo:_elem [ -641 x:num -642 y:_elem -643 ] -644 exclusive-container bar [ -645 x:num -646 y:num -647 ] -648 def main [ -649 1:foo:bar <- merge 23, 1/y, 34 -650 ] -651 +mem: storing 23 in location 1 -652 +mem: storing 1 in location 2 -653 +mem: storing 34 in location 3 -654 $error: 0 -655 -656 :(scenario merge_check_shape_shifting_container_containing_exclusive_container_2) -657 % Hide_errors = true; -658 container foo:_elem [ -659 x:num -660 y:_elem -661 ] -662 exclusive-container bar [ -663 x:num -664 y:num -665 ] -666 def main [ -667 1:foo:bar <- merge 23, 1/y, 34, 35 -668 ] -669 +error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35' -670 -671 :(scenario merge_check_shape_shifting_exclusive_container_containing_container) -672 exclusive-container foo:_elem [ -673 x:num -674 y:_elem -675 ] -676 container bar [ -677 x:num -678 y:num -679 ] -680 def main [ -681 1:foo:bar <- merge 1/y, 23, 34 -682 ] -683 +mem: storing 1 in location 1 -684 +mem: storing 23 in location 2 -685 +mem: storing 34 in location 3 -686 $error: 0 -687 -688 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_2) -689 exclusive-container foo:_elem [ -690 x:num -691 y:_elem -692 ] -693 container bar [ -694 x:num -695 y:num -696 ] -697 def main [ -698 1:foo:bar <- merge 0/x, 23 -699 ] -700 $error: 0 -701 -702 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_3) -703 % Hide_errors = true; -704 exclusive-container foo:_elem [ -705 x:num -706 y:_elem -707 ] -708 container bar [ -709 x:num -710 y:num -711 ] -712 def main [ -713 1:foo:bar <- merge 1/y, 23 -714 ] -715 +error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23' +550 :(before "End compute_container_sizes Non-atom Special-cases") +551 const type_tree* root = get_base_type(type); +552 if (contains_key(Type, root->value)) { +553 type_info& info = get(Type, root->value); +554 if (info.kind == CONTAINER) { +555 compute_container_sizes(info, type, pending_metadata, location_for_error_messages); +556 return; +557 } +558 if (info.kind == EXCLUSIVE_CONTAINER) { +559 compute_exclusive_container_sizes(info, type, pending_metadata, location_for_error_messages); +560 return; +561 } +562 } // otherwise error raised elsewhere +563 +564 :(before "End Unit Tests") +565 void test_container_sizes_shape_shifting_container() { +566 run("container foo:_t [\n" +567 " x:num\n" +568 " y:_t\n" +569 "]\n"); +570 reagent r("x:foo:point"); +571 compute_container_sizes(r, ""); +572 CHECK_EQ(r.metadata.size, 3); +573 } +574 +575 void test_container_sizes_shape_shifting_exclusive_container() { +576 run("exclusive-container foo:_t [\n" +577 " x:num\n" +578 " y:_t\n" +579 "]\n"); +580 reagent r("x:foo:point"); +581 compute_container_sizes(r, ""); +582 CHECK_EQ(r.metadata.size, 3); +583 reagent r2("x:foo:num"); +584 compute_container_sizes(r2, ""); +585 CHECK_EQ(r2.metadata.size, 2); +586 } +587 +588 void test_container_sizes_compound_type_ingredient() { +589 run("container foo:_t [\n" +590 " x:num\n" +591 " y:_t\n" +592 "]\n"); +593 reagent r("x:foo:&:point"); +594 compute_container_sizes(r, ""); +595 CHECK_EQ(r.metadata.size, 2); +596 // scan also pre-computes metadata for type ingredient +597 reagent point("x:point"); +598 CHECK(contains_key(Container_metadata, point.type)); +599 CHECK_EQ(get(Container_metadata, point.type).size, 2); +600 } +601 +602 void test_container_sizes_recursive_shape_shifting_container() { +603 run("container foo:_t [\n" +604 " x:num\n" +605 " y:&:foo:_t\n" +606 "]\n"); +607 reagent r2("x:foo:num"); +608 compute_container_sizes(r2, ""); +609 CHECK_EQ(r2.metadata.size, 2); +610 } +611 +612 :(scenario typos_in_container_definitions) +613 % Hide_errors = true; +614 container foo:_t [ +615 x:adress:_t # typo +616 ] +617 def main [ +618 local-scope +619 x:address:foo:num <- new {(foo num): type} +620 ] +621 # no crash +622 +623 :(scenario typos_in_recipes) +624 % Hide_errors = true; +625 def foo [ +626 local-scope +627 x:adress:array:number <- copy 0 # typo +628 ] +629 # shouldn't crash +630 +631 //:: 'merge' on shape-shifting containers +632 +633 :(scenario merge_check_shape_shifting_container_containing_exclusive_container) +634 container foo:_elem [ +635 x:num +636 y:_elem +637 ] +638 exclusive-container bar [ +639 x:num +640 y:num +641 ] +642 def main [ +643 1:foo:bar <- merge 23, 1/y, 34 +644 ] +645 +mem: storing 23 in location 1 +646 +mem: storing 1 in location 2 +647 +mem: storing 34 in location 3 +648 $error: 0 +649 +650 :(scenario merge_check_shape_shifting_container_containing_exclusive_container_2) +651 % Hide_errors = true; +652 container foo:_elem [ +653 x:num +654 y:_elem +655 ] +656 exclusive-container bar [ +657 x:num +658 y:num +659 ] +660 def main [ +661 1:foo:bar <- merge 23, 1/y, 34, 35 +662 ] +663 +error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35' +664 +665 :(scenario merge_check_shape_shifting_exclusive_container_containing_container) +666 exclusive-container foo:_elem [ +667 x:num +668 y:_elem +669 ] +670 container bar [ +671 x:num +672 y:num +673 ] +674 def main [ +675 1:foo:bar <- merge 1/y, 23, 34 +676 ] +677 +mem: storing 1 in location 1 +678 +mem: storing 23 in location 2 +679 +mem: storing 34 in location 3 +680 $error: 0 +681 +682 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_2) +683 exclusive-container foo:_elem [ +684 x:num +685 y:_elem +686 ] +687 container bar [ +688 x:num +689 y:num +690 ] +691 def main [ +692 1:foo:bar <- merge 0/x, 23 +693 ] +694 $error: 0 +695 +696 :(scenario merge_check_shape_shifting_exclusive_container_containing_container_3) +697 % Hide_errors = true; +698 exclusive-container foo:_elem [ +699 x:num +700 y:_elem +701 ] +702 container bar [ +703 x:num +704 y:num +705 ] +706 def main [ +707 1:foo:bar <- merge 1/y, 23 +708 ] +709 +error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23' diff --git a/html/057immutable.cc.html b/html/057immutable.cc.html index 9fe98f1e..f0ea4e20 100644 --- a/html/057immutable.cc.html +++ b/html/057immutable.cc.html @@ -411,13 +411,13 @@ if ('onhashchange' in window) { 348 if (!caller.has_header) return; // skip check for old-style recipes calling next-ingredient directly 349 for (int i = 0; i < SIZE(caller.ingredients); ++i) { 350 const reagent& current_ingredient = caller.ingredients.at(i); -351 if (is_present_in_products(caller, current_ingredient.name)) continue; // not expected to be immutable +351 if (is_present_in_products(caller, current_ingredient.name)) continue; // not expected to be immutable 352 // End Immutable Ingredients Special-cases 353 set<reagent> immutable_vars; 354 immutable_vars.insert(current_ingredient); 355 for (int i = 0; i < SIZE(caller.steps); ++i) { 356 const instruction& inst = caller.steps.at(i); -357 check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller); +357 check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller); 358 if (inst.operation == INDEX && SIZE(inst.ingredients) > 1 && inst.ingredients.at(1).name == current_ingredient.name) continue; 359 update_aliases(inst, immutable_vars); 360 } @@ -425,7 +425,7 @@ if ('onhashchange' in window) { 362 } 363 364 void update_aliases(const instruction& inst, set<reagent>& current_ingredient_and_aliases) { -365 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); +365 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); 366 if (!contains_key(Recipe, inst.operation)) { 367 // primitive recipe 368 switch (inst.operation) { @@ -455,10 +455,10 @@ if ('onhashchange' in window) { 392 } 393 } 394 -395 set<int> scan_contained_in_product_indices(const instruction& inst, set<int>& ingredient_indices) { +395 set<int> scan_contained_in_product_indices(const instruction& inst, set<int>& ingredient_indices) { 396 set<reagent> selected_ingredients; 397 const recipe& callee = get(Recipe, inst.operation); -398 for (set<int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) { +398 for (set<int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) { 399 if (*p >= SIZE(callee.ingredients)) continue; // optional immutable ingredient 400 selected_ingredients.insert(callee.ingredients.at(*p)); 401 } @@ -472,183 +472,209 @@ if ('onhashchange' in window) { 409 return result; 410 } 411 -412 :(scenarios transform) -413 :(scenario immutability_infects_contained_in_variables) -414 % Hide_errors = true; -415 container test-list [ -416 value:num -417 next:&:test-list -418 ] -419 def main [ -420 local-scope -421 p:&:test-list <- new test-list:type -422 foo p -423 ] -424 def foo p:&:test-list [ # p is immutable -425 local-scope -426 load-ingredients -427 p2:&:test-list <- test-next p # p2 is immutable -428 *p2 <- put *p2, value:offset, 34 -429 ] -430 def test-next x:&:test-list -> y:&:test-list/contained-in:x [ -431 local-scope -432 load-ingredients -433 y <- get *x, next:offset -434 ] -435 +error: foo: cannot modify 'p2' in instruction '*p2 <- put *p2, value:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product -436 -437 :(code) -438 void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) { -439 // first check if the instruction is directly modifying something it shouldn't -440 for (int i = 0; i < SIZE(inst.products); ++i) { -441 if (has_property(inst.products.at(i), "lookup") -442 && current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) { -443 string current_product_name = inst.products.at(i).name; -444 if (current_product_name == original_ingredient_name) -445 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -446 else -447 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because that would modify " << original_ingredient_name << " which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -448 return; -449 } -450 } -451 // check if there's any indirect modification going on -452 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); -453 if (current_ingredient_indices.empty()) return; // ingredient not found in call -454 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) { -455 const int current_ingredient_index = *p; -456 reagent current_ingredient = inst.ingredients.at(current_ingredient_index); -457 canonize_type(current_ingredient); -458 const string& current_ingredient_name = current_ingredient.name; -459 if (!contains_key(Recipe, inst.operation)) { -460 // primitive recipe -461 // we got here only because we got an instruction with an implicit product, and the instruction didn't explicitly spell it out -462 // put x, y:offset, z -463 // instead of -464 // x <- put x, y:offset, z -465 if (inst.operation == PUT || inst.operation == PUT_INDEX) { -466 if (current_ingredient_index == 0) { -467 if (current_ingredient_name == original_ingredient_name) -468 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -469 else -470 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -471 } -472 } -473 } -474 else { -475 // defined recipe -476 if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) { -477 if (current_ingredient_name == original_ingredient_name) -478 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -479 else -480 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); -481 } -482 } -483 } -484 } -485 -486 bool is_modified_in_recipe(const recipe_ordinal r, const int ingredient_index, const recipe& caller) { -487 const recipe& callee = get(Recipe, r); -488 if (!callee.has_header) { -489 raise << maybe(caller.name) << "can't check mutability of ingredients in recipe " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end(); -490 return true; -491 } -492 if (ingredient_index >= SIZE(callee.ingredients)) return false; // optional immutable ingredient -493 return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name); -494 } -495 -496 bool is_present_in_products(const recipe& callee, const string& ingredient_name) { -497 for (int i = 0; i < SIZE(callee.products); ++i) { -498 if (callee.products.at(i).name == ingredient_name) -499 return true; -500 } -501 return false; -502 } -503 -504 set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) { -505 set<int> result; -506 for (int i = 0; i < SIZE(inst.ingredients); ++i) { -507 if (is_literal(inst.ingredients.at(i))) continue; -508 if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end()) -509 result.insert(i); -510 } -511 return result; -512 } -513 -514 //: Sometimes you want to pass in two addresses, one pointing inside the -515 //: other. For example, you want to delete a node from a linked list. You -516 //: can't pass both pointers back out, because if a caller tries to make both -517 //: identical then you can't tell which value will be written on the way out. -518 //: -519 //: Experimental solution: just tell Mu that one points inside the other. -520 //: This way we can return just one pointer as high up as necessary to capture -521 //: all modifications performed by a recipe. -522 //: -523 //: We'll see if we end up wanting to abuse /contained-in for other reasons. -524 -525 :(scenarios transform) -526 :(scenario can_modify_contained_in_addresses) -527 container test-list [ -528 value:num -529 next:&:test-list -530 ] -531 def main [ -532 local-scope -533 p:&:test-list <- new test-list:type -534 foo p -535 ] -536 def foo p:&:test-list -> p:&:test-list [ -537 local-scope -538 load-ingredients -539 p2:&:test-list <- test-next p -540 p <- test-remove p2, p -541 ] -542 def test-next x:&:test-list -> y:&:test-list [ -543 local-scope -544 load-ingredients -545 y <- get *x, next:offset -546 ] -547 def test-remove x:&:test-list/contained-in:from, from:&:test-list -> from:&:test-list [ -548 local-scope -549 load-ingredients -550 *x <- put *x, value:offset, 34 # can modify x -551 ] -552 $error: 0 -553 -554 :(before "End Immutable Ingredients Special-cases") -555 if (has_property(current_ingredient, "contained-in")) { -556 const string_tree* tmp = property(current_ingredient, "contained-in"); -557 if (!tmp->atom -558 || (!is_present_in_ingredients(caller, tmp->value) -559 && !is_present_in_products(caller, tmp->value))) { -560 raise << maybe(caller.name) << "/contained-in can only point to another ingredient or product, but got '" << to_string(property(current_ingredient, "contained-in")) << "'\n" << end(); -561 } -562 continue; -563 } -564 -565 :(scenario contained_in_product) -566 container test-list [ -567 value:num -568 next:&:test-list -569 ] -570 def foo x:&:test-list/contained-in:result -> result:&:test-list [ -571 local-scope -572 load-ingredients -573 result <- copy 0 -574 ] -575 $error: 0 -576 -577 :(scenario contained_in_is_mutable) -578 container test-list [ -579 value:num -580 next:&:test-list -581 ] -582 def foo x:&:test-list/contained-in:result -> result:&:test-list [ -583 local-scope -584 load-ingredients -585 result <- copy x -586 put *x, value:offset, 34 -587 ] -588 $error: 0 +412 bool is_mu_container(const reagent& r) { +413 return is_mu_container(r.type); +414 } +415 bool is_mu_container(const type_tree* type) { +416 if (!type) return false; +417 if (!type->atom) +418 return is_mu_container(get_base_type(type)); +419 if (type->value == 0) return false; +420 if (!contains_key(Type, type->value)) return false; // error raised elsewhere +421 type_info& info = get(Type, type->value); +422 return info.kind == CONTAINER; +423 } +424 +425 bool is_mu_exclusive_container(const reagent& r) { +426 return is_mu_exclusive_container(r.type); +427 } +428 bool is_mu_exclusive_container(const type_tree* type) { +429 if (!type) return false; +430 if (!type->atom) +431 return is_mu_exclusive_container(get_base_type(type)); +432 if (type->value == 0) return false; +433 if (!contains_key(Type, type->value)) return false; // error raised elsewhere +434 type_info& info = get(Type, type->value); +435 return info.kind == EXCLUSIVE_CONTAINER; +436 } +437 +438 :(scenarios transform) +439 :(scenario immutability_infects_contained_in_variables) +440 % Hide_errors = true; +441 container test-list [ +442 value:num +443 next:&:test-list +444 ] +445 def main [ +446 local-scope +447 p:&:test-list <- new test-list:type +448 foo p +449 ] +450 def foo p:&:test-list [ # p is immutable +451 local-scope +452 load-ingredients +453 p2:&:test-list <- test-next p # p2 is immutable +454 *p2 <- put *p2, value:offset, 34 +455 ] +456 def test-next x:&:test-list -> y:&:test-list/contained-in:x [ +457 local-scope +458 load-ingredients +459 y <- get *x, next:offset +460 ] +461 +error: foo: cannot modify 'p2' in instruction '*p2 <- put *p2, value:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product +462 +463 :(code) +464 void check_immutable_ingredient_in_instruction(const instruction& inst, const set<reagent>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) { +465 // first check if the instruction is directly modifying something it shouldn't +466 for (int i = 0; i < SIZE(inst.products); ++i) { +467 if (has_property(inst.products.at(i), "lookup") +468 && current_ingredient_and_aliases.find(inst.products.at(i)) != current_ingredient_and_aliases.end()) { +469 string current_product_name = inst.products.at(i).name; +470 if (current_product_name == original_ingredient_name) +471 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +472 else +473 raise << maybe(caller.name) << "cannot modify '" << current_product_name << "' in instruction '" << to_original_string(inst) << "' because that would modify " << original_ingredient_name << " which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +474 return; +475 } +476 } +477 // check if there's any indirect modification going on +478 set<int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases); +479 if (current_ingredient_indices.empty()) return; // ingredient not found in call +480 for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) { +481 const int current_ingredient_index = *p; +482 reagent current_ingredient = inst.ingredients.at(current_ingredient_index); +483 canonize_type(current_ingredient); +484 const string& current_ingredient_name = current_ingredient.name; +485 if (!contains_key(Recipe, inst.operation)) { +486 // primitive recipe +487 // we got here only because we got an instruction with an implicit product, and the instruction didn't explicitly spell it out +488 // put x, y:offset, z +489 // instead of +490 // x <- put x, y:offset, z +491 if (inst.operation == PUT || inst.operation == PUT_INDEX) { +492 if (current_ingredient_index == 0) { +493 if (current_ingredient_name == original_ingredient_name) +494 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +495 else +496 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +497 } +498 } +499 } +500 else { +501 // defined recipe +502 if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) { +503 if (current_ingredient_name == original_ingredient_name) +504 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because it's an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +505 else +506 raise << maybe(caller.name) << "cannot modify '" << current_ingredient_name << "' in instruction '" << to_original_string(inst) << "' because that would modify '" << original_ingredient_name << "' which is an ingredient of recipe " << caller.name << " but not also a product\n" << end(); +507 } +508 } +509 } +510 } +511 +512 bool is_modified_in_recipe(const recipe_ordinal r, const int ingredient_index, const recipe& caller) { +513 const recipe& callee = get(Recipe, r); +514 if (!callee.has_header) { +515 raise << maybe(caller.name) << "can't check mutability of ingredients in recipe " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end(); +516 return true; +517 } +518 if (ingredient_index >= SIZE(callee.ingredients)) return false; // optional immutable ingredient +519 return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name); +520 } +521 +522 bool is_present_in_products(const recipe& callee, const string& ingredient_name) { +523 for (int i = 0; i < SIZE(callee.products); ++i) { +524 if (callee.products.at(i).name == ingredient_name) +525 return true; +526 } +527 return false; +528 } +529 +530 set<int> ingredient_indices(const instruction& inst, const set<reagent>& ingredient_names) { +531 set<int> result; +532 for (int i = 0; i < SIZE(inst.ingredients); ++i) { +533 if (is_literal(inst.ingredients.at(i))) continue; +534 if (ingredient_names.find(inst.ingredients.at(i)) != ingredient_names.end()) +535 result.insert(i); +536 } +537 return result; +538 } +539 +540 //: Sometimes you want to pass in two addresses, one pointing inside the +541 //: other. For example, you want to delete a node from a linked list. You +542 //: can't pass both pointers back out, because if a caller tries to make both +543 //: identical then you can't tell which value will be written on the way out. +544 //: +545 //: Experimental solution: just tell Mu that one points inside the other. +546 //: This way we can return just one pointer as high up as necessary to capture +547 //: all modifications performed by a recipe. +548 //: +549 //: We'll see if we end up wanting to abuse /contained-in for other reasons. +550 +551 :(scenarios transform) +552 :(scenario can_modify_contained_in_addresses) +553 container test-list [ +554 value:num +555 next:&:test-list +556 ] +557 def main [ +558 local-scope +559 p:&:test-list <- new test-list:type +560 foo p +561 ] +562 def foo p:&:test-list -> p:&:test-list [ +563 local-scope +564 load-ingredients +565 p2:&:test-list <- test-next p +566 p <- test-remove p2, p +567 ] +568 def test-next x:&:test-list -> y:&:test-list [ +569 local-scope +570 load-ingredients +571 y <- get *x, next:offset +572 ] +573 def test-remove x:&:test-list/contained-in:from, from:&:test-list -> from:&:test-list [ +574 local-scope +575 load-ingredients +576 *x <- put *x, value:offset, 34 # can modify x +577 ] +578 $error: 0 +579 +580 :(before "End Immutable Ingredients Special-cases") +581 if (has_property(current_ingredient, "contained-in")) { +582 const string_tree* tmp = property(current_ingredient, "contained-in"); +583 if (!tmp->atom +584 || (!is_present_in_ingredients(caller, tmp->value) +585 && !is_present_in_products(caller, tmp->value))) { +586 raise << maybe(caller.name) << "/contained-in can only point to another ingredient or product, but got '" << to_string(property(current_ingredient, "contained-in")) << "'\n" << end(); +587 } +588 continue; +589 } +590 +591 :(scenario contained_in_product) +592 container test-list [ +593 value:num +594 next:&:test-list +595 ] +596 def foo x:&:test-list/contained-in:result -> result:&:test-list [ +597 local-scope +598 load-ingredients +599 result <- copy 0 +600 ] +601 $error: 0 +602 +603 :(scenario contained_in_is_mutable) +604 container test-list [ +605 value:num +606 next:&:test-list +607 ] +608 def foo x:&:test-list/contained-in:result -> result:&:test-list [ +609 local-scope +610 load-ingredients +611 result <- copy x +612 put *x, value:offset, 34 +613 ] +614 $error: 0 -- cgit 1.4.1-2-gfad0