diff options
-rw-r--r-- | 021check_instruction.cc | 2 | ||||
-rw-r--r-- | 059shape_shifting_recipe.cc | 11 | ||||
-rw-r--r-- | 060immutable.cc | 211 | ||||
-rw-r--r-- | 075duplex_list.mu | 97 |
4 files changed, 51 insertions, 270 deletions
diff --git a/021check_instruction.cc b/021check_instruction.cc index 5e258b9b..1d831369 100644 --- a/021check_instruction.cc +++ b/021check_instruction.cc @@ -73,8 +73,6 @@ recipe main [ +error: main: can't copy 34 to 1:address:number; types don't match :(code) -// copy arguments because later layers will want to make changes to them -// without perturbing the caller bool types_match(reagent lhs, reagent rhs) { // '_' never raises type error if (is_dummy(lhs)) return true; diff --git a/059shape_shifting_recipe.cc b/059shape_shifting_recipe.cc index 7ee400cb..e308be3a 100644 --- a/059shape_shifting_recipe.cc +++ b/059shape_shifting_recipe.cc @@ -193,11 +193,6 @@ void save_or_deduce_type_name(reagent& x, map<string, string_tree*>& type_name) } void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& inst, map<string, const string_tree*>& mappings, const recipe& caller_recipe, bool* error) { - if (SIZE(exemplar.ingredients) > SIZE(inst.ingredients)) { - raise << maybe(caller_recipe.name) << "too few ingredients in call '" << inst.to_string() << "'\n" << end(); - *error = true; - return; - } for (long long int i = 0; i < SIZE(exemplar.ingredients); ++i) { const reagent& exemplar_reagent = exemplar.ingredients.at(i); reagent ingredient = inst.ingredients.at(i); @@ -205,11 +200,6 @@ void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& canonize_type(ingredient); accumulate_type_ingredients(exemplar_reagent, ingredient, mappings, exemplar, inst, caller_recipe, error); } - if (SIZE(exemplar.products) > SIZE(inst.products)) { - raise << maybe(caller_recipe.name) << "too few products in call '" << inst.to_string() << "'\n" << end(); - *error = true; - return; - } for (long long int i = 0; i < SIZE(exemplar.products); ++i) { const reagent& exemplar_reagent = exemplar.products.at(i); reagent product = inst.products.at(i); @@ -227,7 +217,6 @@ void accumulate_type_ingredients(const reagent& exemplar_reagent, reagent& refin void accumulate_type_ingredients(const string_tree* exemplar_type, const string_tree* refinement_type, map<string, const string_tree*>& mappings, const recipe& exemplar, const reagent& exemplar_reagent, const instruction& call_instruction, const recipe& caller_recipe, bool* error) { if (!exemplar_type) return; if (!refinement_type) { - // todo: make this smarter; only warn if exemplar_type contains some *new* type ingredient raise_error << maybe(exemplar.name) << "missing type ingredient in " << exemplar_reagent.original_string << '\n' << end(); return; } diff --git a/060immutable.cc b/060immutable.cc deleted file mode 100644 index 9acd74e3..00000000 --- a/060immutable.cc +++ /dev/null @@ -1,211 +0,0 @@ -//: Addresses passed into of a recipe are meant to be immutable unless they're -//: also products. This layer will start enforcing this check. - -:(scenario can_modify_value_ingredients) -% Hide_warnings = true; -recipe main [ - local-scope - p:address:point <- new point:type - foo *p -] -recipe foo p:point [ - local-scope - load-ingredients - x:address:number <- get-address p, x:offset - *x <- copy 34 -] -$warn: 0 - -:(scenario can_modify_ingredients_that_are_also_products) -% Hide_warnings = true; -recipe main [ - local-scope - p:address:point <- new point:type - p <- foo p -] -recipe foo p:address:point -> p:address:point [ - local-scope - load-ingredients - x:address:number <- get-address *p, x:offset - *x <- copy 34 -] -$warn: 0 - -:(scenario cannot_take_address_inside_immutable_ingredients) -% Hide_warnings = true; -recipe main [ - local-scope - p:address:point <- new point:type - foo p -] -recipe foo p:address:point [ - local-scope - load-ingredients - x:address:number <- get-address *p, x:offset - *x <- copy 34 -] -+warn: foo: cannot modify ingredient p after instruction 'x:address:number <- get-address *p, x:offset' because it's not also a product of foo - -:(scenario cannot_call_mutating_recipes_on_immutable_ingredients) -% Hide_warnings = true; -recipe main [ - local-scope - p:address:point <- new point:type - foo p -] -recipe foo p:address:point [ - local-scope - load-ingredients - bar p -] -recipe bar p:address:point -> p:address:point [ - local-scope - load-ingredients - x:address:number <- get-address *p, x:offset - *x <- copy 34 -] -+warn: foo: cannot modify ingredient p at instruction 'bar p' because it's not also a product of foo - -:(scenario can_traverse_immutable_ingredients) -% Hide_warnings = true; -container test-list [ - next:address:test-list -] -recipe main [ - local-scope - p:address:test-list <- new test-list:type - foo p -] -recipe foo p:address:test-list [ - local-scope - load-ingredients - p2:address:test-list <- bar p -] -recipe bar x:address:test-list -> y:address:test-list [ - local-scope - load-ingredients - y <- get *x, next:offset -] -$warn: 0 - -:(before "End Transforms") -Transform.push_back(check_immutable_ingredients); // idempotent - -:(code) -void check_immutable_ingredients(recipe_ordinal r) { - // to ensure a reagent isn't modified, it suffices to show that we never - // call get-address or index-address with it, and that any non-primitive - // recipe calls in the body aren't returning it as a product. - const recipe& caller = get(Recipe, r); - if (!caller.has_header) return; // skip check for old-style recipes calling next-ingredient directly - for (long long int i = 0; i < SIZE(caller.ingredients); ++i) { - const reagent& current_ingredient = caller.ingredients.at(i); - if (!is_mu_address(current_ingredient)) continue; // will be copied - if (is_present_in_products(caller, current_ingredient.name)) continue; // not expected to be immutable - // End Immutable Ingredients Special-cases - for (long long int i = 0; i < SIZE(caller.steps); ++i) { - check_immutable_ingredient_in_instruction(caller.steps.at(i), current_ingredient.name, caller); - } - } -} - -void check_immutable_ingredient_in_instruction(const instruction& inst, const string& current_ingredient_name, const recipe& caller) { - long long int current_ingredient_index = find_ingredient_index(inst, current_ingredient_name); - if (current_ingredient_index == -1) return; // ingredient not found in call - reagent current_ingredient = inst.ingredients.at(current_ingredient_index); - canonize_type(current_ingredient); - if (!contains_key(Recipe, inst.operation)) { - // primitive recipe - if (inst.operation == GET_ADDRESS || inst.operation == INDEX_ADDRESS) - raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " after instruction '" << inst.to_string() << "' because it's not also a product of " << caller.name << '\n' << end(); - } - else { - // defined recipe - if (!is_mu_address(current_ingredient)) return; // making a copy is ok - if (is_modified_in_recipe(inst.operation, current_ingredient_index, caller)) - raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " at instruction '" << inst.to_string() << "' because it's not also a product of " << caller.name << '\n' << end(); - } -} - -bool is_modified_in_recipe(recipe_ordinal r, long long int ingredient_index, const recipe& caller) { - const recipe& callee = get(Recipe, r); - if (!callee.has_header) { - raise << maybe(caller.name) << "can't check mutability of ingredients in " << callee.name << " because it uses 'next-ingredient' directly, rather than a recipe header.\n" << end(); - return true; - } - return is_present_in_products(callee, callee.ingredients.at(ingredient_index).name); -} - -bool is_present_in_products(const recipe& callee, const string& ingredient_name) { - for (long long int i = 0; i < SIZE(callee.products); ++i) { - if (callee.products.at(i).name == ingredient_name) - return true; - } - return false; -} - -bool is_present_in_ingredients(const recipe& callee, const string& ingredient_name) { - for (long long int i = 0; i < SIZE(callee.ingredients); ++i) { - if (callee.ingredients.at(i).name == ingredient_name) - return true; - } - return false; -} - -long long int find_ingredient_index(const instruction& inst, const string& ingredient_name) { - for (long long int i = 0; i < SIZE(inst.ingredients); ++i) { - if (inst.ingredients.at(i).name == ingredient_name) - return i; - } - return -1; -} - -//: Sometimes you want to pass in two addresses, one pointing inside the -//: other. For example, you want to delete a node from a linked list. You -//: can't pass both pointers back out, because if a caller tries to make both -//: identical then you can't tell which value will be written on the way out. -//: -//: Experimental solution: just tell mu that one points inside the other. -//: This way we can return just one pointer as high up as necessary to capture -//: all modifications performed by a recipe. -//: -//: We'll see if we end up wanting to abuse /contained-in for other reasons. - -:(scenarios transform) -:(scenario can_modify_contained_in_addresses) -#% Hide_warnings = true; -container test-list [ - next:address:test-list -] -recipe main [ - local-scope - p:address:test-list <- new test-list:type - foo p -] -recipe foo p:address:test-list -> p:address:test-list [ - local-scope - load-ingredients - p2:address:test-list <- test-next p - p <- test-remove p2, p -] -recipe test-next x:address:test-list -> y:address:test-list [ - local-scope - load-ingredients - y <- get *x, next:offset -] -recipe test-remove x:address:test-list/contained-in:from, from:address:test-list -> from:address:test-list [ - local-scope - load-ingredients - x2:address:address:test-list <- get-address *x, next:offset # pretend modification -] -$warn: 0 - -:(before "End Immutable Ingredients Special-cases") -if (has_property(current_ingredient, "contained-in")) { - const string_tree* tmp = property(current_ingredient, "contained-in"); - if (tmp->left || tmp->right - || !is_present_in_ingredients(caller, tmp->value) - || !is_present_in_products(caller, tmp->value)) - raise_error << maybe(caller.name) << "contained-in can only point to another ingredient+product, but got " << debug_string(property(current_ingredient, "contained-in")) << '\n' << end(); - continue; -} diff --git a/075duplex_list.mu b/075duplex_list.mu index e98710ed..765a6a17 100644 --- a/075duplex_list.mu +++ b/075duplex_list.mu @@ -6,20 +6,17 @@ container duplex-list:_elem [ prev:address:duplex-list:_elem ] -recipe push-duplex x:_elem, in:address:duplex-list:_elem -> in:address:duplex-list:_elem [ +recipe push-duplex x:_elem, in:address:duplex-list:_elem -> result:address:duplex-list:_elem [ local-scope load-ingredients - result:address:duplex-list:_elem <- new {(duplex-list _elem): type} + result <- new {(duplex-list _elem): type} val:address:_elem <- get-address *result, value:offset *val <- copy x next:address:address:duplex-list:_elem <- get-address *result, next:offset *next <- copy in - { - break-unless in - prev:address:address:duplex-list:_elem <- get-address *in, prev:offset - *prev <- copy result - } - reply result # needed explicitly because we need to replace 'in' with 'result' + reply-unless in + prev:address:address:duplex-list:_elem <- get-address *in, prev:offset + *prev <- copy result ] recipe first-duplex in:address:duplex-list:_elem -> result:_elem [ @@ -86,11 +83,11 @@ scenario duplex-list-handling [ ] ] -# insert 'x' after 'in' -recipe insert-duplex x:_elem, in:address:duplex-list:_elem -> in:address:duplex-list:_elem [ +# Inserts 'x' after 'in'. Returns some pointer into the list. +recipe insert-duplex x:_elem, in:address:duplex-list:_elem -> new-node:address:duplex-list:_elem [ local-scope load-ingredients - new-node:address:duplex-list:_elem <- new {(duplex-list _elem): type} + new-node <- new {(duplex-list _elem): type} val:address:_elem <- get-address *new-node, value:offset *val <- copy x next-node:address:duplex-list:_elem <- get *in, next:offset @@ -104,10 +101,11 @@ recipe insert-duplex x:_elem, in:address:duplex-list:_elem -> in:address:duplex- y <- get-address *new-node, next:offset *y <- copy next-node # if next-node is not null - reply-unless next-node + reply-unless next-node, new-node # next-node.prev = new-node y <- get-address *next-node, prev:offset *y <- copy new-node + reply new-node # just signalling something changed; don't rely on the result ] scenario inserting-into-duplex-list [ @@ -191,7 +189,7 @@ scenario inserting-after-start-of-duplex-list [ 1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character 1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character 1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character - 1:address:duplex-list:character <- insert-duplex 6, 1:address:duplex-list:character + 2:address:duplex-list:character <- insert-duplex 6, 1:address:duplex-list:character # check structure like before 2:address:duplex-list:character <- copy 1:address:duplex-list:character 3:character <- first-duplex 2:address:duplex-list:character @@ -221,38 +219,38 @@ scenario inserting-after-start-of-duplex-list [ ] ] -# remove 'x' from its surrounding list 'in' +# Removes 'in' from its surrounding list. Returns some valid pointer into the +# rest of the list. # -# Returns null if and only if list is empty. Beware: in that case any other -# pointers to the head are now invalid. -recipe remove-duplex x:address:duplex-list:_elem/contained-in:in, in:address:duplex-list:_elem -> in:address:duplex-list:_elem [ +# Returns null if and only if list is empty. Beware: in that case any pointers +# to the head are now invalid. +recipe remove-duplex in:address:duplex-list:_elem -> next-node:address:duplex-list:_elem [ local-scope load-ingredients # if 'in' is null, return - reply-unless x - next-node:address:duplex-list:_elem <- get *x, next:offset - prev-node:address:duplex-list:_elem <- get *x, prev:offset - # null x's pointers - tmp:address:address:duplex-list:_elem <- get-address *x, next:offset - *tmp <- copy 0 - tmp <- get-address *x, prev:offset - *tmp <- copy 0 - # if next-node is not null, set its prev pointer + reply-unless in, in + next-node:address:duplex-list:_elem <- get *in, next:offset + prev-node:address:duplex-list:_elem <- get *in, prev:offset + # null in's pointers + x:address:address:duplex-list:_elem <- get-address *in, next:offset + *x <- copy 0 + x <- get-address *in, prev:offset + *x <- copy 0 { + # if next-node is not null break-unless next-node - tmp <- get-address *next-node, prev:offset - *tmp <- copy prev-node + # next-node.prev = prev-node + x <- get-address *next-node, prev:offset + *x <- copy prev-node } - # if prev-node is not null, set its next pointer and return { + # if prev-node is not null break-unless prev-node # prev-node.next = next-node - tmp <- get-address *prev-node, next:offset - *tmp <- copy next-node - reply + x <- get-address *prev-node, next:offset + *x <- copy next-node + reply prev-node } - # if prev-node is null, then we removed the node at 'in' - # return the new head rather than the old 'in' reply next-node ] @@ -263,7 +261,7 @@ scenario removing-from-duplex-list [ 1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character 1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character 2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character # 2 points at second element - 1:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character, 1:address:duplex-list:character + 2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character 3:boolean <- equal 2:address:duplex-list:character, 0 # check structure like before 2:address:duplex-list:character <- copy 1:address:duplex-list:character @@ -291,8 +289,8 @@ scenario removing-from-start-of-duplex-list [ 1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character 1:address:duplex-list:character <- push-duplex 4, 1:address:duplex-list:character 1:address:duplex-list:character <- push-duplex 5, 1:address:duplex-list:character - # remove from head - 1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character, 1:address:duplex-list:character + # removing from head? return value matters. + 1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character # check structure like before 2:address:duplex-list:character <- copy 1:address:duplex-list:character 3:character <- first-duplex 2:address:duplex-list:character @@ -321,7 +319,7 @@ scenario removing-from-end-of-duplex-list [ # delete last element 2:address:duplex-list:character <- next-duplex 1:address:duplex-list:character 2:address:duplex-list:character <- next-duplex 2:address:duplex-list:character - 1:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character, 1:address:duplex-list:character + 2:address:duplex-list:character <- remove-duplex 2:address:duplex-list:character 3:boolean <- equal 2:address:duplex-list:character, 0 # check structure like before 2:address:duplex-list:character <- copy 1:address:duplex-list:character @@ -347,15 +345,22 @@ scenario removing-from-singleton-list [ run [ 1:address:duplex-list:character <- copy 0 # 1 points to singleton list 1:address:duplex-list:character <- push-duplex 3, 1:address:duplex-list:character - 1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character, 1:address:duplex-list:character + 2:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character + 3:address:duplex-list:character <- get *1:address:duplex-list:character, next:offset + 4:address:duplex-list:character <- get *1:address:duplex-list:character, prev:offset ] memory-should-contain [ - 1 <- 0 # back to an empty list + 2 <- 0 # remove returned null + 3 <- 0 # removed node is also detached + 4 <- 0 ] ] -# remove values between 'start' and 'end' (both exclusive) -recipe remove-duplex-between start:address:duplex-list:_elem, end:address:duplex-list:_elem/contained-in:start -> start:address:duplex-list:_elem [ +# l:address:duplex-list <- remove-duplex-between start:address:duplex-list, end:address:duplex-list +# Remove values between 'start' and 'end' (both exclusive). Returns some valid +# pointer into the rest of the list. +# Also clear pointers back out from start/end for hygiene. +recipe remove-duplex-between start:address:duplex-list:_elem, end:address:duplex-list:_elem -> start:address:duplex-list:_elem [ local-scope load-ingredients reply-unless start @@ -465,8 +470,8 @@ scenario remove-range-empty [ ] ] -# insert list beginning at 'new' after 'in' -recipe insert-duplex-range in:address:duplex-list:_elem, start:address:duplex-list:_elem/contained-in:in -> in:address:duplex-list:_elem [ +# Inserts list beginning at 'new' after 'in'. Returns some pointer into the list. +recipe insert-duplex-range in:address:duplex-list:_elem, start:address:duplex-list:_elem -> in:address:duplex-list:_elem [ local-scope load-ingredients reply-unless in @@ -492,7 +497,7 @@ recipe insert-duplex-range in:address:duplex-list:_elem, start:address:duplex-li *dest <- copy in ] -recipe append-duplex in:address:duplex-list:_elem, new:address:duplex-list:_elem/contained-in:in -> in:address:duplex-list:_elem [ +recipe append-duplex in:address:duplex-list:_elem, new:address:duplex-list:_elem -> in:address:duplex-list:_elem [ local-scope load-ingredients last:address:duplex-list:_elem <- last-duplex in @@ -542,7 +547,7 @@ recipe force-specialization-duplex-list-character [ 1:address:duplex-list:character <- next-duplex 1:address:duplex-list:character 1:address:duplex-list:character <- prev-duplex 1:address:duplex-list:character 1:address:duplex-list:character <- insert-duplex 2:character, 1:address:duplex-list:character - 1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character, 1:address:duplex-list:character + 1:address:duplex-list:character <- remove-duplex 1:address:duplex-list:character 1:address:duplex-list:character <- remove-duplex-between 1:address:duplex-list:character, 1:address:duplex-list:character 1:address:duplex-list:character <- insert-duplex-range 1:address:duplex-list:character, 1:address:duplex-list:character 1:address:duplex-list:character <- append-duplex 1:address:duplex-list:character, 1:address:duplex-list:character |