From 4690ce81e079fc58cae8d6d583e5e3eb3ed81a83 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Wed, 9 Mar 2016 02:56:27 -0800 Subject: 2743 Looks like "TOhtml | " doesn't work on Mac OS X for some reason.. --- html/059shape_shifting_recipe.cc.html | 581 ++++++++++++++++++---------------- 1 file changed, 311 insertions(+), 270 deletions(-) (limited to 'html/059shape_shifting_recipe.cc.html') diff --git a/html/059shape_shifting_recipe.cc.html b/html/059shape_shifting_recipe.cc.html index c71467b8..255f487a 100644 --- a/html/059shape_shifting_recipe.cc.html +++ b/html/059shape_shifting_recipe.cc.html @@ -3,52 +3,45 @@ Mu - 059shape_shifting_recipe.cc - - + + - - + - - -
+
 //:: Like container definitions, recipes too can contain type parameters.
 
 :(scenario shape_shifting_recipe)
-recipe main [
+def main [
   10:point <- merge 14, 15
   11:point <- foo 10:point
 ]
 # non-matching variant
-recipe foo a:number -> result:number [
+def foo a:number -> result:number [
   local-scope
   load-ingredients
   result <- copy 34
 ]
 # matching shape-shifting variant
-recipe foo a:_t -> result:_t [
+def foo a:_t -> result:_t [
   local-scope
   load-ingredients
   result <- copy a
@@ -62,19 +55,20 @@ recipe foo a:_t -> result:_t [
 //: ingredients filled in.
 
 :(before "End Transform Checks")
-if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue;
+if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue;
 
 :(after "Running One Instruction")
-if (Current_routine->calls.front().running_step_index == 0
+if (Current_routine->calls.front().running_step_index == 0
     && any_type_ingredient_in_header(Current_routine->calls.front().running_recipe)) {
 //?   DUMP("");
-  raise_error << "ran into unspecialized shape-shifting recipe " << current_recipe_name() << '\n' << end();
+  raise << "ran into unspecialized shape-shifting recipe " << current_recipe_name() << '\n' << end();
+//?   exit(0);
 }
 
 //: Make sure we don't match up literals with type ingredients without
 //: specialization.
 :(before "End Matching Types For Literal(to)")
-if (contains_type_ingredient_name(to)) return false;
+if (contains_type_ingredient_name(to)) return false;
 
 //: We'll be creating recipes without loading them from anywhere by
 //: *specializing* existing recipes.
@@ -102,17 +96,17 @@ result.original_name = result:(after "Static Dispatch Phase 2")
 candidates = strictly_matching_shape_shifting_variants(inst, variants);
-if (!candidates.empty()) {
+if (!candidates.empty()) {
   recipe_ordinal exemplar = best_shape_shifting_variant(inst, candidates);
   trace(9992, "transform") << "found variant to specialize: " << exemplar << ' ' << get(Recipe, exemplar).name << end();
   recipe_ordinal new_recipe_ordinal = new_variant(exemplar, inst, caller_recipe);
-  if (new_recipe_ordinal == 0) goto skip_shape_shifting_variants;
+  if (new_recipe_ordinal == 0) goto skip_shape_shifting_variants;
   variants.push_back(new_recipe_ordinal);  // side-effect
   recipe& variant = get(Recipe, new_recipe_ordinal);
   // perform all transforms on the new specialization
-  if (!variant.steps.empty()) {
+  if (!variant.steps.empty()) {
     trace(9992, "transform") << "transforming new specialization: " << variant.name << end();
-    for (long long int t = 0; t < SIZE(Transform); ++t) {
+    for (long long int t = 0; t < SIZE(Transform); ++t) {
       (*Transform.at(t))(new_recipe_ordinal);
     }
   }
@@ -120,49 +114,49 @@ if (!candidates.em
   trace(9992, "transform") << "new specialization: " << variant.name << end();
   return variant.name;
 }
-skip_shape_shifting_variants:;
+skip_shape_shifting_variants:;
 
 //: make sure we have no unspecialized shape-shifting recipes being called
 //: before running mu programs
 
 :(before "End Instruction Operation Checks")
-if (contains_key(Recipe, inst.operation) && inst.operation >= MAX_PRIMITIVE_RECIPES
+if (contains_key(Recipe, inst.operation) && inst.operation >= MAX_PRIMITIVE_RECIPES
     && any_type_ingredient_in_header(inst.operation)) {
-  raise_error << maybe(caller.name) << "instruction " << inst.name << " has no valid specialization\n" << end();
+  raise << maybe(caller.name) << "instruction " << inst.name << " has no valid specialization\n" << end();
   return;
 }
 
 :(code)
 // phase 2 of static dispatch
-vector<recipe_ordinal> strictly_matching_shape_shifting_variants(const instruction& inst, vector<recipe_ordinal>& variants) {
+vector<recipe_ordinal> strictly_matching_shape_shifting_variants(const instruction& inst, vector<recipe_ordinal>& variants) {
   vector<recipe_ordinal> result;
-  for (long long int i = 0; i < SIZE(variants); ++i) {
-    if (variants.at(i) == -1) continue;
-    if (!any_type_ingredient_in_header(variants.at(i))) continue;
-    if (all_concrete_header_reagents_strictly_match(inst, get(Recipe, variants.at(i))))
+  for (long long int i = 0; i < SIZE(variants); ++i) {
+    if (variants.at(i) == -1) continue;
+    if (!any_type_ingredient_in_header(variants.at(i))) continue;
+    if (all_concrete_header_reagents_strictly_match(inst, get(Recipe, variants.at(i))))
       result.push_back(variants.at(i));
   }
   return result;
 }
 
-bool all_concrete_header_reagents_strictly_match(const instruction& inst, const recipe& variant) {
-  if (SIZE(inst.ingredients) < SIZE(variant.ingredients)) {
+bool all_concrete_header_reagents_strictly_match(const instruction& inst, const recipe& variant) {
+  if (SIZE(inst.ingredients) < SIZE(variant.ingredients)) {
     trace(9993, "transform") << "too few ingredients" << end();
     return false;
   }
-  if (SIZE(variant.products) < SIZE(inst.products)) {
+  if (SIZE(variant.products) < SIZE(inst.products)) {
     trace(9993, "transform") << "too few products" << end();
     return false;
   }
-  for (long long int i = 0; i < SIZE(variant.ingredients); ++i) {
-    if (!concrete_type_names_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
+  for (long long int i = 0; i < SIZE(variant.ingredients); ++i) {
+    if (!concrete_type_names_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
       trace(9993, "transform") << "concrete-type match failed: ingredient " << i << end();
       return false;
     }
   }
-  for (long long int i = 0; i < SIZE(inst.products); ++i) {
-    if (is_dummy(inst.products.at(i))) continue;
-    if (!concrete_type_names_strictly_match(variant.products.at(i), inst.products.at(i))) {
+  for (long long int i = 0; i < SIZE(inst.products); ++i) {
+    if (is_dummy(inst.products.at(i))) continue;
+    if (!concrete_type_names_strictly_match(variant.products.at(i), inst.products.at(i))) {
       trace(9993, "transform") << "strict match failed: product " << i << end();
       return false;
     }
@@ -171,27 +165,27 @@ bool all_concrete_header_reagents_strictly_match(
 }
 
 // tie-breaker for phase 2
-recipe_ordinal best_shape_shifting_variant(const instruction& inst, vector<recipe_ordinal>& candidates) {
+recipe_ordinal best_shape_shifting_variant(const instruction& inst, vector<recipe_ordinal>& candidates) {
   assert(!candidates.empty());
   // primary score
-  long long int max_score = -1;
-  for (long long int i = 0; i < SIZE(candidates); ++i) {
-    long long int score = number_of_concrete_type_names(candidates.at(i));
+  long long int max_score = -1;
+  for (long long int i = 0; i < SIZE(candidates); ++i) {
+    long long int score = number_of_concrete_type_names(candidates.at(i));
     assert(score > -1);
-    if (score > max_score) max_score = score;
+    if (score > max_score) max_score = score;
   }
   // break any ties at max_score by a secondary score
-  long long int min_score2 = 999;
-  long long int best_index = 0;
-  for (long long int i = 0; i < SIZE(candidates); ++i) {
-    long long int score1 = number_of_concrete_type_names(candidates.at(i));
+  long long int min_score2 = 999;
+  long long int best_index = 0;
+  for (long long int i = 0; i < SIZE(candidates); ++i) {
+    long long int score1 = number_of_concrete_type_names(candidates.at(i));
     assert(score1 <= max_score);
-    if (score1 != max_score) continue;
-    const recipe& candidate = get(Recipe, candidates.at(i));
-    long long int score2 = (SIZE(candidate.products)-SIZE(inst.products))
+    if (score1 != max_score) continue;
+    const recipe& candidate = get(Recipe, candidates.at(i));
+    long long int score2 = (SIZE(candidate.products)-SIZE(inst.products))
                            + (SIZE(inst.ingredients)-SIZE(candidate.ingredients));
     assert(score2 < 999);
-    if (score2 < min_score2) {
+    if (score2 < min_score2) {
       min_score2 = score2;
       best_index = i;
     }
@@ -199,79 +193,79 @@ recipe_ordinal best_shape_shifting_variant(const
   return candidates.at(best_index);
 }
 
-bool any_type_ingredient_in_header(recipe_ordinal variant) {
-  const recipe& caller = get(Recipe, variant);
-  for (long long int i = 0; i < SIZE(caller.ingredients); ++i) {
-    if (contains_type_ingredient_name(caller.ingredients.at(i)))
+bool any_type_ingredient_in_header(recipe_ordinal variant) {
+  const recipe& caller = get(Recipe, variant);
+  for (long long int i = 0; i < SIZE(caller.ingredients); ++i) {
+    if (contains_type_ingredient_name(caller.ingredients.at(i)))
       return true;
   }
-  for (long long int i = 0; i < SIZE(caller.products); ++i) {
-    if (contains_type_ingredient_name(caller.products.at(i)))
+  for (long long int i = 0; i < SIZE(caller.products); ++i) {
+    if (contains_type_ingredient_name(caller.products.at(i)))
       return true;
   }
   return false;
 }
 
-bool concrete_type_names_strictly_match(reagent to, reagent from) {
+bool concrete_type_names_strictly_match(reagent to, reagent from) {
   canonize_type(to);
   canonize_type(from);
   return concrete_type_names_strictly_match(to.type, from.type, from);
 }
 
-long long int number_of_concrete_type_names(recipe_ordinal r) {
-  const recipe& caller = get(Recipe, r);
-  long long int result = 0;
-  for (long long int i = 0; i < SIZE(caller.ingredients); ++i)
+long long int number_of_concrete_type_names(recipe_ordinal r) {
+  const recipe& caller = get(Recipe, r);
+  long long int result = 0;
+  for (long long int i = 0; i < SIZE(caller.ingredients); ++i)
     result += number_of_concrete_type_names(caller.ingredients.at(i));
-  for (long long int i = 0; i < SIZE(caller.products); ++i)
+  for (long long int i = 0; i < SIZE(caller.products); ++i)
     result += number_of_concrete_type_names(caller.products.at(i));
   return result;
 }
 
-long long int number_of_concrete_type_names(const reagent& r) {
+long long int number_of_concrete_type_names(const reagent& r) {
   return number_of_concrete_type_names(r.type);
 }
 
-long long int number_of_concrete_type_names(const type_tree* type) {
-  if (!type) return 0;
-  long long int result = 0;
-  if (!type->name.empty() && !is_type_ingredient_name(type->name))
+long long int number_of_concrete_type_names(const type_tree* type) {
+  if (!type) return 0;
+  long long int result = 0;
+  if (!type->name.empty() && !is_type_ingredient_name(type->name))
     result++;
   result += number_of_concrete_type_names(type->left);
   result += number_of_concrete_type_names(type->right);
   return result;
 }
 
-bool concrete_type_names_strictly_match(const type_tree* to, const type_tree* from, const reagent& rhs_reagent) {
-  if (!to) return !from;
-  if (!from) return !to;
-  if (is_type_ingredient_name(to->name)) return true;  // type ingredient matches anything
-  if (to->name == "literal" && from->name == "literal")
+bool concrete_type_names_strictly_match(const type_tree* to, const type_tree* from, const reagent& rhs_reagent) {
+  if (!to) return !from;
+  if (!from) return !to;
+  if (is_type_ingredient_name(to->name)) return true;  // type ingredient matches anything
+  if (to->name == "literal" && from->name == "literal")
     return true;
-  if (to->name == "literal"
+  if (to->name == "literal"
       && Literal_type_names.find(from->name) != Literal_type_names.end())
     return true;
-  if (from->name == "literal"
+  if (from->name == "literal"
       && Literal_type_names.find(to->name) != Literal_type_names.end())
     return true;
-  if (from->name == "literal" && to->name == "address")
+  if (from->name == "literal" && to->name == "address")
     return rhs_reagent.name == "0";
   return to->name == from->name
       && concrete_type_names_strictly_match(to->left, from->left, rhs_reagent)
       && concrete_type_names_strictly_match(to->right, from->right, rhs_reagent);
 }
 
-bool contains_type_ingredient_name(const reagent& x) {
+bool contains_type_ingredient_name(const reagent& x) {
   return contains_type_ingredient_name(x.type);
 }
 
-bool contains_type_ingredient_name(const type_tree* type) {
-  if (!type) return false;
-  if (is_type_ingredient_name(type->name)) return true;
+bool contains_type_ingredient_name(const type_tree* type) {
+  if (!type) return false;
+  if (is_type_ingredient_name(type->name)) return true;
   return contains_type_ingredient_name(type->left) || contains_type_ingredient_name(type->right);
 }
 
-recipe_ordinal new_variant(recipe_ordinal exemplar, const instruction& inst, const recipe& caller_recipe) {
+recipe_ordinal new_variant(recipe_ordinal exemplar, const instruction& inst, const recipe& caller_recipe) {
   string new_name = next_unused_recipe_name(inst.name);
   assert(!contains_key(Recipe_ordinal, new_name));
   recipe_ordinal new_recipe_ordinal = put(Recipe_ordinal, new_name, Next_recipe_ordinal++);
@@ -289,170 +283,203 @@ recipe_ordinal new_variant(recipe_ordinal exempla
   compute_type_names(new_recipe);
   // that gives enough information to replace type-ingredients with concrete types
   {
-    map<string, const type_tree*> mappings;
-    bool error = false;
+    map<string, const type_tree*> mappings;
+    bool error = false;
     compute_type_ingredient_mappings(get(Recipe, exemplar), inst, mappings, caller_recipe, &error);
-    if (!error) replace_type_ingredients(new_recipe, mappings);
-    for (map<string, const type_tree*>::iterator p = mappings.begin(); p != mappings.end(); ++p)
-      delete p->second;
-    if (error) return 0;  // todo: delete new_recipe_ordinal from Recipes and other global state
+    if (!error) replace_type_ingredients(new_recipe, mappings);
+    for (map<string, const type_tree*>::iterator p = mappings.begin(); p != mappings.end(); ++p)
+      delete p->second;
+    if (error) return 0;  // todo: delete new_recipe_ordinal from Recipes and other global state
   }
   ensure_all_concrete_types(new_recipe, get(Recipe, exemplar));
   return new_recipe_ordinal;
 }
 
-void compute_type_names(recipe& variant) {
+void compute_type_names(recipe& variant) {
   trace(9993, "transform") << "compute type names: " << variant.name << end();
   map<string, type_tree*> type_names;
-  for (long long int i = 0; i < SIZE(variant.ingredients); ++i)
+  for (long long int i = 0; i < SIZE(variant.ingredients); ++i)
     save_or_deduce_type_name(variant.ingredients.at(i), type_names, variant);
-  for (long long int i = 0; i < SIZE(variant.products); ++i)
+  for (long long int i = 0; i < SIZE(variant.products); ++i)
     save_or_deduce_type_name(variant.products.at(i), type_names, variant);
-  for (long long int i = 0; i < SIZE(variant.steps); ++i) {
+  for (long long int i = 0; i < SIZE(variant.steps); ++i) {
     instruction& inst = variant.steps.at(i);
     trace(9993, "transform") << "  instruction: " << to_string(inst) << end();
-    for (long long int in = 0; in < SIZE(inst.ingredients); ++in)
+    for (long long int in = 0; in < SIZE(inst.ingredients); ++in)
       save_or_deduce_type_name(inst.ingredients.at(in), type_names, variant);
-    for (long long int out = 0; out < SIZE(inst.products); ++out)
+    for (long long int out = 0; out < SIZE(inst.products); ++out)
       save_or_deduce_type_name(inst.products.at(out), type_names, variant);
   }
 }
 
-void save_or_deduce_type_name(reagent& x, map<string, type_tree*>& type, const recipe& variant) {
+void save_or_deduce_type_name(reagent& x, map<string, type_tree*>& type, const recipe& variant) {
   trace(9994, "transform") << "    checking " << to_string(x) << ": " << names_to_string(x.type) << end();
-  if (!x.type && contains_key(type, x.name)) {
-    x.type = new type_tree(*get(type, x.name)); //TODO
+  if (!x.type && contains_key(type, x.name)) {
+    x.type = new type_tree(*get(type, x.name)); //TODO
     trace(9994, "transform") << "    deducing type to " << names_to_string(x.type) << end();
     return;
   }
-  if (!x.type) {
-    raise_error << maybe(variant.original_name) << "unknown type for " << x.original_string << " (check the name for typos)\n" << end();
+  if (!x.type) {
+    raise << maybe(variant.original_name) << "unknown type for " << x.original_string << " (check the name for typos)\n" << end();
     return;
   }
-  if (contains_key(type, x.name)) return;
-  if (x.type->name == "offset" || x.type->name == "variant") return;  // special-case for container-access instructions
+  if (contains_key(type, x.name)) return;
+  if (x.type->name == "offset" || x.type->name == "variant") return;  // special-case for container-access instructions
   put(type, x.name, x.type);
   trace(9993, "transform") << "type of " << x.name << " is " << names_to_string(x.type) << end();
 }
 
-void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& inst, map<string, const type_tree*>& mappings, const recipe& caller_recipe, bool* error) {
-  long long int limit = min(SIZE(inst.ingredients), SIZE(exemplar.ingredients));
-  for (long long int i = 0; i < limit; ++i) {
-    const reagent& exemplar_reagent = exemplar.ingredients.at(i);
+void compute_type_ingredient_mappings(const recipe& exemplar, const instruction& inst, map<string, const type_tree*>& mappings, const recipe& caller_recipe, bool* error) {
+  long long int limit = min(SIZE(inst.ingredients), SIZE(exemplar.ingredients));
+  for (long long int i = 0; i < limit; ++i) {
+    const reagent& exemplar_reagent = exemplar.ingredients.at(i);
     reagent ingredient = inst.ingredients.at(i);
     canonize_type(ingredient);
-    if (is_mu_address(exemplar_reagent) && ingredient.name == "0") continue;  // assume it matches
+    if (is_mu_address(exemplar_reagent) && ingredient.name == "0") continue;  // assume it matches
     accumulate_type_ingredients(exemplar_reagent, ingredient, mappings, exemplar, inst, caller_recipe, error);
   }
   limit = min(SIZE(inst.products), SIZE(exemplar.products));
-  for (long long int i = 0; i < limit; ++i) {
-    const reagent& exemplar_reagent = exemplar.products.at(i);
+  for (long long int i = 0; i < limit; ++i) {
+    const reagent& exemplar_reagent = exemplar.products.at(i);
     reagent product = inst.products.at(i);
     canonize_type(product);
     accumulate_type_ingredients(exemplar_reagent, product, mappings, exemplar, inst, caller_recipe, error);
   }
 }
 
-inline long long int min(long long int a, long long int b) {
+inline long long int min(long long int a, long long int b) {
   return (a < b) ? a : b;
 }
 
-void accumulate_type_ingredients(const reagent& exemplar_reagent, reagent& refinement, map<string, const type_tree*>& mappings, const recipe& exemplar, const instruction& call_instruction, const recipe& caller_recipe, bool* error) {
+void accumulate_type_ingredients(const reagent& exemplar_reagent, reagent& refinement, map<string, const type_tree*>& mappings, const recipe& exemplar, const instruction& call_instruction, const recipe& caller_recipe, bool* error) {
   assert(refinement.type);
   accumulate_type_ingredients(exemplar_reagent.type, refinement.type, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error);
 }
 
-void accumulate_type_ingredients(const type_tree* exemplar_type, const type_tree* refinement_type, map<string, const type_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) {
+void accumulate_type_ingredients(const type_tree* exemplar_type, const type_tree* refinement_type, map<string, const type_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 flag an error if exemplar_type contains some *new* type ingredient
-    raise_error << maybe(exemplar.name) << "missing type ingredient in " << exemplar_reagent.original_string << '\n' << end();
+    raise << maybe(exemplar.name) << "missing type ingredient in " << exemplar_reagent.original_string << '\n' << end();
     return;
   }
-  if (is_type_ingredient_name(exemplar_type->name)) {
-    assert(!refinement_type->name.empty());
-    if (!contains_key(mappings, exemplar_type->name)) {
-      trace(9993, "transform") << "adding mapping from " << exemplar_type->name << " to " << to_string(refinement_type) << end();
-      put(mappings, exemplar_type->name, new type_tree(*refinement_type));
+  if (is_type_ingredient_name(exemplar_type->name)) {
+    const type_tree* curr_refinement_type = NULL;  // temporary heap allocation; must always be deleted before it goes out of scope
+    if (refinement_type->left)
+      curr_refinement_type = new type_tree(*refinement_type->left);
+    else if (exemplar_type->right)
+      // splice out refinement_type->right, it'll be used later by the exemplar_type->right
+      curr_refinement_type = new type_tree(refinement_type->name, refinement_type->value, NULL);
+    else
+      curr_refinement_type = new type_tree(*refinement_type);
+    assert(!curr_refinement_type->left);
+    if (!contains_key(mappings, exemplar_type->name)) {
+      trace(9993, "transform") << "adding mapping from " << exemplar_type->name << " to " << to_string(curr_refinement_type) << end();
+      put(mappings, exemplar_type->name, new type_tree(*curr_refinement_type));
     }
-    else {
-      if (!deeply_equal_type_names(get(mappings, exemplar_type->name), refinement_type)) {
-        raise_error << maybe(caller_recipe.name) << "no call found for '" << to_string(call_instruction) << "'\n" << end();
+    else {
+      if (!deeply_equal_type_names(get(mappings, exemplar_type->name), curr_refinement_type)) {
+        raise << maybe(caller_recipe.name) << "no call found for '" << to_string(call_instruction) << "'\n" << end();
         *error = true;
+        delete curr_refinement_type;
         return;
       }
-      if (get(mappings, exemplar_type->name)->name == "literal") {
-        delete get(mappings, exemplar_type->name);
-        put(mappings, exemplar_type->name, new type_tree(*refinement_type));
+      if (get(mappings, exemplar_type->name)->name == "literal") {
+        delete get(mappings, exemplar_type->name);
+        put(mappings, exemplar_type->name, new type_tree(*curr_refinement_type));
       }
     }
+    delete curr_refinement_type;
   }
-  else {
+  else {
     accumulate_type_ingredients(exemplar_type->left, refinement_type->left, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error);
   }
   accumulate_type_ingredients(exemplar_type->right, refinement_type->right, mappings, exemplar, exemplar_reagent, call_instruction, caller_recipe, error);
 }
 
-void replace_type_ingredients(recipe& new_recipe, const map<string, const type_tree*>& mappings) {
+void replace_type_ingredients(recipe& new_recipe, const map<string, const type_tree*>& mappings) {
   // update its header
-  if (mappings.empty()) return;
+  if (mappings.empty()) return;
   trace(9993, "transform") << "replacing in recipe header ingredients" << end();
-  for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i)
+  for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i)
     replace_type_ingredients(new_recipe.ingredients.at(i), mappings, new_recipe);
   trace(9993, "transform") << "replacing in recipe header products" << end();
-  for (long long int i = 0; i < SIZE(new_recipe.products); ++i)
+  for (long long int i = 0; i < SIZE(new_recipe.products); ++i)
     replace_type_ingredients(new_recipe.products.at(i), mappings, new_recipe);
   // update its body
-  for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) {
+  for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) {
     instruction& inst = new_recipe.steps.at(i);
     trace(9993, "transform") << "replacing in instruction '" << to_string(inst) << "'" << end();
-    for (long long int j = 0; j < SIZE(inst.ingredients); ++j)
+    for (long long int j = 0; j < SIZE(inst.ingredients); ++j)
       replace_type_ingredients(inst.ingredients.at(j), mappings, new_recipe);
-    for (long long int j = 0; j < SIZE(inst.products); ++j)
+    for (long long int j = 0; j < SIZE(inst.products); ++j)
       replace_type_ingredients(inst.products.at(j), mappings, new_recipe);
     // special-case for new: replace type ingredient in first ingredient *value*
-    if (inst.name == "new" && inst.ingredients.at(0).type->name != "literal-string") {
+    if (inst.name == "new" && inst.ingredients.at(0).type->name != "literal-string") {
       type_tree* type = parse_type_tree(inst.ingredients.at(0).name);
       replace_type_ingredients(type, mappings);
       inst.ingredients.at(0).name = inspect(type);
-      delete type;
+      delete type;
     }
   }
 }
 
-void replace_type_ingredients(reagent& x, const map<string, const type_tree*>& mappings, const recipe& caller) {
+void replace_type_ingredients(reagent& x, const map<string, const type_tree*>& mappings, const recipe& caller) {
   string before = to_string(x);
   trace(9993, "transform") << "replacing in ingredient " << x.original_string << end();
-  if (!x.type) {
-    raise_error << "specializing " << caller.original_name << ": missing type for " << x.original_string << '\n' << end();
+  if (!x.type) {
+    raise << "specializing " << caller.original_name << ": missing type for " << x.original_string << '\n' << end();
     return;
   }
   replace_type_ingredients(x.type, mappings);
 }
 
-void replace_type_ingredients(type_tree* type, const map<string, const type_tree*>& mappings) {
-  if (!type) return;
-  if (contains_key(Type_ordinal, type->name))  // todo: ugly side effect
+// todo: too complicated and likely incomplete; maybe avoid replacing in place?
+void replace_type_ingredients(type_tree* type, const map<string, const type_tree*>& mappings) {
+  if (!type) return;
+  if (contains_key(Type_ordinal, type->name))  // todo: ugly side effect
     type->value = get(Type_ordinal, type->name);
-  if (is_type_ingredient_name(type->name) && contains_key(mappings, type->name)) {
-    const type_tree* replacement = get(mappings, type->name);
-    trace(9993, "transform") << type->name << " => " << names_to_string(replacement) << end();
-    if (replacement->name == "literal") {
-      type->name = "number";
-      type->value = get(Type_ordinal, "number");
+  if (!is_type_ingredient_name(type->name) || !contains_key(mappings, type->name)) {
+    replace_type_ingredients(type->left, mappings);
+    replace_type_ingredients(type->right, mappings);
+    return;
+  }
+
+  const type_tree* replacement = get(mappings, type->name);
+  trace(9993, "transform") << type->name << " => " << names_to_string(replacement) << end();
+
+  // type is a single type ingredient
+  assert(!type->left);
+  if (!type->right) assert(!replacement->left);
+
+  if (!replacement->right) {
+    if (!replacement->left) {
+      type->name = (replacement->name == "literal") ? "number" : replacement->name;
+      type->value = get(Type_ordinal, type->name);
     }
-    else {
-      type->name = replacement->name;
-      type->value = replacement->value;
+    else {
+      type->name = "";
+      type->value = 0;
+      type->left = new type_tree(*replacement);
     }
-    if (replacement->left) type->left = new type_tree(*replacement->left);
-    if (replacement->right) type->right = new type_tree(*replacement->right);
+    replace_type_ingredients(type->right, mappings);
+  }
+  // replace non-last type?
+  else if (type->right) {
+    type->name = "";
+    type->value = 0;
+    type->left = new type_tree(*replacement);
+    replace_type_ingredients(type->right, mappings);
+  }
+  // replace last type?
+  else {
+    type->name = replacement->name;
+    type->value = get(Type_ordinal, type->name);
+    type->right = new type_tree(*replacement->right);
   }
-  replace_type_ingredients(type->left, mappings);
-  replace_type_ingredients(type->right, mappings);
 }
 
-type_tree* parse_type_tree(const string& s) {
+type_tree* parse_type_tree(const string& s) {
   istringstream in(s);
   in >> std::noskipws;
   return parse_type_tree(in);
@@ -460,31 +487,31 @@ type_tree* parse_type_tree(const string& s(istream& in) {
   skip_whitespace_but_not_newline(in);
-  if (!has_data(in)) return NULL;
-  if (in.peek() == ')') {
+  if (!has_data(in)) return NULL;
+  if (in.peek() == ')') {
     in.get();
     return NULL;
   }
-  if (in.peek() != '(') {
+  if (in.peek() != '(') {
     string type_name = next_word(in);
-    if (!contains_key(Type_ordinal, type_name))
+    if (!contains_key(Type_ordinal, type_name))
       put(Type_ordinal, type_name, Next_type_ordinal++);
-    type_tree* result = new type_tree(type_name, get(Type_ordinal, type_name));
+    type_tree* result = new type_tree(type_name, get(Type_ordinal, type_name));
     return result;
   }
   in.get();  // skip '('
   type_tree* result = NULL;
   type_tree** curr = &result;
-  while (in.peek() != ')') {
+  while (in.peek() != ')') {
     assert(has_data(in));
-    *curr = new type_tree("", 0);
+    *curr = new type_tree("", 0);
     skip_whitespace_but_not_newline(in);
-    if (in.peek() == '(')
+    if (in.peek() == '(')
       (*curr)->left = parse_type_tree(in);
-    else {
+    else {
       (*curr)->name = next_word(in);
-      if (!is_type_ingredient_name((*curr)->name)) {
-        if (!contains_key(Type_ordinal, (*curr)->name))
+      if (!is_type_ingredient_name((*curr)->name)) {
+        if (!contains_key(Type_ordinal, (*curr)->name))
           put(Type_ordinal, (*curr)->name, Next_type_ordinal++);
         (*curr)->value = get(Type_ordinal, (*curr)->name);
       }
@@ -495,67 +522,67 @@ type_tree* parse_type_tree(istream& inreturn result;
 }
 
-string inspect(const type_tree* x) {
+string inspect(const type_tree* x) {
   ostringstream out;
   dump_inspect(x, out);
   return out.str();
 }
 
-void dump_inspect(const type_tree* x, ostream& out) {
-  if (!x->left && !x->right) {
+void dump_inspect(const type_tree* x, ostream& out) {
+  if (!x->left && !x->right) {
     out << x->name;
     return;
   }
   out << '(';
-  for (const type_tree* curr = x; curr; curr = curr->right) {
-    if (curr != x) out << ' ';
-    if (curr->left)
+  for (const type_tree* curr = x; curr; curr = curr->right) {
+    if (curr != x) out << ' ';
+    if (curr->left)
       dump_inspect(curr->left, out);
-    else
+    else
       out << curr->name;
   }
   out << ')';
 }
 
-void ensure_all_concrete_types(/*const*/ recipe& new_recipe, const recipe& exemplar) {
-  for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i)
+void ensure_all_concrete_types(/*const*/ recipe& new_recipe, const recipe& exemplar) {
+  for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i)
     ensure_all_concrete_types(new_recipe.ingredients.at(i), exemplar);
-  for (long long int i = 0; i < SIZE(new_recipe.products); ++i)
+  for (long long int i = 0; i < SIZE(new_recipe.products); ++i)
     ensure_all_concrete_types(new_recipe.products.at(i), exemplar);
-  for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) {
+  for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) {
     instruction& inst = new_recipe.steps.at(i);
-    for (long long int j = 0; j < SIZE(inst.ingredients); ++j)
+    for (long long int j = 0; j < SIZE(inst.ingredients); ++j)
       ensure_all_concrete_types(inst.ingredients.at(j), exemplar);
-    for (long long int j = 0; j < SIZE(inst.products); ++j)
+    for (long long int j = 0; j < SIZE(inst.products); ++j)
       ensure_all_concrete_types(inst.products.at(j), exemplar);
   }
 }
 
-void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) {
-  if (!x.type || contains_type_ingredient_name(x.type)) {
-    raise_error << maybe(exemplar.name) << "failed to map a type to " << x.original_string << '\n' << end();
-    if (!x.type) x.type = new type_tree("", 0);  // just to prevent crashes later
+void ensure_all_concrete_types(/*const*/ reagent& x, const recipe& exemplar) {
+  if (!x.type || contains_type_ingredient_name(x.type)) {
+    raise << maybe(exemplar.name) << "failed to map a type to " << x.original_string << '\n' << end();
+    if (!x.type) x.type = new type_tree("", 0);  // just to prevent crashes later
     return;
   }
-  if (x.type->value == -1) {
-    raise_error << maybe(exemplar.name) << "failed to map a type to the unknown " << x.original_string << '\n' << end();
+  if (x.type->value == -1) {
+    raise << maybe(exemplar.name) << "failed to map a type to the unknown " << x.original_string << '\n' << end();
     return;
   }
 }
 
 :(scenario shape_shifting_recipe_2)
-recipe main [
+def main [
   10:point <- merge 14, 15
   11:point <- foo 10:point
 ]
 # non-matching shape-shifting variant
-recipe foo a:_t, b:_t -> result:number [
+def foo a:_t, b:_t -> result:number [
   local-scope
   load-ingredients
   result <- copy 34
 ]
 # matching shape-shifting variant
-recipe foo a:_t -> result:_t [
+def foo a:_t -> result:_t [
   local-scope
   load-ingredients
   result <- copy a
@@ -564,12 +591,12 @@ recipe foo a:_t -> result:_t [
 +mem: storing 15 in location 12
 
 :(scenario shape_shifting_recipe_nonroot)
-recipe main [
+def main [
   10:foo:point <- merge 14, 15, 16
   20:point/raw <- bar 10:foo:point
 ]
 # shape-shifting recipe with type ingredient following some other type
-recipe bar a:foo:_t -> result:_t [
+def bar a:foo:_t -> result:_t [
   local-scope
   load-ingredients
   result <- get a, x:offset
@@ -581,12 +608,27 @@ container foo:_t [
 +mem: storing 14 in location 20
 +mem: storing 15 in location 21
 
+:(scenario shape_shifting_recipe_nested)
+container c:_a:_b [
+  a:_a
+  b:_b
+]
+def main [
+  s:address:shared:array:character <- new [abc]
+  {x: (c (address shared array character) number)} <- merge s, 34
+  foo x
+]
+def foo x:c:_bar:_baz [
+  local-scope
+  load-ingredients
+]
+
 :(scenario shape_shifting_recipe_type_deduction_ignores_offsets)
-recipe main [
+def main [
   10:foo:point <- merge 14, 15, 16
   20:point/raw <- bar 10:foo:point
 ]
-recipe bar a:foo:_t -> result:_t [
+def bar a:foo:_t -> result:_t [
   local-scope
   load-ingredients
   x:number <- copy 1
@@ -600,16 +642,16 @@ container foo:_t [
 +mem: storing 15 in location 21
 
 :(scenario shape_shifting_recipe_empty)
-recipe main [
+def main [
   foo 1
 ]
 # shape-shifting recipe with no body
-recipe foo a:_t [
+def foo a:_t [
 ]
 # shouldn't crash
 
 :(scenario shape_shifting_recipe_handles_shape_shifting_new_ingredient)
-recipe main [
+def main [
   1:address:shared:foo:point <- bar 3
   11:foo:point <- copy *1:address:shared:foo:point
 ]
@@ -617,26 +659,26 @@ container foo:_t [
   x:_t
   y:number
 ]
-recipe bar x:number -> result:address:shared:foo:_t [
+def bar x:number -> result:address:shared:foo:_t [
   local-scope
   load-ingredients
   # new refers to _t in its ingredient *value*
-  result <- new {(foo _t) : type}
+  result <- new {(foo _t) : type}
 ]
 +mem: storing 0 in location 11
 +mem: storing 0 in location 12
 +mem: storing 0 in location 13
 
 :(scenario shape_shifting_recipe_handles_shape_shifting_new_ingredient_2)
-recipe main [
+def main [
   1:address:shared:foo:point <- bar 3
   11:foo:point <- copy *1:address:shared:foo:point
 ]
-recipe bar x:number -> result:address:shared:foo:_t [
+def bar x:number -> result:address:shared:foo:_t [
   local-scope
   load-ingredients
   # new refers to _t in its ingredient *value*
-  result <- new {(foo _t) : type}
+  result <- new {(foo _t) : type}
 ]
 # container defined after use
 container foo:_t [
@@ -648,14 +690,14 @@ container foo:_t [
 +mem: storing 0 in location 13
 
 :(scenario shape_shifting_recipe_supports_compound_types)
-recipe main [
-  1:address:shared:point <- new point:type
+def main [
+  1:address:shared:point <- new point:type
   2:address:number <- get-address *1:address:shared:point, y:offset
   *2:address:number <- copy 34
   3:address:shared:point <- bar 1:address:shared:point  # specialize _t to address:shared:point
   4:point <- copy *3:address:shared:point
 ]
-recipe bar a:_t -> result:_t [
+def bar a:_t -> result:_t [
   local-scope
   load-ingredients
   result <- copy a
@@ -664,26 +706,26 @@ recipe bar a:_t -> result:_t [
 
 :(scenario shape_shifting_recipe_error)
 % Hide_errors = true;
-recipe main [
+def main [
   a:number <- copy 3
   b:address:shared:number <- foo a
 ]
-recipe foo a:_t -> b:_t [
+def foo a:_t -> b:_t [
   load-ingredients
   b <- copy a
 ]
 +error: main: no call found for 'b:address:shared:number <- foo a'
 
 :(scenario specialize_inside_recipe_without_header)
-recipe main [
+def main [
   foo 3
 ]
-recipe foo [
+def foo [
   local-scope
   x:number <- next-ingredient  # ensure no header
   1:number/raw <- bar x  # call a shape-shifting recipe
 ]
-recipe bar x:_elem -> y:_elem [
+def bar x:_elem -> y:_elem [
   local-scope
   load-ingredients
   y <- add x, 1
@@ -691,12 +733,12 @@ recipe bar x:_elem -> y:_elem [
 +mem: storing 4 in location 1
 
 :(scenario specialize_with_literal)
-recipe main [
+def main [
   local-scope
   # permit literal to map to number
   1:number/raw <- foo 3
 ]
-recipe foo x:_elem -> y:_elem [
+def foo x:_elem -> y:_elem [
   local-scope
   load-ingredients
   y <- add x, 1
@@ -704,12 +746,12 @@ recipe foo x:_elem -> y:_elem [
 +mem: storing 4 in location 1
 
 :(scenario specialize_with_literal_2)
-recipe main [
+def main [
   local-scope
   # permit literal to map to character
   1:character/raw <- foo 3
 ]
-recipe foo x:_elem -> y:_elem [
+def foo x:_elem -> y:_elem [
   local-scope
   load-ingredients
   y <- add x, 1
@@ -717,12 +759,12 @@ recipe foo x:_elem -> y:_elem [
 +mem: storing 4 in location 1
 
 :(scenario specialize_with_literal_3)
-recipe main [
+def main [
   local-scope
   # permit '0' to map to address to shape-shifting type-ingredient
   1:address:shared:character/raw <- foo 0
 ]
-recipe foo x:address:_elem -> y:address:_elem [
+def foo x:address:_elem -> y:address:_elem [
   local-scope
   load-ingredients
   y <- copy x
@@ -732,12 +774,12 @@ $error: 0
 
 :(scenario specialize_with_literal_4)
 % Hide_errors = true;
-recipe main [
+def main [
   local-scope
   # ambiguous call: what's the type of its ingredient?!
   foo 0
 ]
-recipe foo x:address:_elem -> y:address:_elem [
+def foo x:address:_elem -> y:address:_elem [
   local-scope
   load-ingredients
   y <- copy x
@@ -746,10 +788,10 @@ recipe foo x:address:_elem -> y:address:_elem
 +error: foo: failed to map a type to y
 
 :(scenario specialize_with_literal_5)
-recipe main [
+def main [
   foo 3, 4  # recipe mapping two variables to literals
 ]
-recipe foo x:_elem, y:_elem [
+def foo x:_elem, y:_elem [
   local-scope
   load-ingredients
   1:number/raw <- add x, y
@@ -758,22 +800,22 @@ recipe foo x:_elem, y:_elem [
 
 :(scenario multiple_shape_shifting_variants)
 # try to call two different shape-shifting recipes with the same name
-recipe main [
+def main [
   e1:d1:number <- merge 3
   e2:d2:number <- merge 4, 5
   1:number/raw <- foo e1
   2:number/raw <- foo e2
 ]
 # the two shape-shifting definitions
-recipe foo a:d1:_elem -> b:number [
+def foo a:d1:_elem -> b:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
-recipe foo a:d2:_elem -> b:number [
+def foo a:d2:_elem -> b:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 # the shape-shifting containers they use
 container d1:_elem [
@@ -788,21 +830,21 @@ container d2:_elem [
 
 :(scenario multiple_shape_shifting_variants_2)
 # static dispatch between shape-shifting variants, _including pointer lookups_
-recipe main [
+def main [
   e1:d1:number <- merge 3
-  e2:address:shared:d2:number <- new {(d2 number): type}
+  e2:address:shared:d2:number <- new {(d2 number): type}
   1:number/raw <- foo e1
   2:number/raw <- foo *e2  # different from previous scenario
 ]
-recipe foo a:d1:_elem -> b:number [
+def foo a:d1:_elem -> b:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
-recipe foo a:d2:_elem -> b:number [
+def foo a:d2:_elem -> b:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 container d1:_elem [
   x:_elem
@@ -816,15 +858,15 @@ container d2:_elem [
 
 :(scenario missing_type_in_shape_shifting_recipe)
 % Hide_errors = true;
-recipe main [
+def main [
   a:d1:number <- merge 3
   foo a
 ]
-recipe foo a:d1:_elem -> b:number [
+def foo a:d1:_elem -> b:number [
   local-scope
   load-ingredients
   copy e  # no such variable
-  reply 34
+  return 34
 ]
 container d1:_elem [
   x:_elem
@@ -835,15 +877,15 @@ container d1:_elem [
 
 :(scenario missing_type_in_shape_shifting_recipe_2)
 % Hide_errors = true;
-recipe main [
+def main [
   a:d1:number <- merge 3
   foo a
 ]
-recipe foo a:d1:_elem -> b:number [
+def foo a:d1:_elem -> b:number [
   local-scope
   load-ingredients
   get e, x:offset  # unknown variable in a 'get', which does some extra checking
-  reply 34
+  return 34
 ]
 container d1:_elem [
   x:_elem
@@ -854,112 +896,111 @@ container d1:_elem [
 
 :(scenarios transform)
 :(scenario specialize_recursive_shape_shifting_recipe)
-recipe main [
+def main [
   1:number <- copy 34
   2:number <- foo 1:number
 ]
-recipe foo x:_elem -> y:number [
+def foo x:_elem -> y:number [
   local-scope
   load-ingredients
   {
     break
     y:number <- foo x
   }
-  reply y
+  return y
 ]
 +transform: new specialization: foo_2
 # transform terminates
 
 :(scenarios run)
 :(scenario specialize_most_similar_variant)
-recipe main [
-  1:address:shared:number <- new number:type
+def main [
+  1:address:shared:number <- new number:type
   2:number <- foo 1:address:shared:number
 ]
-recipe foo x:_elem -> y:number [
+def foo x:_elem -> y:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
-recipe foo x:address:shared:_elem -> y:number [
+def foo x:address:shared:_elem -> y:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 +mem: storing 35 in location 2
 
 :(scenario specialize_most_similar_variant_2)
 # version with headers padded with lots of unrelated concrete types
-recipe main [
+def main [
   1:number <- copy 23
   2:address:shared:array:number <- copy 0
   3:number <- foo 2:address:shared:array:number, 1:number
 ]
 # variant with concrete type
-recipe foo dummy:address:shared:array:number, x:number -> y:number, dummy:address:shared:array:number [
+def foo dummy:address:shared:array:number, x:number -> y:number, dummy:address:shared:array:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
 # shape-shifting variant
-recipe foo dummy:address:shared:array:number, x:_elem -> y:number, dummy:address:shared:array:number [
+def foo dummy:address:shared:array:number, x:_elem -> y:number, dummy:address:shared:array:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 # prefer the concrete variant
 +mem: storing 34 in location 3
 
 :(scenario specialize_most_similar_variant_3)
-recipe main [
-  1:address:shared:array:character <- new [abc]
+def main [
+  1:address:shared:array:character <- new [abc]
   foo 1:address:shared:array:character
 ]
-recipe foo x:address:shared:array:character [
+def foo x:address:shared:array:character [
   2:number <- copy 34
 ]
-recipe foo x:address:_elem [
+def foo x:address:_elem [
   2:number <- copy 35
 ]
 # make sure the more precise version was used
 +mem: storing 34 in location 2
 
 :(scenario specialize_literal_as_number)
-recipe main [
+def main [
   1:number <- foo 23
 ]
-recipe foo x:_elem -> y:number [
+def foo x:_elem -> y:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
-recipe foo x:character -> y:number [
+def foo x:character -> y:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 +mem: storing 34 in location 1
 
 :(scenario specialize_literal_as_number_2)
 # version calling with literal
-recipe main [
+def main [
   1:number <- foo 0
 ]
 # variant with concrete type
-recipe foo x:number -> y:number [
+def foo x:number -> y:number [
   local-scope
   load-ingredients
-  reply 34
+  return 34
 ]
 # shape-shifting variant
-recipe foo x:address:shared:_elem -> y:number [
+def foo x:address:shared:_elem -> y:number [
   local-scope
   load-ingredients
-  reply 35
+  return 35
 ]
 # prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant
 +mem: storing 34 in location 1
 
- -- cgit 1.4.1-2-gfad0