1 //: Ingredients of a recipe are meant to be immutable unless they're also
  2 //: products. This layer will start enforcing this check.
  3 //:
  4 //: One hole for now: variables in surrounding spaces are implicitly mutable.
  5 //: [tag: todo]
  6 
  7 :(scenario can_modify_ingredients_that_are_also_products)
  8 # mutable container
  9 def main [
 10   local-scope
 11   p:point <- merge 34, 35
 12   p <- foo p
 13 ]
 14 def foo p:point -> p:point [
 15   local-scope
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
// "a type" "needed cpp_includes"
"vector" "#include <vector>
using namespace std;"

"list" "#include <list>
using namespace std;"

"map" "#include <map>
using namespace std;"

"string" "#include <string>
using namespace std;"
table address to primitive 76 def foo x:&:num [ 77 local-scope 78 load-ingredients 79 *x <- copy 34 80 ] 81 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product 82 83 :(scenario cannot_modify_immutable_containers) 84 % Hide_errors = true; 85 def main [ 86 local-scope 87 x:point-number <- merge 34, 35, 36 88 foo x 89 ] 90 # immutable container 91 def foo x:point-number [ 92 local-scope 93 load-ingredients 94 # copy an element: ok 95 y:point <- get x, xy:offset 96 # modify the element: boom 97 # This could be ok if y contains no addresses, but we're not going to try to be that smart. 98 # It also makes the rules easier to reason about. If it's just an ingredient, just don't try to change it. 99 y <- put y, x:offset, 37 100 ] 101 +error: foo: cannot modify 'y' in instruction 'y <- put y, x:offset, 37' because that would modify 'x' which is an ingredient of recipe foo but not also a product 102 103 :(scenario can_modify_immutable_pointers) 104 def main [ 105 local-scope 106 x:&:num <- new number:type 107 foo x 108 ] 109 def foo x:&:num [ 110 local-scope 111 load-ingredients 112 # modify the address, not the payload 113 x <- copy 0 114 ] 115 $error: 0 116 117 :(scenario can_modify_immutable_pointers_but_not_their_payloads) 118 % Hide_errors = true; 119 def main [ 120 local-scope 121 x:&:num <- new number:type 122 foo x 123 ] 124 def foo x:&:num [ 125 local-scope 126 load-ingredients 127 # modify address; ok 128 x <- new number:type 129 # modify payload: boom 130 # this could be ok, but we're not going to try to be that smart 131 *x <- copy 34 132 ] 133 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because it's an ingredient of recipe foo but not also a product 134 135 :(scenario cannot_call_mutating_recipes_on_immutable_ingredients) 136 % Hide_errors = true; 137 def main [ 138 local-scope 139 p:&:point <- new point:type 140 foo p 141 ] 142 def foo p:&:point [ 143 local-scope 144 load-ingredients 145 bar p 146 ] 147 def bar p:&:point -> p:&:point [ 148 local-scope 149 load-ingredients 150 # p could be modified here, but it doesn't have to be, it's already marked 151 # mutable in the header 152 ] 153 +error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product 154 155 :(scenario cannot_modify_copies_of_immutable_ingredients) 156 % Hide_errors = true; 157 def main [ 158 local-scope 159 p:&:point <- new point:type 160 foo p 161 ] 162 def foo p:&:point [ 163 local-scope 164 load-ingredients 165 q:&:point <- copy p 166 *q <- put *q, x:offset, 34 167 ] 168 +error: foo: cannot modify 'q' in instruction '*q <- put *q, x:offset, 34' because that would modify p which is an ingredient of recipe foo but not also a product 169 170 :(scenario can_modify_copies_of_mutable_ingredients) 171 def main [ 172 local-scope 173 p:&:point <- new point:type 174 foo p 175 ] 176 def foo p:&:point -> p:&:point [ 177 local-scope 178 load-ingredients 179 q:&:point <- copy p 180 *q <- put *q, x:offset, 34 181 ] 182 $error: 0 183 184 :(scenario cannot_modify_address_inside_immutable_ingredients) 185 % Hide_errors = true; 186 container foo [ 187 x:&:@:num # contains an address 188 ] 189 def main [ 190 # don't run anything 191 ] 192 def foo a:&:foo [ 193 local-scope 194 load-ingredients 195 x:&:@:num <- get *a, x:offset # just a regular get of the container 196 *x <- put-index *x, 0, 34 # but then a put-index on the result 197 ] 198 +error: foo: cannot modify 'x' in instruction '*x <- put-index *x, 0, 34' because that would modify a which is an ingredient of recipe foo but not also a product 199 200 :(scenario cannot_modify_address_inside_immutable_ingredients_2) 201 container foo [ 202 x:&:@:num # contains an address 203 ] 204 def main [ 205 # don't run anything 206 ] 207 def foo a:&:foo [ 208 local-scope 209 load-ingredients 210 b:foo <- merge 0 211 # modify b, completely unrelated to immutable ingredient a 212 x:&:@:num <- get b, x:offset 213 *x <- put-index *x, 0, 34 214 ] 215 $error: 0 216 217 :(scenario cannot_modify_address_inside_immutable_ingredients_3) 218 % Hide_errors = true; 219 def main [ 220 # don't run anything 221 ] 222 def foo a:&:@:&:num [ 223 local-scope 224 load-ingredients 225 x:&:num <- index *a, 0 # just a regular index of the array 226 *x <- copy 34 # but then modify the result 227 ] 228 +error: foo: cannot modify 'x' in instruction '*x <- copy 34' because that would modify a which is an ingredient of recipe foo but not also a product 229 230 :(scenario cannot_modify_address_inside_immutable_ingredients_4) 231 def main [ 232 # don't run anything 233 ] 234 def foo a:&:@:&:num [ 235 local-scope 236 load-ingredients 237 b:&:@:&:num <- new {(address number): type}, 3 238 # modify b, completely unrelated to immutable ingredient a 239 x:&:num <- index *b, 0 240 *x <- copy 34 241 ] 242 $error: 0 243 244 :(scenario latter_ingredient_of_index_is_immutable) 245 def main [ 246 # don't run anything 247 ] 248 def foo a:&:@:&:@:num, b:num -> a:&:@:&:@:num [ 249 local-scope 250 load-ingredients 251 x:&:@:num <- index *a, b 252 *x <- put-index *x, 0, 34 253 ] 254 $error: 0 255 256 :(scenario can_traverse_immutable_ingredients) 257 container test-list [ 258 next:&:test-list 259 ] 260 def main [ 261 local-scope 262 p:&:test-list <- new test-list:type 263 foo p 264 ] 265 def foo p:&:test-list [ 266 local-scope 267 load-ingredients 268 p2:&:test-list <- bar p 269 ] 270 def bar x:&:test-list -> y:&:test-list [ 271 local-scope 272 load-ingredients 273 y <- get *x, next:offset 274 ] 275 $error: 0 276 277 :(scenario treat_optional_ingredients_as_mutable) 278 def main [ 279 k:&:num <- new number:type 280 test k 281 ] 282 # recipe taking an immutable address ingredient 283 def test k:&:num [ 284 local-scope 285 load-ingredients 286 foo k 287 ] 288 # ..calling a recipe with an optional address ingredient 289 def foo -> [ 290 local-scope 291 load-ingredients 292 k:&:num, found?:bool <- next-ingredient 293 # we don't further check k for immutability, but assume it's mutable 294 ] 295 $error: 0 296 297 :(scenario treat_optional_ingredients_as_mutable_2) 298 % Hide_errors = true; 299 def main [ 300 local-scope 301 p:&:point <- new point:type 302 foo p 303 ] 304 def foo p:&:point [ 305 local-scope 306 load-ingredients 307 bar p 308 ] 309 def bar [ 310 local-scope 311 load-ingredients 312 p:&:point <- next-ingredient # optional ingredient; assumed to be mutable 313 ] 314 +error: foo: cannot modify 'p' in instruction 'bar p' because it's an ingredient of recipe foo but not also a product 315 316 //: when checking for immutable ingredients, remember to take space into account 317 :(scenario check_space_of_reagents_in_immutability_checks) 318 def main [ 319 a:space <- new-closure 320 b:&:num <- new number:type 321 run-closure b:&:num, a:space 322 ] 323 def new-closure [ 324 new-default-space 325 x:&:num <- new number:type 326 return default-space 327 ] 328 def run-closure x:&:num, s:space [ 329 local-scope 330 load-ingredients 331 0:space/names:new-closure <- copy s 332 # different space; always mutable 333 *x:&:num/space:1 <- copy 34 334 ] 335 $error: 0 336 337 :(before "End Transforms") 338 Transform.push_back(check_immutable_ingredients); // idempotent 339 340 :(code) 341 void check_immutable_ingredients(const recipe_ordinal r) { 342 // to ensure an address reagent isn't modified, it suffices to show that 343 // a) we never write to its contents directly, 344 // b) we never call 'put' or 'put-index' on it, and 345 // c) any non-primitive recipe calls in the body aren't returning it as a product 346 const recipe& caller = get(Recipe, r); 347 trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end(); 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 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); 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 ¦ } 361 } 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); 366 if (!contains_key(Recipe, inst.operation)) { 367 ¦ // primitive recipe 368 ¦ switch (inst.operation) { 369 ¦ ¦ case COPY: 370 ¦ ¦ ¦ for (set<int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) 371 ¦ ¦ ¦ ¦ current_ingredient_and_aliases.insert(inst.products.at(*p).name); 372 ¦ ¦ ¦ break; 373 ¦ ¦ case GET: 374 ¦ ¦ case INDEX: 375 ¦ ¦ case MAYBE_CONVERT: 376 ¦ ¦ ¦ // current_ingredient_indices can only have 0 or one value 377 ¦ ¦ ¦ if (!current_ingredient_indices.empty() && !inst.products.empty()) { 378 ¦ ¦ ¦ ¦ if (is_mu_address(inst.products.at(0)) || is_mu_container(inst.products.at(0)) || is_mu_exclusive_container(inst.products.at(0))) 379 ¦ ¦ ¦ ¦ ¦ current_ingredient_and_aliases.insert(inst.products.at(0)); 380 ¦ ¦ ¦ } 381 ¦ ¦ ¦ break; 382 ¦ ¦ default: break; 383 ¦ } 384 } 385 else { 386 ¦ // defined recipe 387 ¦ set<int> contained_in_product_indices = scan_contained_in_product_indices(inst, current_ingredient_indices); 388 ¦ for (set<int>::iterator p = contained_in_product_indices.begin(); p != contained_in_product_indices.end(); ++p) { 389 ¦ ¦ if (*p < SIZE(inst.products)) 390 ¦ ¦ ¦ current_ingredient_and_aliases.insert(inst.products.at(*p)); 391 ¦ } 392 } 393 } 394 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) { 399 ¦ if (*p >= SIZE(callee.ingredients)) continue; // optional immutable ingredient 400 ¦ selected_ingredients.insert(callee.ingredients.at(*p)); 401 } 402 set<int> result; 403 for (int i = 0; i < SIZE(callee.products); ++i) { 404 ¦ const reagent& current_product = callee.products.at(i); 405 ¦ const string_tree* contained_in_name = property(current_product, "contained-in"); 406 ¦ if (contained_in_name && selected_ingredients.find(contained_in_name->value) != selected_ingredients.end()) 407 ¦ ¦ result.insert(i); 408 } 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_check) 566 container test-list [ 567 value:num 568 next:&:test-list 569 ] 570 def test-remove x:&:test-list/contained-in:result, from:&:test-list -> result:&:test-list [ 571 local-scope 572 load-ingredients 573 result <- copy 0 574 ] 575 $error: 0