about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-02-22 15:40:51 -0800
committerKartik K. Agaram <vc@akkartik.com>2016-02-22 15:40:51 -0800
commitf86084013db0fc8f827badea151bfb03bc9ac156 (patch)
tree5fa6bd03c79ae19a5c78813c2d293d189afa61b9
parentbe45c431146bc0740a30c77ebd559840104c1140 (diff)
downloadmu-f86084013db0fc8f827badea151bfb03bc9ac156.tar.gz
2686
-rw-r--r--mu.cc.modified12001
1 files changed, 0 insertions, 12001 deletions
diff --git a/mu.cc.modified b/mu.cc.modified
deleted file mode 100644
index e595855c..00000000
--- a/mu.cc.modified
+++ /dev/null
@@ -1,12001 +0,0 @@
-// Includes
-#include<stdlib.h>
-
-#define SIZE(X) (assert((X).size() < (1LL<<(sizeof(long long int)*8-2))), static_cast<long long int>((X).size()))
-#include<assert.h>
-
-#include<iostream>
-using std::istream;
-using std::ostream;
-using std::iostream;
-using std::cin;
-using std::cout;
-using std::cerr;
-
-#include<cstring>
-#include<string>
-using std::string;
-
-#include<cstdlib>
-
-#define CHECK_TRACE_CONTENTS(...)  check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)
-
-#include<vector>
-using std::vector;
-#include<list>
-using std::list;
-#include<map>
-using std::map;
-#include<set>
-using std::set;
-#include<algorithm>
-
-#include<iostream>
-using std::istream;
-using std::ostream;
-using std::cin;
-using std::cout;
-using std::cerr;
-#include<iomanip>
-
-#include<sstream>
-using std::istringstream;
-using std::ostringstream;
-
-#include<fstream>
-using std::ifstream;
-using std::ofstream;
-
-#include"termbox/termbox.h"
-
-#define unused  __attribute__((unused))
-
-#include<utility>
-using std::pair;
-#include<math.h>
-
-#include<dirent.h>
-#include<sys/stat.h>
-
-
-#include <stack>
-using std::stack;
-
-using std::min;
-using std::max;
-
-using std::abs;
-
-#include<math.h>
-
-// End Includes
-
-// Types
-// Mu types encode how the numbers stored in different parts of memory are
-// interpreted. A location tagged as a 'character' type will interpret the
-// value 97 as the letter 'a', while a different location of type 'number'
-// would not.
-//
-// Unlike most computers today, mu stores types in a single big table, shared
-// by all the mu programs on the computer. This is useful in providing a
-// seamless experience to help understand arbitrary mu programs.
-typedef long long int type_ordinal;
-typedef long long int recipe_ordinal;
-
-typedef void (*test_fn)(void);
-
-struct trace_line {
-  int depth;  // optional field just to help browse traces later
-  string label;
-  string contents;
-  trace_line(string l, string c) :depth(0), label(l), contents(c) {}
-  trace_line(int d, string l, string c) :depth(d), label(l), contents(c) {}
-};
-
-struct end {};
-// Recipes are lists of instructions. To perform or 'run' a recipe, the
-// computer runs its instructions.
-// Each instruction is either of the form:
-//   product1, product2, product3, ... <- operation ingredient1, ingredient2, ingredient3, ...
-// or just a single 'label' starting with a non-alphanumeric character
-//   +label
-// Labels don't do anything, they're just waypoints.
-// Ingredients and products are a single species -- a reagent. Reagents refer
-// either to numbers or to locations in memory along with 'type' tags telling
-// us how to interpret them. They also can contain arbitrary other lists of
-// properties besides types, but we're getting ahead of ourselves.
-struct property {
-  vector<string> values;
-};
-
-// Types can range from a simple type ordinal, to arbitrarily complex trees of
-// type parameters, like (map (address array character) (list number))
-struct type_tree {
-  string name;
-  type_ordinal value;
-  type_tree* left;
-  type_tree* right;
-  ~type_tree();
-  type_tree(const type_tree& old);
-  // simple: type ordinal
-  explicit type_tree(string name, type_ordinal v) :name(name), value(v), left(NULL), right(NULL) {}
-  // intermediate: list of type ordinals
-  type_tree(string name, type_ordinal v, type_tree* r) :name(name), value(v), left(NULL), right(r) {}
-  // advanced: tree containing type ordinals
-  type_tree(type_tree* l, type_tree* r) :value(0), left(l), right(r) {}
-};
-
-struct string_tree {
-  string value;
-  string_tree* left;
-  string_tree* right;
-  ~string_tree();
-  string_tree(const string_tree& old);
-  // simple: flat string
-  explicit string_tree(string v) :value(v), left(NULL), right(NULL) {}
-  // intermediate: list of strings
-  string_tree(string v, string_tree* r) :value(v), left(NULL), right(r) {}
-  // advanced: tree containing strings
-  string_tree(string_tree* l, string_tree* r) :left(l), right(r) {}
-};
-
-struct reagent {
-  string original_string;
-  string name;
-  type_tree* type;
-  vector<pair<string, string_tree*> > properties;
-  double value;
-  bool initialized;
-  reagent(string s);
-  reagent() :type(NULL), value(0), initialized(false) {}
-  ~reagent();
-  void clear();
-  reagent(const reagent& old);
-  reagent& operator=(const reagent& old);
-  void set_value(double v) { value = v; initialized = true; }
-};
-
-struct instruction {
-  bool is_label;
-  string label;  // only if is_label
-  string name;  // only if !is_label
-  string old_name;  // before our automatic rewrite rules
-  string original_string;
-  recipe_ordinal operation;  // get(Recipe_ordinal, name)
-  vector<reagent> ingredients;  // only if !is_label
-  vector<reagent> products;  // only if !is_label
-  mutable bool tangle_done;
-  // End instruction Fields
-  instruction();
-  void clear();
-  bool is_empty();
-};
-
-struct recipe {
-  string name;
-  vector<instruction> steps;
-  long long int transformed_until;
-  bool has_header;
-  vector<reagent> ingredients;
-  vector<reagent> products;
-  map<string, int> ingredient_index;
-
-  string original_name;
-  // End recipe Fields
-  recipe();
-};
-
-// You can construct arbitrary new types. New types are either 'containers'
-// with multiple 'elements' of other types, or 'exclusive containers' containing
-// one of multiple 'variants'. (These are similar to C structs and unions,
-// respectively, though exclusive containers implicitly include a tag element
-// recording which variant they should be interpreted as.)
-//
-// For example, storing bank balance and name for an account might require a
-// container, but if bank accounts may be either for individuals or groups,
-// with different properties for each, that may require an exclusive container
-// whose variants are individual-account and joint-account containers.
-enum kind_of_type {
-  PRIMITIVE,
-  CONTAINER,
-  EXCLUSIVE_CONTAINER
-};
-
-struct type_info {
-  string name;
-  kind_of_type kind;
-  long long int size;  // only if type is not primitive; primitives and addresses have size 1 (except arrays are dynamic)
-  vector<reagent> elements;
-  map<string, type_ordinal> type_ingredient_names;
-
-
-  // End type_info Fields
-  type_info() :kind(PRIMITIVE), size(0) {}
-};
-
-enum primitive_recipes {
-  IDLE = 0,
-  COPY,
-  ADD,
-  SUBTRACT,
-  MULTIPLY,
-  DIVIDE,
-  DIVIDE_WITH_REMAINDER,
-  SHIFT_LEFT,
-  SHIFT_RIGHT,
-  AND_BITS,
-  OR_BITS,
-  XOR_BITS,
-  FLIP_BITS,
-  AND,
-  OR,
-  NOT,
-  JUMP,
-  JUMP_IF,
-  JUMP_UNLESS,
-  EQUAL,
-  GREATER_THAN,
-  LESSER_THAN,
-  GREATER_OR_EQUAL,
-  LESSER_OR_EQUAL,
-  TRACE,
-  STASH,
-  HIDE_ERRORS,
-  SHOW_ERRORS,
-  TRACE_UNTIL,
-  _DUMP_TRACE,
-  _CLEAR_TRACE,
-  _SAVE_TRACE,
-  ASSERT,
-  _PRINT,
-  _EXIT,
-  _SYSTEM,
-  _DUMP_MEMORY,
-  _LOG,
-  GET,
-  GET_ADDRESS,
-  MERGE,
-  _DUMP,
-  _FOO,
-  CREATE_ARRAY,
-  INDEX,
-  INDEX_ADDRESS,
-  LENGTH,
-  MAYBE_CONVERT,
-  NEXT_INGREDIENT,
-  REWIND_INGREDIENTS,
-  INGREDIENT,
-  REPLY,
-  NEW,
-  ALLOCATE,
-  ABANDON,
-  TO_LOCATION_ARRAY,
-  BREAK,
-  BREAK_IF,
-  BREAK_UNLESS,
-  LOOP,
-  LOOP_IF,
-  LOOP_UNLESS,
-  RUN,
-  MEMORY_SHOULD_CONTAIN,
-  TRACE_SHOULD_CONTAIN,
-  TRACE_SHOULD_NOT_CONTAIN,
-  CHECK_TRACE_COUNT_FOR_LABEL,
-  NEXT_INGREDIENT_WITHOUT_TYPECHECKING,
-  CALL,
-  START_RUNNING,
-  ROUTINE_STATE,
-  RESTART,
-  STOP,
-  _DUMP_ROUTINES,
-  LIMIT_TIME,
-  WAIT_FOR_LOCATION,
-  WAIT_FOR_ROUTINE,
-  SWITCH,
-  RANDOM,
-  MAKE_RANDOM_NONDETERMINISTIC,
-  ROUND,
-  HASH,
-  HASH_OLD,
-  OPEN_CONSOLE,
-  CLOSE_CONSOLE,
-  CLEAR_DISPLAY,
-  SYNC_DISPLAY,
-  CLEAR_LINE_ON_DISPLAY,
-  PRINT_CHARACTER_TO_DISPLAY,
-  CURSOR_POSITION_ON_DISPLAY,
-  MOVE_CURSOR_ON_DISPLAY,
-  MOVE_CURSOR_DOWN_ON_DISPLAY,
-  MOVE_CURSOR_UP_ON_DISPLAY,
-  MOVE_CURSOR_RIGHT_ON_DISPLAY,
-  MOVE_CURSOR_LEFT_ON_DISPLAY,
-  DISPLAY_WIDTH,
-  DISPLAY_HEIGHT,
-  HIDE_CURSOR_ON_DISPLAY,
-  SHOW_CURSOR_ON_DISPLAY,
-  HIDE_DISPLAY,
-  SHOW_DISPLAY,
-  WAIT_FOR_SOME_INTERACTION,
-  CHECK_FOR_INTERACTION,
-  INTERACTIONS_LEFT,
-  CLEAR_DISPLAY_FROM,
-  SCREEN_SHOULD_CONTAIN,
-  SCREEN_SHOULD_CONTAIN_IN_COLOR,
-  _DUMP_SCREEN,
-  ASSUME_CONSOLE,
-  REPLACE_IN_CONSOLE,
-  _BROWSE_TRACE,
-  RUN_INTERACTIVE,
-  _START_TRACKING_PRODUCTS,
-  _STOP_TRACKING_PRODUCTS,
-  _MOST_RECENT_PRODUCTS,
-  SAVE_ERRORS_WARNINGS,
-  SAVE_APP_TRACE,
-  _CLEANUP_RUN_INTERACTIVE,
-  RELOAD,
-  RESTORE,
-  SAVE,
-  // End Primitive Recipe Declarations
-  MAX_PRIMITIVE_RECIPES,
-};
-struct no_scientific {
-  double x;
-  explicit no_scientific(double y) :x(y) {}
-};
-
-typedef void (*transform_fn)(recipe_ordinal);
-
-// Book-keeping while running a recipe.
-// Everytime a recipe runs another, we interrupt it and start running the new
-// recipe. When that finishes, we continue this one where we left off.
-// This requires maintaining a 'stack' of interrupted recipes or 'calls'.
-struct call {
-  recipe_ordinal running_recipe;
-  long long int running_step_index;
-  vector<vector<double> > ingredient_atoms;
-  vector<reagent> ingredients;
-  long long int next_ingredient_to_process;
-  long long int default_space;
-  // End call Fields
-  call(recipe_ordinal r) {
-    running_recipe = r;
-    running_step_index = 0;
-    next_ingredient_to_process = 0;
-
-    default_space = 0;
-
-    // End call Constructor
-  }
-  ~call() {
-    // End call Destructor
-  }
-};
-typedef list<call> call_stack;
-
-enum routine_state {
-  RUNNING,
-  COMPLETED,
-  DISCONTINUED,
-  WAITING,
-  // End routine States
-};
-struct routine {
-  call_stack calls;
-  long long int alloc, alloc_max;
-  long long int global_space;
-  enum routine_state state;
-  long long int id;
-  // todo: really should be routine_id, but that's less efficient.
-  long long int parent_index;  // only < 0 if there's no parent_index
-  long long int limit;
-  // only if state == WAITING
-  long long int waiting_on_location;
-  int old_value_of_waiting_location;
-  // only if state == WAITING
-  long long int waiting_on_routine;
-  // End routine Fields
-  routine(recipe_ordinal r);
-  bool completed() const;
-  const vector<instruction>& steps() const;
-};
-
-struct merge_check_point {
-  reagent container;
-  long long int container_element_index;
-  merge_check_point(const reagent& c, long long int i) :container(c), container_element_index(i) {}
-};
-
-struct merge_check_state {
-  stack<merge_check_point> data;
-};
-
-struct scenario {
-  string name;
-  string to_run;
-};
-
-// scan an array of characters in a unicode-aware, bounds-checked manner
-struct raw_string_stream {
-  long long int index;
-  const long long int max;
-  const char* buf;
-
-  raw_string_stream(const string&);
-  uint32_t get();  // unicode codepoint
-  uint32_t peek();  // unicode codepoint
-  bool at_end() const;
-  void skip_whitespace_and_comments();
-};
-
-// End Types
-
-// prototypes are auto-generated in the makefile; define your functions in any order
-#include "function_list"  // by convention, files ending with '_list' are auto-generated
-
-// from http://stackoverflow.com/questions/152643/idiomatic-c-for-reading-from-a-const-map
-template<typename T> typename T::mapped_type& get(T& map, typename T::key_type const& key) {
-  typename T::iterator iter(map.find(key));
-  assert(iter != map.end());
-  return iter->second;
-}
-template<typename T> typename T::mapped_type const& get(const T& map, typename T::key_type const& key) {
-  typename T::const_iterator iter(map.find(key));
-  assert(iter != map.end());
-  return iter->second;
-}
-template<typename T> typename T::mapped_type const& put(T& map, typename T::key_type const& key, typename T::mapped_type const& value) {
-  map[key] = value;
-  return map[key];
-}
-template<typename T> bool contains_key(T& map, typename T::key_type const& key) {
-  return map.find(key) != map.end();
-}
-template<typename T> typename T::mapped_type& get_or_insert(T& map, typename T::key_type const& key) {
-  return map[key];
-}
-bool has_data(istream& in) {
-  return in && !in.eof();
-}
-
-// Globals
-const test_fn Tests[] = {
-};
-
-bool Run_tests = false;
-bool Passed = true;  // set this to false inside any test to indicate failure
-long Num_failures = 0;
-
-#define CHECK(X) \
-  if (!(X)) { \
-    ++Num_failures; \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << '\n'; \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-#define CHECK_EQ(X, Y) \
-  if ((X) != (Y)) { \
-    ++Num_failures; \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): " << #X << " == " << #Y << '\n'; \
-    cerr << "  got " << (X) << '\n';  /* BEWARE: multiple eval */ \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-const int Max_depth = 9999;
-const int Error_depth = 0;  // definitely always print the error that caused death
-const int Warning_depth = 1;
-const int App_depth = 2;  // temporarily where all mu code will trace to
-const int Initial_callstack_depth = 101;
-const int Max_callstack_depth = 9989;
-
-map<recipe_ordinal, recipe> Recipe;
-map<string, recipe_ordinal> Recipe_ordinal;
-recipe_ordinal Next_recipe_ordinal = 1;
-
-// Locations refer to a common 'memory'. Each location can store a number.
-map<long long int, double> Memory;
-map<string, type_ordinal> Type_ordinal;
-map<type_ordinal, type_info> Type;
-type_ordinal Next_type_ordinal = 1;
-const string Ignore(",");  // commas are ignored in mu except within [] strings
-// word boundaries
-const string Terminators("(){}");
-bool Disable_redefine_warnings = false;
-vector<recipe_ordinal> Recently_added_recipes;
-long long int Reserved_for_tests = 1000;
-vector<transform_fn> Transform;
-
-routine* Current_routine = NULL;
-map<string, long long int> Instructions_running;
-map<string, long long int> Locations_read;
-map<string, long long int> Locations_read_by_instruction;
-
-ofstream LOG;
-vector<type_ordinal> Recently_added_types;
-long long int foo = -1;
-long long int Memory_allocated_until = Reserved_for_tests;
-long long int Initial_memory_per_routine = 100000;
-map<long long int, long long int> Free_list;
-map<recipe_ordinal, map<string, long long int> > Name;
-bool Warn_on_missing_default_space = false;
-map<recipe_ordinal, recipe_ordinal> Surrounding_space;
-
-vector<scenario> Scenarios;
-set<string> Scenario_names;
-
-
-long long int Num_core_mu_tests = 0;
-const scenario* Current_scenario = NULL;
-bool Scenario_testing_scenario = false;
-map<string /*label*/, recipe> Before_fragments, After_fragments;
-set<string /*label*/> Fragments_used;
-bool Transform_check_insert_fragments_Ran = false;
-map<string, vector<recipe_ordinal> > Recipe_variants;
-set<string> Literal_type_names;
-list<call> resolve_stack;
-
-// We'll use large type ordinals to mean "the following type of the variable".
-const int START_TYPE_INGREDIENTS = 2000;
-vector<recipe_ordinal> Recently_added_shape_shifting_recipes;
-vector<routine*> Routines;
-long long int Current_routine_index = 0;
-long long int Scheduling_interval = 500;
-long long int Next_routine_id = 1;
-long long int Display_row = 0, Display_column = 0;
-bool Autodisplay = true;
-
-// Scenarios may not define default-space, so they should fit within the
-// initial area of memory reserved for tests. We'll put the predefined
-// variables available to them at the end of that region.
-const long long int Max_variables_in_scenarios = Reserved_for_tests-100;
-long long int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
-// Scenario Globals.
-const long long int SCREEN = Next_predefined_global_for_scenarios++;
-const long long int CONSOLE = Next_predefined_global_for_scenarios++;
-// End Scenario Globals.
-map<string, long long int> Key;
-set<long long int> Visible;
-long long int Top_of_screen = 0;
-long long int Last_printed_row = 0;
-map<int, long long int> Trace_index;  // screen row -> trace index
-
-bool Track_most_recent_products = false;
-string Most_recent_products;
-// End Globals
-
-bool Hide_errors = false;
-bool Hide_warnings = false;
-struct trace_stream {
-  vector<trace_line> past_lines;
-  // accumulator for current line
-  ostringstream* curr_stream;
-  string curr_label;
-  int curr_depth;
-  int callstack_depth;
-  int collect_depth;
-  ofstream null_stream;  // never opens a file, so writes silently fail
-  trace_stream() :curr_stream(NULL), curr_depth(Max_depth), callstack_depth(0), collect_depth(Max_depth) {}
-  ~trace_stream() { if (curr_stream) delete curr_stream; }
-
-  ostream& stream(string label) {
-    return stream(Max_depth, label);
-  }
-
-  ostream& stream(int depth, string label) {
-    if (depth > collect_depth) return null_stream;
-    curr_stream = new ostringstream;
-    curr_label = label;
-    curr_depth = depth;
-    return *curr_stream;
-  }
-
-  // be sure to call this before messing with curr_stream or curr_label
-  void newline() {
-    if (!curr_stream) return;
-    string curr_contents = curr_stream->str();
-    if (curr_contents.empty()) return;
-    past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents));  // preserve indent in contents
-    if (!Hide_errors && curr_label == "error")
-      cerr << curr_label << ": " << curr_contents << '\n';
-    else if (!Hide_warnings && curr_label == "warn")
-      cerr << curr_label << ": " << curr_contents << '\n';
-    delete curr_stream;
-    curr_stream = NULL;
-    curr_label.clear();
-    curr_depth = Max_depth;
-  }
-
-  // Useful for debugging.
-  string readable_contents(string label) {  // missing label = everything
-    ostringstream output;
-    label = trim(label);
-    for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p)
-      if (label.empty() || label == p->label) {
-        output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n';
-      }
-    return output.str();
-  }
-};
-
-
-
-trace_stream* Trace_stream = NULL;
-
-// Top-level helper. IMPORTANT: can't nest.
-#define trace(...)  !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__)
-#define raise  (!Trace_stream ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Warning_depth, "warn"))
-#define raise_error  (!Trace_stream ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error"))
-
-ostream& operator<<(ostream& os, unused end) {
-  if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
-    Current_routine->state = COMPLETED;
-  }
-
-
-  if (Trace_stream) Trace_stream->newline();
-  return os;
-}
-
-#define CLEAR_TRACE  delete Trace_stream, Trace_stream = new trace_stream;
-
-#define DUMP(label)  if (Trace_stream) cerr << Trace_stream->readable_contents(label);
-
-// All scenarios save their traces in the repo, just like code. This gives
-// future readers more meat when they try to make sense of a new project.
-static string Trace_dir = ".traces/";
-string Trace_file;
-
-// Trace_stream is a resource, lease_tracer uses RAII to manage it.
-struct lease_tracer {
-  lease_tracer() { Trace_stream = new trace_stream; }
-  ~lease_tracer() {
-    if (!Trace_stream) return;  // in case tests close Trace_stream
-    if (!Trace_file.empty()) {
-      ofstream fout((Trace_dir+Trace_file).c_str());
-      fout << Trace_stream->readable_contents("");
-      fout.close();
-    }
-    delete Trace_stream, Trace_stream = NULL, Trace_file = "";
-  }
-};
-
-#define START_TRACING_UNTIL_END_OF_SCOPE  lease_tracer leased_tracer;
-bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) {
-  if (!Trace_stream) return false;
-  vector<string> expected_lines = split(expected, "");
-  long long int curr_expected_line = 0;
-  while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty())
-    ++curr_expected_line;
-  if (curr_expected_line == SIZE(expected_lines)) return true;
-  string label, contents;
-  split_label_contents(expected_lines.at(curr_expected_line), &label, &contents);
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (label != p->label)
-      continue;
-
-    if (contents != trim(p->contents))
-      continue;
-
-    ++curr_expected_line;
-    while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty())
-      ++curr_expected_line;
-    if (curr_expected_line == SIZE(expected_lines)) return true;
-    split_label_contents(expected_lines.at(curr_expected_line), &label, &contents);
-  }
-
-  ++Num_failures;
-  cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n";
-  DUMP(label);
-  Passed = false;
-  return false;
-}
-
-void split_label_contents(const string& s, string* label, string* contents) {
-  static const string delim(": ");
-  size_t pos = s.find(delim);
-  if (pos == string::npos) {
-    *label = "";
-    *contents = trim(s);
-  }
-  else {
-    *label = trim(s.substr(0, pos));
-    *contents = trim(s.substr(pos+SIZE(delim)));
-  }
-}
-
-
-
-int trace_count(string label) {
-  return trace_count(label, "");
-}
-
-int trace_count(string label, string line) {
-  long result = 0;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (label == p->label) {
-      if (line == "" || trim(line) == trim(p->contents))
-        ++result;
-    }
-  }
-  return result;
-}
-
-#define CHECK_TRACE_CONTAINS_ERROR()  CHECK(trace_count("error") > 0)
-#define CHECK_TRACE_DOESNT_CONTAIN_ERROR() \
-  if (trace_count("error") > 0) { \
-    ++Num_failures; \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
-    DUMP("error"); \
-    Passed = false; \
-    return; \
-  }
-
-#define CHECK_TRACE_COUNT(label, count) \
-  if (trace_count(label) != (count)) { \
-    ++Num_failures; \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \
-    cerr << "  got " << trace_count(label) << '\n';  /* multiple eval */ \
-    DUMP(label); \
-    Passed = false; \
-    return;  /* Currently we stop at the very first failure. */ \
-  }
-
-bool trace_doesnt_contain(string label, string line) {
-  return trace_count(label, line) == 0;
-}
-
-bool trace_doesnt_contain(string expected) {
-  vector<string> tmp = split_first(expected, ": ");
-  return trace_doesnt_contain(tmp.at(0), tmp.at(1));
-}
-
-#define CHECK_TRACE_DOESNT_CONTAIN(...)  CHECK(trace_doesnt_contain(__VA_ARGS__))
-
-
-
-vector<string> split(string s, string delim) {
-  vector<string> result;
-  size_t begin=0, end=s.find(delim);
-  while (true) {
-    if (end == string::npos) {
-      result.push_back(string(s, begin, string::npos));
-      break;
-    }
-    result.push_back(string(s, begin, end-begin));
-    begin = end+SIZE(delim);
-    end = s.find(delim, begin);
-  }
-  return result;
-}
-
-vector<string> split_first(string s, string delim) {
-  vector<string> result;
-  size_t end=s.find(delim);
-  result.push_back(string(s, 0, end));
-  if (end != string::npos)
-    result.push_back(string(s, end+SIZE(delim), string::npos));
-  return result;
-}
-
-string trim(const string& s) {
-  string::const_iterator first = s.begin();
-  while (first != s.end() && isspace(*first))
-    ++first;
-  if (first == s.end()) return "";
-
-  string::const_iterator last = --s.end();
-  while (last != s.begin() && isspace(*last))
-    --last;
-  ++last;
-  return string(first, last);
-}
-
-trace_stream* Save_trace_stream = NULL;
-string Save_trace_file;
-vector<recipe_ordinal> Save_recently_added_recipes;
-vector<recipe_ordinal> Save_recently_added_shape_shifting_recipes;
-// End Tracing  // hack to ensure most code in this layer comes before anything else
-
-int main() {
-  // Begin Transforms
-    // Begin Instruction Inserting/Deleting Transforms
-    Transform.push_back(insert_fragments);  // NOT idempotent
-    Transform.push_back(check_insert_fragments);  // idempotent
-    Transform.push_back(rewrite_stashes_to_text);
-    // End Instruction Inserting/Deleting Transforms
-    // Begin Instruction Modifying Transforms
-    Transform.push_back(check_header_ingredients);  // idempotent
-    Transform.push_back(fill_in_reply_ingredients);  // idempotent
-    Transform.push_back(check_or_set_types_by_name);  // idempotent
-    // Begin Type Modifying Transforms
-    Transform.push_back(deduce_types_from_header);  // idempotent
-    Transform.push_back(check_or_set_invalid_types);  // idempotent
-    // End Type Modifying Transforms
-    Transform.push_back(collect_surrounding_spaces);  // idempotent
-    Transform.push_back(transform_names);  // idempotent
-    Transform.push_back(resolve_ambiguous_calls);  // idempotent
-    Transform.push_back(update_instruction_operations);  // idempotent
-    Transform.push_back(transform_braces);  // idempotent
-    Transform.push_back(transform_labels);  // idempotent
-    // End Instruction Modifying Transforms
-  Transform.push_back(check_immutable_ingredients);  // idempotent
-  // End Transforms
-  // Begin Checks
-  Transform.push_back(check_instruction);  // idempotent
-  Transform.push_back(check_indirect_calls_against_header);  // idempotent
-  Transform.push_back(check_calls_against_header);  // idempotent
-  Transform.push_back(transform_new_to_allocate);  // idempotent
-  Transform.push_back(check_merge_calls);
-  Transform.push_back(check_types_of_reply_instructions);
-  Transform.push_back(check_default_space);  // idempotent
-  Transform.push_back(check_reply_instructions_against_header);  // idempotent
-  // End Checks
-
-  setup_types();
-  setup_recipes();
-  assert(MAX_PRIMITIVE_RECIPES < 200);  // level 0 is primitives; until 199
-  Next_recipe_ordinal = 200;
-  put(Recipe_ordinal, "main", Next_recipe_ordinal++);
-  put(Recipe_variants, "main", vector<recipe_ordinal>());  // since we manually added main to Recipe_ordinal
-  Literal_type_names.insert("number");
-  Literal_type_names.insert("character");
-
-  load_permanently("core.mu");
-  for (long long int t = 0; t < SIZE(Transform); ++t) {
-    for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-      recipe& r = p->second;
-      if (r.steps.empty()) continue;
-      if (r.transformed_until != t-1) continue;
-      if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue;
-      (*Transform.at(t))(/*recipe_ordinal*/p->first);
-      r.transformed_until = t;
-    }
-  }
-  load(
-    "recipe new-editor [\n"
-    "  local-scope\n"
-    "  init:address:shared:list:character <- push 97/a, 0\n"
-    "]\n");
-  cerr << "AAA " << contains_key(Type_ordinal, "_elem") << '\n';
-  for (long long int t = 0; t < SIZE(Transform); ++t) {
-    cerr << "BBB " << t << ' ' << contains_key(Type_ordinal, "_elem") << '\n';
-    for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-      recipe& r = p->second;
-      if (r.steps.empty()) continue;
-      if (r.transformed_until != t-1) continue;
-      cerr << "CCC " << t << ' ' << p->second.name << ' ' << contains_key(Type_ordinal, "_elem") << '\n';
-      if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue;
-      (*Transform.at(t))(/*recipe_ordinal*/p->first);
-      r.transformed_until = t;
-    }
-  }
-  parse_int_reagents();  // do this after all other transforms have run
-  check_container_field_types();
-  test_replace_type_ingredients_entire();
-  return 0;
-}
-
-bool is_integer(const string& s) {
-  return s.find_first_not_of("0123456789-") == string::npos
-      && s.find('-', 1) == string::npos
-      && s.find_first_of("0123456789") != string::npos;
-}
-
-long long int to_integer(string n) {
-  char* end = NULL;
-  // safe because string.c_str() is guaranteed to be null-terminated
-  long long int result = strtoll(n.c_str(), &end, /*any base*/0);
-  if (*end != '\0') cerr << "tried to convert " << n << " to number\n";
-  assert(*end == '\0');
-  return result;
-}
-
-void test_is_integer() {
-  CHECK(is_integer("1234"));
-  CHECK(is_integer("-1"));
-  CHECK(!is_integer("234.0"));
-  CHECK(is_integer("-567"));
-  CHECK(!is_integer("89-0"));
-  CHECK(!is_integer("-"));
-  CHECK(!is_integer("1e3"));  // not supported
-}
-
-
-void test_trace_check_compares() {
-  trace("test layer") << "foo" << end();
-  CHECK_TRACE_CONTENTS("test layer: foo");
-}
-
-void test_trace_check_ignores_other_layers() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 2") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-  CHECK_TRACE_DOESNT_CONTAIN("test layer 2: foo");
-}
-
-void test_trace_check_ignores_leading_whitespace() {
-  trace("test layer 1") << " foo" << end();
-  CHECK(trace_count("test layer 1", /*too little whitespace*/"foo") == 1);
-  CHECK(trace_count("test layer 1", /*too much whitespace*/"  foo") == 1);
-}
-
-void test_trace_check_ignores_other_lines() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_check_ignores_other_lines2() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: bar");
-}
-
-void test_trace_ignores_trailing_whitespace() {
-  trace("test layer 1") << "foo\n" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_ignores_trailing_whitespace2() {
-  trace("test layer 1") << "foo " << end();
-  CHECK_TRACE_CONTENTS("test layer 1: foo");
-}
-
-void test_trace_orders_across_layers() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 2") << "bar" << end();
-  trace("test layer 1") << "qux" << end();
-  CHECK_TRACE_CONTENTS("test layer 1: footest layer 2: bartest layer 1: qux");
-}
-
-void test_trace_supports_count() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "foo" << end();
-  CHECK_EQ(trace_count("test layer 1", "foo"), 2);
-}
-
-void test_trace_supports_count2() {
-  trace("test layer 1") << "foo" << end();
-  trace("test layer 1") << "bar" << end();
-  CHECK_EQ(trace_count("test layer 1"), 2);
-}
-
-void test_trace_count_ignores_trailing_whitespace() {
-  trace("test layer 1") << "foo\n" << end();
-  CHECK(trace_count("test layer 1", "foo") == 1);
-}
-
-// pending: DUMP tests
-// pending: readable_contents() adds newline if necessary.
-// pending: raise also prints to stderr.
-// pending: raise doesn't print to stderr if Hide_errors is set.
-// pending: raise doesn't have to be saved if Hide_errors is set, just printed.
-// pending: raise prints to stderr if Trace_stream is NULL.
-// pending: raise prints to stderr if Trace_stream is NULL even if Hide_errors is set.
-// pending: raise << ... die() doesn't die if Hide_errors is set.
-
-
-
-// can't check trace because trace methods call 'split'
-
-void test_split_returns_at_least_one_elem() {
-  vector<string> result = split("", ",");
-  CHECK_EQ(result.size(), 1);
-  CHECK_EQ(result.at(0), "");
-}
-
-void test_split_returns_entire_input_when_no_delim() {
-  vector<string> result = split("abc", ",");
-  CHECK_EQ(result.size(), 1);
-  CHECK_EQ(result.at(0), "abc");
-}
-
-void test_split_works() {
-  vector<string> result = split("abc,def", ",");
-  CHECK_EQ(result.size(), 2);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-}
-
-void test_split_works2() {
-  vector<string> result = split("abc,def,ghi", ",");
-  CHECK_EQ(result.size(), 3);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-  CHECK_EQ(result.at(2), "ghi");
-}
-
-void test_split_handles_multichar_delim() {
-  vector<string> result = split("abc,,def,,ghi", ",,");
-  CHECK_EQ(result.size(), 3);
-  CHECK_EQ(result.at(0), "abc");
-  CHECK_EQ(result.at(1), "def");
-  CHECK_EQ(result.at(2), "ghi");
-}
-
-void test_trim() {
-  CHECK_EQ(trim(""), "");
-  CHECK_EQ(trim(" "), "");
-  CHECK_EQ(trim("  "), "");
-  CHECK_EQ(trim("a"), "a");
-  CHECK_EQ(trim(" a"), "a");
-  CHECK_EQ(trim("  a"), "a");
-  CHECK_EQ(trim("  ab"), "ab");
-  CHECK_EQ(trim("a "), "a");
-  CHECK_EQ(trim("a  "), "a");
-  CHECK_EQ(trim("ab  "), "ab");
-  CHECK_EQ(trim(" a "), "a");
-  CHECK_EQ(trim("  a  "), "a");
-  CHECK_EQ(trim("  ab  "), "ab");
-}
-
-void setup_types() {
-  Type.clear();  Type_ordinal.clear();
-  put(Type_ordinal, "literal", 0);
-  Next_type_ordinal = 1;
-  // Mu Types Initialization
-  type_ordinal number = put(Type_ordinal, "number", Next_type_ordinal++);
-  put(Type_ordinal, "location", get(Type_ordinal, "number"));  // wildcard type: either a pointer or a scalar
-  get_or_insert(Type, number).name = "number";
-  type_ordinal address = put(Type_ordinal, "address", Next_type_ordinal++);
-  get_or_insert(Type, address).name = "address";
-  type_ordinal boolean = put(Type_ordinal, "boolean", Next_type_ordinal++);
-  get_or_insert(Type, boolean).name = "boolean";
-  type_ordinal character = put(Type_ordinal, "character", Next_type_ordinal++);
-  get_or_insert(Type, character).name = "character";
-  // Array types are a special modifier to any other type. For example,
-  // array:number or array:address:boolean.
-  type_ordinal array = put(Type_ordinal, "array", Next_type_ordinal++);
-  get_or_insert(Type, array).name = "array";
-  put(Type_ordinal, "literal-string", 0);
-
-  put(Type_ordinal, "offset", 0);
-
-  type_ordinal point = put(Type_ordinal, "point", Next_type_ordinal++);
-  get_or_insert(Type, point).size = 2;
-  get(Type, point).kind = CONTAINER;
-  get(Type, point).name = "point";
-  get(Type, point).elements.push_back(reagent("x:number"));
-  get(Type, point).elements.push_back(reagent("y:number"));
-
-
-  // A more complex container, containing another container as one of its
-  // elements.
-  type_ordinal point_number = put(Type_ordinal, "point-number", Next_type_ordinal++);
-  get_or_insert(Type, point_number).size = 2;
-  get(Type, point_number).kind = CONTAINER;
-  get(Type, point_number).name = "point-number";
-  get(Type, point_number).elements.push_back(reagent("xy:point"));
-  get(Type, point_number).elements.push_back(reagent("z:number"));
-
-  {
-  type_ordinal tmp = put(Type_ordinal, "number-or-point", Next_type_ordinal++);
-  get_or_insert(Type, tmp).size = 2;
-  get(Type, tmp).kind = EXCLUSIVE_CONTAINER;
-  get(Type, tmp).name = "number-or-point";
-  get(Type, tmp).elements.push_back(reagent("i:number"));
-  get(Type, tmp).elements.push_back(reagent("p:point"));
-  }
-
-  put(Type_ordinal, "variant", 0);
-
-  type_ordinal shared = put(Type_ordinal, "shared", Next_type_ordinal++);
-  get_or_insert(Type, shared).name = "shared";
-  put(Type_ordinal, "type", 0);
-
-  put(Type_ordinal, "label", 0);
-
-  put(Type_ordinal, "recipe-literal", 0);
-  // 'recipe' variables can store recipe-literal
-  type_ordinal recipe = put(Type_ordinal, "recipe", Next_type_ordinal++);
-  get_or_insert(Type, recipe).name = "recipe";
-
-  // End Mu Types Initialization
-}
-void teardown_types() {
-  for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) {
-    for (long long int i = 0; i < SIZE(p->second.elements); ++i)
-      p->second.elements.clear();
-  }
-  Type_ordinal.clear();
-}
-void setup_recipes() {
-  Recipe.clear();  Recipe_ordinal.clear();
-  put(Recipe_ordinal, "idle", IDLE);
-  // Primitive Recipe Numbers
-  put(Recipe_ordinal, "copy", COPY);
-  put(Recipe_ordinal, "add", ADD);
-  put(Recipe_ordinal, "subtract", SUBTRACT);
-  put(Recipe_ordinal, "multiply", MULTIPLY);
-  put(Recipe_ordinal, "divide", DIVIDE);
-  put(Recipe_ordinal, "divide-with-remainder", DIVIDE_WITH_REMAINDER);
-  put(Recipe_ordinal, "shift-left", SHIFT_LEFT);
-  put(Recipe_ordinal, "shift-right", SHIFT_RIGHT);
-  put(Recipe_ordinal, "and-bits", AND_BITS);
-  put(Recipe_ordinal, "or-bits", OR_BITS);
-  put(Recipe_ordinal, "xor-bits", XOR_BITS);
-  put(Recipe_ordinal, "flip-bits", FLIP_BITS);
-  put(Recipe_ordinal, "and", AND);
-  put(Recipe_ordinal, "or", OR);
-  put(Recipe_ordinal, "not", NOT);
-  put(Recipe_ordinal, "jump", JUMP);
-  put(Recipe_ordinal, "jump-if", JUMP_IF);
-  put(Recipe_ordinal, "jump-unless", JUMP_UNLESS);
-  put(Recipe_ordinal, "equal", EQUAL);
-  put(Recipe_ordinal, "greater-than", GREATER_THAN);
-  put(Recipe_ordinal, "lesser-than", LESSER_THAN);
-  put(Recipe_ordinal, "greater-or-equal", GREATER_OR_EQUAL);
-  put(Recipe_ordinal, "lesser-or-equal", LESSER_OR_EQUAL);
-  put(Recipe_ordinal, "trace", TRACE);
-  put(Recipe_ordinal, "stash", STASH);
-  put(Recipe_ordinal, "hide-errors", HIDE_ERRORS);
-  put(Recipe_ordinal, "show-errors", SHOW_ERRORS);
-  put(Recipe_ordinal, "trace-until", TRACE_UNTIL);
-  put(Recipe_ordinal, "$dump-trace", _DUMP_TRACE);
-  put(Recipe_ordinal, "$clear-trace", _CLEAR_TRACE);
-  put(Recipe_ordinal, "$save-trace", _SAVE_TRACE);
-  put(Recipe_ordinal, "assert", ASSERT);
-  put(Recipe_ordinal, "$print", _PRINT);
-  put(Recipe_ordinal, "$exit", _EXIT);
-  put(Recipe_ordinal, "$system", _SYSTEM);
-  put(Recipe_ordinal, "$dump-memory", _DUMP_MEMORY);
-  put(Recipe_ordinal, "$log", _LOG);
-  put(Recipe_ordinal, "get", GET);
-  put(Recipe_ordinal, "get-address", GET_ADDRESS);
-  put(Recipe_ordinal, "merge", MERGE);
-  put(Recipe_ordinal, "$dump", _DUMP);
-  put(Recipe_ordinal, "$foo", _FOO);
-  put(Recipe_ordinal, "create-array", CREATE_ARRAY);
-  put(Recipe_ordinal, "index", INDEX);
-  put(Recipe_ordinal, "index-address", INDEX_ADDRESS);
-  put(Recipe_ordinal, "length", LENGTH);
-  put(Recipe_ordinal, "maybe-convert", MAYBE_CONVERT);
-  put(Recipe_ordinal, "next-ingredient", NEXT_INGREDIENT);
-  put(Recipe_ordinal, "rewind-ingredients", REWIND_INGREDIENTS);
-  put(Recipe_ordinal, "ingredient", INGREDIENT);
-  put(Recipe_ordinal, "reply", REPLY);
-  put(Recipe_ordinal, "new", NEW);
-  put(Recipe_ordinal, "allocate", ALLOCATE);
-  put(Recipe_ordinal, "abandon", ABANDON);
-  put(Recipe_ordinal, "to-location-array", TO_LOCATION_ARRAY);
-  put(Recipe_ordinal, "break", BREAK);
-  put(Recipe_ordinal, "break-if", BREAK_IF);
-  put(Recipe_ordinal, "break-unless", BREAK_UNLESS);
-  put(Recipe_ordinal, "loop", LOOP);
-  put(Recipe_ordinal, "loop-if", LOOP_IF);
-  put(Recipe_ordinal, "loop-unless", LOOP_UNLESS);
-  put(Recipe_ordinal, "run", RUN);
-  put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN);
-  put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN);
-  put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN);
-  put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL);
-  put(Recipe_ordinal, "next-ingredient-without-typechecking", NEXT_INGREDIENT_WITHOUT_TYPECHECKING);
-  put(Recipe_ordinal, "call", CALL);
-  put(Recipe_ordinal, "start-running", START_RUNNING);
-  put(Recipe_ordinal, "routine-state", ROUTINE_STATE);
-  put(Recipe_ordinal, "restart", RESTART);
-  put(Recipe_ordinal, "stop", STOP);
-  put(Recipe_ordinal, "$dump-routines", _DUMP_ROUTINES);
-  put(Recipe_ordinal, "limit-time", LIMIT_TIME);
-  put(Recipe_ordinal, "wait-for-location", WAIT_FOR_LOCATION);
-  put(Recipe_ordinal, "wait-for-routine", WAIT_FOR_ROUTINE);
-  put(Recipe_ordinal, "switch", SWITCH);
-  put(Recipe_ordinal, "random", RANDOM);
-  put(Recipe_ordinal, "make-random-nondeterministic", MAKE_RANDOM_NONDETERMINISTIC);
-  put(Recipe_ordinal, "round", ROUND);
-  put(Recipe_ordinal, "hash", HASH);
-  put(Recipe_ordinal, "hash_old", HASH_OLD);
-  put(Recipe_ordinal, "open-console", OPEN_CONSOLE);
-  put(Recipe_ordinal, "close-console", CLOSE_CONSOLE);
-  put(Recipe_ordinal, "clear-display", CLEAR_DISPLAY);
-  put(Recipe_ordinal, "sync-display", SYNC_DISPLAY);
-  put(Recipe_ordinal, "clear-line-on-display", CLEAR_LINE_ON_DISPLAY);
-  put(Recipe_ordinal, "print-character-to-display", PRINT_CHARACTER_TO_DISPLAY);
-  put(Recipe_ordinal, "cursor-position-on-display", CURSOR_POSITION_ON_DISPLAY);
-  put(Recipe_ordinal, "move-cursor-on-display", MOVE_CURSOR_ON_DISPLAY);
-  put(Recipe_ordinal, "move-cursor-down-on-display", MOVE_CURSOR_DOWN_ON_DISPLAY);
-  put(Recipe_ordinal, "move-cursor-up-on-display", MOVE_CURSOR_UP_ON_DISPLAY);
-  put(Recipe_ordinal, "move-cursor-right-on-display", MOVE_CURSOR_RIGHT_ON_DISPLAY);
-  put(Recipe_ordinal, "move-cursor-left-on-display", MOVE_CURSOR_LEFT_ON_DISPLAY);
-  put(Recipe_ordinal, "display-width", DISPLAY_WIDTH);
-  put(Recipe_ordinal, "display-height", DISPLAY_HEIGHT);
-  put(Recipe_ordinal, "hide-cursor-on-display", HIDE_CURSOR_ON_DISPLAY);
-  put(Recipe_ordinal, "show-cursor-on-display", SHOW_CURSOR_ON_DISPLAY);
-  put(Recipe_ordinal, "hide-display", HIDE_DISPLAY);
-  put(Recipe_ordinal, "show-display", SHOW_DISPLAY);
-  put(Recipe_ordinal, "wait-for-some-interaction", WAIT_FOR_SOME_INTERACTION);
-  put(Recipe_ordinal, "check-for-interaction", CHECK_FOR_INTERACTION);
-  put(Recipe_ordinal, "interactions-left?", INTERACTIONS_LEFT);
-  put(Recipe_ordinal, "clear-display-from", CLEAR_DISPLAY_FROM);
-  put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN);
-  put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR);
-  put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN);
-  put(Recipe_ordinal, "assume-console", ASSUME_CONSOLE);
-  put(Recipe_ordinal, "replace-in-console", REPLACE_IN_CONSOLE);
-  put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
-  put(Recipe_ordinal, "run-interactive", RUN_INTERACTIVE);
-  put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
-  put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
-  put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
-  put(Recipe_ordinal, "save-errors-warnings", SAVE_ERRORS_WARNINGS);
-  put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
-  put(Recipe_ordinal, "$cleanup-run-interactive", _CLEANUP_RUN_INTERACTIVE);
-  put(Recipe_ordinal, "reload", RELOAD);
-  put(Recipe_ordinal, "restore", RESTORE);
-  put(Recipe_ordinal, "save", SAVE);
-  // End Primitive Recipe Numbers
-}
-recipe::recipe() {
-  transformed_until = -1;
-
-  has_header = false;
-
-  // End recipe Constructor
-}
-
-instruction::instruction() :is_label(false), operation(IDLE) {
-  tangle_done = false;
-
-  // End instruction Constructor
-}
-void instruction::clear() { is_label=false; label.clear(); name.clear(); old_name.clear(); operation=IDLE; ingredients.clear(); products.clear(); original_string.clear(); }
-bool instruction::is_empty() { return !is_label && name.empty(); }
-
-// Reagents have the form <name>:<type>:<type>:.../<property>/<property>/...
-reagent::reagent(string s) :original_string(s), type(NULL), value(0), initialized(false) {
-  // Parsing reagent(string s)
-  if (s.at(0) == '{') {
-    assert(properties.empty());
-    istringstream in(s);
-    in >> std::noskipws;
-    in.get();  // skip '{'
-    name = slurp_key(in);
-    if (name.empty()) {
-      raise_error << "invalid reagent " << s << " without a name\n";
-      return;
-    }
-    if (name == "}") {
-      raise_error << "invalid empty reagent " << s << '\n';
-      return;
-    }
-    {
-      string_tree* value = new string_tree(next_word(in));
-      value = parse_string_tree(value);
-
-      // End Parsing Reagent Type Property(value)
-      type = new_type_tree(value);
-      delete value;
-    }
-    while (has_data(in)) {
-      string key = slurp_key(in);
-      if (key.empty()) continue;
-      if (key == "}") continue;
-      string_tree* value = new string_tree(next_word(in));
-      value = parse_string_tree(value);
-      // End Parsing Reagent Property(value)
-      properties.push_back(pair<string, string_tree*>(key, value));
-    }
-    return;
-  }
-
-  if (is_noninteger(s)) {
-    name = s;
-    type = new type_tree("literal-fractional-number", 0);
-    set_value(to_double(s));
-    return;
-  }
-
-  if (s.at(0) == '[') {
-    assert(*s.rbegin() == ']');
-    // delete [] delimiters
-    s.erase(0, 1);
-    strip_last(s);
-    name = s;
-    type = new type_tree("literal-string", 0);
-    return;
-  }
-
-
-  istringstream in(s);
-  in >> std::noskipws;
-  // name and type
-  istringstream first_row(slurp_until(in, '/'));
-  first_row >> std::noskipws;
-  name = slurp_until(first_row, ':');
-  string_tree* type_names = parse_property_list(first_row);
-  type = new_type_tree(type_names);
-  delete type_names;
-  // special cases
-  if (is_integer(name) && type == NULL)
-    type = new type_tree("literal", get(Type_ordinal, "literal"));
-  if (name == "_" && type == NULL)
-    type = new type_tree("literal", get(Type_ordinal, "literal"));
-  // other properties
-  while (has_data(in)) {
-    istringstream row(slurp_until(in, '/'));
-    row >> std::noskipws;
-    string key = slurp_until(row, ':');
-    string_tree* value = parse_property_list(row);
-    properties.push_back(pair<string, string_tree*>(key, value));
-  }
-  {
-    while (!name.empty() && name.at(0) == '*') {
-      name.erase(0, 1);
-      properties.push_back(pair<string, string_tree*>("lookup", NULL));
-    }
-    if (name.empty())
-      raise_error << "illegal name " << original_string << '\n' << end();
-  }
-
-
-  // End Parsing reagent
-}
-
-string_tree* parse_property_list(istream& in) {
-  skip_whitespace_but_not_newline(in);
-  if (!has_data(in)) return NULL;
-  string_tree* result = new string_tree(slurp_until(in, ':'));
-  result->right = parse_property_list(in);
-  return result;
-}
-
-// TODO: delete
-type_tree* new_type_tree(const string_tree* properties) {
-  if (!properties) return NULL;
-  type_tree* result = new type_tree("", 0);
-  if (!properties->value.empty()) {
-    const string& type_name = result->name = properties->value;
-    if (contains_key(Type_ordinal, type_name))
-      result->value = get(Type_ordinal, type_name);
-    else if (is_integer(type_name))  // sometimes types will contain non-type tags, like numbers for the size of an array
-      result->value = 0;
-    else if (properties->value != "->")  // used in recipe types
-      result->value = -1;  // should never happen; will trigger errors later
-  }
-  result->left = new_type_tree(properties->left);
-  result->right = new_type_tree(properties->right);
-  return result;
-}
-
-
-reagent::reagent(const reagent& old) {
-  original_string = old.original_string;
-  name = old.name;
-  value = old.value;
-  initialized = old.initialized;
-  properties.clear();
-  for (long long int i = 0; i < SIZE(old.properties); ++i) {
-    properties.push_back(pair<string, string_tree*>(old.properties.at(i).first,
-                                                    old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL));
-  }
-  type = old.type ? new type_tree(*old.type) : NULL;
-}
-
-type_tree::type_tree(const type_tree& old) {
-  name = old.name;
-  value = old.value;
-  left = old.left ? new type_tree(*old.left) : NULL;
-  right = old.right ? new type_tree(*old.right) : NULL;
-}
-
-string_tree::string_tree(const string_tree& old) {  // :value(old.value) {
-  value = old.value;
-  left = old.left ? new string_tree(*old.left) : NULL;
-  right = old.right ? new string_tree(*old.right) : NULL;
-}
-
-reagent& reagent::operator=(const reagent& old) {
-  original_string = old.original_string;
-  properties.clear();
-  for (long long int i = 0; i < SIZE(old.properties); ++i)
-    properties.push_back(pair<string, string_tree*>(old.properties.at(i).first, old.properties.at(i).second ? new string_tree(*old.properties.at(i).second) : NULL));
-  name = old.name;
-  value = old.value;
-  initialized = old.initialized;
-  type = old.type ? new type_tree(*old.type) : NULL;
-  return *this;
-}
-
-reagent::~reagent() {
-  clear();
-}
-
-void reagent::clear() {
-  for (long long int i = 0; i < SIZE(properties); ++i) {
-    if (properties.at(i).second) {
-      delete properties.at(i).second;
-      properties.at(i).second = NULL;
-    }
-  }
-  delete type;
-  type = NULL;
-}
-type_tree::~type_tree() {
-  delete left;
-  delete right;
-}
-string_tree::~string_tree() {
-  delete left;
-  delete right;
-}
-
-string slurp_until(istream& in, char delim) {
-  ostringstream out;
-  char c;
-  while (in >> c) {
-    if (c == delim) {
-      // drop the delim
-      break;
-    }
-    out << c;
-  }
-  return out.str();
-}
-
-bool has_property(reagent x, string name) {
-  for (long long int i = 0; i < SIZE(x.properties); ++i) {
-    if (x.properties.at(i).first == name) return true;
-  }
-  return false;
-}
-
-string_tree* property(const reagent& r, const string& name) {
-  for (long long int p = 0; p != SIZE(r.properties); ++p) {
-    if (r.properties.at(p).first == name)
-      return r.properties.at(p).second;
-  }
-  return NULL;
-}
-
-void skip_whitespace_but_not_newline(istream& in) {
-  while (true) {
-    if (!has_data(in)) break;
-    else if (in.peek() == '\n') break;
-    else if (isspace(in.peek())) in.get();
-    else if (Ignore.find(in.peek()) != string::npos) in.get();
-    else break;
-  }
-}
-
-void dump_memory() {
-  for (map<long long int, double>::iterator p = Memory.begin(); p != Memory.end(); ++p) {
-    cout << p->first << ": " << no_scientific(p->second) << '\n';
-  }
-}
-
-
-string to_string(const recipe& r) {
-  ostringstream out;
-  out << "recipe " << r.name << " [\n";
-  for (long long int i = 0; i < SIZE(r.steps); ++i)
-    out << "  " << to_string(r.steps.at(i)) << '\n';
-  out << "]\n";
-  return out.str();
-}
-
-string debug_string(const recipe& x) {
-  ostringstream out;
-  out << "- recipe " << x.name << '\n';
-  // Begin debug_string(recipe x)
-  out << "ingredients:\n";
-  for (long long int i = 0; i < SIZE(x.ingredients); ++i)
-    out << "  " << debug_string(x.ingredients.at(i)) << '\n';
-  out << "products:\n";
-  for (long long int i = 0; i < SIZE(x.products); ++i)
-    out << "  " << debug_string(x.products.at(i)) << '\n';
-
-
-  for (long long int index = 0; index < SIZE(x.steps); ++index) {
-    const instruction& inst = x.steps.at(index);
-    out << "inst: " << to_string(inst) << '\n';
-    out << "  ingredients\n";
-    for (long long int i = 0; i < SIZE(inst.ingredients); ++i)
-      out << "    " << debug_string(inst.ingredients.at(i)) << '\n';
-    out << "  products\n";
-    for (long long int i = 0; i < SIZE(inst.products); ++i)
-      out << "    " << debug_string(inst.products.at(i)) << '\n';
-  }
-  return out.str();
-}
-
-string to_string(const instruction& inst) {
-  if (inst.is_label) return inst.label;
-  ostringstream out;
-  for (long long int i = 0; i < SIZE(inst.products); ++i) {
-    if (i > 0) out << ", ";
-    out << inst.products.at(i).original_string;
-  }
-  if (!inst.products.empty()) out << " <- ";
-  out << inst.name << ' ';
-  for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-    if (i > 0) out << ", ";
-    out << inst.ingredients.at(i).original_string;
-  }
-  return out.str();
-}
-
-string to_string(const reagent& r) {
-  if (is_literal_string(r))
-    return emit_literal_string(r.name);
-
-  ostringstream out;
-  out << r.name << ": " << names_to_string(r.type);
-  if (!r.properties.empty()) {
-    out << ", {";
-    for (long long int i = 0; i < SIZE(r.properties); ++i) {
-      if (i > 0) out << ", ";
-      out << "\"" << r.properties.at(i).first << "\": " << to_string(r.properties.at(i).second);
-    }
-    out << "}";
-  }
-  return out.str();
-}
-
-string debug_string(const reagent& x) {
-  ostringstream out;
-  out << x.name << ": " << x.value << ' ' << to_string(x.type) << " -- " << to_string(x);
-  return out.str();
-}
-
-string to_string(const string_tree* property) {
-  if (!property) return "()";
-  ostringstream out;
-  if (!property->left && !property->right)
-    // abbreviate a single-node tree to just its contents
-    out << '"' << property->value << '"';
-  else
-    dump(property, out);
-  return out.str();
-}
-
-void dump(const string_tree* x, ostream& out) {
-  if (!x->left && !x->right) {
-    out << x->value;
-    return;
-  }
-  out << '(';
-  for (const string_tree* curr = x; curr; curr = curr->right) {
-    if (curr != x) out << ' ';
-    if (curr->left)
-      dump(curr->left, out);
-    else
-      out << '"' << curr->value << '"';
-  }
-  out << ')';
-}
-
-string to_string(const type_tree* type) {
-  // abbreviate a single-node tree to just its contents
-  if (!type) return "NULLNULLNULL";  // should never happen
-  ostringstream out;
-  dump(type, out);
-  return out.str();
-}
-
-void dump(const type_tree* x, ostream& out) {
-  if (!x->left && !x->right) {
-    out << x->value;
-    return;
-  }
-  out << '(';
-  for (const type_tree* curr = x; curr; curr = curr->right) {
-    if (curr != x) out << ' ';
-    if (curr->left)
-      dump(curr->left, out);
-    else
-      dump(curr->value, out);
-  }
-  out << ')';
-}
-
-void dump(type_ordinal type, ostream& out) {
-  if (contains_key(Type, type))
-    out << get(Type, type).name;
-  else
-    out << "?" << type;
-}
-
-string names_to_string(const type_tree* type) {
-  // abbreviate a single-node tree to just its contents
-  if (!type) return "()";  // should never happen
-  ostringstream out;
-  dump_names(type, out);
-  return out.str();
-}
-
-void dump_names(const type_tree* type, ostream& out) {
-  if (!type->left && !type->right) {
-    out << '"' << type->name << '"';
-    return;
-  }
-  out << '(';
-  for (const type_tree* curr = type; curr; curr = curr->right) {
-    if (curr != type) out << ' ';
-    if (curr->left)
-      dump_names(curr->left, out);
-    else
-      out << '"' << curr->name << '"';
-  }
-  out << ')';
-}
-
-string names_to_string_without_quotes(const type_tree* type) {
-  // abbreviate a single-node tree to just its contents
-  if (!type) return "NULLNULLNULL";  // should never happen
-  ostringstream out;
-  dump_names_without_quotes(type, out);
-  return out.str();
-}
-
-void dump_names_without_quotes(const type_tree* type, ostream& out) {
-  if (!type->left && !type->right) {
-    out << type->name;
-    return;
-  }
-  out << '(';
-  for (const type_tree* curr = type; curr; curr = curr->right) {
-    if (curr != type) out << ' ';
-    if (curr->left)
-      dump_names_without_quotes(curr->left, out);
-    else
-      out << curr->name;
-  }
-  out << ')';
-}
-
-
-ostream& operator<<(ostream& os, no_scientific x) {
-  if (!isfinite(x.x)) {
-    // Infinity or NaN
-    os << x.x;
-    return os;
-  }
-  ostringstream tmp;
-  tmp << std::fixed << x.x;
-  os << trim_floating_point(tmp.str());
-  return os;
-}
-
-string trim_floating_point(const string& in) {
-  if (in.empty()) return "";
-  long long int len = SIZE(in);
-  while (len > 1) {
-    if (in.at(len-1) != '0') break;
-    --len;
-  }
-  if (in.at(len-1) == '.') --len;
-  return in.substr(0, len);
-}
-
-void test_trim_floating_point() {
-  CHECK_EQ("", trim_floating_point(""));
-  CHECK_EQ("0", trim_floating_point("000000000"));
-  CHECK_EQ("1.5", trim_floating_point("1.5000"));
-  CHECK_EQ("1.000001", trim_floating_point("1.000001"));
-  CHECK_EQ("23", trim_floating_point("23.000000"));
-  CHECK_EQ("23", trim_floating_point("23.0"));
-  CHECK_EQ("23", trim_floating_point("23."));
-  CHECK_EQ("23", trim_floating_point("23"));
-  CHECK_EQ("3", trim_floating_point("3.000000"));
-  CHECK_EQ("3", trim_floating_point("3.0"));
-  CHECK_EQ("3", trim_floating_point("3."));
-  CHECK_EQ("3", trim_floating_point("3"));
-}
-
-
-void test_first_recipe() {
-  Trace_file = "first_recipe";
-  load("recipe main [\n  1:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"");
-}
-vector<recipe_ordinal> load(string form) {
-  istringstream in(form);
-  in >> std::noskipws;
-  return load(in);
-}
-
-vector<recipe_ordinal> load(istream& in) {
-  in >> std::noskipws;
-  vector<recipe_ordinal> result;
-  while (has_data(in)) {
-    skip_whitespace_and_comments(in);
-    if (!has_data(in)) break;
-    string command = next_word(in);
-    // Command Handlers
-    if (command == "recipe") {
-      result.push_back(slurp_recipe(in));
-    }
-    else if (command == "recipe!") {
-      Disable_redefine_warnings = true;
-      result.push_back(slurp_recipe(in));
-      Disable_redefine_warnings = false;
-    }
-    else if (command == "container") {
-      insert_container(command, CONTAINER, in);
-    }
-
-    else if (command == "exclusive-container") {
-      insert_container(command, EXCLUSIVE_CONTAINER, in);
-    }
-
-
-    else if (command == "scenario") {
-      Scenarios.push_back(parse_scenario(in));
-    }
-
-    else if (command == "before") {
-      string label = next_word(in);
-      recipe tmp;
-      slurp_body(in, tmp);
-      if (is_waypoint(label))
-        Before_fragments[label].steps.insert(Before_fragments[label].steps.end(), tmp.steps.begin(), tmp.steps.end());
-      else
-        raise_error << "can't tangle before label " << label << '\n' << end();
-    }
-    else if (command == "after") {
-      string label = next_word(in);
-      recipe tmp;
-      slurp_body(in, tmp);
-      if (is_waypoint(label))
-        After_fragments[label].steps.insert(After_fragments[label].steps.begin(), tmp.steps.begin(), tmp.steps.end());
-      else
-        raise_error << "can't tangle after label " << label << '\n' << end();
-    }
-
-
-    // End Command Handlers
-    else {
-      raise_error << "unknown top-level command: " << command << '\n' << end();
-    }
-  }
-  return result;
-}
-
-long long int slurp_recipe(istream& in) {
-  recipe result;
-  result.name = next_word(in);
-  result.original_name = result.name;
-
-  // End Load Recipe Name
-  skip_whitespace_but_not_newline(in);
-  if (in.peek() != '[') {
-    trace(9999, "parse") << "recipe has a header; parsing" << end();
-    load_recipe_header(in, result);
-  }
-
-  // End Recipe Refinements
-  if (result.name.empty())
-    raise_error << "empty result.name\n" << end();
-  trace(9991, "parse") << "--- defining " << result.name << end();
-  if (!contains_key(Recipe_ordinal, result.name))
-    put(Recipe_ordinal, result.name, Next_recipe_ordinal++);
-  if (Recipe.find(get(Recipe_ordinal, result.name)) != Recipe.end()) {
-    trace(9991, "parse") << "already exists" << end();
-    if (warn_on_redefine(result.name))
-      raise << "redefining recipe " << result.name << "\n" << end();
-    Recipe.erase(get(Recipe_ordinal, result.name));
-  }
-  slurp_body(in, result);
-  if (!result.has_header) {
-    result.has_header = true;
-    for (long long int i = 0; i < SIZE(result.steps); ++i) {
-      const instruction& inst = result.steps.at(i);
-      if ((inst.name == "reply" && !inst.ingredients.empty())
-          || inst.name == "next-ingredient"
-          || inst.name == "ingredient"
-          || inst.name == "rewind-ingredients") {
-        result.has_header = false;
-        break;
-      }
-    }
-  }
-  if (result.has_header) {
-    trace(9999, "parse") << "recipe " << result.name << " has a header" << end();
-  }
-
-
-  // End Recipe Body(result)
-  put(Recipe, get(Recipe_ordinal, result.name), result);
-  // track added recipes because we may need to undo them in tests; see below
-  Recently_added_recipes.push_back(get(Recipe_ordinal, result.name));
-  return get(Recipe_ordinal, result.name);
-}
-
-void slurp_body(istream& in, recipe& result) {
-  in >> std::noskipws;
-  skip_whitespace_but_not_newline(in);
-  if (in.get() != '[')
-    raise_error << "recipe body must begin with '['\n" << end();
-  skip_whitespace_and_comments(in);  // permit trailing comment after '['
-  instruction curr;
-  while (next_instruction(in, &curr)) {
-    // rewrite `reply-if a, b, c, ...` to
-    //   ```
-    //   jump-unless a, 1:offset
-    //   reply b, c, ...
-    //   ```
-    if (curr.name == "reply-if") {
-      if (curr.products.empty()) {
-        curr.operation = get(Recipe_ordinal, "jump-unless");
-        curr.name = "jump-unless";
-        vector<reagent> results;
-        copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
-        curr.ingredients.resize(1);
-        curr.ingredients.push_back(reagent("1:offset"));
-        result.steps.push_back(curr);
-        curr.clear();
-        curr.operation = get(Recipe_ordinal, "reply");
-        curr.name = "reply";
-        curr.ingredients.swap(results);
-      }
-      else {
-        raise_error << "'reply-if' never yields any products\n" << end();
-      }
-    }
-    // rewrite `reply-unless a, b, c, ...` to
-    //   ```
-    //   jump-if a, 1:offset
-    //   reply b, c, ...
-    //   ```
-    if (curr.name == "reply-unless") {
-      if (curr.products.empty()) {
-        curr.operation = get(Recipe_ordinal, "jump-if");
-        curr.name = "jump-if";
-        vector<reagent> results;
-        copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
-        curr.ingredients.resize(1);
-        curr.ingredients.push_back(reagent("1:offset"));
-        result.steps.push_back(curr);
-        curr.clear();
-        curr.operation = get(Recipe_ordinal, "reply");
-        curr.name = "reply";
-        curr.ingredients.swap(results);
-      }
-      else {
-        raise_error << "'reply-unless' never yields any products\n" << end();
-      }
-    }
-
-    // rewrite `new-default-space` to
-    //   `default-space:address:shared:array:location <- new location:type, number-of-locals:literal`
-    // where N is Name[recipe][""]
-    if (curr.name == "new-default-space") {
-      rewrite_default_space_instruction(curr);
-    }
-    if (curr.name == "local-scope") {
-      rewrite_default_space_instruction(curr);
-    }
-
-    if (curr.name == "load-ingredients") {
-      curr.clear();
-      recipe_ordinal op = get(Recipe_ordinal, "next-ingredient-without-typechecking");
-      for (long long int i = 0; i < SIZE(result.ingredients); ++i) {
-        curr.operation = op;
-        curr.name = "next-ingredient-without-typechecking";
-        curr.products.push_back(result.ingredients.at(i));
-        result.steps.push_back(curr);
-        curr.clear();
-      }
-    }
-
-    // rewrite `assume-screen width, height` to
-    // `screen:address:shared:screen <- new-fake-screen width, height`
-    if (curr.name == "assume-screen") {
-      curr.name = "new-fake-screen";
-      assert(curr.products.empty());
-      curr.products.push_back(reagent("screen:address:shared:screen"));
-      curr.products.at(0).set_value(SCREEN);
-    }
-
-    // End Rewrite Instruction(curr, recipe result)
-    trace(9992, "load") << "after rewriting: " << to_string(curr) << end();
-    if (!curr.is_empty()) {
-      curr.original_string = to_string(curr);
-      result.steps.push_back(curr);
-    }
-  }
-}
-
-bool next_instruction(istream& in, instruction* curr) {
-  curr->clear();
-  skip_whitespace_and_comments(in);
-  if (!has_data(in)) {
-    raise_error << "0: unbalanced '[' for recipe\n" << end();
-    return false;
-  }
-
-  vector<string> words;
-  while (has_data(in) && in.peek() != '\n') {
-    skip_whitespace_but_not_newline(in);
-    if (!has_data(in)) {
-      raise_error << "1: unbalanced '[' for recipe\n" << end();
-      return false;
-    }
-    string word = next_word(in);
-    words.push_back(word);
-    skip_whitespace_but_not_newline(in);
-  }
-  skip_whitespace_and_comments(in);
-  if (SIZE(words) == 1 && words.at(0) == "]")
-    return false;  // end of recipe
-
-  if (SIZE(words) == 1 && !isalnum(words.at(0).at(0)) && words.at(0).at(0) != '$') {
-    curr->is_label = true;
-    curr->label = words.at(0);
-    trace(9993, "parse") << "label: " << curr->label << end();
-    if (!has_data(in)) {
-      raise_error << "7: unbalanced '[' for recipe\n" << end();
-      return false;
-    }
-    return true;
-  }
-
-  vector<string>::iterator p = words.begin();
-  if (find(words.begin(), words.end(), "<-") != words.end()) {
-    for (; *p != "<-"; ++p)
-      curr->products.push_back(reagent(*p));
-    ++p;  // skip <-
-  }
-
-  if (p == words.end()) {
-    raise_error << "instruction prematurely ended with '<-'\n" << end();
-    return false;
-  }
-  curr->old_name = curr->name = *p;  p++;
-  // curr->operation will be set in a later layer
-
-  for (; p != words.end(); ++p)
-    curr->ingredients.push_back(reagent(*p));
-
-  trace(9993, "parse") << "instruction: " << curr->name << end();
-  trace(9993, "parse") << "  number of ingredients: " << SIZE(curr->ingredients) << end();
-  for (vector<reagent>::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p)
-    trace(9993, "parse") << "  ingredient: " << to_string(*p) << end();
-  for (vector<reagent>::iterator p = curr->products.begin(); p != curr->products.end(); ++p)
-    trace(9993, "parse") << "  product: " << to_string(*p) << end();
-  if (!has_data(in)) {
-    raise_error << "9: unbalanced '[' for recipe\n" << end();
-    return false;
-  }
-  return true;
-}
-
-string next_word(istream& in) {
-  skip_whitespace_but_not_newline(in);
-  if (in.peek() == '[') {
-    string result = slurp_quoted(in);
-    skip_whitespace_and_comments_but_not_newline(in);
-    return result;
-  }
-
-  if (in.peek() == '(')
-    return slurp_balanced_bracket(in);
-  // treat curlies mostly like parens, but don't mess up labels
-  if (start_of_dilated_reagent(in))
-    return slurp_balanced_bracket(in);
-
-  // End next_word Special-cases
-  ostringstream out;
-  slurp_word(in, out);
-  skip_whitespace_and_comments_but_not_newline(in);
-  return out.str();
-}
-
-void slurp_word(istream& in, ostream& out) {
-  char c;
-  if (has_data(in) && Terminators.find(in.peek()) != string::npos) {
-    in >> c;
-    out << c;
-    return;
-  }
-  while (in >> c) {
-    if (isspace(c) || Terminators.find(c) != string::npos || Ignore.find(c) != string::npos) {
-      in.putback(c);
-      break;
-    }
-    out << c;
-  }
-}
-
-void skip_whitespace_and_comments(istream& in) {
-  while (true) {
-    if (!has_data(in)) break;
-    if (isspace(in.peek())) in.get();
-    else if (Ignore.find(in.peek()) != string::npos) in.get();
-    else if (in.peek() == '#') skip_comment(in);
-    else break;
-  }
-}
-
-// confusing; move to the next line only to skip a comment, but never otherwise
-void skip_whitespace_and_comments_but_not_newline(istream& in) {
-  while (true) {
-    if (!has_data(in)) break;
-    if (in.peek() == '\n') break;
-    if (isspace(in.peek())) in.get();
-    else if (Ignore.find(in.peek()) != string::npos) in.get();
-    else if (in.peek() == '#') skip_comment(in);
-    else break;
-  }
-}
-
-void skip_comment(istream& in) {
-  if (has_data(in) && in.peek() == '#') {
-    in.get();
-    while (has_data(in) && in.peek() != '\n') in.get();
-  }
-}
-
-bool warn_on_redefine(const string& recipe_name) {
-  if (recipe_name.find("scenario-") == 0) return true;
-
-
-
-  if (Disable_redefine_warnings) return false;
-  return true;
-}
-
-// for debugging
-void show_rest_of_stream(istream& in) {
-  cerr << '^';
-  char c;
-  while (in >> c)
-    cerr << c;
-  cerr << "$\n";
-  exit(0);
-}
-
-void clear_recently_added_recipes() {
-  for (long long int i = 0; i < SIZE(Recently_added_recipes); ++i) {
-    if (Recently_added_recipes.at(i) >= Reserved_for_tests  // don't renumber existing recipes, like 'interactive'
-        && contains_key(Recipe, Recently_added_recipes.at(i)))  // in case previous test had duplicate definitions
-      Recipe_ordinal.erase(get(Recipe, Recently_added_recipes.at(i)).name);
-    Recipe.erase(Recently_added_recipes.at(i));
-  }
-  for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) {
-    for (long long int i = 0; i < SIZE(p->second); ++i) {
-      if (find(Recently_added_recipes.begin(), Recently_added_recipes.end(), p->second.at(i)) != Recently_added_recipes.end())
-        p->second.at(i) = -1;  // just leave a ghost
-    }
-  }
-
-  // Clear Other State For Recently_added_recipes
-  for (long long int i = 0; i < SIZE(Recently_added_recipes); ++i) {
-    Name.erase(Recently_added_recipes.at(i));
-  }
-
-  Recently_added_recipes.clear();
-}
-
-void test_parse_comment_outside_recipe() {
-  Trace_file = "parse_comment_outside_recipe";
-  load("# this comment will be dropped by the tangler, so we need a dummy recipe to stop that\nrecipe f1 [ ]\n# this comment will go through to 'load'\nrecipe main [\n  1:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"");
-}
-void test_parse_comment_amongst_instruction() {
-  Trace_file = "parse_comment_amongst_instruction";
-  load("recipe main [\n  # comment\n  1:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"");
-}
-void test_parse_comment_amongst_instruction_2() {
-  Trace_file = "parse_comment_amongst_instruction_2";
-  load("recipe main [\n  # comment\n  1:number <- copy 23\n  # comment\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"");
-}
-void test_parse_comment_amongst_instruction_3() {
-  Trace_file = "parse_comment_amongst_instruction_3";
-  load("recipe main [\n  1:number <- copy 23\n  # comment\n  2:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 2: \"number\"");
-}
-void test_parse_comment_after_instruction() {
-  Trace_file = "parse_comment_after_instruction";
-  load("recipe main [\n  1:number <- copy 23  # comment\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"");
-}
-void test_parse_label() {
-  Trace_file = "parse_label";
-  load("recipe main [\n  +foo\n]\n");
-  CHECK_TRACE_CONTENTS("parse: label: +foo");
-}
-void test_parse_dollar_as_recipe_name() {
-  Trace_file = "parse_dollar_as_recipe_name";
-  load("recipe main [\n  $foo\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: $foo");
-}
-void test_parse_multiple_properties() {
-  Trace_file = "parse_multiple_properties";
-  load("recipe main [\n  1:number <- copy 23/foo:bar:baz\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\", {\"foo\": (\"bar\" \"baz\")}parse:   product: 1: \"number\"");
-}
-void test_parse_multiple_products() {
-  Trace_file = "parse_multiple_products";
-  load("recipe main [\n  1:number, 2:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   product: 1: \"number\"parse:   product: 2: \"number\"");
-}
-void test_parse_multiple_ingredients() {
-  Trace_file = "parse_multiple_ingredients";
-  load("recipe main [\n  1:number, 2:number <- copy 23, 4:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   ingredient: 4: \"number\"parse:   product: 1: \"number\"parse:   product: 2: \"number\"");
-}
-void test_parse_multiple_types() {
-  Trace_file = "parse_multiple_types";
-  load("recipe main [\n  1:number, 2:address:number <- copy 23, 4:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: instruction: copyparse:   ingredient: 23: \"literal\"parse:   ingredient: 4: \"number\"parse:   product: 1: \"number\"parse:   product: 2: (\"address\" \"number\")");
-}
-void test_parse_properties() {
-  Trace_file = "parse_properties";
-  load("recipe main [\n  1:address:number/lookup <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   product: 1: (\"address\" \"number\"), {\"lookup\": ()}");
-}
-void test_parse_comment_terminated_by_eof() {
-  Trace_file = "parse_comment_terminated_by_eof";
-  load("recipe main [\n"
-       "  a:number <- copy 34\n"
-       "]\n"
-       "# abc");  // no newline after comment
-  cerr << ".";  // termination = success
-}
-
-void test_warn_on_redefine() {
-  Trace_file = "warn_on_redefine";
-  Hide_warnings = true;
-  load("recipe main [\n  1:number <- copy 23\n]\nrecipe main [\n  1:number <- copy 24\n]\n");
-  CHECK_TRACE_CONTENTS("warn: redefining recipe main");
-}
-void test_redefine_without_warning() {
-  Trace_file = "redefine_without_warning";
-  Hide_warnings = true;
-  load("recipe main [\n  1:number <- copy 23\n]\nrecipe! main [\n  1:number <- copy 24\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("warn: redefining recipe main");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-
-void transform_all() {
-  trace(9990, "transform") << "=== transform_all()" << end();
-  for (long long int t = 0; t < SIZE(Transform); ++t) {
-//?     cerr << "transform " << t << '\n';
-    for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-      recipe& r = p->second;
-      if (r.steps.empty()) continue;
-      if (r.transformed_until != t-1) continue;
-      if (any_type_ingredient_in_header(/*recipe_ordinal*/p->first)) continue;
-
-      // End Transform Checks
-      (*Transform.at(t))(/*recipe_ordinal*/p->first);
-      r.transformed_until = t;
-    }
-  }
-//?   cerr << "wrapping up transform\n";
-  parse_int_reagents();  // do this after all other transforms have run
-  check_container_field_types();
-
-  // End Transform All
-}
-
-void parse_int_reagents() {
-  trace(9991, "transform") << "--- parsing any uninitialized reagents as integers" << end();
-//?   cerr << "--- parsing any uninitialized reagents as integers" << '\n';
-  for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-    recipe& r = p->second;
-    if (r.steps.empty()) continue;
-    for (long long int index = 0; index < SIZE(r.steps); ++index) {
-      instruction& inst = r.steps.at(index);
-      for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-        populate_value(inst.ingredients.at(i));
-      }
-      for (long long int i = 0; i < SIZE(inst.products); ++i) {
-        populate_value(inst.products.at(i));
-      }
-    }
-  }
-}
-
-void populate_value(reagent& r) {
-  if (r.initialized) return;
-  // End Reagent-parsing Exceptions
-  if (!is_integer(r.name)) return;
-  r.set_value(to_integer(r.name));
-}
-
-
-void update_instruction_operations(recipe_ordinal r) {
-  trace(9991, "transform") << "--- compute instruction operations for recipe " << get(Recipe, r).name << end();
-  recipe& caller = get(Recipe, r);
-//?   cerr << "--- compute instruction operations for recipe " << caller.name << '\n';
-  for (long long int index = 0; index < SIZE(caller.steps); ++index) {
-    instruction& inst = caller.steps.at(index);
-    if (inst.is_label) continue;
-    if (!contains_key(Recipe_ordinal, inst.name)) {
-      raise_error << maybe(caller.name) << "instruction " << inst.name << " has no recipe\n" << end();
-      return;
-    }
-    inst.operation = get(Recipe_ordinal, inst.name);
-    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();
-      return;
-    }
-
-    // End Instruction Operation Checks
-  }
-}
-
-// hook to suppress inserting recipe name into errors and warnings (for later layers)
-string maybe(string s) {
-  if (s == "interactive") return "";
-
-  return s + ": ";
-}
-
-// temporarily suppress run
-void transform(string form) {
-  load(form);
-  transform_all();
-}
-
-
-void test_string_literal() {
-  Trace_file = "string_literal";
-  load("recipe main [\n  1:address:array:character <- copy [abc def]  # copy can't really take a string\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"abc def\": \"literal-string\"");
-}
-void test_string_literal_with_colons() {
-  Trace_file = "string_literal_with_colons";
-  load("recipe main [\n  1:address:array:character <- copy [abc:def/ghi]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"abc:def/ghi\": \"literal-string\"");
-}
-string slurp_quoted(istream& in) {
-  ostringstream out;
-  assert(has_data(in));  assert(in.peek() == '[');  out << static_cast<char>(in.get());  // slurp the '['
-  if (is_code_string(in, out))
-    slurp_quoted_comment_aware(in, out);
-  else
-    slurp_quoted_comment_oblivious(in, out);
-  return out.str();
-}
-
-// A string is a code string if it contains a newline before any non-whitespace
-// todo: support comments before the newline. But that gets messy.
-bool is_code_string(istream& in, ostream& out) {
-  while (has_data(in)) {
-    char c = in.get();
-    if (!isspace(c)) {
-      in.putback(c);
-      return false;
-    }
-    out << c;
-    if (c == '\n') {
-      return true;
-    }
-  }
-  return false;
-}
-
-// Read a regular string. Regular strings can only contain other regular
-// strings.
-void slurp_quoted_comment_oblivious(istream& in, ostream& out) {
-  int brace_depth = 1;
-  while (has_data(in)) {
-    char c = in.get();
-    if (c == '\\') {
-      out << static_cast<char>(in.get());
-      continue;
-    }
-    out << c;
-    if (c == '[') ++brace_depth;
-    if (c == ']') --brace_depth;
-    if (brace_depth == 0) break;
-  }
-  if (!has_data(in) && brace_depth > 0) {
-    raise_error << "unbalanced '['\n" << end();
-    out.clear();
-  }
-}
-
-// Read a code string. Code strings can contain either code or regular strings.
-void slurp_quoted_comment_aware(istream& in, ostream& out) {
-  char c;
-  while (in >> c) {
-    if (c == '\\') {
-      out << static_cast<char>(in.get());
-      continue;
-    }
-    if (c == '#') {
-      out << c;
-      while (has_data(in) && in.peek() != '\n') out << static_cast<char>(in.get());
-      continue;
-    }
-    if (c == '[') {
-      in.putback(c);
-      // recurse
-      out << slurp_quoted(in);
-      continue;
-    }
-    out << c;
-    if (c == ']') return;
-  }
-  raise_error << "unbalanced '['\n" << end();
-  out.clear();
-}
-
-bool is_literal_string(const reagent& x) {
-  return x.type && x.type->name == "literal-string";
-}
-
-string emit_literal_string(string name) {
-  size_t pos = 0;
-  while (pos != string::npos)
-    pos = replace(name, "\n", "\\n", pos);
-  return '"'+name+"\": \"literal-string\"";
-}
-
-size_t replace(string& str, const string& from, const string& to, size_t n) {
-  size_t result = str.find(from, n);
-  if (result != string::npos)
-    str.replace(result, from.length(), to);
-  return result;
-}
-
-void strip_last(string& s) {
-  if (!s.empty()) s.erase(SIZE(s)-1);
-}
-
-void test_string_literal_nested() {
-  Trace_file = "string_literal_nested";
-  load("recipe main [\n  1:address:array:character <- copy [abc [def]]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"abc [def]\": \"literal-string\"");
-}
-void test_string_literal_escaped() {
-  Trace_file = "string_literal_escaped";
-  load("recipe main [\n  1:address:array:character <- copy [abc \\[def]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"abc [def\": \"literal-string\"");
-}
-void test_string_literal_escaped_comment_aware() {
-  Trace_file = "string_literal_escaped_comment_aware";
-  load("recipe main [\n  1:address:array:character <- copy [\nabc \\\\\\[def]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"\\nabc \\[def\": \"literal-string\"");
-}
-void test_string_literal_and_comment() {
-  Trace_file = "string_literal_and_comment";
-  load("recipe main [\n  1:address:array:character <- copy [abc]  # comment\n]\n");
-  CHECK_TRACE_CONTENTS("parse: --- defining mainparse: instruction: copyparse:   number of ingredients: 1parse:   ingredient: \"abc\": \"literal-string\"parse:   product: 1: (\"address\" \"array\" \"character\")");
-}
-void test_string_literal_escapes_newlines_in_trace() {
-  Trace_file = "string_literal_escapes_newlines_in_trace";
-  load("recipe main [\n  copy [abc\ndef]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"abc\\ndef\": \"literal-string\"");
-}
-void test_string_literal_can_skip_past_comments() {
-  Trace_file = "string_literal_can_skip_past_comments";
-  load("recipe main [\n  copy [\n    # ']' inside comment\n    bar\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"\\n    # ']' inside comment\\n    bar\\n  \": \"literal-string\"");
-}
-void test_string_literal_empty() {
-  Trace_file = "string_literal_empty";
-  load("recipe main [\n  copy []\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: \"\": \"literal-string\"");
-}
-
-void test_noninteger_literal() {
-  Trace_file = "noninteger_literal";
-  load("recipe main [\n  1:number <- copy 3.14159\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   ingredient: 3.14159: \"literal-fractional-number\"");
-}
-bool is_noninteger(const string& s) {
-  return s.find_first_not_of("0123456789-.") == string::npos
-      && s.find_first_of    ("0123456789-") != string::npos
-      && std::count(s.begin(), s.end(), '.') == 1;
-}
-
-double to_double(string n) {
-  char* end = NULL;
-  // safe because string.c_str() is guaranteed to be null-terminated
-  double result = strtod(n.c_str(), &end);
-  assert(*end == '\0');
-  return result;
-}
-
-void test_is_noninteger() {
-  CHECK(!is_noninteger("1234"));
-  CHECK(!is_noninteger("1a2"));
-  CHECK(is_noninteger("234.0"));
-  CHECK(!is_noninteger("..."));
-  CHECK(!is_noninteger("."));
-  CHECK(is_noninteger("2."));
-  CHECK(is_noninteger(".2"));
-}
-
-
-void test_copy_literal() {
-  Trace_file = "copy_literal";
-  run("recipe main [\n  1:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:number <- copy 23mem: storing 23 in location 1");
-}
-void test_copy() {
-  Trace_file = "copy";
-  run("recipe main [\n  1:number <- copy 23\n  2:number <- copy 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("run: 2:number <- copy 1:numbermem: location 1 is 23mem: storing 23 in location 2");
-}
-void test_copy_multiple() {
-  Trace_file = "copy_multiple";
-  run("recipe main [\n  1:number, 2:number <- copy 23, 24\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 24 in location 2");
-}
-void run(recipe_ordinal r) {
-  run(new routine(r));
-}
-
-
-void run_current_routine(long long int time_slice)
-{  // curly on a separate line, because later layers will modify header
-  long long int ninstrs = 0;
-  while (Current_routine->state == RUNNING && ninstrs < time_slice)
-  {
-    // when we reach the end of one call, we may reach the end of the one below
-    // it, and the one below that, and so on
-    while (current_step_index() >= SIZE(Current_routine->steps())) {
-      // Falling Through End Of Recipe
-      try_reclaim_locals();
-      if (Trace_stream) {
-        trace(9999, "trace") << "fall-through: exiting " << current_recipe_name() << "; decrementing callstack depth from " << Trace_stream->callstack_depth << end();
-        --Trace_stream->callstack_depth;
-        assert(Trace_stream->callstack_depth >= 0);
-      }
-      Current_routine->calls.pop_front();
-      if (Current_routine->calls.empty()) return;
-      // Complete Call Fallthrough
-      // todo: fail if no products returned
-      ++current_step_index();
-    }
-
-    // Running One Instruction
-    ninstrs++;
-
-
-    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();
-    }
-
-    if (current_instruction().is_label) { ++current_step_index(); continue; }
-    trace(Initial_callstack_depth + Trace_stream->callstack_depth, "run") << to_string(current_instruction()) << end();
-    if (get_or_insert(Memory, 0) != 0) {
-      raise_error << "something wrote to location 0; this should never happen\n" << end();
-      put(Memory, 0, 0);
-    }
-    // read all ingredients from memory, each potentially spanning multiple locations
-    vector<vector<double> > ingredients;
-    if (should_copy_ingredients()) {
-      for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i)
-        ingredients.push_back(read_memory(current_instruction().ingredients.at(i)));
-    }
-    // instructions below will write to 'products'
-    vector<vector<double> > products;
-    switch (current_instruction().operation) {
-      // Primitive Recipe Implementations
-      case COPY: {
-        copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
-        break;
-      }
-      case ADD: {
-        double result = 0;
-        for (long long int i = 0; i < SIZE(ingredients); ++i) {
-          result += ingredients.at(i).at(0);
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case SUBTRACT: {
-        double result = ingredients.at(0).at(0);
-        for (long long int i = 1; i < SIZE(ingredients); ++i)
-          result -= ingredients.at(i).at(0);
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-      case MULTIPLY: {
-        double result = 1;
-        for (long long int i = 0; i < SIZE(ingredients); ++i) {
-          result *= ingredients.at(i).at(0);
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case DIVIDE: {
-        double result = ingredients.at(0).at(0);
-        for (long long int i = 1; i < SIZE(ingredients); ++i)
-          result /= ingredients.at(i).at(0);
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case DIVIDE_WITH_REMAINDER: {
-        products.resize(2);
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        if (b == 0) {
-          raise_error << maybe(current_recipe_name()) << "divide by zero in '" << to_string(current_instruction()) << "'\n" << end();
-          products.resize(2);
-          products.at(0).push_back(0);
-          products.at(1).push_back(0);
-          break;
-        }
-        long long int quotient = a / b;
-        long long int remainder = a % b;
-        // very large integers will lose precision
-        products.at(0).push_back(quotient);
-        products.at(1).push_back(remainder);
-        break;
-      }
-
-      case SHIFT_LEFT: {
-        // ingredients must be integers
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        products.resize(1);
-        if (b < 0) {
-          raise_error << maybe(current_recipe_name()) << "second ingredient can't be negative in '" << to_string(current_instruction()) << "'\n" << end();
-          products.at(0).push_back(0);
-          break;
-        }
-        products.at(0).push_back(a<<b);
-        break;
-      }
-
-      case SHIFT_RIGHT: {
-        // ingredients must be integers
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        products.resize(1);
-        if (b < 0) {
-          raise_error << maybe(current_recipe_name()) << "second ingredient can't be negative in '" << to_string(current_instruction()) << "'\n" << end();
-          products.at(0).push_back(0);
-          break;
-        }
-        products.at(0).push_back(a>>b);
-        break;
-      }
-
-      case AND_BITS: {
-        // ingredients must be integers
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        products.resize(1);
-        products.at(0).push_back(a&b);
-        break;
-      }
-
-      case OR_BITS: {
-        // ingredients must be integers
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        products.resize(1);
-        products.at(0).push_back(a|b);
-        break;
-      }
-
-      case XOR_BITS: {
-        // ingredients must be integers
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        long long int b = static_cast<long long int>(ingredients.at(1).at(0));
-        products.resize(1);
-        products.at(0).push_back(a^b);
-        break;
-      }
-
-      case FLIP_BITS: {
-        // ingredient must be integer
-        long long int a = static_cast<long long int>(ingredients.at(0).at(0));
-        products.resize(1);
-        products.at(0).push_back(~a);
-        break;
-      }
-
-      case AND: {
-        bool result = true;
-        for (long long int i = 0; i < SIZE(ingredients); ++i)
-          result = result && ingredients.at(i).at(0);
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case OR: {
-        bool result = false;
-        for (long long int i = 0; i < SIZE(ingredients); ++i)
-          result = result || ingredients.at(i).at(0);
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case NOT: {
-        products.resize(SIZE(ingredients));
-        for (long long int i = 0; i < SIZE(ingredients); ++i) {
-          products.at(i).push_back(!ingredients.at(i).at(0));
-        }
-        break;
-      }
-
-      case JUMP: {
-        assert(current_instruction().ingredients.at(0).initialized);
-        current_step_index() += ingredients.at(0).at(0)+1;
-        trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
-        continue;  // skip rest of this instruction
-      }
-
-      case JUMP_IF: {
-        assert(current_instruction().ingredients.at(1).initialized);
-        if (!ingredients.at(0).at(0)) {
-          trace(9998, "run") << "jump-if fell through" << end();
-          break;
-        }
-        current_step_index() += ingredients.at(1).at(0)+1;
-        trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
-        continue;  // skip rest of this instruction
-      }
-
-      case JUMP_UNLESS: {
-        assert(current_instruction().ingredients.at(1).initialized);
-        if (ingredients.at(0).at(0)) {
-          trace(9998, "run") << "jump-unless fell through" << end();
-          break;
-        }
-        current_step_index() += ingredients.at(1).at(0)+1;
-        trace(9998, "run") << "jumping to instruction " << current_step_index() << end();
-        continue;  // skip rest of this instruction
-      }
-
-      case EQUAL: {
-        vector<double>& exemplar = ingredients.at(0);
-        bool result = true;
-        for (long long int i = 1; i < SIZE(ingredients); ++i) {
-          if (!equal(ingredients.at(i).begin(), ingredients.at(i).end(), exemplar.begin())) {
-            result = false;
-            break;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case GREATER_THAN: {
-        bool result = true;
-        for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
-          if (ingredients.at(i-1).at(0) <= ingredients.at(i).at(0)) {
-            result = false;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case LESSER_THAN: {
-        bool result = true;
-        for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
-          if (ingredients.at(i-1).at(0) >= ingredients.at(i).at(0)) {
-            result = false;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case GREATER_OR_EQUAL: {
-        bool result = true;
-        for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
-          if (ingredients.at(i-1).at(0) < ingredients.at(i).at(0)) {
-            result = false;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case LESSER_OR_EQUAL: {
-        bool result = true;
-        for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
-          if (ingredients.at(i-1).at(0) > ingredients.at(i).at(0)) {
-            result = false;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case TRACE: {
-        long long int depth = ingredients.at(0).at(0);
-        string label = current_instruction().ingredients.at(1).name;
-        ostringstream out;
-        for (long long int i = 2; i < SIZE(current_instruction().ingredients); ++i) {
-          out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i));
-        }
-        trace(depth, label) << out.str() << end();
-        break;
-      }
-
-
-      case STASH: {
-        ostringstream out;
-        for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) {
-          out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i));
-        }
-        trace(2, "app") << out.str() << end();
-        break;
-      }
-
-      case HIDE_ERRORS: {
-        Hide_errors = true;
-        Hide_warnings = true;
-        break;
-      }
-
-      case SHOW_ERRORS: {
-        Hide_errors = false;
-        Hide_warnings = false;
-        break;
-      }
-
-      case TRACE_UNTIL: {
-        if (Trace_stream) {
-          Trace_stream->collect_depth = ingredients.at(0).at(0);
-        }
-        break;
-      }
-
-      case _DUMP_TRACE: {
-        if (ingredients.empty()) {
-          DUMP("");
-        }
-        else {
-          DUMP(current_instruction().ingredients.at(0).name);
-        }
-        break;
-      }
-
-      case _CLEAR_TRACE: {
-        if (Trace_stream) Trace_stream->past_lines.clear();
-        break;
-      }
-
-      case _SAVE_TRACE: {
-        if (!Trace_file.empty()) {
-          ofstream fout((Trace_dir+Trace_file).c_str());
-          fout << Trace_stream->readable_contents("");
-          fout.close();
-        }
-        break;
-      }
-
-
-      case ASSERT: {
-        if (!ingredients.at(0).at(0)) {
-          raise_error << current_instruction().ingredients.at(1).name << '\n' << end();
-        }
-        break;
-      }
-
-
-      case _PRINT: {
-        for (long long int i = 0; i < SIZE(ingredients); ++i) {
-          if (is_literal(current_instruction().ingredients.at(i))) {
-            trace(9998, "run") << "$print: " << current_instruction().ingredients.at(i).name << end();
-            if (has_property(current_instruction().ingredients.at(i), "newline"))
-              cout << '\n';
-            else
-              cout << current_instruction().ingredients.at(i).name;
-          }
-          else {
-            for (long long int j = 0; j < SIZE(ingredients.at(i)); ++j) {
-              trace(9998, "run") << "$print: " << ingredients.at(i).at(j) << end();
-              if (j > 0) cout << " ";
-              cout << no_scientific(ingredients.at(i).at(j));
-            }
-          }
-        }
-        cout.flush();
-        break;
-      }
-
-      case _EXIT: {
-        exit(0);
-        break;
-      }
-
-      case _SYSTEM: {
-        if (Current_scenario) break;
-
-
-        int status = system(current_instruction().ingredients.at(0).name.c_str());
-        products.resize(1);
-        products.at(0).push_back(status);
-        break;
-      }
-
-      case _DUMP_MEMORY: {
-        dump_memory();
-        break;
-      }
-
-
-      case _LOG: {
-        ostringstream out;
-        for (long long int i = 0; i < SIZE(current_instruction().ingredients); ++i) {
-          out << print_mu(current_instruction().ingredients.at(i), ingredients.at(i));
-        }
-        LOG << out.str() << '\n';
-        break;
-      }
-
-      case GET: {
-        reagent base = current_instruction().ingredients.at(0);
-        // Update GET base in Run
-        canonize(base);
-
-        long long int base_address = base.value;
-        if (base_address == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        type_ordinal base_type = base.type->value;
-        long long int offset = ingredients.at(1).at(0);
-        if (offset < 0 || offset >= SIZE(get(Type, base_type).elements)) break;  // copied from Check above
-        long long int src = base_address;
-        for (long long int i = 0; i < offset; ++i) {
-          const type_tree* type = get(Type, base_type).elements.at(i).type;
-          if (type->value >= START_TYPE_INGREDIENTS) {
-            long long int size = size_of_type_ingredient(type, base.type->right);
-            if (!size)
-              raise_error << "illegal field type '" << to_string(type) << "' seems to be missing a type ingredient or three\n" << end();
-            src += size;
-            continue;
-          }
-
-          // End GET field Cases
-          src += size_of(element_type(base, i));
-        }
-        trace(9998, "run") << "address to copy is " << src << end();
-        reagent tmp = element_type(base, offset);
-        tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
-
-
-        tmp.set_value(src);
-        trace(9998, "run") << "its type is " << to_string(tmp.type) << end();
-        products.push_back(read_memory(tmp));
-        break;
-      }
-
-      case GET_ADDRESS: {
-        reagent base = current_instruction().ingredients.at(0);
-        // Update GET_ADDRESS base in Run
-        canonize(base);
-
-
-        long long int base_address = base.value;
-        if (base_address == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        type_ordinal base_type = base.type->value;
-        long long int offset = ingredients.at(1).at(0);
-        if (offset < 0 || offset >= SIZE(get(Type, base_type).elements)) break;  // copied from Check above
-        long long int result = base_address;
-        for (long long int i = 0; i < offset; ++i) {
-          const type_tree* type = get(Type, base_type).elements.at(i).type;
-          if (type->value >= START_TYPE_INGREDIENTS) {
-            long long int size = size_of_type_ingredient(type, base.type->right);
-            if (!size)
-              raise_error << "illegal type '" << to_string(type) << "' seems to be missing a type ingredient or three\n" << end();
-            result += size;
-            continue;
-          }
-
-
-          // End GET_ADDRESS field Cases
-          result += size_of(element_type(base, i));
-        }
-        trace(9998, "run") << "address to copy is " << result << end();
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case MERGE: {
-        products.resize(1);
-        for (long long int i = 0; i < SIZE(ingredients); ++i)
-          for (long long int j = 0; j < SIZE(ingredients.at(i)); ++j)
-            products.at(0).push_back(ingredients.at(i).at(j));
-        break;
-      }
-
-
-      case _DUMP: {
-        reagent after_canonize = current_instruction().ingredients.at(0);
-        canonize(after_canonize);
-        cerr << maybe(current_recipe_name()) << current_instruction().ingredients.at(0).name << ' ' << no_scientific(current_instruction().ingredients.at(0).value) << " => " << no_scientific(after_canonize.value) << " => " << no_scientific(get_or_insert(Memory, after_canonize.value)) << '\n';
-        break;
-      }
-
-      case _FOO: {
-        if (current_instruction().ingredients.empty()) {
-          if (foo != -1) cerr << foo << ": " << no_scientific(get_or_insert(Memory, foo)) << '\n';
-          else cerr << '\n';
-        }
-        else {
-          reagent tmp = current_instruction().ingredients.at(0);
-          canonize(tmp);
-          foo = tmp.value;
-        }
-        break;
-      }
-
-      case CREATE_ARRAY: {
-        reagent product = current_instruction().products.at(0);
-        canonize(product);
-        long long int base_address = product.value;
-        long long int array_size = to_integer(product.type->right->right->name);
-        // initialize array size, so that size_of will work
-        put(Memory, base_address, array_size);  // in array elements
-        long long int size = size_of(product);  // in locations
-        trace(9998, "run") << "creating array of size " << size << '\n' << end();
-        // initialize array
-        for (long long int i = 1; i <= size_of(product); ++i) {
-          put(Memory, base_address+i, 0);
-        }
-        // dummy product; doesn't actually do anything
-        products.resize(1);
-        products.at(0).push_back(array_size);
-        break;
-      }
-
-      case INDEX: {
-        reagent base = current_instruction().ingredients.at(0);
-        canonize(base);
-        long long int base_address = base.value;
-        trace(9998, "run") << "base address is " << base_address << end();
-        if (base_address == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        reagent offset = current_instruction().ingredients.at(1);
-        canonize(offset);
-        vector<double> offset_val(read_memory(offset));
-        type_tree* element_type = array_element(base.type);
-        if (offset_val.at(0) < 0 || offset_val.at(0) >= get_or_insert(Memory, base_address)) {
-          raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
-          break;
-        }
-        long long int src = base_address + 1 + offset_val.at(0)*size_of(element_type);
-        trace(9998, "run") << "address to copy is " << src << end();
-        trace(9998, "run") << "its type is " << get(Type, element_type->value).name << end();
-        reagent tmp;
-        tmp.properties.push_back(pair<string, string_tree*>("raw", NULL));
-
-
-        tmp.set_value(src);
-        tmp.type = new type_tree(*element_type);
-        products.push_back(read_memory(tmp));
-        break;
-      }
-
-      case INDEX_ADDRESS: {
-        reagent base = current_instruction().ingredients.at(0);
-        canonize(base);
-        long long int base_address = base.value;
-        if (base_address == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        reagent offset = current_instruction().ingredients.at(1);
-        canonize(offset);
-        vector<double> offset_val(read_memory(offset));
-        type_tree* element_type = array_element(base.type);
-        if (offset_val.at(0) < 0 || offset_val.at(0) >= get_or_insert(Memory, base_address)) {
-          raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
-          break;
-        }
-        long long int result = base_address + 1 + offset_val.at(0)*size_of(element_type);
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case LENGTH: {
-        reagent x = current_instruction().ingredients.at(0);
-        canonize(x);
-        if (x.value == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        products.resize(1);
-        products.at(0).push_back(get_or_insert(Memory, x.value));
-        break;
-      }
-
-      case MAYBE_CONVERT: {
-        reagent base = current_instruction().ingredients.at(0);
-        canonize(base);
-        long long int base_address = base.value;
-        if (base_address == 0) {
-          raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-          break;
-        }
-        long long int tag = current_instruction().ingredients.at(1).value;
-        long long int result;
-        if (tag == static_cast<long long int>(get_or_insert(Memory, base_address))) {
-          result = base_address+1;
-        }
-        else {
-          result = 0;
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-      case NEXT_INGREDIENT: {
-        assert(!Current_routine->calls.empty());
-        if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) {
-          reagent product = current_instruction().products.at(0);
-          canonize_type(product);
-          if (current_recipe_name() == "main") {
-            // no ingredient types since the call might be implicit; assume ingredients are always strings
-            // todo: how to test this?
-            if (!is_mu_string(product))
-              raise_error << "main: wrong type for ingredient " << product.original_string << '\n' << end();
-          }
-          else if (!types_coercible(product,
-                                    current_call().ingredients.at(current_call().next_ingredient_to_process))) {
-            raise_error << maybe(current_recipe_name()) << "wrong type for ingredient " << product.original_string << '\n' << end();
-            // End next-ingredient Type Mismatch Error
-          }
-          products.push_back(
-              current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
-          assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
-          products.at(1).push_back(1);
-          ++current_call().next_ingredient_to_process;
-        }
-        else {
-          if (SIZE(current_instruction().products) < 2)
-            raise_error << maybe(current_recipe_name()) << "no ingredient to save in " << current_instruction().products.at(0).original_string << '\n' << end();
-          if (current_instruction().products.empty()) break;
-          products.resize(2);
-          // pad the first product with sufficient zeros to match its type
-          long long int size = size_of(current_instruction().products.at(0));
-          for (long long int i = 0; i < size; ++i)
-            products.at(0).push_back(0);
-          products.at(1).push_back(0);
-        }
-        break;
-      }
-
-      case REWIND_INGREDIENTS: {
-        current_call().next_ingredient_to_process = 0;
-        break;
-      }
-
-      case INGREDIENT: {
-        if (static_cast<long long int>(ingredients.at(0).at(0)) < SIZE(current_call().ingredient_atoms)) {
-          current_call().next_ingredient_to_process = ingredients.at(0).at(0);
-          products.push_back(
-              current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
-          assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
-          products.at(1).push_back(1);
-          ++current_call().next_ingredient_to_process;
-        }
-        else {
-          if (SIZE(current_instruction().products) > 1) {
-            products.resize(2);
-            products.at(0).push_back(0);  // todo: will fail noisily if we try to read a compound value
-            products.at(1).push_back(0);
-          }
-        }
-        break;
-      }
-
-      case REPLY: {
-        // Starting Reply
-        try_reclaim_locals();
-
-        if (Trace_stream) {
-          trace(9999, "trace") << "reply: decrementing callstack depth from " << Trace_stream->callstack_depth << end();
-          --Trace_stream->callstack_depth;
-          if (Trace_stream->callstack_depth < 0) {
-            Current_routine->calls.clear();
-            goto stop_running_current_routine;
-          }
-        }
-        Current_routine->calls.pop_front();
-        // just in case 'main' returns a value, drop it for now
-        if (Current_routine->calls.empty()) goto stop_running_current_routine;
-        const instruction& caller_instruction = current_instruction();
-        for (long long int i = 0; i < SIZE(caller_instruction.products); ++i)
-          trace(9998, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
-
-        // make reply products available to caller
-        copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
-        // End Reply
-        break;  // continue to process rest of *caller* instruction
-      }
-
-      case ALLOCATE: {
-        // compute the space we need
-        long long int size = ingredients.at(0).at(0);
-        if (SIZE(ingredients) > 1) {
-          // array
-          trace(9999, "mem") << "array size is " << ingredients.at(1).at(0) << end();
-          size = /*space for length*/1 + size*ingredients.at(1).at(0);
-        }
-        // include space for refcount
-        size++;
-        trace(9999, "mem") << "allocating size " << size << end();
-      //?   Total_alloc += size;
-      //?   Num_alloc++;
-        // compute the region of memory to return
-        // really crappy at the moment
-        if (get_or_insert(Free_list, size)) {
-          trace(9999, "abandon") << "picking up space from free-list of size " << size << end();
-          long long int result = get_or_insert(Free_list, size);
-          put(Free_list, size, get_or_insert(Memory, result));
-          for (long long int curr = result+1; curr < result+size; ++curr) {
-            if (get_or_insert(Memory, curr) != 0) {
-              raise_error << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
-              break;  // always fatal
-            }
-          }
-          if (SIZE(current_instruction().ingredients) > 1)
-            put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0));
-          else
-            put(Memory, result, 0);
-          products.resize(1);
-          products.at(0).push_back(result);
-          break;
-        }
-
-        ensure_space(size);
-        const long long int result = Current_routine->alloc;
-        trace(9999, "mem") << "new alloc: " << result << end();
-        // save result
-        products.resize(1);
-        products.at(0).push_back(result);
-        // initialize allocated space
-        for (long long int address = result; address < result+size; ++address)
-          put(Memory, address, 0);
-        // initialize array length
-        if (SIZE(current_instruction().ingredients) > 1) {
-          trace(9999, "mem") << "storing " << ingredients.at(1).at(0) << " in location " << result+/*skip refcount*/1 << end();
-          put(Memory, result+/*skip refcount*/1, ingredients.at(1).at(0));
-        }
-        // bump
-        Current_routine->alloc += size;
-        // no support for reclaiming memory
-        assert(Current_routine->alloc <= Current_routine->alloc_max);
-        break;
-      }
-
-      case NEW: {
-        if (is_literal_string(current_instruction().ingredients.at(0))) {
-          products.resize(1);
-          products.at(0).push_back(new_mu_string(current_instruction().ingredients.at(0).name));
-          break;
-        }
-
-        raise << "no implementation for 'new'; why wasn't it translated to 'allocate'?\n" << end();
-        break;
-      }
-
-      //? :(before "End Globals")
-      //? long long int Total_alloc = 0;
-      //? long long int Num_alloc = 0;
-      //? long long int Total_free = 0;
-      //? long long int Num_free = 0;
-      //? :(before "End Setup")
-      //? Total_alloc = Num_alloc = Total_free = Num_free = 0;
-      //? :(before "End Teardown")
-      //? cerr << Total_alloc << "/" << Num_alloc
-      //?      << " vs " << Total_free << "/" << Num_free << '\n';
-      //? cerr << SIZE(Memory) << '\n';
-
-      case ABANDON: {
-        long long int address = ingredients.at(0).at(0);
-        trace(9999, "abandon") << "address to abandon is " << address << end();
-        reagent types = current_instruction().ingredients.at(0);
-        trace(9999, "abandon") << "value of ingredient is " << types.value << end();
-        canonize(types);
-        // lookup_memory without drop_one_lookup {
-        trace(9999, "abandon") << "value of ingredient after canonization is " << types.value << end();
-        long long int address_location = types.value;
-        types.set_value(get_or_insert(Memory, types.value)+/*skip refcount*/1);
-        drop_from_type(types, "address");
-        drop_from_type(types, "shared");
-        // }
-        abandon(address, size_of(types)+/*refcount*/1);
-        // clear the address
-        trace(9999, "mem") << "resetting location " << address_location << end();
-        put(Memory, address_location, 0);
-        break;
-      }
-
-      case TO_LOCATION_ARRAY: {
-        long long int array_size = SIZE(ingredients.at(0));
-        long long int allocation_size = array_size + /*refcount*/1 + /*length*/1;
-        ensure_space(allocation_size);
-        const long long int result = Current_routine->alloc;
-        products.resize(1);
-        products.at(0).push_back(result);
-        // initialize array refcount
-        put(Memory, result, 0);
-        // initialize array length
-        put(Memory, result+1, array_size);
-        // now copy over data
-        for (long long int i = 0; i < array_size; ++i)
-          put(Memory, result+2+i, ingredients.at(0).at(i));
-        break;
-      }
-
-      case RUN: {
-        assert(Name[Next_recipe_ordinal].empty());
-        ostringstream tmp;
-        tmp << "recipe run_" << Next_recipe_ordinal << " [ " << current_instruction().ingredients.at(0).name << " ]";
-      //?   cerr << tmp.str() << '\n';
-      //?   cerr << "before load\n";
-        vector<recipe_ordinal> tmp_recipe = load(tmp.str());
-      //?   cerr << "before bind\n";
-        bind_special_scenario_names(tmp_recipe.at(0));
-      //?   cerr << "before transform\n";
-        transform_all();
-        // There's a restriction on the number of variables 'run' can use, so that
-        // it can avoid colliding with the dynamic allocator in case it doesn't
-        // initialize a default-space.
-        assert(Name[tmp_recipe.at(0)][""] < Max_variables_in_scenarios);
-
-      //?   cerr << "end\n";
-        if (Trace_stream) {
-          ++Trace_stream->callstack_depth;
-          trace(9998, "trace") << "run: incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-          assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-        }
-        Current_routine->calls.push_front(call(tmp_recipe.at(0)));
-        continue;  // not done with caller; don't increment current_step_index()
-      }
-
-      // Some variables for fake resources always get special addresses in
-      // scenarios.
-      case MEMORY_SHOULD_CONTAIN: {
-        if (!Passed) break;
-        check_memory(current_instruction().ingredients.at(0).name);
-        break;
-      }
-
-      case TRACE_SHOULD_CONTAIN: {
-        if (!Passed) break;
-        check_trace(current_instruction().ingredients.at(0).name);
-        break;
-      }
-
-      case TRACE_SHOULD_NOT_CONTAIN: {
-        if (!Passed) break;
-        check_trace_missing(current_instruction().ingredients.at(0).name);
-        break;
-      }
-
-      case CHECK_TRACE_COUNT_FOR_LABEL: {
-        if (!Passed) break;
-        long long int expected_count = ingredients.at(0).at(0);
-        string label = current_instruction().ingredients.at(1).name;
-        long long int count = trace_count(label);
-        if (count != expected_count) {
-          if (Current_scenario && !Scenario_testing_scenario) {
-            // genuine test in a mu file
-            raise_error << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace: ";
-            DUMP(label);
-            raise_error;
-          }
-          else {
-            // just testing scenario support
-            raise_error << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace\n" << end();
-          }
-          if (!Scenario_testing_scenario) {
-            Passed = false;
-            ++Num_failures;
-          }
-        }
-        break;
-      }
-
-      case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: {
-        assert(!Current_routine->calls.empty());
-        if (current_call().next_ingredient_to_process < SIZE(current_call().ingredient_atoms)) {
-          products.push_back(
-              current_call().ingredient_atoms.at(current_call().next_ingredient_to_process));
-          assert(SIZE(products) == 1);  products.resize(2);  // push a new vector
-          products.at(1).push_back(1);
-          ++current_call().next_ingredient_to_process;
-        }
-        else {
-          products.resize(2);
-          // pad the first product with sufficient zeros to match its type
-          long long int size = size_of(current_instruction().products.at(0));
-          for (long long int i = 0; i < size; ++i)
-            products.at(0).push_back(0);
-          products.at(1).push_back(0);
-        }
-        break;
-      }
-
-
-      case CALL: {
-        // Begin Call
-        if (Trace_stream) {
-          ++Trace_stream->callstack_depth;
-          trace("trace") << "indirect 'call': incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-          assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-        }
-        const instruction& caller_instruction = current_instruction();
-        Current_routine->calls.push_front(call(ingredients.at(0).at(0)));
-        ingredients.erase(ingredients.begin());  // drop the callee
-        finish_call_housekeeping(caller_instruction, ingredients);
-        continue;
-      }
-
-
-      case START_RUNNING: {
-        routine* new_routine = new routine(ingredients.at(0).at(0));
-        new_routine->parent_index = Current_routine_index;
-        // populate ingredients
-        for (long long int i = 1; i < SIZE(current_instruction().ingredients); ++i) {
-          new_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i));
-          reagent ingredient = current_instruction().ingredients.at(i);
-          canonize_type(ingredient);
-          new_routine->calls.front().ingredients.push_back(ingredient);
-        }
-        Routines.push_back(new_routine);
-        products.resize(1);
-        products.at(0).push_back(new_routine->id);
-        break;
-      }
-
-      case ROUTINE_STATE: {
-        long long int id = ingredients.at(0).at(0);
-        long long int result = -1;
-        for (long long int i = 0; i < SIZE(Routines); ++i) {
-          if (Routines.at(i)->id == id) {
-            result = Routines.at(i)->state;
-            break;
-          }
-        }
-        products.resize(1);
-        products.at(0).push_back(result);
-        break;
-      }
-
-
-      case RESTART: {
-        long long int id = ingredients.at(0).at(0);
-        for (long long int i = 0; i < SIZE(Routines); ++i) {
-          if (Routines.at(i)->id == id) {
-            Routines.at(i)->state = RUNNING;
-            break;
-          }
-        }
-        break;
-      }
-
-      case STOP: {
-        long long int id = ingredients.at(0).at(0);
-        for (long long int i = 0; i < SIZE(Routines); ++i) {
-          if (Routines.at(i)->id == id) {
-            Routines.at(i)->state = COMPLETED;
-            break;
-          }
-        }
-        break;
-      }
-
-      case _DUMP_ROUTINES: {
-        for (long long int i = 0; i < SIZE(Routines); ++i) {
-          cerr << i << ": " << Routines.at(i)->id << ' ' << Routines.at(i)->state << ' ' << Routines.at(i)->parent_index << '\n';
-        }
-        break;
-      }
-
-
-      case LIMIT_TIME: {
-        long long int id = ingredients.at(0).at(0);
-        for (long long int i = 0; i < SIZE(Routines); ++i) {
-          if (Routines.at(i)->id == id) {
-            Routines.at(i)->limit = ingredients.at(1).at(0);
-            break;
-          }
-        }
-        break;
-      }
-
-
-      case WAIT_FOR_LOCATION: {
-        reagent loc = current_instruction().ingredients.at(0);
-        canonize(loc);
-        Current_routine->state = WAITING;
-        Current_routine->waiting_on_location = loc.value;
-        Current_routine->old_value_of_waiting_location = get_or_insert(Memory, loc.value);
-        trace(9998, "run") << "waiting for location " << loc.value << " to change from " << no_scientific(get_or_insert(Memory, loc.value)) << end();
-        break;
-      }
-
-
-      case WAIT_FOR_ROUTINE: {
-        if (ingredients.at(0).at(0) == Current_routine->id) {
-          raise_error << maybe(current_recipe_name()) << "routine can't wait for itself! " << to_string(current_instruction()) << '\n' << end();
-          break;
-        }
-        Current_routine->state = WAITING;
-        Current_routine->waiting_on_routine = ingredients.at(0).at(0);
-        trace(9998, "run") << "waiting for routine " << ingredients.at(0).at(0) << end();
-        break;
-      }
-
-      case SWITCH: {
-        long long int id = some_other_running_routine();
-        if (id) {
-          assert(id != Current_routine->id);
-          Current_routine->state = WAITING;
-          Current_routine->waiting_on_routine = id;
-        }
-        break;
-      }
-
-      case RANDOM: {
-        // todo: limited range of numbers, might be imperfectly random
-        // todo: thread state in extra ingredients and products
-        products.resize(1);
-        products.at(0).push_back(rand());
-        break;
-      }
-
-      case MAKE_RANDOM_NONDETERMINISTIC: {
-        srand(time(NULL));
-        break;
-      }
-
-      case ROUND: {
-        products.resize(1);
-        products.at(0).push_back(rint(ingredients.at(0).at(0)));
-        break;
-      }
-
-      case HASH: {
-        reagent input = current_instruction().ingredients.at(0);  // copy
-        products.resize(1);
-        products.at(0).push_back(hash(0, input));
-        break;
-      }
-
-
-      case HASH_OLD: {
-        string input = read_mu_string(ingredients.at(0).at(0));
-        size_t h = 0 ;
-
-        for (long long int i = 0; i < SIZE(input); ++i) {
-          h += static_cast<size_t>(input.at(i));
-          h += (h<<10);
-          h ^= (h>>6);
-
-          h += (h<<3);
-          h ^= (h>>11);
-          h += (h<<15);
-        }
-
-        products.resize(1);
-        products.at(0).push_back(h);
-        break;
-      }
-
-      case OPEN_CONSOLE: {
-        tb_init();
-        Display_row = Display_column = 0;
-        long long int width = tb_width();
-        long long int height = tb_height();
-        if (width > 222 || height > 222) tb_shutdown();
-        if (width > 222)
-          raise_error << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end();
-        if (height > 222)
-          raise_error << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end();
-        break;
-      }
-
-      case CLOSE_CONSOLE: {
-        tb_shutdown();
-        break;
-      }
-
-      case CLEAR_DISPLAY: {
-        tb_clear();
-        Display_row = Display_column = 0;
-        break;
-      }
-
-      case SYNC_DISPLAY: {
-        tb_sync();
-        break;
-      }
-
-      case CLEAR_LINE_ON_DISPLAY: {
-        long long int width = tb_width();
-        for (long long int x = Display_column; x < width; ++x) {
-          tb_change_cell(x, Display_row, ' ', TB_WHITE, TB_BLACK);
-        }
-        tb_set_cursor(Display_column, Display_row);
-        if (Autodisplay) tb_present();
-        break;
-      }
-
-      case PRINT_CHARACTER_TO_DISPLAY: {
-        int h=tb_height(), w=tb_width();
-        long long int height = (h >= 0) ? h : 0;
-        long long int width = (w >= 0) ? w : 0;
-        long long int c = ingredients.at(0).at(0);
-        int color = TB_BLACK;
-        if (SIZE(ingredients) > 1) {
-          color = ingredients.at(1).at(0);
-        }
-        int bg_color = TB_BLACK;
-        if (SIZE(ingredients) > 2) {
-          bg_color = ingredients.at(2).at(0);
-          if (bg_color == 0) bg_color = TB_BLACK;
-        }
-        tb_change_cell(Display_column, Display_row, c, color, bg_color);
-        if (c == '\n' || c == '\r') {
-          if (Display_row < height-1) {
-            Display_column = 0;
-            ++Display_row;
-            tb_set_cursor(Display_column, Display_row);
-            if (Autodisplay) tb_present();
-          }
-          break;
-        }
-        if (c == '\b') {
-          if (Display_column > 0) {
-            tb_change_cell(Display_column-1, Display_row, ' ', color, bg_color);
-            --Display_column;
-            tb_set_cursor(Display_column, Display_row);
-            if (Autodisplay) tb_present();
-          }
-          break;
-        }
-        if (Display_column < width-1) {
-          ++Display_column;
-          tb_set_cursor(Display_column, Display_row);
-        }
-        if (Autodisplay) tb_present();
-        break;
-      }
-
-      case CURSOR_POSITION_ON_DISPLAY: {
-        products.resize(2);
-        products.at(0).push_back(Display_row);
-        products.at(1).push_back(Display_column);
-        break;
-      }
-
-      case MOVE_CURSOR_ON_DISPLAY: {
-        Display_row = ingredients.at(0).at(0);
-        Display_column = ingredients.at(1).at(0);
-        tb_set_cursor(Display_column, Display_row);
-        if (Autodisplay) tb_present();
-        break;
-      }
-
-      case MOVE_CURSOR_DOWN_ON_DISPLAY: {
-        int h=tb_height();
-        long long int height = (h >= 0) ? h : 0;
-        if (Display_row < height-1) {
-          Display_row++;
-          tb_set_cursor(Display_column, Display_row);
-          if (Autodisplay) tb_present();
-        }
-        break;
-      }
-
-      case MOVE_CURSOR_UP_ON_DISPLAY: {
-        if (Display_row > 0) {
-          Display_row--;
-          tb_set_cursor(Display_column, Display_row);
-          if (Autodisplay) tb_present();
-        }
-        break;
-      }
-
-      case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
-        int w=tb_width();
-        long long int width = (w >= 0) ? w : 0;
-        if (Display_column < width-1) {
-          Display_column++;
-          tb_set_cursor(Display_column, Display_row);
-          if (Autodisplay) tb_present();
-        }
-        break;
-      }
-
-      case MOVE_CURSOR_LEFT_ON_DISPLAY: {
-        if (Display_column > 0) {
-          Display_column--;
-          tb_set_cursor(Display_column, Display_row);
-          if (Autodisplay) tb_present();
-        }
-        break;
-      }
-
-      case DISPLAY_WIDTH: {
-        products.resize(1);
-        products.at(0).push_back(tb_width());
-        break;
-      }
-
-      case DISPLAY_HEIGHT: {
-        products.resize(1);
-        products.at(0).push_back(tb_height());
-        break;
-      }
-
-      case HIDE_CURSOR_ON_DISPLAY: {
-        tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR);
-        break;
-      }
-
-      case SHOW_CURSOR_ON_DISPLAY: {
-        tb_set_cursor(Display_row, Display_column);
-        break;
-      }
-
-      case HIDE_DISPLAY: {
-        Autodisplay = false;
-        break;
-      }
-
-      case SHOW_DISPLAY: {
-        Autodisplay = true;
-        tb_present();
-        break;
-      }
-
-
-      case WAIT_FOR_SOME_INTERACTION: {
-        tb_event event;
-        tb_poll_event(&event);
-        break;
-      }
-
-      case CHECK_FOR_INTERACTION: {
-        products.resize(2);  // result and status
-        tb_event event;
-        int event_type = tb_peek_event(&event, 5/*ms*/);
-        if (event_type == TB_EVENT_KEY && event.ch) {
-          products.at(0).push_back(/*text event*/0);
-          products.at(0).push_back(event.ch);
-          products.at(0).push_back(0);
-          products.at(0).push_back(0);
-          products.at(1).push_back(/*found*/true);
-          break;
-        }
-        // treat keys within ascii as unicode characters
-        if (event_type == TB_EVENT_KEY && event.key < 0xff) {
-          products.at(0).push_back(/*text event*/0);
-          if (event.key == TB_KEY_CTRL_C) {
-            tb_shutdown();
-            exit(1);
-          }
-          if (event.key == TB_KEY_BACKSPACE2) event.key = TB_KEY_BACKSPACE;
-          if (event.key == TB_KEY_CARRIAGE_RETURN) event.key = TB_KEY_NEWLINE;
-          products.at(0).push_back(event.key);
-          products.at(0).push_back(0);
-          products.at(0).push_back(0);
-          products.at(1).push_back(/*found*/true);
-          break;
-        }
-        // keys outside ascii aren't unicode characters but arbitrary termbox inventions
-        if (event_type == TB_EVENT_KEY) {
-          products.at(0).push_back(/*keycode event*/1);
-          products.at(0).push_back(event.key);
-          products.at(0).push_back(0);
-          products.at(0).push_back(0);
-          products.at(1).push_back(/*found*/true);
-          break;
-        }
-        if (event_type == TB_EVENT_MOUSE) {
-          products.at(0).push_back(/*touch event*/2);
-          products.at(0).push_back(event.key);  // which button, etc.
-          products.at(0).push_back(event.y);  // row
-          products.at(0).push_back(event.x);  // column
-          products.at(1).push_back(/*found*/true);
-          break;
-        }
-        if (event_type == TB_EVENT_RESIZE) {
-          products.at(0).push_back(/*resize event*/3);
-          products.at(0).push_back(event.w);  // width
-          products.at(0).push_back(event.h);  // height
-          products.at(0).push_back(0);
-          products.at(1).push_back(/*found*/true);
-          break;
-        }
-        assert(event_type == 0);
-        products.at(0).push_back(0);
-        products.at(0).push_back(0);
-        products.at(0).push_back(0);
-        products.at(0).push_back(0);
-        products.at(1).push_back(/*found*/false);
-        break;
-      }
-
-      case INTERACTIONS_LEFT: {
-        products.resize(1);
-        products.at(0).push_back(tb_event_ready());
-        break;
-      }
-
-
-      case CLEAR_DISPLAY_FROM: {
-        // todo: error checking
-        int row = ingredients.at(0).at(0);
-        int column = ingredients.at(1).at(0);
-        int left = ingredients.at(2).at(0);
-        int right = ingredients.at(3).at(0);
-        int height=tb_height();
-        for (; row < height; ++row, column=left) {  // start column from left in every inner loop except first
-          for (; column <= right; ++column) {
-            tb_change_cell(column, row, ' ', TB_WHITE, TB_BLACK);
-          }
-        }
-        if (Autodisplay) tb_present();
-        break;
-      }
-
-      case SCREEN_SHOULD_CONTAIN: {
-      //?   cerr << SIZE(get(Recipe_variants, "insert")) << '\n';
-      //?   cerr << debug_string(get(Recipe, get(Recipe_ordinal, "insert_4"))) << '\n';
-        if (!Passed) break;
-        assert(scalar(ingredients.at(0)));
-        check_screen(current_instruction().ingredients.at(0).name, -1);
-        break;
-      }
-
-      case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
-        if (!Passed) break;
-        assert(scalar(ingredients.at(0)));
-        assert(scalar(ingredients.at(1)));
-        check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
-        break;
-      }
-
-      case _DUMP_SCREEN: {
-        dump_screen();
-        break;
-      }
-
-      case ASSUME_CONSOLE: {
-        // create a temporary recipe just for parsing; it won't contain valid instructions
-        istringstream in("[" + current_instruction().ingredients.at(0).name + "]");
-        recipe r;
-        slurp_body(in, r);
-        long long int num_events = count_events(r);
-        // initialize the events like in new-fake-console
-        long long int size = /*space for refcount*/1 + /*space for length*/1 + num_events*size_of_event();
-        ensure_space(size);
-        long long int event_data_address = Current_routine->alloc;
-        // store length
-        put(Memory, Current_routine->alloc+/*skip refcount*/1, num_events);
-        Current_routine->alloc += /*skip refcount and length*/2;
-        for (long long int i = 0; i < SIZE(r.steps); ++i) {
-          const instruction& curr = r.steps.at(i);
-          if (curr.name == "left-click") {
-            trace(9999, "mem") << "storing 'left-click' event starting at " << Current_routine->alloc << end();
-            put(Memory, Current_routine->alloc, /*tag for 'touch-event' variant of 'event' exclusive-container*/2);
-            put(Memory, Current_routine->alloc+1+/*offset of 'type' in 'mouse-event'*/0, TB_KEY_MOUSE_LEFT);
-            put(Memory, Current_routine->alloc+1+/*offset of 'row' in 'mouse-event'*/1, to_integer(curr.ingredients.at(0).name));
-            put(Memory, Current_routine->alloc+1+/*offset of 'column' in 'mouse-event'*/2, to_integer(curr.ingredients.at(1).name));
-            Current_routine->alloc += size_of_event();
-          }
-          else if (curr.name == "press") {
-            trace(9999, "mem") << "storing 'press' event starting at " << Current_routine->alloc << end();
-            string key = curr.ingredients.at(0).name;
-            if (is_integer(key))
-              put(Memory, Current_routine->alloc+1, to_integer(key));
-            else if (contains_key(Key, key))
-              put(Memory, Current_routine->alloc+1, Key[key]);
-            else
-              raise_error << "assume-console: can't press " << key << '\n' << end();
-            if (get_or_insert(Memory, Current_routine->alloc+1) < 256)
-              // these keys are in ascii
-              put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
-            else {
-              // distinguish from unicode
-              put(Memory, Current_routine->alloc, /*tag for 'keycode' variant of 'event' exclusive-container*/1);
-            }
-            Current_routine->alloc += size_of_event();
-          }
-          // End Event Handlers
-          else {
-            // keyboard input
-            assert(curr.name == "type");
-            trace(9999, "mem") << "storing 'type' event starting at " << Current_routine->alloc << end();
-            const string& contents = curr.ingredients.at(0).name;
-            const char* raw_contents = contents.c_str();
-            long long int num_keyboard_events = unicode_length(contents);
-            long long int curr = 0;
-            for (long long int i = 0; i < num_keyboard_events; ++i) {
-              trace(9999, "mem") << "storing 'text' tag at " << Current_routine->alloc << end();
-              put(Memory, Current_routine->alloc, /*tag for 'text' variant of 'event' exclusive-container*/0);
-              uint32_t curr_character;
-              assert(curr < SIZE(contents));
-              tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-              trace(9999, "mem") << "storing character " << curr_character << " at " << Current_routine->alloc+1 << end();
-              put(Memory, Current_routine->alloc+/*skip exclusive container tag*/1, curr_character);
-              curr += tb_utf8_char_length(raw_contents[curr]);
-              Current_routine->alloc += size_of_event();
-            }
-          }
-        }
-        assert(Current_routine->alloc == event_data_address+size);
-        // wrap the array of events in a console object
-        ensure_space(size_of_console());
-        put(Memory, CONSOLE, Current_routine->alloc);
-        trace(9999, "mem") << "storing console in " << Current_routine->alloc << end();
-        Current_routine->alloc += size_of_console();
-        long long int console_address = get_or_insert(Memory, CONSOLE);
-        trace(9999, "mem") << "storing console data in " << console_address+2 << end();
-        put(Memory, console_address+/*skip refcount*/1+/*offset of 'data' in container 'events'*/1, event_data_address);
-        break;
-      }
-
-      case REPLACE_IN_CONSOLE: {
-        assert(scalar(ingredients.at(0)));
-        if (!get_or_insert(Memory, CONSOLE)) {
-          raise_error << "console not initialized\n" << end();
-          break;
-        }
-        long long int console_address = get_or_insert(Memory, CONSOLE);
-        long long int console_data = get_or_insert(Memory, console_address+1);
-        long long int size = get_or_insert(Memory, console_data);  // array size
-        for (long long int i = 0, curr = console_data+1; i < size; ++i, curr+=size_of_event()) {
-          if (get_or_insert(Memory, curr) != /*text*/0) continue;
-          if (get_or_insert(Memory, curr+1) != ingredients.at(0).at(0)) continue;
-          for (long long int n = 0; n < size_of_event(); ++n)
-            put(Memory, curr+n, ingredients.at(1).at(n));
-        }
-        break;
-      }
-
-      case _BROWSE_TRACE: {
-        start_trace_browser();
-        break;
-      }
-
-      case RUN_INTERACTIVE: {
-        bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
-        if (!new_code_pushed_to_stack) {
-          products.resize(5);
-          products.at(0).push_back(0);
-          products.at(1).push_back(trace_error_warning_contents());
-          products.at(2).push_back(0);
-          products.at(3).push_back(trace_app_contents());
-          products.at(4).push_back(1);  // completed
-          run_code_end();
-          break;  // done with this instruction
-        }
-        else {
-          continue;  // not done with caller; don't increment current_step_index()
-        }
-      }
-
-      case _START_TRACKING_PRODUCTS: {
-        Track_most_recent_products = true;
-        break;
-      }
-
-      case _STOP_TRACKING_PRODUCTS: {
-        Track_most_recent_products = false;
-        break;
-      }
-
-      case _MOST_RECENT_PRODUCTS: {
-        products.resize(1);
-        products.at(0).push_back(new_mu_string(Most_recent_products));
-        break;
-      }
-
-      case SAVE_ERRORS_WARNINGS: {
-        products.resize(1);
-        products.at(0).push_back(trace_error_warning_contents());
-        break;
-      }
-
-      case SAVE_APP_TRACE: {
-        products.resize(1);
-        products.at(0).push_back(trace_app_contents());
-        break;
-      }
-
-      case _CLEANUP_RUN_INTERACTIVE: {
-        run_code_end();
-        break;
-      }
-
-      case RELOAD: {
-      //?   cerr << "== reload\n";
-        // clear any containers in advance
-        for (long long int i = 0; i < SIZE(Recently_added_types); ++i) {
-          if (!contains_key(Type, Recently_added_types.at(i))) continue;
-          Type_ordinal.erase(get(Type, Recently_added_types.at(i)).name);
-          Type.erase(Recently_added_types.at(i));
-        }
-        for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) {
-      //?     cerr << p->first << ":\n";
-          vector<recipe_ordinal>& variants = p->second;
-          for (long long int i = 0; i < SIZE(p->second); ++i) {
-            if (variants.at(i) == -1) continue;
-            if (find(Recently_added_shape_shifting_recipes.begin(), Recently_added_shape_shifting_recipes.end(), variants.at(i)) != Recently_added_shape_shifting_recipes.end()) {
-      //?         cerr << "  " << variants.at(i) << ' ' << get(Recipe, variants.at(i)).name << '\n';
-              variants.at(i) = -1;  // ghost
-            }
-          }
-        }
-        for (long long int i = 0; i < SIZE(Recently_added_shape_shifting_recipes); ++i) {
-          Recipe_ordinal.erase(get(Recipe, Recently_added_shape_shifting_recipes.at(i)).name);
-          Recipe.erase(Recently_added_shape_shifting_recipes.at(i));
-        }
-        Recently_added_shape_shifting_recipes.clear();
-        string code = read_mu_string(ingredients.at(0).at(0));
-        run_code_begin(/*snapshot_recently_added_recipes*/false);
-        routine* save_current_routine = Current_routine;
-        Current_routine = NULL;
-        vector<recipe_ordinal> recipes_reloaded = load(code);
-        // clear a few things from previous runs
-        // ad hoc list; we've probably missed a few
-        for (long long int i = 0; i < SIZE(recipes_reloaded); ++i)
-          Name.erase(recipes_reloaded.at(i));
-        transform_all();
-        Trace_stream->newline();  // flush trace
-        Current_routine = save_current_routine;
-        products.resize(1);
-        products.at(0).push_back(trace_error_warning_contents());
-        run_code_end();  // wait until we're done with the trace contents
-      //?   cerr << "reload done\n";
-        break;
-      }
-
-      case RESTORE: {
-        string filename;
-        if (is_literal_string(current_instruction().ingredients.at(0))) {
-          filename = current_instruction().ingredients.at(0).name;
-        }
-        else if (is_mu_string(current_instruction().ingredients.at(0))) {
-          filename = read_mu_string(ingredients.at(0).at(0));
-        }
-        if (Current_scenario) {
-          // do nothing in tests
-          products.resize(1);
-          products.at(0).push_back(0);
-          break;
-        }
-        string contents = slurp("lesson/"+filename);
-        products.resize(1);
-        if (contents.empty())
-          products.at(0).push_back(0);
-        else
-          products.at(0).push_back(new_mu_string(contents));
-        break;
-      }
-
-      case SAVE: {
-        if (Current_scenario) break;  // do nothing in tests
-        string filename;
-        if (is_literal_string(current_instruction().ingredients.at(0))) {
-          filename = current_instruction().ingredients.at(0).name;
-        }
-        else if (is_mu_string(current_instruction().ingredients.at(0))) {
-          filename = read_mu_string(ingredients.at(0).at(0));
-        }
-        ofstream fout(("lesson/"+filename).c_str());
-        string contents = read_mu_string(ingredients.at(1).at(0));
-        fout << contents;
-        fout.close();
-        if (!exists("lesson/.git")) break;
-        // bug in git: git diff -q messes up --exit-code
-        // explicitly say '--all' for git 1.9
-        int status = system("cd lesson; git add --all .; git diff HEAD --exit-code >/dev/null || git commit -a -m . >/dev/null");
-        if (status != 0)
-          raise_error << "error in commit: contents " << contents << '\n' << end();
-        break;
-      }
-
-      // End Primitive Recipe Implementations
-      default: {
-        const instruction& call_instruction = current_instruction();
-        if (Recipe.find(current_instruction().operation) == Recipe.end()) {  // duplicate from Checks
-          // stop running this instruction immediately
-          ++current_step_index();
-          continue;
-        }
-        // not a primitive; look up the book of recipes
-        if (Trace_stream) {
-          ++Trace_stream->callstack_depth;
-          trace(9999, "trace") << "incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-          assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-        }
-        Current_routine->calls.push_front(call(current_instruction().operation));
-        finish_call_housekeeping(call_instruction, ingredients);
-        continue;  // not done with caller; don't increment step_index of caller
-      }
-    }
-    if (SIZE(products) < SIZE(current_instruction().products)) {
-      raise_error << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products! " << to_string(current_instruction()) << '\n' << end();
-    }
-    else {
-      for (long long int i = 0; i < SIZE(current_instruction().products); ++i) {
-        write_memory(current_instruction().products.at(i), products.at(i));
-      }
-    }
-    if (Track_most_recent_products) {
-      track_most_recent_products(current_instruction(), products);
-    }
-    // End of Instruction
-    ++current_step_index();
-  }
-  stop_running_current_routine:;
-}
-
-bool should_copy_ingredients() {
-  recipe_ordinal r = current_instruction().operation;
-  if (r == CREATE_ARRAY || r == INDEX || r == INDEX_ADDRESS || r == LENGTH)
-    return false;
-
-  // End should_copy_ingredients Special-cases
-  return true;
-}
-
-
-inline long long int& current_step_index() {
-  assert(!Current_routine->calls.empty());
-  return current_call().running_step_index;
-}
-
-inline const string& current_recipe_name() {
-  assert(!Current_routine->calls.empty());
-  return get(Recipe, current_call().running_recipe).name;
-}
-
-inline const instruction& current_instruction() {
-  assert(!Current_routine->calls.empty());
-  return to_instruction(current_call());
-}
-
-inline bool routine::completed() const {
-  return calls.empty();
-}
-
-inline const vector<instruction>& routine::steps() const {
-  assert(!calls.empty());
-  return get(Recipe, calls.front().running_recipe).steps;
-}
-
-
-
-void run_main(int argc, char* argv[]) {
-  recipe_ordinal r = get(Recipe_ordinal, "main");
-  assert(r);
-  routine* main_routine = new routine(r);
-  // pass in commandline args as ingredients to main
-  // todo: test this
-  Current_routine = main_routine;
-  for (long long int i = 1; i < argc; ++i) {
-    vector<double> arg;
-    arg.push_back(new_mu_string(argv[i]));
-    current_call().ingredient_atoms.push_back(arg);
-  }
-  run(main_routine);
-}
-
-
-
-void dump_profile() {
-  for (map<string, long long int>::iterator p = Instructions_running.begin(); p != Instructions_running.end(); ++p) {
-    cerr << p->first << ": " << p->second << '\n';
-  }
-  cerr << "== locations read\n";
-  for (map<string, long long int>::iterator p = Locations_read.begin(); p != Locations_read.end(); ++p) {
-    cerr << p->first << ": " << p->second << '\n';
-  }
-  cerr << "== locations read by instruction\n";
-  for (map<string, long long int>::iterator p = Locations_read_by_instruction.begin(); p != Locations_read_by_instruction.end(); ++p) {
-    cerr << p->first << ": " << p->second << '\n';
-  }
-}
-void cleanup_main() {
-  if (!Trace_file.empty() && Trace_stream) {
-    ofstream fout((Trace_dir+Trace_file).c_str());
-    fout << Trace_stream->readable_contents("");
-    fout.close();
-  }
-}
-void load_permanently(string filename) {
-  if (is_directory(filename)) {
-    load_all_permanently(filename);
-    return;
-  }
-  ifstream fin(filename.c_str());
-  fin.peek();
-  if (!fin) {
-    raise_error << "no such file " << filename << '\n' << end();
-    return;
-  }
-  fin >> std::noskipws;
-  trace(9990, "load") << "=== " << filename << end();
-  load(fin);
-  fin.close();
-  // freeze everything so it doesn't get cleared by tests
-  Recently_added_recipes.clear();
-  Recently_added_types.clear();
-  // End load_permanently.
-}
-
-bool is_directory(string path) {
-  struct stat info;
-  if (stat(path.c_str(), &info)) return false;  // error
-  return info.st_mode & S_IFDIR;
-}
-
-void load_all_permanently(string dir) {
-  dirent** files;
-  int num_files = scandir(dir.c_str(), &files, NULL, alphasort);
-  for (int i = 0; i < num_files; ++i) {
-    string curr_file = files[i]->d_name;
-    if (!isdigit(curr_file.at(0))) continue;
-    load_permanently(dir+'/'+curr_file);
-    free(files[i]);
-    files[i] = NULL;
-  }
-  free(files);
-}
-vector<double> read_memory(reagent x) {
-  if (x.name == "number-of-locals") {
-    vector<double> result;
-    result.push_back(Name[get(Recipe_ordinal, current_recipe_name())][""]);
-    if (result.back() == 0)
-      raise_error << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names?\n" << end();
-    return result;
-  }
-  if (x.name == "default-space") {
-    vector<double> result;
-    result.push_back(current_call().default_space);
-    return result;
-  }
-
-
-  vector<double> result;
-  if (is_literal(x)) {
-    result.push_back(x.value);
-    return result;
-  }
-  canonize(x);
-
-  long long int base = x.value;
-  long long int size = size_of(x);
-  for (long long int offset = 0; offset < size; ++offset) {
-    double val = get_or_insert(Memory, base+offset);
-    trace(9999, "mem") << "location " << base+offset << " is " << no_scientific(val) << end();
-    result.push_back(val);
-  }
-  return result;
-}
-
-void write_memory(reagent x, vector<double> data) {
-  if (x.name == "global-space") {
-    if (!scalar(data)
-        || !x.type
-        || x.type->value != get(Type_ordinal, "address")
-        || !x.type->right
-        || x.type->right->value != get(Type_ordinal, "shared")
-        || !x.type->right->right
-        || x.type->right->right->value != get(Type_ordinal, "array")
-        || !x.type->right->right->right
-        || x.type->right->right->right->value != get(Type_ordinal, "location")
-        || x.type->right->right->right->right) {
-      raise_error << maybe(current_recipe_name()) << "'global-space' should be of type address:shared:array:location, but tried to write " << to_string(data) << '\n' << end();
-    }
-    if (Current_routine->global_space)
-      raise_error << "routine already has a global-space; you can't over-write your globals" << end();
-    Current_routine->global_space = data.at(0);
-    return;
-  }
-
-  if (x.name == "number-of-locals") {
-    raise_error << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
-    return;
-  }
-
-
-  if (x.name == "default-space") {
-    if (!scalar(data)
-        || !x.type
-        || x.type->value != get(Type_ordinal, "address")
-        || !x.type->right
-        || x.type->right->value != get(Type_ordinal, "shared")
-        || !x.type->right->right
-        || x.type->right->right->value != get(Type_ordinal, "array")
-        || !x.type->right->right->right
-        || x.type->right->right->right->value != get(Type_ordinal, "location")
-        || x.type->right->right->right->right) {
-      raise_error << maybe(current_recipe_name()) << "'default-space' should be of type address:shared:array:location, but tried to write " << to_string(data) << '\n' << end();
-    }
-    current_call().default_space = data.at(0);
-    return;
-  }
-
-  if (!x.type) {
-    raise_error << "can't write to " << to_string(x) << "; no type\n" << end();
-    return;
-  }
-  if (is_dummy(x)) return;
-  if (is_literal(x)) return;
-  canonize(x);
-  if (x.value == 0) {
-    raise_error << "can't write to location 0 in '" << to_string(current_instruction()) << "'\n" << end();
-    return;
-  }
-
-  long long int base = x.value;
-  if (base == 0) return;
-  if (size_mismatch(x, data)) {
-    raise_error << maybe(current_recipe_name()) << "size mismatch in storing to " << x.original_string << " (" << size_of(x.type) << " vs " << SIZE(data) << ") at '" << to_string(current_instruction()) << "'\n" << end();
-    return;
-  }
-  if (x.type->value == get(Type_ordinal, "address")
-      && x.type->right
-      && x.type->right->value == get(Type_ordinal, "shared")) {
-    // compute old address of x, as well as new address we want to write in
-    long long int old_address = get_or_insert(Memory, x.value);
-    assert(scalar(data));
-    long long int new_address = data.at(0);
-    // decrement refcount of old address
-    if (old_address) {
-      long long int old_refcount = get_or_insert(Memory, old_address);
-      trace(9999, "mem") << "decrementing refcount of " << old_address << ": " << old_refcount << " -> " << (old_refcount-1) << end();
-      put(Memory, old_address, old_refcount-1);
-    }
-    // perform the write
-    trace(9999, "mem") << "storing " << no_scientific(data.at(0)) << " in location " << base << end();
-    put(Memory, base, new_address);
-    // increment refcount of new address
-    if (new_address) {
-      long long int new_refcount = get_or_insert(Memory, new_address);
-      assert(new_refcount >= 0);  // == 0 only when new_address == old_address
-      trace(9999, "mem") << "incrementing refcount of " << new_address << ": " << new_refcount << " -> " << (new_refcount+1) << end();
-      put(Memory, new_address, new_refcount+1);
-    }
-    // abandon old address if necessary
-    // do this after all refcount updates are done just in case old and new are identical
-    assert(get_or_insert(Memory, old_address) >= 0);
-    if (old_address && get_or_insert(Memory, old_address) == 0) {
-      // lookup_memory without drop_one_lookup {
-      trace(9999, "mem") << "automatically abandoning " << old_address << end();
-      trace(9999, "mem") << "computing size to abandon at " << x.value << end();
-      x.set_value(get_or_insert(Memory, x.value)+/*skip refcount*/1);
-      drop_from_type(x, "address");
-      drop_from_type(x, "shared");
-      // }
-      abandon(old_address, size_of(x)+/*refcount*/1);
-    }
-    return;
-  }
-
-  // End write_memory(reagent x, long long int base) Special-cases
-  for (long long int offset = 0; offset < SIZE(data); ++offset) {
-    assert(base+offset > 0);
-    trace(9999, "mem") << "storing " << no_scientific(data.at(offset)) << " in location " << base+offset << end();
-    put(Memory, base+offset, data.at(offset));
-  }
-}
-
-long long int size_of(const reagent& r) {
-  if (r.type == NULL) return 0;
-  if (r.type && r.type->value == get(Type_ordinal, "array")) {
-    if (!r.type->right) {
-      raise_error << maybe(current_recipe_name()) << "'" << r.original_string << "' is an array of what?\n" << end();
-      return 1;
-    }
-  //?   trace(9999, "mem") << "computing size of array starting at " << r.value << end();
-    return 1 + get_or_insert(Memory, r.value)*size_of(array_element(r.type));
-  }
-
-
-  // End size_of(reagent) Cases
-  return size_of(r.type);
-}
-long long int size_of(const type_tree* type) {
-  if (type == NULL) return 0;
-  if (type->value == -1) {
-    // error value, but we'll raise it elsewhere
-    return 1;
-  }
-  if (type->value == 0) {
-    assert(!type->left && !type->right);
-    return 1;
-  }
-  if (!contains_key(Type, type->value)) {
-    raise_error << "no such type " << type->value << '\n' << end();
-    return 0;
-  }
-  type_info t = get(Type, type->value);
-  if (t.kind == CONTAINER) {
-    // size of a container is the sum of the sizes of its elements
-    long long int result = 0;
-    for (long long int i = 0; i < SIZE(t.elements); ++i) {
-      // todo: strengthen assertion to disallow mutual type recursion
-      if (t.elements.at(i).type->value == type->value) {
-        raise_error << "container " << t.name << " can't include itself as a member\n" << end();
-        return 0;
-      }
-      reagent tmp;
-      tmp.type = new type_tree(*type);
-      result += size_of(element_type(tmp, i));
-    }
-    return result;
-  }
-
-  if (t.kind == EXCLUSIVE_CONTAINER) {
-    // size of an exclusive container is the size of its largest variant
-    // (So like containers, it can't contain arrays.)
-    long long int result = 0;
-    for (long long int i = 0; i < t.size; ++i) {
-      reagent tmp;
-      tmp.type = new type_tree(*type);
-      long long int size = size_of(variant_type(tmp, i));
-      if (size > result) result = size;
-    }
-    // ...+1 for its tag.
-    return result+1;
-  }
-
-
-  // End size_of(type) Cases
-  return 1;
-}
-
-bool size_mismatch(const reagent& x, const vector<double>& data) {
-  if (x.type == NULL) return true;
-  if (x.type && x.type->value == get(Type_ordinal, "array")) return false;
-  if (current_instruction().operation == MERGE
-      && !current_instruction().products.empty()
-      && current_instruction().products.at(0).type) {
-    reagent x = current_instruction().products.at(0);
-    canonize(x);
-    if (get(Type, x.type->value).kind == EXCLUSIVE_CONTAINER) {
-      return size_of(x) < SIZE(data);
-    }
-  }
-
-  // End size_mismatch(x) Cases
-//?   if (size_of(x) != SIZE(data)) cerr << size_of(x) << " vs " << SIZE(data) << '\n';
-  return size_of(x) != SIZE(data);
-}
-
-inline bool is_dummy(const reagent& x) {
-  return x.name == "_";
-}
-
-inline bool is_literal(const reagent& r) {
-  if (!r.type) return false;
-  if (r.type->value == 0)
-    assert(!r.type->left && !r.type->right);
-  return r.type->value == 0;
-}
-
-inline bool scalar(const vector<long long int>& x) {
-  return SIZE(x) == 1;
-}
-inline bool scalar(const vector<double>& x) {
-  return SIZE(x) == 1;
-}
-
-// helper for tests
-void run(string form) {
-  vector<recipe_ordinal> tmp = load(form);
-  transform_all();
-  if (tmp.empty()) return;
-  if (trace_count("error") > 0) return;
-  run(tmp.front());
-}
-
-void test_run_label() {
-  Trace_file = "run_label";
-  run("recipe main [\n  +foo\n  1:number <- copy 23\n  2:number <- copy 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:number <- copy 23run: 2:number <- copy 1:number");
-  CHECK_TRACE_DOESNT_CONTAIN("run: +foo");
-}
-void test_run_dummy() {
-  Trace_file = "run_dummy";
-  run("recipe main [\n  _ <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("run: _ <- copy 0");
-}
-void test_write_to_0_disallowed() {
-  Trace_file = "write_to_0_disallowed";
-  Hide_errors = true;
-  run("recipe main [\n  0:number <- copy 34\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 0");
-}
-void test_comma_without_space() {
-  Trace_file = "comma_without_space";
-  run("recipe main [\n  1:number, 2:number <- copy 2,2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_space_without_comma() {
-  Trace_file = "space_without_comma";
-  run("recipe main [\n  1:number, 2:number <- copy 2 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_comma_before_space() {
-  Trace_file = "comma_before_space";
-  run("recipe main [\n  1:number, 2:number <- copy 2, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_comma_after_space() {
-  Trace_file = "comma_after_space";
-  run("recipe main [\n  1:number, 2:number <- copy 2 ,2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-
-void check_instruction(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- perform checks for recipe " << get(Recipe, r).name << end();
-//?   cerr << "--- perform checks for recipe " << get(Recipe, r).name << '\n';
-  map<string, vector<type_ordinal> > metadata;
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    instruction& inst = get(Recipe, r).steps.at(i);
-    if (inst.is_label) continue;
-    switch (inst.operation) {
-      // Primitive Recipe Checks
-      case COPY: {
-        if (SIZE(inst.products) != SIZE(inst.ingredients)) {
-          raise_error << "ingredients and products should match in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!types_coercible(inst.products.at(i), inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "can't copy " << inst.ingredients.at(i).original_string << " to " << inst.products.at(i).original_string << "; types don't match\n" << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case ADD: {
-        // primary goal of these checks is to forbid address arithmetic
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'add' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'add' yields exactly one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'add' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case SUBTRACT: {
-        if (inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'subtract' has no ingredients\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (is_raw(inst.ingredients.at(i))) continue;  // permit address offset computations in tests
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'subtract' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'subtract' yields exactly one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'subtract' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case MULTIPLY: {
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'multiply' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'multiply' yields exactly one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'multiply' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case DIVIDE: {
-        if (inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide' has no ingredients\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'divide' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide' yields exactly one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case DIVIDE_WITH_REMAINDER: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' yields two products in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.products); ++i) {
-          if (!is_dummy(inst.products.at(i)) && !is_mu_number(inst.products.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'divide-with-remainder' should yield a number, but got " << inst.products.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case SHIFT_LEFT: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-left' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-left' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-left' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-left' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case SHIFT_RIGHT: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-right' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-right' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-right' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'shift-right' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case AND_BITS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'and-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'and-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'and-bits' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'and-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case OR_BITS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'or-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'or-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'or-bits' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'or-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case XOR_BITS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'xor-bits' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'xor-bits' requires number ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'xor-bits' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'xor-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case FLIP_BITS: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'flip-bits' requires exactly one ingredient, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'flip-bits' requires a number ingredient, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (SIZE(inst.products) > 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'flip-bits' yields one product in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'flip-bits' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
-          goto finish_checking_instruction;
-        }
-        break;
-      }
-      case AND: {
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_scalar(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case OR: {
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_scalar(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case NOT: {
-        if (SIZE(inst.products) > SIZE(inst.ingredients)) {
-          raise_error << maybe(get(Recipe, r).name) << "'not' cannot have fewer ingredients than products in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_scalar(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'not' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case JUMP: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'jump' should be a label or offset, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case JUMP_IF: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-if' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case JUMP_UNLESS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'jump-unless' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case EQUAL: {
-        if (SIZE(inst.ingredients) <= 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        break;
-      }
-      case GREATER_THAN: {
-        if (SIZE(inst.ingredients) <= 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'greater-than' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'greater-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case LESSER_THAN: {
-        if (SIZE(inst.ingredients) <= 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'lesser-than' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'lesser-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case GREATER_OR_EQUAL: {
-        if (SIZE(inst.ingredients) <= 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'greater-or-equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'greater-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case LESSER_OR_EQUAL: {
-        if (SIZE(inst.ingredients) <= 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'lesser-or-equal' needs at least two ingredients to compare in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-          if (!is_mu_number(inst.ingredients.at(i))) {
-            raise_error << maybe(get(Recipe, r).name) << "'lesser-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
-            goto finish_checking_instruction;
-          }
-        }
-        break;
-      }
-      case TRACE: {
-        if (SIZE(inst.ingredients) < 3) {
-          raise_error << maybe(get(Recipe, r).name) << "'trace' takes three or more ingredients rather than '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'trace' should be a number (depth), but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_literal_string(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'trace' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case STASH: {
-        break;
-      }
-      case HIDE_ERRORS: {
-        break;
-      }
-      case SHOW_ERRORS: {
-        break;
-      }
-      case TRACE_UNTIL: {
-        break;
-      }
-      case _DUMP_TRACE: {
-        break;
-      }
-      case _CLEAR_TRACE: {
-        break;
-      }
-      case _SAVE_TRACE: {
-        break;
-      }
-      case ASSERT: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'assert' takes exactly two ingredients rather than '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_scalar(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'assert' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_literal_string(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "'assert' requires a literal string for its second ingredient, but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case _PRINT: {
-        break;
-      }
-      case _EXIT: {
-        break;
-      }
-      case _SYSTEM: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'$system' requires exactly one ingredient, but got none\n" << end();
-          break;
-        }
-        break;
-      }
-      case _DUMP_MEMORY: {
-        break;
-      }
-      case _LOG: {
-        break;
-      }
-      case GET: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'get' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent base = inst.ingredients.at(0);  // new copy for every invocation
-        // Update GET base in Check
-        if (!canonize_type(base)) break;
-        if (!base.type || !base.type->value || !contains_key(Type, base.type->value) || get(Type, base.type->value).kind != CONTAINER) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'get' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        type_ordinal base_type = base.type->value;
-        reagent offset = inst.ingredients.at(1);
-        if (!is_literal(offset) || !is_mu_scalar(offset)) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        long long int offset_value = 0;
-        if (is_integer(offset.name))  // later layers permit non-integer offsets
-          offset_value = to_integer(offset.name);
-        else
-          offset_value = offset.value;
-        if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type).elements)) {
-          raise_error << maybe(get(Recipe, r).name) << "invalid offset " << offset_value << " for " << get(Type, base_type).name << '\n' << end();
-          break;
-        }
-        if (inst.products.empty()) break;
-        reagent product = inst.products.at(0);
-        // Update GET product in Check
-        if (!canonize_type(product)) break;
-        const reagent element = element_type(base, offset_value);
-        if (!types_coercible(product, element)) {
-          raise_error << maybe(get(Recipe, r).name) << "'get " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but " << product.name << " has type " << names_to_string_without_quotes(product.type) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case GET_ADDRESS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'get-address' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent base = inst.ingredients.at(0);
-        // Update GET_ADDRESS base in Check
-        if (!canonize_type(base)) break;
-        if (!base.type || !base.type->value || !contains_key(Type, base.type->value) || get(Type, base.type->value).kind != CONTAINER) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'get-address' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        type_ordinal base_type = base.type->value;
-        reagent offset = inst.ingredients.at(1);
-        if (!is_literal(offset) || !is_mu_scalar(offset)) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        long long int offset_value = 0;
-        if (is_integer(offset.name)) {  // later layers permit non-integer offsets
-          offset_value = to_integer(offset.name);
-          if (offset_value < 0 || offset_value >= SIZE(get(Type, base_type).elements)) {
-            raise_error << maybe(get(Recipe, r).name) << "invalid offset " << offset_value << " for " << get(Type, base_type).name << '\n' << end();
-            break;
-          }
-        }
-        else {
-          offset_value = offset.value;
-        }
-        reagent product = inst.products.at(0);
-        // Update GET_ADDRESS product in Check
-        if (!canonize_type(base)) break;
-        // same type as for GET..
-        reagent element = element_type(base, offset_value);
-        // ..except for an address at the start
-        element.type = new type_tree("address", get(Type_ordinal, "address"), element.type);
-        if (!types_coercible(product, element)) {
-          raise_error << maybe(get(Recipe, r).name) << "'get-address " << base.original_string << ", " << offset.original_string << "' should write to " << names_to_string_without_quotes(element.type) << " but " << product.name << " has type " << names_to_string_without_quotes(product.type) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case MERGE: {
-        // type-checking in a separate transform below
-        break;
-      }
-      case CREATE_ARRAY: {
-        if (inst.products.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'create-array' needs one product and no ingredients but got '" << to_string(inst) << '\n' << end();
-          break;
-        }
-        reagent product = inst.products.at(0);
-        canonize_type(product);
-        if (!is_mu_array(product)) {
-          raise_error << maybe(get(Recipe, r).name) << "'create-array' cannot create non-array " << product.original_string << '\n' << end();
-          break;
-        }
-        if (!product.type->right) {
-          raise_error << maybe(get(Recipe, r).name) << "create array of what? " << to_string(inst) << '\n' << end();
-          break;
-        }
-        // 'create-array' will need to check properties rather than types
-        if (!product.type->right->right) {
-          raise_error << maybe(get(Recipe, r).name) << "create array of what size? " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_integer(product.type->right->right->name)) {
-          raise_error << maybe(get(Recipe, r).name) << "'create-array' product should specify size of array after its element type, but got " << product.type->right->right->name << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case INDEX: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'index' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent base = inst.ingredients.at(0);
-        canonize_type(base);
-        if (!is_mu_array(base)) {
-          raise_error << maybe(get(Recipe, r).name) << "'index' on a non-array " << base.original_string << '\n' << end();
-          break;
-        }
-        if (inst.products.empty()) break;
-        reagent product = inst.products.at(0);
-        canonize_type(product);
-        reagent element;
-        element.type = new type_tree(*array_element(base.type));
-        if (!types_coercible(product, element)) {
-          raise_error << maybe(get(Recipe, r).name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << names_to_string_without_quotes(element.type) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case INDEX_ADDRESS: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'index-address' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent base = inst.ingredients.at(0);
-        canonize_type(base);
-        if (!is_mu_array(base)) {
-          raise_error << maybe(get(Recipe, r).name) << "'index-address' on a non-array " << base.original_string << '\n' << end();
-          break;
-        }
-        if (inst.products.empty()) break;
-        reagent product = inst.products.at(0);
-        canonize_type(product);
-        reagent element;
-        element.type = new type_tree("address", get(Type_ordinal, "address"),
-                                     new type_tree(*array_element(base.type)));
-        if (!types_coercible(product, element)) {
-          raise_error << maybe(get(Recipe, r).name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << names_to_string_without_quotes(element.type) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case LENGTH: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'length' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent x = inst.ingredients.at(0);
-        canonize_type(x);
-        if (!is_mu_array(x)) {
-          raise_error << "tried to calculate length of non-array " << x.original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case MAYBE_CONVERT: {
-        const recipe& caller = get(Recipe, r);
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(caller.name) << "'maybe-convert' expects exactly 2 ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent base = inst.ingredients.at(0);
-        canonize_type(base);
-        if (!base.type || !base.type->value || get(Type, base.type->value).kind != EXCLUSIVE_CONTAINER) {
-          raise_error << maybe(caller.name) << "first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end();
-          break;
-        }
-        if (!is_literal(inst.ingredients.at(1))) {
-          raise_error << maybe(caller.name) << "second ingredient of 'maybe-convert' should have type 'variant', but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        if (inst.products.empty()) break;
-        reagent product = inst.products.at(0);
-        if (!canonize_type(product)) break;
-        reagent& offset = inst.ingredients.at(1);
-        populate_value(offset);
-        if (offset.value >= SIZE(get(Type, base.type->value).elements)) {
-          raise_error << maybe(caller.name) << "invalid tag " << offset.value << " in '" << to_string(inst) << '\n' << end();
-          break;
-        }
-        reagent variant = variant_type(base, offset.value);
-        variant.type = new type_tree("address", get(Type_ordinal, "address"), variant.type);
-        if (!types_coercible(product, variant)) {
-          raise_error << maybe(caller.name) << "'maybe-convert " << base.original_string << ", " << inst.ingredients.at(1).original_string << "' should write to " << to_string(variant.type) << " but " << product.name << " has type " << to_string(product.type) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case NEXT_INGREDIENT: {
-        if (!inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'next-ingredient' didn't expect any ingredients in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        break;
-      }
-      case REWIND_INGREDIENTS: {
-        break;
-      }
-      case INGREDIENT: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'ingredient' expects exactly one ingredient, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_literal(inst.ingredients.at(0)) && !is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'ingredient' expects a literal ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case REPLY: {
-        break;  // checks will be performed by a transform below
-      }
-      case NEW: {
-        const recipe& caller = get(Recipe, r);
-        if (inst.ingredients.empty() || SIZE(inst.ingredients) > 2) {
-          raise_error << maybe(caller.name) << "'new' requires one or two ingredients, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (is_literal_string(inst.ingredients.at(0))) break;
-        // End NEW Check Special-cases
-        reagent type = inst.ingredients.at(0);
-        if (!is_mu_type_literal(type)) {
-          raise_error << maybe(caller.name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end();
-          break;
-        }
-        if (inst.products.empty()) {
-          raise_error << maybe(caller.name) << "result of 'new' should never be ignored\n" << end();
-          break;
-        }
-        if (!product_of_new_is_valid(inst)) {
-          raise_error << maybe(caller.name) << "product of 'new' has incorrect type: " << to_string(inst) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case ALLOCATE: {
-        raise << "never call 'allocate' directly'; always use 'new'\n" << end();
-        break;
-      }
-
-      case ABANDON: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'abandon' requires one ingredient, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        reagent types = inst.ingredients.at(0);
-        canonize_type(types);
-        if (!types.type || types.type->value != get(Type_ordinal, "address") || types.type->right->value != get(Type_ordinal, "shared")) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'abandon' should be an address:shared:___, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case TO_LOCATION_ARRAY: {
-        const recipe& caller = get(Recipe, r);
-        if (!is_shared_address_of_array_of_numbers(inst.products.at(0))) {
-          raise_error << maybe(caller.name) << "product of 'to-location-array' has incorrect type: " << to_string(inst) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case BREAK: break;
-      case BREAK_IF: break;
-      case BREAK_UNLESS: break;
-      case LOOP: break;
-      case LOOP_IF: break;
-      case LOOP_UNLESS: break;
-
-      case RUN: {
-        break;
-      }
-      case MEMORY_SHOULD_CONTAIN: {
-        break;
-      }
-      case TRACE_SHOULD_CONTAIN: {
-        break;
-      }
-      case TRACE_SHOULD_NOT_CONTAIN: {
-        break;
-      }
-      case CHECK_TRACE_COUNT_FOR_LABEL: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'check-trace-for-label' requires exactly two ingredients, but got '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-for-label' should be a number (count), but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_literal_string(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-for-label' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case NEXT_INGREDIENT_WITHOUT_TYPECHECKING: {
-        break;
-      }
-      case CALL: {
-        if (inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
-          break;
-        }
-        if (!is_mu_recipe(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'call' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case START_RUNNING: {
-        if (inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
-          break;
-        }
-        if (!is_mu_recipe(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'start-running' should be a recipe, but got " << to_string(inst.ingredients.at(0)) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case ROUTINE_STATE: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'routine-state' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case RESTART: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'restart' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restart' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case STOP: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'stop' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case _DUMP_ROUTINES: {
-        break;
-      }
-      case LIMIT_TIME: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'limit-time' requires exactly two ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case WAIT_FOR_LOCATION: {
-        break;
-      }
-      case WAIT_FOR_ROUTINE: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'wait-for-routine' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case SWITCH: {
-        break;
-      }
-      case RANDOM: {
-        break;
-      }
-      case MAKE_RANDOM_NONDETERMINISTIC: {
-        break;
-      }
-      case ROUND: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'round' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case HASH: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'hash' takes exactly one ingredient rather than '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        break;
-      }
-      case HASH_OLD: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'hash_old' takes exactly one ingredient rather than '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        if (!is_mu_string(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "'hash_old' currently only supports strings (address:shared:array:character), but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case OPEN_CONSOLE: {
-        break;
-      }
-      case CLOSE_CONSOLE: {
-        break;
-      }
-      case CLEAR_DISPLAY: {
-        break;
-      }
-      case SYNC_DISPLAY: {
-        break;
-      }
-      case CLEAR_LINE_ON_DISPLAY: {
-        break;
-      }
-      case PRINT_CHARACTER_TO_DISPLAY: {
-        if (inst.ingredients.empty()) {
-          raise_error << maybe(get(Recipe, r).name) << "'print-character-to-display' requires at least one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'print-character-to-display' should be a character, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (SIZE(inst.ingredients) > 1) {
-          if (!is_mu_number(inst.ingredients.at(1))) {
-            raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
-            break;
-          }
-        }
-        if (SIZE(inst.ingredients) > 2) {
-          if (!is_mu_number(inst.ingredients.at(2))) {
-            raise_error << maybe(get(Recipe, r).name) << "third ingredient of 'print-character-to-display' should be a background color number, but got " << inst.ingredients.at(2).original_string << '\n' << end();
-            break;
-          }
-        }
-        break;
-      }
-      case CURSOR_POSITION_ON_DISPLAY: {
-        break;
-      }
-      case MOVE_CURSOR_ON_DISPLAY: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'move-cursor-on-display' requires two ingredients, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        if (!is_mu_number(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case MOVE_CURSOR_DOWN_ON_DISPLAY: {
-        break;
-      }
-      case MOVE_CURSOR_UP_ON_DISPLAY: {
-        break;
-      }
-      case MOVE_CURSOR_RIGHT_ON_DISPLAY: {
-        break;
-      }
-      case MOVE_CURSOR_LEFT_ON_DISPLAY: {
-        break;
-      }
-      case DISPLAY_WIDTH: {
-        break;
-      }
-      case DISPLAY_HEIGHT: {
-        break;
-      }
-      case HIDE_CURSOR_ON_DISPLAY: {
-        break;
-      }
-      case SHOW_CURSOR_ON_DISPLAY: {
-        break;
-      }
-      case HIDE_DISPLAY: {
-        break;
-      }
-      case SHOW_DISPLAY: {
-        break;
-      }
-      case WAIT_FOR_SOME_INTERACTION: {
-        break;
-      }
-      case CHECK_FOR_INTERACTION: {
-        break;
-      }
-      case INTERACTIONS_LEFT: {
-        break;
-      }
-      case CLEAR_DISPLAY_FROM: {
-        break;
-      }
-      case SCREEN_SHOULD_CONTAIN: {
-        break;
-      }
-      case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
-        break;
-      }
-      case _DUMP_SCREEN: {
-        break;
-      }
-      case ASSUME_CONSOLE: {
-        break;
-      }
-      case REPLACE_IN_CONSOLE: {
-        break;
-      }
-      case _BROWSE_TRACE: {
-        break;
-      }
-      case RUN_INTERACTIVE: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'run-interactive' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_string(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'run-interactive' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case _START_TRACKING_PRODUCTS: {
-        break;
-      }
-      case _STOP_TRACKING_PRODUCTS: {
-        break;
-      }
-      case _MOST_RECENT_PRODUCTS: {
-        break;
-      }
-      case SAVE_ERRORS_WARNINGS: {
-        break;
-      }
-      case SAVE_APP_TRACE: {
-        break;
-      }
-      case _CLEANUP_RUN_INTERACTIVE: {
-        break;
-      }
-      case RELOAD: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (!is_mu_string(inst.ingredients.at(0))) {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got " << inst.ingredients.at(0).original_string << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case RESTORE: {
-        if (SIZE(inst.ingredients) != 1) {
-          raise_error << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        string filename;
-        if (is_literal_string(inst.ingredients.at(0))) {
-          ;
-        }
-        else if (is_mu_string(inst.ingredients.at(0))) {
-          ;
-        }
-        else {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      case SAVE: {
-        if (SIZE(inst.ingredients) != 2) {
-          raise_error << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got " << to_string(inst) << '\n' << end();
-          break;
-        }
-        if (is_literal_string(inst.ingredients.at(0))) {
-          ;
-        }
-        else if (is_mu_string(inst.ingredients.at(0))) {
-          ;
-        }
-        else {
-          raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end();
-          break;
-        }
-        if (!is_mu_string(inst.ingredients.at(1))) {
-          raise_error << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got " << to_string(inst.ingredients.at(1)) << '\n' << end();
-          break;
-        }
-        break;
-      }
-      // End Primitive Recipe Checks
-      default: {
-        // Defined Recipe Checks
-        // not a primitive; check that it's present in the book of recipes
-        if (!contains_key(Recipe, inst.operation)) {
-          raise_error << maybe(get(Recipe, r).name) << "undefined operation in '" << to_string(inst) << "'\n" << end();
-          break;
-        }
-        // End Defined Recipe Checks
-      }
-    }
-    finish_checking_instruction:;
-  }
-}
-
-void test_copy_checks_reagent_count() {
-  Trace_file = "copy_checks_reagent_count";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy 34, 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: ingredients and products should match in '1:number <- copy 34, 35'");
-}
-void test_write_scalar_to_array_disallowed() {
-  Trace_file = "write_scalar_to_array_disallowed";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:number <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy 34 to 1:array:number; types don't match");
-}
-void test_write_scalar_to_array_disallowed_2() {
-  Trace_file = "write_scalar_to_array_disallowed_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:number, 2:array:number <- copy 34, 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy 35 to 2:array:number; types don't match");
-}
-void test_write_scalar_to_address_disallowed() {
-  Trace_file = "write_scalar_to_address_disallowed";
-  Hide_errors = true;
-  run("recipe main [\n  1:address:number <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy 34 to 1:address:number; types don't match");
-}
-void test_write_address_to_number_allowed() {
-  Trace_file = "write_address_to_number_allowed";
-  Hide_errors = true;
-  run("recipe main [\n  1:address:number <- copy 12/unsafe\n  2:number <- copy 1:address:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 2");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_write_boolean_to_number_allowed() {
-  Trace_file = "write_boolean_to_number_allowed";
-  Hide_errors = true;
-  run("recipe main [\n  1:boolean <- copy 1/true\n  2:number <- copy 1:boolean\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 2");
-  CHECK_TRACE_COUNT("error", 0);
-}
-// types_match with some leniency
-bool types_coercible(const reagent& to, const reagent& from) {
-  if (types_match(to, from)) return true;
-  if (is_mu_address(from) && is_mu_number(to)) return true;
-  if (is_mu_boolean(from) && is_mu_number(to)) return true;
-  // End types_coercible Special-cases
-  return false;
-}
-
-bool types_match(const reagent& to, const reagent& from) {
-  // to sidestep type-checking, use /unsafe in the source.
-  // this will be highlighted in red inside vim. just for setting up some tests.
-  if (is_unsafe(from)) return true;
-  if (is_literal(from)) {
-    if (is_mu_array(to)) return false;
-    if (contains_type_ingredient_name(to)) return false;
-
-    if (is_mu_recipe(to)) {
-      if (!contains_key(Recipe, from.value)) {
-        raise_error << "trying to store recipe " << from.name << " into " << to_string(to) << " but there's no such recipe\n" << end();
-        return false;
-      }
-      const recipe& rrhs = get(Recipe, from.value);
-      const recipe& rlhs = from_reagent(to);
-      for (long int i = 0; i < min(SIZE(rlhs.ingredients), SIZE(rrhs.ingredients)); ++i) {
-        if (!types_match(rlhs.ingredients.at(i), rrhs.ingredients.at(i)))
-          return false;
-      }
-      for (long int i = 0; i < min(SIZE(rlhs.products), SIZE(rrhs.products)); ++i) {
-        if (!types_match(rlhs.products.at(i), rrhs.products.at(i)))
-          return false;
-      }
-      return true;
-    }
-
-    // End Matching Types For Literal(to)
-    // allow writing 0 to any address
-    if (is_mu_address(to)) return from.name == "0";
-    if (!to.type) return false;
-    if (to.type->value == get(Type_ordinal, "boolean"))
-      return boolean_matches_literal(to, from);
-    return size_of(to) == 1;  // literals are always scalars
-  }
-  return types_strictly_match(to, from);
-}
-
-bool types_strictly_match_except_literal_against_boolean(const reagent& to, const reagent& from) {
-  // to sidestep type-checking, use /unsafe in the source.
-  // this will be highlighted in red inside vim. just for setting up some tests.
-  if (is_literal(from)
-      && to.type && to.type->value == get(Type_ordinal, "boolean"))
-    return boolean_matches_literal(to, from);
-  return types_strictly_match(to, from);
-}
-
-bool boolean_matches_literal(const reagent& to, const reagent& from) {
-  if (!is_literal(from)) return false;
-  if (!to.type) return false;
-  if (to.type->value != get(Type_ordinal, "boolean")) return false;
-  return from.name == "0" || from.name == "1";
-}
-
-// copy arguments because later layers will want to make changes to them
-// without perturbing the caller
-bool types_strictly_match(reagent to, reagent from) {
-  if (!canonize_type(to)) return false;
-  if (!canonize_type(from)) return false;
-
-  if (is_literal(from) && to.type->value == get(Type_ordinal, "number")) return true;
-  // to sidestep type-checking, use /unsafe in the source.
-  // this will be highlighted in red inside vim. just for setting up some tests.
-  if (is_unsafe(from)) return true;
-  // '_' never raises type error
-  if (is_dummy(to)) return true;
-  if (!to.type) return !from.type;
-  return types_strictly_match(to.type, from.type);
-}
-
-// two types match if the second begins like the first
-// (trees perform the same check recursively on each subtree)
-bool types_strictly_match(type_tree* to, type_tree* from) {
-  if (!to) return true;
-  if (!from) return to->value == 0;
-  if (to->value != from->value) return false;
-  return types_strictly_match(to->left, from->left) && types_strictly_match(to->right, from->right);
-}
-
-bool is_unsafe(const reagent& r) {
-  return has_property(r, "unsafe");
-}
-
-bool is_mu_array(reagent r) {
-  if (!canonize_type(r)) return false;
-
-  if (!r.type) return false;
-  if (is_literal(r)) return false;
-  return r.type->value == get(Type_ordinal, "array");
-}
-
-bool is_mu_address(reagent r) {
-  if (!canonize_type(r)) return false;
-
-  if (!r.type) return false;
-  if (is_literal(r)) return false;
-  return r.type->value == get(Type_ordinal, "address");
-}
-
-bool is_mu_boolean(reagent r) {
-  if (!r.type) return false;
-  if (is_literal(r)) return false;
-  return r.type->value == get(Type_ordinal, "boolean");
-}
-
-bool is_mu_number(reagent r) {
-  if (!canonize_type(r)) return false;
-
-  if (!r.type) return false;
-  if (is_literal(r)) {
-    if (!r.type) return false;
-    return r.type->name == "literal-fractional-number"
-        || r.type->name == "literal";
-  }
-  if (r.type->value == get(Type_ordinal, "character")) return true;  // permit arithmetic on unicode code points
-  return r.type->value == get(Type_ordinal, "number");
-}
-
-bool is_mu_scalar(reagent r) {
-  if (!r.type) return false;
-  if (is_literal(r))
-    return !r.type || r.type->name != "literal-string";
-  if (is_mu_array(r)) return false;
-  return size_of(r) == 1;
-}
-
-
-void test_add_literal() {
-  Trace_file = "add_literal";
-  run("recipe main [\n  1:number <- add 23, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 57 in location 1");
-}
-void test_add() {
-  Trace_file = "add";
-  run("recipe main [\n  1:number <- copy 23\n  2:number <- copy 34\n  3:number <- add 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 57 in location 3");
-}
-void test_add_multiple() {
-  Trace_file = "add_multiple";
-  run("recipe main [\n  1:number <- add 3, 4, 5\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 1");
-}
-void test_add_checks_type() {
-  Trace_file = "add_checks_type";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- add 2:boolean, 1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'add' requires number ingredients, but got 2:boolean");
-}
-void test_add_checks_return_type() {
-  Trace_file = "add_checks_return_type";
-  Hide_errors = true;
-  run("recipe main [\n  1:address:number <- add 2, 2\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'add' should yield a number, but got 1:address:number");
-}
-bool is_raw(const reagent& r) {
-  return has_property(r, "raw");
-}
-
-void test_subtract_literal() {
-  Trace_file = "subtract_literal";
-  run("recipe main [\n  1:number <- subtract 5, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 1");
-}
-void test_subtract() {
-  Trace_file = "subtract";
-  run("recipe main [\n  1:number <- copy 23\n  2:number <- copy 34\n  3:number <- subtract 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing -11 in location 3");
-}
-void test_subtract_multiple() {
-  Trace_file = "subtract_multiple";
-  run("recipe main [\n  1:number <- subtract 6, 3, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_multiply_literal() {
-  Trace_file = "multiply_literal";
-  run("recipe main [\n  1:number <- multiply 2, 3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 6 in location 1");
-}
-void test_multiply() {
-  Trace_file = "multiply";
-  run("recipe main [\n  1:number <- copy 4\n  2:number <- copy 6\n  3:number <- multiply 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 24 in location 3");
-}
-void test_multiply_multiple() {
-  Trace_file = "multiply_multiple";
-  run("recipe main [\n  1:number <- multiply 2, 3, 4\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 24 in location 1");
-}
-void test_divide_literal() {
-  Trace_file = "divide_literal";
-  run("recipe main [\n  1:number <- divide 8, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_divide() {
-  Trace_file = "divide";
-  run("recipe main [\n  1:number <- copy 27\n  2:number <- copy 3\n  3:number <- divide 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 9 in location 3");
-}
-void test_divide_multiple() {
-  Trace_file = "divide_multiple";
-  run("recipe main [\n  1:number <- divide 12, 3, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_divide_with_remainder_literal() {
-  Trace_file = "divide_with_remainder_literal";
-  run("recipe main [\n  1:number, 2:number <- divide-with-remainder 9, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1mem: storing 1 in location 2");
-}
-void test_divide_with_remainder() {
-  Trace_file = "divide_with_remainder";
-  run("recipe main [\n  1:number <- copy 27\n  2:number <- copy 11\n  3:number, 4:number <- divide-with-remainder 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 3mem: storing 5 in location 4");
-}
-void test_divide_with_decimal_point() {
-  Trace_file = "divide_with_decimal_point";
-  run("recipe main [\n  1:number <- divide 5, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2.5 in location 1");
-}
-void test_divide_by_zero() {
-  Trace_file = "divide_by_zero";
-  run("recipe main [\n  1:number <- divide 4, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing inf in location 1");
-}
-void test_divide_by_zero_2() {
-  Trace_file = "divide_by_zero_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- divide-with-remainder 4, 0\n]\n# integer division can't return floating-point infinity\n");
-  CHECK_TRACE_CONTENTS("error: main: divide by zero in '1:number <- divide-with-remainder 4, 0'");
-}
-void test_shift_left_by_zero() {
-  Trace_file = "shift_left_by_zero";
-  run("recipe main [\n  1:number <- shift-left 1, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_shift_left_1() {
-  Trace_file = "shift_left_1";
-  run("recipe main [\n  1:number <- shift-left 1, 4\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 16 in location 1");
-}
-void test_shift_left_2() {
-  Trace_file = "shift_left_2";
-  run("recipe main [\n  1:number <- shift-left 3, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 1");
-}
-void test_shift_left_by_negative() {
-  Trace_file = "shift_left_by_negative";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- shift-left 3, -1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: second ingredient can't be negative in '1:number <- shift-left 3, -1'");
-}
-void test_shift_left_ignores_fractional_part() {
-  Trace_file = "shift_left_ignores_fractional_part";
-  run("recipe main [\n  1:number <- divide 3, 2\n  2:number <- shift-left 1:number, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 2");
-}
-void test_shift_right_by_zero() {
-  Trace_file = "shift_right_by_zero";
-  run("recipe main [\n  1:number <- shift-right 1, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_shift_right_1() {
-  Trace_file = "shift_right_1";
-  run("recipe main [\n  1:number <- shift-right 1024, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 512 in location 1");
-}
-void test_shift_right_2() {
-  Trace_file = "shift_right_2";
-  run("recipe main [\n  1:number <- shift-right 3, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_shift_right_by_negative() {
-  Trace_file = "shift_right_by_negative";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- shift-right 4, -1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: second ingredient can't be negative in '1:number <- shift-right 4, -1'");
-}
-void test_shift_right_ignores_fractional_part() {
-  Trace_file = "shift_right_ignores_fractional_part";
-  run("recipe main [\n  1:number <- divide 3, 2\n  2:number <- shift-right 1:number, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2");
-}
-void test_and_bits_1() {
-  Trace_file = "and_bits_1";
-  run("recipe main [\n  1:number <- and-bits 8, 3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_and_bits_2() {
-  Trace_file = "and_bits_2";
-  run("recipe main [\n  1:number <- and-bits 3, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_and_bits_3() {
-  Trace_file = "and_bits_3";
-  run("recipe main [\n  1:number <- and-bits 14, 3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_and_bits_negative() {
-  Trace_file = "and_bits_negative";
-  run("recipe main [\n  1:number <- and-bits -3, 4\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_or_bits_1() {
-  Trace_file = "or_bits_1";
-  run("recipe main [\n  1:number <- or-bits 3, 8\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 11 in location 1");
-}
-void test_or_bits_2() {
-  Trace_file = "or_bits_2";
-  run("recipe main [\n  1:number <- or-bits 3, 10\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 11 in location 1");
-}
-void test_or_bits_3() {
-  Trace_file = "or_bits_3";
-  run("recipe main [\n  1:number <- or-bits 4, 6\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 6 in location 1");
-}
-void test_xor_bits_1() {
-  Trace_file = "xor_bits_1";
-  run("recipe main [\n  1:number <- xor-bits 3, 8\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 11 in location 1");
-}
-void test_xor_bits_2() {
-  Trace_file = "xor_bits_2";
-  run("recipe main [\n  1:number <- xor-bits 3, 10\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 9 in location 1");
-}
-void test_xor_bits_3() {
-  Trace_file = "xor_bits_3";
-  run("recipe main [\n  1:number <- xor-bits 4, 6\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_flip_bits_zero() {
-  Trace_file = "flip_bits_zero";
-  run("recipe main [\n  1:number <- flip-bits 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing -1 in location 1");
-}
-void test_flip_bits_negative() {
-  Trace_file = "flip_bits_negative";
-  run("recipe main [\n  1:number <- flip-bits -1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_flip_bits_1() {
-  Trace_file = "flip_bits_1";
-  run("recipe main [\n  1:number <- flip-bits 3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing -4 in location 1");
-}
-void test_flip_bits_2() {
-  Trace_file = "flip_bits_2";
-  run("recipe main [\n  1:number <- flip-bits 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing -13 in location 1");
-}
-
-void test_and() {
-  Trace_file = "and";
-  run("recipe main [\n  1:boolean <- copy 1\n  2:boolean <- copy 0\n  3:boolean <- and 1:boolean, 2:boolean\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_and_2() {
-  Trace_file = "and_2";
-  run("recipe main [\n  1:boolean <- and 1, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_and_multiple() {
-  Trace_file = "and_multiple";
-  run("recipe main [\n  1:boolean <- and 1, 1, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_and_multiple_2() {
-  Trace_file = "and_multiple_2";
-  run("recipe main [\n  1:boolean <- and 1, 1, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_or() {
-  Trace_file = "or";
-  run("recipe main [\n  1:boolean <- copy 1\n  2:boolean <- copy 0\n  3:boolean <- or 1:boolean, 2:boolean\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_or_2() {
-  Trace_file = "or_2";
-  run("recipe main [\n  1:boolean <- or 0, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_or_multiple() {
-  Trace_file = "or_multiple";
-  run("recipe main [\n  1:boolean <- and 0, 0, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_or_multiple_2() {
-  Trace_file = "or_multiple_2";
-  run("recipe main [\n  1:boolean <- or 0, 0, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_not() {
-  Trace_file = "not";
-  run("recipe main [\n  1:boolean <- copy 1\n  2:boolean <- not 1:boolean\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2");
-}
-void test_not_multiple() {
-  Trace_file = "not_multiple";
-  run("recipe main [\n  1:boolean, 2:boolean, 3:boolean <- not 1, 0, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 1 in location 2mem: storing 0 in location 3");
-}
-
-void test_jump_can_skip_instructions() {
-  Trace_file = "jump_can_skip_instructions";
-  run("recipe main [\n  jump 1:offset\n  1:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("run: jump 1:offset");
-  CHECK_TRACE_DOESNT_CONTAIN("run: 1:number <- copy 1");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 1");
-}
-void test_jump_backward() {
-  Trace_file = "jump_backward";
-  run("recipe main [\n  jump 1:offset  # 0 -+\n  jump 3:offset  #    |   +-+ 1\n                 #   \\/  /\\ |\n  jump -2:offset #  2 +-->+ |\n]                #         \\/ 3\n");
-  CHECK_TRACE_CONTENTS("run: jump 1:offsetrun: jump -2:offsetrun: jump 3:offset");
-}
-void test_jump_if() {
-  Trace_file = "jump_if";
-  run("recipe main [\n  jump-if 999, 1:offset\n  123:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("run: jump-if 999, 1:offsetrun: jumping to instruction 2");
-  CHECK_TRACE_DOESNT_CONTAIN("run: 1:number <- copy 1");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 123");
-}
-void test_jump_if_fallthrough() {
-  Trace_file = "jump_if_fallthrough";
-  run("recipe main [\n  jump-if 0, 1:offset\n  123:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("run: jump-if 0, 1:offsetrun: jump-if fell throughrun: 123:number <- copy 1mem: storing 1 in location 123");
-}
-void test_jump_unless() {
-  Trace_file = "jump_unless";
-  run("recipe main [\n  jump-unless 0, 1:offset\n  123:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("run: jump-unless 0, 1:offsetrun: jumping to instruction 2");
-  CHECK_TRACE_DOESNT_CONTAIN("run: 123:number <- copy 1");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 1 in location 123");
-}
-void test_jump_unless_fallthrough() {
-  Trace_file = "jump_unless_fallthrough";
-  run("recipe main [\n  jump-unless 999, 1:offset\n  123:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("run: jump-unless 999, 1:offsetrun: jump-unless fell throughrun: 123:number <- copy 1mem: storing 1 in location 123");
-}
-
-void test_equal() {
-  Trace_file = "equal";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 33\n  3:number <- equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: location 1 is 34mem: location 2 is 33mem: storing 0 in location 3");
-}
-void test_equal_2() {
-  Trace_file = "equal_2";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 34\n  3:number <- equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: location 1 is 34mem: location 2 is 34mem: storing 1 in location 3");
-}
-void test_equal_multiple() {
-  Trace_file = "equal_multiple";
-  run("recipe main [\n  1:number <- equal 34, 34, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_equal_multiple_2() {
-  Trace_file = "equal_multiple_2";
-  run("recipe main [\n  1:number <- equal 34, 34, 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_greater_than() {
-  Trace_file = "greater_than";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 33\n  3:boolean <- greater-than 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_greater_than_2() {
-  Trace_file = "greater_than_2";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 34\n  3:boolean <- greater-than 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_greater_than_multiple() {
-  Trace_file = "greater_than_multiple";
-  run("recipe main [\n  1:boolean <- greater-than 36, 35, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_greater_than_multiple_2() {
-  Trace_file = "greater_than_multiple_2";
-  run("recipe main [\n  1:boolean <- greater-than 36, 35, 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_lesser_than() {
-  Trace_file = "lesser_than";
-  run("recipe main [\n  1:number <- copy 32\n  2:number <- copy 33\n  3:boolean <- lesser-than 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_lesser_than_2() {
-  Trace_file = "lesser_than_2";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 33\n  3:boolean <- lesser-than 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_lesser_than_multiple() {
-  Trace_file = "lesser_than_multiple";
-  run("recipe main [\n  1:boolean <- lesser-than 34, 35, 36\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_lesser_than_multiple_2() {
-  Trace_file = "lesser_than_multiple_2";
-  run("recipe main [\n  1:boolean <- lesser-than 34, 35, 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_greater_or_equal() {
-  Trace_file = "greater_or_equal";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 33\n  3:boolean <- greater-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_greater_or_equal_2() {
-  Trace_file = "greater_or_equal_2";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 34\n  3:boolean <- greater-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_greater_or_equal_3() {
-  Trace_file = "greater_or_equal_3";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 35\n  3:boolean <- greater-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_greater_or_equal_multiple() {
-  Trace_file = "greater_or_equal_multiple";
-  run("recipe main [\n  1:boolean <- greater-or-equal 36, 35, 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_greater_or_equal_multiple_2() {
-  Trace_file = "greater_or_equal_multiple_2";
-  run("recipe main [\n  1:boolean <- greater-or-equal 36, 35, 36\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-void test_lesser_or_equal() {
-  Trace_file = "lesser_or_equal";
-  run("recipe main [\n  1:number <- copy 32\n  2:number <- copy 33\n  3:boolean <- lesser-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_lesser_or_equal_2() {
-  Trace_file = "lesser_or_equal_2";
-  run("recipe main [\n  1:number <- copy 33\n  2:number <- copy 33\n  3:boolean <- lesser-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_lesser_or_equal_3() {
-  Trace_file = "lesser_or_equal_3";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 33\n  3:boolean <- lesser-or-equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_lesser_or_equal_multiple() {
-  Trace_file = "lesser_or_equal_multiple";
-  run("recipe main [\n  1:boolean <- lesser-or-equal 34, 35, 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1");
-}
-void test_lesser_or_equal_multiple_2() {
-  Trace_file = "lesser_or_equal_multiple_2";
-  run("recipe main [\n  1:boolean <- lesser-or-equal 34, 35, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-}
-
-void test_trace() {
-  Trace_file = "trace";
-  run("recipe main [\n  trace 1, [foo], [this is a trace in mu]\n]\n");
-  CHECK_TRACE_CONTENTS("foo: this is a trace in mu");
-}
-void test_stash_literal_string() {
-  Trace_file = "stash_literal_string";
-  run("recipe main [\n  stash [foo]\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo");
-}
-void test_stash_literal_number() {
-  Trace_file = "stash_literal_number";
-  run("recipe main [\n  stash [foo:], 4\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: 4");
-}
-void test_stash_number() {
-  Trace_file = "stash_number";
-  run("recipe main [\n  1:number <- copy 34\n  stash [foo:], 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: 34");
-}
-string print_mu(const reagent& r, const vector<double>& data) {
-  if (is_literal(r))
-    return r.name+' ';
-  if (is_mu_string(r)) {
-    assert(scalar(data));
-    return read_mu_string(data.at(0))+' ';
-  }
-
-  // End print Special-cases(reagent r, data)
-  ostringstream out;
-  for (long long i = 0; i < SIZE(data); ++i)
-    out << no_scientific(data.at(i)) << ' ';
-  return out.str();
-}
-
-void test_assert() {
-  Trace_file = "assert";
-  Hide_errors = true;  // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line.
-  run("recipe main [\n  assert 0, [this is an assert in mu]\n]\n");
-  CHECK_TRACE_CONTENTS("error: this is an assert in mu");
-}
-
-void test_copy_multiple_locations() {
-  Trace_file = "copy_multiple_locations";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 35\n  3:point <- copy 1:point/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 3mem: storing 35 in location 4");
-}
-void test_copy_checks_size() {
-  Trace_file = "copy_checks_size";
-  Hide_errors = true;
-  run("recipe main [\n  2:point <- copy 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy 1:number to 2:point; types don't match");
-}
-void test_copy_handles_nested_container_elements() {
-  Trace_file = "copy_handles_nested_container_elements";
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  15:point-number <- copy 12:point-number/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 36 in location 17");
-}
-void test_compare_multiple_locations() {
-  Trace_file = "compare_multiple_locations";
-  run("recipe main [\n  1:number <- copy 34  # first\n  2:number <- copy 35\n  3:number <- copy 36\n  4:number <- copy 34  # second\n  5:number <- copy 35\n  6:number <- copy 36\n  7:boolean <- equal 1:point-number/raw, 4:point-number/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 7");
-}
-void test_compare_multiple_locations_2() {
-  Trace_file = "compare_multiple_locations_2";
-  run("recipe main [\n  1:number <- copy 34  # first\n  2:number <- copy 35\n  3:number <- copy 36\n  4:number <- copy 34  # second\n  5:number <- copy 35\n  6:number <- copy 37  # different\n  7:boolean <- equal 1:point-number/raw, 4:point-number/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 7");
-}
-void test_stash_container() {
-  Trace_file = "stash_container";
-  run("recipe main [\n  1:number <- copy 34  # first\n  2:number <- copy 35\n  3:number <- copy 36\n  stash [foo:], 1:point-number/raw\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: 34 35 36");
-}
-void test_get() {
-  Trace_file = "get";
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  15:number <- get 12:point/raw, 1:offset  # unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 15");
-}
-const reagent element_type(const reagent& canonized_base, long long int offset_value) {
-  assert(offset_value >= 0);
-  assert(contains_key(Type, canonized_base.type->value));
-  assert(!get(Type, canonized_base.type->value).name.empty());
-  const type_info& info = get(Type, canonized_base.type->value);
-  assert(info.kind == CONTAINER);
-  reagent element = info.elements.at(offset_value);
-  if (contains_type_ingredient(element)) {
-    if (!canonized_base.type->right)
-      raise_error << "illegal type " << names_to_string(canonized_base.type) << " seems to be missing a type ingredient or three\n" << end();
-    replace_type_ingredients(element.type, canonized_base.type->right, info);
-  }
-
-  // End element_type Special-cases
-  return element;
-}
-
-void test_get_handles_nested_container_elements() {
-  Trace_file = "get_handles_nested_container_elements";
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  15:number <- get 12:point-number/raw, 1:offset  # unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 36 in location 15");
-}
-void test_get_out_of_bounds() {
-  Trace_file = "get_out_of_bounds";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  get 12:point-number/raw, 2:offset  # point-number occupies 3 locations but has only 2 fields; out of bounds\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid offset 2 for point-number");
-}
-void test_get_out_of_bounds_2() {
-  Trace_file = "get_out_of_bounds_2";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  get 12:point-number/raw, -1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid offset -1 for point-number");
-}
-void test_get_product_type_mismatch() {
-  Trace_file = "get_product_type_mismatch";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  15:address:number <- get 12:point-number/raw, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'get 12:point-number/raw, 1:offset' should write to number but 15 has type (address number)");
-}
-void test_get_without_product() {
-  Trace_file = "get_without_product";
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  get 12:point/raw, 1:offset  # unsafe\n]\n# just don't die\n");
-}
-void test_get_address() {
-  Trace_file = "get_address";
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  15:address:number <- get-address 12:point/raw, 1:offset  # unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 13 in location 15");
-}
-void test_get_address_out_of_bounds() {
-  Trace_file = "get_address_out_of_bounds";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  get-address 12:point-number/raw, 2:offset  # point-number occupies 3 locations but has only 2 fields; out of bounds\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid offset 2 for point-number");
-}
-void test_get_address_out_of_bounds_2() {
-  Trace_file = "get_address_out_of_bounds_2";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 34\n  13:number <- copy 35\n  14:number <- copy 36\n  get-address 12:point-number/raw, -1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid offset -1 for point-number");
-}
-void test_get_address_product_type_mismatch() {
-  Trace_file = "get_address_product_type_mismatch";
-  Hide_errors = true;
-  run("container boolbool [\n  x:boolean\n  y:boolean\n]\nrecipe main [\n  12:boolean <- copy 1\n  13:boolean <- copy 0\n  15:boolean <- get-address 12:boolbool, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'get-address 12:boolbool, 1:offset' should write to (address boolean) but 15 has type boolean");
-}
-void test_container() {
-  Trace_file = "container";
-  load("container foo [\n  x:number\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: element: x: \"number\"parse: element: y: \"number\"");
-}
-void test_container_use_before_definition() {
-  Trace_file = "container_use_before_definition";
-  load("container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  x:number\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: type number: 1000parse:   element: x: \"number\"parse:   element: y: \"bar\"parse: --- defining container barparse: type number: 1001parse:   element: x: \"number\"parse:   element: y: \"number\"");
-}
-void insert_container(const string& command, kind_of_type kind, istream& in) {
-  skip_whitespace_but_not_newline(in);
-  string name = next_word(in);
-  if (name.find(':') != string::npos) {
-    trace(9999, "parse") << "container has type ingredients; parsing" << end();
-    read_type_ingredients(name);
-  }
-
-  // End container Name Refinements
-  trace(9991, "parse") << "--- defining " << command << ' ' << name << end();
-  if (!contains_key(Type_ordinal, name)
-      || get(Type_ordinal, name) == 0) {
-    put(Type_ordinal, name, Next_type_ordinal++);
-  }
-  trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end();
-  skip_bracket(in, "'container' must begin with '['");
-  type_info& info = get_or_insert(Type, get(Type_ordinal, name));
-  Recently_added_types.push_back(get(Type_ordinal, name));
-  info.name = name;
-  info.kind = kind;
-  while (has_data(in)) {
-    skip_whitespace_and_comments(in);
-    string element = next_word(in);
-    if (element == "]") break;
-    info.elements.push_back(reagent(element));
-    replace_unknown_types_with_unique_ordinals(info.elements.back().type, info);
-    trace(9993, "parse") << "  element: " << to_string(info.elements.back()) << end();
-    {
-      const type_tree* type = info.elements.back().type;
-      if (type->name == "array") {
-        if (!type->right) {
-          raise_error << "container '" << name << "' doesn't specify type of array elements for " << info.elements.back().name << '\n' << end();
-          continue;
-        }
-        if (!type->right->right) {  // array has no length
-          raise_error << "container '" << name << "' cannot determine size of element " << info.elements.back().name << '\n' << end();
-          continue;
-        }
-      }
-    }
-
-
-    // End Load Container Element Definition
-  }
-  info.size = SIZE(info.elements);
-}
-
-void replace_unknown_types_with_unique_ordinals(type_tree* type, const type_info& info) {
-  if (!type) return;
-  if (!type->name.empty()) {
-    if (contains_key(Type_ordinal, type->name)) {
-      type->value = get(Type_ordinal, type->name);
-    }
-    else if (is_integer(type->name)) {  // sometimes types will contain non-type tags, like numbers for the size of an array
-      type->value = 0;
-    }
-    // check for use of type ingredients
-    else if (is_type_ingredient_name(type->name)) {
-      type->value = get(info.type_ingredient_names, type->name);
-    }
-    // End insert_container Special-cases
-    else if (type->name != "->") {  // used in recipe types
-      put(Type_ordinal, type->name, Next_type_ordinal++);
-      type->value = get(Type_ordinal, type->name);
-    }
-  }
-  replace_unknown_types_with_unique_ordinals(type->left, info);
-  replace_unknown_types_with_unique_ordinals(type->right, info);
-}
-
-void skip_bracket(istream& in, string message) {
-  skip_whitespace_and_comments(in);
-  if (in.get() != '[')
-    raise_error << message << '\n' << end();
-}
-
-void test_container_define_twice() {
-  Trace_file = "container_define_twice";
-  run("container foo [\n  x:number\n]\ncontainer foo [\n  y:number\n]\nrecipe main [\n  1:number <- copy 34\n  2:number <- copy 35\n  3:number <- get 1:foo, 0:offset\n  4:number <- get 1:foo, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 3mem: storing 35 in location 4");
-}
-void test_run_complains_on_unknown_types() {
-  Trace_file = "run_complains_on_unknown_types";
-  Hide_errors = true;
-  run("recipe main [\n  # integer is not a type\n  1:integer <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: unknown type integer in '1:integer <- copy 0'");
-}
-void test_run_allows_type_definition_after_use() {
-  Trace_file = "run_allows_type_definition_after_use";
-  Hide_errors = true;
-  run("recipe main [\n  1:bar <- copy 0/unsafe\n]\ncontainer bar [\n  x:number\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void check_or_set_invalid_types(const recipe_ordinal r) {
-  recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- check for invalid types in recipe " << caller.name << end();
-  for (long long int index = 0; index < SIZE(caller.steps); ++index) {
-    instruction& inst = caller.steps.at(index);
-    for (long long int i = 0; i < SIZE(inst.ingredients); ++i)
-      check_or_set_invalid_types(inst.ingredients.at(i).type, maybe(caller.name), "'"+to_string(inst)+"'");
-    for (long long int i = 0; i < SIZE(inst.products); ++i)
-      check_or_set_invalid_types(inst.products.at(i).type, maybe(caller.name), "'"+to_string(inst)+"'");
-  }
-  for (long long int i = 0; i < SIZE(caller.ingredients); ++i)
-    check_or_set_invalid_types(caller.ingredients.at(i).type, maybe(caller.name), "recipe header ingredient");
-  for (long long int i = 0; i < SIZE(caller.products); ++i)
-    check_or_set_invalid_types(caller.products.at(i).type, maybe(caller.name), "recipe header product");
-
-  // End check_or_set_invalid_types
-}
-
-void check_or_set_invalid_types(type_tree* type, const string& block, const string& name) {
-  if (!type) return;  // will throw a more precise error elsewhere
-  if (type->value >= START_TYPE_INGREDIENTS
-      && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names))
-    return;
-  // End Container Type Checks
-  if (type->value == 0) return;
-  if (!contains_key(Type, type->value)) {
-    assert(!type->name.empty());
-    if (contains_key(Type_ordinal, type->name))
-      type->value = get(Type_ordinal, type->name);
-    else
-      raise_error << block << "unknown type " << type->name << " in " << name << '\n' << end();
-  }
-  check_or_set_invalid_types(type->left, block, name);
-  check_or_set_invalid_types(type->right, block, name);
-}
-
-void test_container_unknown_field() {
-  Trace_file = "container_unknown_field";
-  Hide_errors = true;
-  run("container foo [\n  x:number\n  y:bar\n]\n");
-  CHECK_TRACE_CONTENTS("error: foo: unknown type in y");
-}
-void test_read_container_with_bracket_in_comment() {
-  Trace_file = "read_container_with_bracket_in_comment";
-  run("container foo [\n  x:number\n  # ']' in comment\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: --- defining container fooparse: element: x: \"number\"parse: element: y: \"number\"");
-}
-void check_container_field_types() {
-  for (map<type_ordinal, type_info>::iterator p = Type.begin(); p != Type.end(); ++p) {
-    const type_info& info = p->second;
-    if (!info.type_ingredient_names.empty()) continue;
-
-    // Check Container Field Types(info)
-    for (long long int i = 0; i < SIZE(info.elements); ++i)
-      check_invalid_types(info.elements.at(i).type, maybe(info.name), info.elements.at(i).name);
-  }
-}
-
-void check_invalid_types(const type_tree* type, const string& block, const string& name) {
-  if (!type) return;  // will throw a more precise error elsewhere
-  if (type->value >= START_TYPE_INGREDIENTS
-      && (type->value - START_TYPE_INGREDIENTS) < SIZE(get(Type, type->value).type_ingredient_names))
-    return;
-
-  // End Container Type Checks2
-  if (type->value == 0) {
-    assert(!type->left && !type->right);
-    return;
-  }
-  if (!contains_key(Type, type->value))
-    raise_error << block << "unknown type in " << name << '\n' << end();
-  check_invalid_types(type->left, block, name);
-  check_invalid_types(type->right, block, name);
-}
-
-
-void test_merge() {
-  Trace_file = "merge";
-  run("container foo [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 3, 4\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 1mem: storing 4 in location 2");
-}
-void test_merge_check() {
-  Trace_file = "merge_check";
-  Hide_errors = true;
-  run("recipe main [\n  1:point <- merge 3, 4\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_missing_element() {
-  Trace_file = "merge_check_missing_element";
-  Hide_errors = true;
-  run("recipe main [\n  1:point <- merge 3\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:point <- merge 3'");
-}
-void test_merge_check_extra_element() {
-  Trace_file = "merge_check_extra_element";
-  Hide_errors = true;
-  run("recipe main [\n  1:point <- merge 3, 4, 5\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:point <- merge 3, 4, 5'");
-}
-void test_merge_check_recursive_containers() {
-  Trace_file = "merge_check_recursive_containers";
-  Hide_errors = true;
-  run("recipe main [\n  1:point <- merge 3, 4\n  1:point-number <- merge 1:point, 5\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_recursive_containers_2() {
-  Trace_file = "merge_check_recursive_containers_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:point <- merge 3, 4\n  2:point-number <- merge 1:point\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too few ingredients in '2:point-number <- merge 1:point'");
-}
-void test_merge_check_recursive_containers_3() {
-  Trace_file = "merge_check_recursive_containers_3";
-  Hide_errors = true;
-  run("recipe main [\n  1:point-number <- merge 3, 4, 5\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_recursive_containers_4() {
-  Trace_file = "merge_check_recursive_containers_4";
-  Hide_errors = true;
-  run("recipe main [\n  1:point-number <- merge 3, 4\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:point-number <- merge 3, 4'");
-}
-void check_merge_calls(const recipe_ordinal r) {
-  const recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- type-check merge instructions in recipe " << caller.name << end();
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    const instruction& inst = caller.steps.at(i);
-    if (inst.name != "merge") continue;
-    if (SIZE(inst.products) != 1) {
-      raise_error << maybe(caller.name) << "'merge' should yield a single product in '" << to_string(inst) << "'\n" << end();
-      continue;
-    }
-    reagent product = inst.products.at(0);
-    // Update product While Type-checking Merge
-    if (!canonize_type(product)) continue;
-
-    type_ordinal product_type = product.type->value;
-    if (product_type == 0 || !contains_key(Type, product_type)) {
-      raise_error << maybe(caller.name) << "'merge' should yield a container in '" << to_string(inst) << "'\n" << end();
-      continue;
-    }
-    const type_info& info = get(Type, product_type);
-    if (info.kind != CONTAINER && info.kind != EXCLUSIVE_CONTAINER) {
-      raise_error << maybe(caller.name) << "'merge' should yield a container in '" << to_string(inst) << "'\n" << end();
-      continue;
-    }
-    check_merge_call(inst.ingredients, product, caller, inst);
-  }
-}
-
-void check_merge_call(const vector<reagent>& ingredients, const reagent& product, const recipe& caller, const instruction& inst) {
-  long long int ingredient_index = 0;
-  merge_check_state state;
-  state.data.push(merge_check_point(product, 0));
-  while (true) {
-    assert(!state.data.empty());
-    trace(9999, "transform") << ingredient_index << " vs " << SIZE(ingredients) << end();
-    if (ingredient_index >= SIZE(ingredients)) {
-      raise_error << maybe(caller.name) << "too few ingredients in '" << to_string(inst) << "'\n" << end();
-      return;
-    }
-    reagent& container = state.data.top().container;
-    type_info& container_info = get(Type, container.type->value);
-    switch (container_info.kind) {
-      case CONTAINER: {
-        reagent expected_ingredient = element_type(container, state.data.top().container_element_index);
-        trace(9999, "transform") << "checking container " << to_string(container) << " || " << to_string(expected_ingredient) << " vs ingredient " << ingredient_index << end();
-        // if the current element is the ingredient we expect, move on to the next element/ingredient
-        if (types_coercible(expected_ingredient, ingredients.at(ingredient_index))) {
-          ++ingredient_index;
-          ++state.data.top().container_element_index;
-          while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements)) {
-            state.data.pop();
-            if (state.data.empty()) {
-              if (ingredient_index < SIZE(ingredients))
-                raise_error << maybe(caller.name) << "too many ingredients in '" << to_string(inst) << "'\n" << end();
-              return;
-            }
-            ++state.data.top().container_element_index;
-          }
-        }
-        // if not, maybe it's a field of the current element
-        else {
-          // no change to ingredient_index
-          state.data.push(merge_check_point(expected_ingredient, 0));
-        }
-        break;
-      }
-      case EXCLUSIVE_CONTAINER: {
-        assert(state.data.top().container_element_index == 0);
-        trace(9999, "transform") << "checking exclusive container " << to_string(container) << " vs ingredient " << ingredient_index << end();
-        if (!is_literal(ingredients.at(ingredient_index))) {
-          raise_error << maybe(caller.name) << "ingredient " << ingredient_index << " of 'merge' should be a literal, for the tag of exclusive-container " << container_info.name << '\n' << end();
-          return;
-        }
-        reagent ingredient = ingredients.at(ingredient_index);  // unnecessary copy just to keep this function from modifying caller
-        populate_value(ingredient);
-        if (ingredient.value >= SIZE(container_info.elements)) {
-          raise_error << maybe(caller.name) << "invalid tag at " << ingredient_index << " for " << container_info.name << " in '" << to_string(inst) << '\n' << end();
-          return;
-        }
-        reagent variant = variant_type(container, ingredient.value);
-        trace(9999, "transform") << "tag: " << ingredient.value << end();
-        // replace union with its variant
-        state.data.pop();
-        state.data.push(merge_check_point(variant, 0));
-        ++ingredient_index;
-        break;
-      }
-
-      // End valid_merge Cases
-      default: {
-        if (!types_coercible(container, ingredients.at(ingredient_index))) {
-          raise_error << maybe(caller.name) << "incorrect type of ingredient " << ingredient_index << " in '" << to_string(inst) << "'\n" << end();
-          cerr << "  expected " << debug_string(container) << '\n';
-          cerr << "  got " << debug_string(ingredients.at(ingredient_index)) << '\n';
-          return;
-        }
-        ++ingredient_index;
-        // ++state.data.top().container_element_index;  // unnecessary, but wouldn't do any harm
-        do {
-          state.data.pop();
-          if (state.data.empty()) {
-            if (ingredient_index < SIZE(ingredients))
-              raise_error << maybe(caller.name) << "too many ingredients in '" << to_string(inst) << "'\n" << end();
-            return;
-          }
-          ++state.data.top().container_element_index;
-        } while (state.data.top().container_element_index >= SIZE(get(Type, state.data.top().container.type->value).elements));
-      }
-    }
-  }
-  // never gets here
-  assert(false);
-}
-
-void test_merge_check_product() {
-  Trace_file = "merge_check_product";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- merge 3\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'merge' should yield a container in '1:number <- merge 3'");
-}
-
-void test_copy_indirect() {
-  Trace_file = "copy_indirect";
-  run("recipe main [\n  1:address:number <- copy 2/unsafe\n  2:number <- copy 34\n  # This loads location 1 as an address and looks up *that* location.\n  3:number <- copy 1:address:number/lookup\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 3");
-}
-void test_store_indirect() {
-  Trace_file = "store_indirect";
-  run("recipe main [\n  1:address:number <- copy 2/unsafe\n  1:address:number/lookup <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-}
-void test_store_to_0_fails() {
-  Trace_file = "store_to_0_fails";
-  Hide_errors = true;
-  run("recipe main [\n  1:address:number <- copy 0\n  1:address:number/lookup <- copy 34\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 0");
-  run("");
-  CHECK_TRACE_CONTENTS("error: can't write to location 0 in '1:address:number/lookup <- copy 34'");
-}
-void canonize(reagent& x) {
-  if (is_literal(x)) return;
-    absolutize(x);
-  // End canonize(x) Special-cases
-  while (has_property(x, "lookup"))
-    lookup_memory(x);
-}
-
-void lookup_memory(reagent& x) {
-  if (!x.type || x.type->value != get(Type_ordinal, "address")) {
-    raise_error << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end();
-    return;
-  }
-  // compute value
-  if (x.value == 0) {
-    raise_error << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end();
-    return;
-  }
-  trace(9999, "mem") << "location " << x.value << " is " << no_scientific(get_or_insert(Memory, x.value)) << end();
-  x.set_value(get_or_insert(Memory, x.value));
-  drop_from_type(x, "address");
-  if (x.type->name == "shared") {
-    trace(9999, "mem") << "skipping refcount at " << x.value << end();
-    x.set_value(x.value+1);  // skip refcount
-    drop_from_type(x, "shared");
-  }
-  // End Drop Address In lookup_memory(x)
-  drop_one_lookup(x);
-}
-
-void test_canonize_non_pointer_fails_without_crashing() {
-  Trace_file = "canonize_non_pointer_fails_without_crashing";
-  Hide_errors = true;
-  run("recipe foo [\n  1:address:number <- get-address *p, x:offset\n]\n# don't crash\n");
-}
-bool canonize_type(reagent& r) {
-  while (has_property(r, "lookup")) {
-    if (!r.type || r.type->value != get(Type_ordinal, "address")) {
-      raise_error << "can't lookup non-address: " << to_string(r) << ": " << to_string(r.type) << '\n' << end();
-      return false;
-    }
-    drop_from_type(r, "address");
-    if (r.type->name == "shared") {
-      drop_from_type(r, "shared");
-    }
-
-    // End Drop Address In canonize_type(r)
-    drop_one_lookup(r);
-  }
-  return true;
-}
-
-void drop_from_type(reagent& r, string expected_type) {
-  if (!r.type) {
-    raise_error << "can't drop " << expected_type << " from " << to_string(r) << '\n' << end();
-    return;
-  }
-  if (r.type->name != expected_type) {
-    raise_error << "can't drop2 " << expected_type << " from " << to_string(r) << '\n' << end();
-    return;
-  }
-  type_tree* tmp = r.type;
-  r.type = tmp->right;
-  tmp->right = NULL;
-  delete tmp;
-}
-
-void drop_one_lookup(reagent& r) {
-  for (vector<pair<string, string_tree*> >::iterator p = r.properties.begin(); p != r.properties.end(); ++p) {
-    if (p->first == "lookup") {
-      r.properties.erase(p);
-      return;
-    }
-  }
-  assert(false);
-}
-
-void test_get_indirect() {
-  Trace_file = "get_indirect";
-  run("recipe main [\n  1:number <- copy 2\n  2:number <- copy 34\n  3:number <- copy 35\n  4:number <- get 1:address:point/lookup, 0:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 4");
-}
-void test_get_indirect2() {
-  Trace_file = "get_indirect2";
-  run("recipe main [\n  1:number <- copy 2\n  2:number <- copy 34\n  3:number <- copy 35\n  4:address:number <- copy 5/unsafe\n  *4:address:number <- get 1:address:point/lookup, 0:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 5");
-}
-void test_include_nonlookup_properties() {
-  Trace_file = "include_nonlookup_properties";
-  run("recipe main [\n  1:number <- copy 2\n  2:number <- copy 34\n  3:number <- copy 35\n  4:number <- get 1:address:point/lookup/foo, 0:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 4");
-}
-void test_get_address_indirect() {
-  Trace_file = "get_address_indirect";
-  run("# 'get' can read from container address\nrecipe main [\n  1:number <- copy 2\n  2:number <- copy 34\n  3:number <- copy 35\n  4:address:number <- get-address 1:address:point/lookup, 0:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 4");
-}
-void test_lookup_abbreviation() {
-  Trace_file = "lookup_abbreviation";
-  run("recipe main [\n  1:address:number <- copy 2/unsafe\n  2:number <- copy 34\n  3:number <- copy *1:address:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: ingredient: 1: (\"address\" \"number\"), {\"lookup\": ()}mem: storing 34 in location 3");
-}
-
-void test_create_array() {
-  Trace_file = "create_array";
-  run("recipe main [\n  # create an array occupying locations 1 (for the size) and 2-4 (for the elements)\n  1:array:number:3 <- create-array\n]\n");
-  CHECK_TRACE_CONTENTS("run: creating array of size 4");
-}
-void test_copy_array() {
-  Trace_file = "copy_array";
-  run("# Arrays can be copied around with a single instruction just like numbers,\n# no matter how large they are.\nrecipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:array:number <- copy 1:array:number:3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 5mem: storing 14 in location 6mem: storing 15 in location 7mem: storing 16 in location 8");
-}
-void test_copy_array_indirect() {
-  Trace_file = "copy_array_indirect";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:address:array:number <- copy 1/unsafe\n  6:array:number <- copy *5:address:array:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 6mem: storing 14 in location 7mem: storing 15 in location 8mem: storing 16 in location 9");
-}
-void test_stash_array() {
-  Trace_file = "stash_array";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  stash [foo:], 1:array:number:3\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: 3 14 15 16");
-}
-void test_container_contains_array() {
-  Trace_file = "container_contains_array";
-  Hide_errors = true;
-  run("container foo [\n  x:array:number:3\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_container_warns_on_dynamic_array_element() {
-  Trace_file = "container_warns_on_dynamic_array_element";
-  Hide_errors = true;
-  run("container foo [\n  x:array:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: container 'foo' cannot determine size of element x");
-}
-void test_index() {
-  Trace_file = "index";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- index 1:array:number:3, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 5");
-}
-void test_index_direct_offset() {
-  Trace_file = "index_direct_offset";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 0\n  6:number <- index 1:array:number, 5:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 6");
-}
-type_tree* array_element(const type_tree* type) {
-  return type->right;
-}
-
-void test_index_indirect() {
-  Trace_file = "index_indirect";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:address:array:number <- copy 1/unsafe\n  6:number <- index *5:address:array:number, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 15 in location 6");
-}
-void test_index_out_of_bounds() {
-  Trace_file = "index_out_of_bounds";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  index *8:address:array:point, 4  # less than size of array in locations, but larger than its length in elements\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid index 4");
-}
-void test_index_out_of_bounds_2() {
-  Trace_file = "index_out_of_bounds_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:point:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  index *8:address:array:point, -1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid index -1");
-}
-void test_index_product_type_mismatch() {
-  Trace_file = "index_product_type_mismatch";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:point:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  9:number <- index *8:address:array:point, 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'index' on *8:address:array:point can't be saved in 9:number; type should be point");
-}
-void test_index_without_product() {
-  Trace_file = "index_without_product";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  index 1:array:number:3, 0\n]\n# just don't die\n");
-}
-void test_index_address() {
-  Trace_file = "index_address";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:address:number <- index-address 1:array:number, 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 5");
-}
-void test_index_address_out_of_bounds() {
-  Trace_file = "index_address_out_of_bounds";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:point:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  index-address *8:address:array:point, 4  # less than size of array in locations, but larger than its length in elements\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid index 4");
-}
-void test_index_address_out_of_bounds_2() {
-  Trace_file = "index_address_out_of_bounds_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:point:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  index-address *8:address:array:point, -1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: invalid index -1");
-}
-void test_index_address_product_type_mismatch() {
-  Trace_file = "index_address_product_type_mismatch";
-  Hide_errors = true;
-  run("recipe main [\n  1:array:point:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- copy 14\n  6:number <- copy 15\n  7:number <- copy 16\n  8:address:array:point <- copy 1/unsafe\n  9:address:number <- index-address *8:address:array:point, 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'index' on *8:address:array:point can't be saved in 9:address:number; type should be (address point)");
-}
-void test_array_length() {
-  Trace_file = "array_length";
-  run("recipe main [\n  1:array:number:3 <- create-array\n  2:number <- copy 14\n  3:number <- copy 15\n  4:number <- copy 16\n  5:number <- length 1:array:number:3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 5");
-}
-bool is_mu_string(const reagent& x) {
-  return x.type
-    && x.type->value == get(Type_ordinal, "address")
-    && x.type->right
-    && x.type->right->value == get(Type_ordinal, "shared")
-    && x.type->right->right
-    && x.type->right->right->value == get(Type_ordinal, "array")
-    && x.type->right->right->right
-    && x.type->right->right->right->value == get(Type_ordinal, "character")
-    && x.type->right->right->right->right == NULL;
-}
-
-
-void test_copy_exclusive_container() {
-  Trace_file = "copy_exclusive_container";
-  run("# Copying exclusive containers copies all their contents and an extra location for the tag.\nrecipe main [\n  1:number <- copy 1  # 'point' variant\n  2:number <- copy 34\n  3:number <- copy 35\n  4:number-or-point <- copy 1:number-or-point/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 4mem: storing 34 in location 5mem: storing 35 in location 6");
-}
-void test_maybe_convert() {
-  Trace_file = "maybe_convert";
-  run("recipe main [\n  12:number <- copy 1\n  13:number <- copy 35\n  14:number <- copy 36\n  20:address:point <- maybe-convert 12:number-or-point/unsafe, 1:variant\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 13 in location 20");
-}
-void test_maybe_convert_fail() {
-  Trace_file = "maybe_convert_fail";
-  run("recipe main [\n  12:number <- copy 1\n  13:number <- copy 35\n  14:number <- copy 36\n  20:address:number <- maybe-convert 12:number-or-point/unsafe, 0:variant\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 20");
-}
-const reagent variant_type(const reagent& canonized_base, long long int tag) {
-  assert(tag >= 0);
-  assert(contains_key(Type, canonized_base.type->value));
-  assert(!get(Type, canonized_base.type->value).name.empty());
-  const type_info& info = get(Type, canonized_base.type->value);
-  assert(info.kind == EXCLUSIVE_CONTAINER);
-  reagent element = info.elements.at(tag);
-  if (contains_type_ingredient(element)) {
-    if (!canonized_base.type->right)
-      raise_error << "illegal type '" << to_string(canonized_base.type) << "' seems to be missing a type ingredient or three\n" << end();
-    replace_type_ingredients(element.type, canonized_base.type->right, info);
-  }
-
-  // End variant_type Special-cases
-  return element;
-}
-
-void test_maybe_convert_product_type_mismatch() {
-  Trace_file = "maybe_convert_product_type_mismatch";
-  Hide_errors = true;
-  run("recipe main [\n  12:number <- copy 1\n  13:number <- copy 35\n  14:number <- copy 36\n  20:address:number <- maybe-convert 12:number-or-point/unsafe, 1:variant\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: 'maybe-convert 12:number-or-point/unsafe, 1:variant' should write to (address point) but 20 has type (address number)");
-}
-void test_exclusive_container() {
-  Trace_file = "exclusive_container";
-  run("exclusive-container foo [\n  x:number\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("parse: --- defining exclusive-container fooparse: element: x: \"number\"parse: element: y: \"number\"");
-}
-void test_exclusive_container_contains_array() {
-  Trace_file = "exclusive_container_contains_array";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:array:number:3\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_exclusive_container_warns_on_dynamic_array_element() {
-  Trace_file = "exclusive_container_warns_on_dynamic_array_element";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:array:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: container 'foo' cannot determine size of element x");
-}
-void test_lift_to_exclusive_container() {
-  Trace_file = "lift_to_exclusive_container";
-  run("exclusive-container foo [\n  x:number\n  y:number\n]\nrecipe main [\n  1:number <- copy 34\n  2:foo <- merge 0/x, 1:number  # tag must be a literal when merging exclusive containers\n  4:foo <- merge 1/y, 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2mem: storing 34 in location 3mem: storing 1 in location 4mem: storing 34 in location 5");
-}
-void test_merge_handles_exclusive_container() {
-  Trace_file = "merge_handles_exclusive_container";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  z:number\n]\nrecipe main [\n  1:foo <- merge 0/x, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 34 in location 2");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_requires_literal_tag_for_exclusive_container() {
-  Trace_file = "merge_requires_literal_tag_for_exclusive_container";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  z:number\n]\nrecipe main [\n  local-scope\n  1:number <- copy 0\n  2:foo <- merge 1:number, 34\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: ingredient 0 of 'merge' should be a literal, for the tag of exclusive-container foo");
-}
-void test_merge_check_container_containing_exclusive_container() {
-  Trace_file = "merge_check_container_containing_exclusive_container";
-  Hide_errors = true;
-  run("container foo [\n  x:number\n  y:bar\n]\nexclusive-container bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 23, 1/y, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 1 in location 2mem: storing 34 in location 3");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_container_containing_exclusive_container_2() {
-  Trace_file = "merge_check_container_containing_exclusive_container_2";
-  Hide_errors = true;
-  run("container foo [\n  x:number\n  y:bar\n]\nexclusive-container bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 23, 1/y, 34, 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:foo <- merge 23, 1/y, 34, 35'");
-}
-void test_merge_check_exclusive_container_containing_container() {
-  Trace_file = "merge_check_exclusive_container_containing_container";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 1/y, 23, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1mem: storing 23 in location 2mem: storing 34 in location 3");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_exclusive_container_containing_container_2() {
-  Trace_file = "merge_check_exclusive_container_containing_container_2";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 0/x, 23\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_exclusive_container_containing_container_3() {
-  Trace_file = "merge_check_exclusive_container_containing_container_3";
-  Hide_errors = true;
-  run("exclusive-container foo [\n  x:number\n  y:bar\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 1/y, 23\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:foo <- merge 1/y, 23'");
-}
-void test_merge_exclusive_container_with_mismatched_sizes() {
-  Trace_file = "merge_exclusive_container_with_mismatched_sizes";
-  run("container foo [\n  x:number\n  y:number\n]\nexclusive-container bar [\n  x:number\n  y:foo\n]\nrecipe main [\n  1:number <- copy 34\n  2:number <- copy 35\n  3:bar <- merge 0/x, 1:number\n  6:bar <- merge 1/foo, 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3mem: storing 34 in location 4mem: storing 1 in location 6mem: storing 34 in location 7mem: storing 35 in location 8");
-}
-
-void test_calling_recipe() {
-  Trace_file = "calling_recipe";
-  run("recipe main [\n  f\n]\nrecipe f [\n  3:number <- add 2, 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 3");
-}
-void test_return_on_fallthrough() {
-  Trace_file = "return_on_fallthrough";
-  run("recipe main [\n  f\n  1:number <- copy 0\n  2:number <- copy 0\n  3:number <- copy 0\n]\nrecipe f [\n  4:number <- copy 0\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("run: frun: 4:number <- copy 0run: 5:number <- copy 0run: 1:number <- copy 0run: 2:number <- copy 0run: 3:number <- copy 0");
-}
-routine::routine(recipe_ordinal r) {
-  if (Trace_stream) {
-    ++Trace_stream->callstack_depth;
-    trace(9999, "trace") << "new routine; incrementing callstack depth to " << Trace_stream->callstack_depth << end();
-    assert(Trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
-  calls.push_front(call(r));
-  alloc = Memory_allocated_until;
-  Memory_allocated_until += Initial_memory_per_routine;
-  alloc_max = Memory_allocated_until;
-  trace(9999, "new") << "routine allocated memory from " << alloc << " to " << alloc_max << end();
-
-  global_space = 0;
-  state = RUNNING;
-
-  id = Next_routine_id;
-  Next_routine_id++;
-
-  parent_index = -1;
-
-  limit = -1;  /* no limit */
-
-  waiting_on_location = old_value_of_waiting_location = 0;
-
-
-  waiting_on_routine = 0;
-
-  // End routine Constructor
-}
-
-inline call& current_call() {
-  return Current_routine->calls.front();
-}
-
-
-inline const instruction& to_instruction(const call& call) {
-  return get(Recipe, call.running_recipe).steps.at(call.running_step_index);
-}
-
-void finish_call_housekeeping(const instruction& call_instruction, const vector<vector<double> >& ingredients) {
-  for (long long int i = 0; i < SIZE(ingredients); ++i) {
-    current_call().ingredient_atoms.push_back(ingredients.at(i));
-    reagent ingredient = call_instruction.ingredients.at(i);
-    canonize_type(ingredient);
-    current_call().ingredients.push_back(ingredient);
-  }
-
-  // End Call Housekeeping
-}
-
-void test_calling_undefined_recipe_fails() {
-  Trace_file = "calling_undefined_recipe_fails";
-  Hide_errors = true;
-  run("recipe main [\n  foo\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: undefined operation in 'foo '");
-}
-void test_calling_undefined_recipe_handles_missing_result() {
-  Trace_file = "calling_undefined_recipe_handles_missing_result";
-  Hide_errors = true;
-  run("recipe main [\n  x:number <- foo\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: undefined operation in 'x:number <- foo '");
-}
-
-void test_next_ingredient() {
-  Trace_file = "next_ingredient";
-  run("recipe main [\n  f 2\n]\nrecipe f [\n  12:number <- next-ingredient\n  13:number <- add 1, 12:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 13");
-}
-void test_next_ingredient_missing() {
-  Trace_file = "next_ingredient_missing";
-  run("recipe main [\n  f\n]\nrecipe f [\n  _, 12:number <- next-ingredient\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 12");
-}
-void test_next_ingredient_fail_on_missing() {
-  Trace_file = "next_ingredient_fail_on_missing";
-  Hide_errors = true;
-  run("recipe main [\n  f\n]\nrecipe f [\n  11:number <- next-ingredient\n]\n");
-  CHECK_TRACE_CONTENTS("error: f: no ingredient to save in 11:number");
-}
-void test_rewind_ingredients() {
-  Trace_file = "rewind_ingredients";
-  run("recipe main [\n  f 2\n]\nrecipe f [\n  12:number <- next-ingredient  # consume ingredient\n  _, 1:boolean <- next-ingredient  # will not find any ingredients\n  rewind-ingredients\n  13:number, 2:boolean <- next-ingredient  # will find ingredient again\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 12mem: storing 0 in location 1mem: storing 2 in location 13mem: storing 1 in location 2");
-}
-void test_ingredient() {
-  Trace_file = "ingredient";
-  run("recipe main [\n  f 1, 2\n]\nrecipe f [\n  12:number <- ingredient 1  # consume second ingredient first\n  13:number, 1:boolean <- next-ingredient  # next-ingredient tries to scan past that\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 12mem: storing 0 in location 1");
-}
-
-void test_reply() {
-  Trace_file = "reply";
-  run("recipe main [\n  1:number, 2:number <- f 34\n]\nrecipe f [\n  12:number <- next-ingredient\n  13:number <- add 1, 12:number\n  reply 12:number, 13:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2");
-}
-void test_reply_container() {
-  Trace_file = "reply_container";
-  run("recipe main [\n  3:point <- f 2\n]\nrecipe f [\n  12:number <- next-ingredient\n  13:number <- copy 35\n  reply 12:point/raw\n]\n");
-  CHECK_TRACE_CONTENTS("run: result 0 is [2, 35]mem: storing 2 in location 3mem: storing 35 in location 4");
-}
-void check_types_of_reply_instructions(recipe_ordinal r) {
-  const recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- check types of reply instructions in recipe " << caller.name << end();
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    const instruction& caller_instruction = caller.steps.at(i);
-    if (caller_instruction.is_label) continue;
-    if (caller_instruction.products.empty()) continue;
-    if (caller_instruction.operation < MAX_PRIMITIVE_RECIPES) continue;
-    const recipe& callee = get(Recipe, caller_instruction.operation);
-    for (long long int i = 0; i < SIZE(callee.steps); ++i) {
-      const instruction& reply_inst = callee.steps.at(i);
-      if (reply_inst.operation != REPLY) continue;
-      // check types with the caller
-      if (SIZE(caller_instruction.products) > SIZE(reply_inst.ingredients)) {
-        raise_error << maybe(caller.name) << "too few values replied from " << callee.name << '\n' << end();
-        break;
-      }
-      for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) {
-        if (!types_coercible(caller_instruction.products.at(i), reply_inst.ingredients.at(i))) {
-          raise_error << maybe(callee.name) << "reply ingredient " << reply_inst.ingredients.at(i).original_string << " can't be saved in " << caller_instruction.products.at(i).original_string << '\n' << end();
-          reagent lhs = reply_inst.ingredients.at(i);
-          canonize_type(lhs);
-          reagent rhs = caller_instruction.products.at(i);
-          canonize_type(rhs);
-          raise_error << to_string(lhs.type) << " vs " << to_string(rhs.type) << '\n' << end();
-          goto finish_reply_check;
-        }
-      }
-      // check that any reply ingredients with /same-as-ingredient connect up
-      // the corresponding ingredient and product in the caller.
-      for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) {
-        if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) {
-          string_tree* tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
-          if (!tmp || tmp->right) {
-            raise_error << maybe(caller.name) << "'same-as-ingredient' metadata should take exactly one value in " << to_string(reply_inst) << '\n' << end();
-            goto finish_reply_check;
-          }
-          long long int ingredient_index = to_integer(tmp->value);
-          if (ingredient_index >= SIZE(caller_instruction.ingredients)) {
-            raise_error << maybe(caller.name) << "too few ingredients in '" << to_string(caller_instruction) << "'\n" << end();
-            goto finish_reply_check;
-          }
-          if (!is_dummy(caller_instruction.products.at(i)) && !is_literal(caller_instruction.ingredients.at(ingredient_index)) && caller_instruction.products.at(i).name != caller_instruction.ingredients.at(ingredient_index).name) {
-            raise_error << maybe(caller.name) << "'" << to_string(caller_instruction) << "' should write to " << caller_instruction.ingredients.at(ingredient_index).original_string << " rather than " << caller_instruction.products.at(i).original_string << '\n' << end();
-          }
-        }
-      }
-      finish_reply_check:;
-    }
-  }
-}
-
-void test_reply_type_mismatch() {
-  Trace_file = "reply_type_mismatch";
-  Hide_errors = true;
-  run("recipe main [\n  3:number <- f 2\n]\nrecipe f [\n  12:number <- next-ingredient\n  13:number <- copy 35\n  14:point <- copy 12:point/raw\n  reply 14:point\n]\n");
-  CHECK_TRACE_CONTENTS("error: f: reply ingredient 14:point can't be saved in 3:number");
-}
-void test_reply_same_as_ingredient() {
-  Trace_file = "reply_same_as_ingredient";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy 0\n  2:number <- test1 1:number  # call with different ingredient and product\n]\nrecipe test1 [\n  10:number <- next-ingredient\n  reply 10:number/same-as-ingredient:0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: '2:number <- test1 1:number' should write to 1:number rather than 2:number");
-}
-void test_reply_same_as_ingredient_dummy() {
-  Trace_file = "reply_same_as_ingredient_dummy";
-  run("# % Hide_errors = true;\nrecipe main [\n  1:number <- copy 0\n  _ <- test1 1:number  # call with different ingredient and product\n]\nrecipe test1 [\n  10:number <- next-ingredient\n  reply 10:number/same-as-ingredient:0\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-string to_string(const vector<double>& in) {
-  if (in.empty()) return "[]";
-  ostringstream out;
-  if (SIZE(in) == 1) {
-    out << no_scientific(in.at(0));
-    return out.str();
-  }
-  out << "[";
-  for (long long int i = 0; i < SIZE(in); ++i) {
-    if (i > 0) out << ", ";
-    out << no_scientific(in.at(i));
-  }
-  out << "]";
-  return out.str();
-}
-
-
-void test_reply_if() {
-  Trace_file = "reply_if";
-  run("recipe main [\n  1:number <- test1\n]\nrecipe test1 [\n  reply-if 0, 34\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 1");
-}
-void test_reply_if_2() {
-  Trace_file = "reply_if_2";
-  run("recipe main [\n  1:number <- test1\n]\nrecipe test1 [\n  reply-if 1, 34\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-
-
-void test_new() {
-  Trace_file = "new";
-  run("# call new two times with identical arguments; you should get back different results\nrecipe main [\n  1:address:shared:number/raw <- new number:type\n  2:address:shared:number/raw <- new number:type\n  3:boolean/raw <- equal 1:address:shared:number/raw, 2:address:shared:number/raw\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-bool product_of_new_is_valid(const instruction& inst) {
-  reagent product = inst.products.at(0);
-  canonize_type(product);
-  if (!product.type || product.type->value != get(Type_ordinal, "address")) return false;
-  drop_from_type(product, "address");
-  if (!product.type || product.type->value != get(Type_ordinal, "shared")) return false;
-  drop_from_type(product, "shared");
-  if (SIZE(inst.ingredients) > 1) {
-    // array allocation
-    if (!product.type || product.type->value != get(Type_ordinal, "array")) return false;
-    drop_from_type(product, "array");
-  }
-  reagent expected_product("x:"+inst.ingredients.at(0).name);
-  {
-    string_tree* tmp_type_names = parse_string_tree(expected_product.type->name);
-    delete expected_product.type;
-    expected_product.type = new_type_tree(tmp_type_names);
-    delete tmp_type_names;
-  }
-  // End Post-processing(expected_product) When Checking 'new'
-  return types_strictly_match(product, expected_product);
-}
-
-void transform_new_to_allocate(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- convert 'new' to 'allocate' for recipe " << get(Recipe, r).name << end();
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    instruction& inst = get(Recipe, r).steps.at(i);
-    if (inst.name == "new" && is_literal_string(inst.ingredients.at(0))) continue;
-    // Convert 'new' To 'allocate'
-    if (inst.name == "new") {
-      inst.operation = ALLOCATE;
-      string_tree* type_name = new string_tree(inst.ingredients.at(0).name);
-      type_name = parse_string_tree(type_name);
-
-      // End Post-processing(type_name) When Converting 'new'
-      type_tree* type = new_type_tree(type_name);
-      inst.ingredients.at(0).set_value(size_of(type));
-      trace(9992, "new") << "size of " << to_string(type_name) << " is " << inst.ingredients.at(0).value << end();
-      delete type;
-      delete type_name;
-    }
-  }
-}
-
-
-void ensure_space(long long int size) {
-  if (size > Initial_memory_per_routine) {
-    tb_shutdown();
-    cerr << "can't allocate " << size << " locations, that's too much compared to " << Initial_memory_per_routine << ".\n";
-    exit(0);
-  }
-  if (Current_routine->alloc + size > Current_routine->alloc_max) {
-    // waste the remaining space and create a new chunk
-    Current_routine->alloc = Memory_allocated_until;
-    Memory_allocated_until += Initial_memory_per_routine;
-    Current_routine->alloc_max = Memory_allocated_until;
-    trace(9999, "new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max << end();
-  }
-}
-
-void test_new_initializes() {
-  Trace_file = "new_initializes";
-  Memory_allocated_until = 10;
-  put(Memory, Memory_allocated_until, 1);
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  2:number <- copy *1:address:shared:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2");
-}
-void test_new_error() {
-  Trace_file = "new_error";
-  Hide_errors = true;
-  run("recipe main [\n  1:address:number/raw <- new number:type\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: product of 'new' has incorrect type: 1:address:number/raw <- new number:type");
-}
-void test_new_array() {
-  Trace_file = "new_array";
-  run("recipe main [\n  1:address:shared:array:number/raw <- new number:type, 5\n  2:address:shared:number/raw <- new number:type\n  3:number/raw <- subtract 2:address:shared:number/raw, 1:address:shared:array:number/raw\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:array:number/raw <- new number:type, 5mem: array size is 5mem: storing 7 in location 3");
-}
-void test_new_empty_array() {
-  Trace_file = "new_empty_array";
-  run("recipe main [\n  1:address:shared:array:number/raw <- new number:type, 0\n  2:address:shared:number/raw <- new number:type\n  3:number/raw <- subtract 2:address:shared:number/raw, 1:address:shared:array:number/raw\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:array:number/raw <- new number:type, 0mem: array size is 0mem: storing 2 in location 3");
-}
-void test_new_overflow() {
-  Trace_file = "new_overflow";
-  Initial_memory_per_routine = 3;  // barely enough room for point allocation below
-  run("recipe main [\n  1:address:shared:number/raw <- new number:type\n  2:address:shared:point/raw <- new point:type  # not enough room in initial page\n]\n");
-  CHECK_TRACE_CONTENTS("new: routine allocated memory from 1000 to 1003new: routine allocated memory from 1003 to 1006");
-}
-void test_new_reclaim() {
-  Trace_file = "new_reclaim";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  2:address:shared:number <- copy 1:address:shared:number  # because 1 will get reset during abandon below\n  abandon 1:address:shared:number  # unsafe\n  3:address:shared:number <- new number:type  # must be same size as abandoned memory to reuse\n  4:boolean <- equal 2:address:shared:number, 3:address:shared:number\n]\n# both allocations should have returned the same address\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 4");
-}
-void abandon(long long int address, long long int size) {
-  trace(9999, "abandon") << "saving in free-list of size " << size << end();
-//?   Total_free += size;
-//?   Num_free++;
-//?   cerr << "abandon: " << size << '\n';
-  // clear memory
-  for (long long int curr = address; curr < address+size; ++curr)
-    put(Memory, curr, 0);
-  // append existing free list to address
-  put(Memory, address, get_or_insert(Free_list, size));
-  put(Free_list, size, address);
-}
-
-void test_new_differing_size_no_reclaim() {
-  Trace_file = "new_differing_size_no_reclaim";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  2:address:shared:number <- copy 1:address:shared:number\n  abandon 1:address:shared:number\n  3:address:shared:array:number <- new number:type, 2  # different size\n  4:boolean <- equal 2:address:shared:number, 3:address:shared:array:number\n]\n# no reuse\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 4");
-}
-void test_new_reclaim_array() {
-  Trace_file = "new_reclaim_array";
-  run("recipe main [\n  1:address:shared:array:number <- new number:type, 2\n  2:address:shared:array:number <- copy 1:address:shared:array:number\n  abandon 1:address:shared:array:number  # unsafe\n  3:address:shared:array:number <- new number:type, 2\n  4:boolean <- equal 2:address:shared:array:number, 3:address:shared:array:number\n]\n# reuse\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 4");
-}
-void test_reset_on_abandon() {
-  Trace_file = "reset_on_abandon";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  abandon 1:address:shared:number\n]\n# reuse\n");
-  CHECK_TRACE_CONTENTS("run: abandon 1:address:shared:numbermem: resetting location 1");
-}
-void test_refcounts() {
-  Trace_file = "refcounts";
-  run("recipe main [\n  1:address:shared:number <- copy 1000/unsafe\n  2:address:shared:number <- copy 1:address:shared:number\n  1:address:shared:number <- copy 0\n  2:address:shared:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- copy 1000/unsafemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- copy 1:address:shared:numbermem: incrementing refcount of 1000: 1 -> 2run: 1:address:shared:number <- copy 0mem: decrementing refcount of 1000: 2 -> 1run: 2:address:shared:number <- copy 0mem: decrementing refcount of 1000: 1 -> 0mem: automatically abandoning 1000");
-}
-void test_refcounts_2() {
-  Trace_file = "refcounts_2";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  # over-writing one allocation with another\n  1:address:shared:number <- new number:type\n  1:address:shared:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 1:address:shared:number <- new number:typemem: automatically abandoning 1000");
-}
-void test_refcounts_3() {
-  Trace_file = "refcounts_3";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  # passing in addresses to recipes increments refcount\n  foo 1:address:shared:number\n  1:address:shared:number <- copy 0\n]\nrecipe foo [\n  2:address:shared:number <- next-ingredient\n  # return does NOT yet decrement refcount; memory must be explicitly managed\n  2:address:shared:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- next-ingredientmem: incrementing refcount of 1000: 1 -> 2run: 2:address:shared:number <- copy 0mem: decrementing refcount of 1000: 2 -> 1run: 1:address:shared:number <- copy 0mem: decrementing refcount of 1000: 1 -> 0mem: automatically abandoning 1000");
-}
-void test_refcounts_4() {
-  Trace_file = "refcounts_4";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  # idempotent copies leave refcount unchanged\n  1:address:shared:number <- copy 1:address:shared:number\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 1:address:shared:number <- copy 1:address:shared:numbermem: decrementing refcount of 1000: 1 -> 0mem: incrementing refcount of 1000: 0 -> 1");
-}
-void test_refcounts_5() {
-  Trace_file = "refcounts_5";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  # passing in addresses to recipes increments refcount\n  foo 1:address:shared:number\n  # return does NOT yet decrement refcount; memory must be explicitly managed\n  1:address:shared:number <- new number:type\n]\nrecipe foo [\n  2:address:shared:number <- next-ingredient\n]\n");
-  CHECK_TRACE_CONTENTS("run: 1:address:shared:number <- new number:typemem: incrementing refcount of 1000: 0 -> 1run: 2:address:shared:number <- next-ingredientmem: incrementing refcount of 1000: 1 -> 2run: 1:address:shared:number <- new number:typemem: decrementing refcount of 1000: 2 -> 1");
-}
-void test_new_string() {
-  Trace_file = "new_string";
-  run("recipe main [\n  1:address:shared:array:character <- new [abc def]\n  2:character <- index *1:address:shared:array:character, 5\n]\n# number code for 'e'\n");
-  CHECK_TRACE_CONTENTS("mem: storing 101 in location 2");
-}
-void test_new_string_handles_unicode() {
-  Trace_file = "new_string_handles_unicode";
-  run("recipe main [\n  1:address:shared:array:character <- new [a«c]\n  2:number <- length *1:address:shared:array:character\n  3:character <- index *1:address:shared:array:character, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 2mem: storing 171 in location 3");
-}
-long long int new_mu_string(const string& contents) {
-  // allocate an array just large enough for it
-  long long int string_length = unicode_length(contents);
-//?   Total_alloc += string_length+1;
-//?   Num_alloc++;
-  ensure_space(string_length+1);  // don't forget the extra location for array size
-  // initialize string
-  long long int result = Current_routine->alloc;
-  // initialize refcount
-  put(Memory, Current_routine->alloc++, 0);
-  // store length
-  put(Memory, Current_routine->alloc++, string_length);
-  long long int curr = 0;
-  const char* raw_contents = contents.c_str();
-  for (long long int i = 0; i < string_length; ++i) {
-    uint32_t curr_character;
-    assert(curr < SIZE(contents));
-    tb_utf8_char_to_unicode(&curr_character, &raw_contents[curr]);
-    put(Memory, Current_routine->alloc, curr_character);
-    curr += tb_utf8_char_length(raw_contents[curr]);
-    ++Current_routine->alloc;
-  }
-  // mu strings are not null-terminated in memory
-  return result;
-}
-
-
-void test_stash_string() {
-  Trace_file = "stash_string";
-  run("recipe main [\n  1:address:shared:array:character <- new [abc]\n  stash [foo:], 1:address:shared:array:character\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: abc");
-}
-void test_unicode_string() {
-  Trace_file = "unicode_string";
-  run("recipe main [\n  1:address:shared:array:character <- new [♠]\n  stash [foo:], 1:address:shared:array:character\n]\n");
-  CHECK_TRACE_CONTENTS("app: foo: ♠");
-}
-void test_stash_space_after_string() {
-  Trace_file = "stash_space_after_string";
-  run("recipe main [\n  1:address:shared:array:character <- new [abc]\n  stash 1:address:shared:array:character, [foo]\n]\n");
-  CHECK_TRACE_CONTENTS("app: abc foo");
-}
-void test_new_string_overflow() {
-  Trace_file = "new_string_overflow";
-  Initial_memory_per_routine = 2;
-  run("recipe main [\n  1:address:shared:number/raw <- new number:type\n  2:address:shared:array:character/raw <- new [a]  # not enough room in initial page, if you take the array size into account\n]\n");
-  CHECK_TRACE_CONTENTS("new: routine allocated memory from 1000 to 1002new: routine allocated memory from 1002 to 1004");
-}
-long long int unicode_length(const string& s) {
-  const char* in = s.c_str();
-  long long int result = 0;
-  long long int curr = 0;
-  while (curr < SIZE(s)) {  // carefully bounds-check on the string
-    // before accessing its raw pointer
-    ++result;
-    curr += tb_utf8_char_length(in[curr]);
-  }
-  return result;
-}
-
-string read_mu_string(long long int address) {
-  if (address == 0) return "";
-  address++;  // skip refcount
-  long long int size = get_or_insert(Memory, address);
-  if (size == 0) return "";
-  ostringstream tmp;
-  for (long long int curr = address+1; curr <= address+size; ++curr) {
-    tmp << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
-  }
-  return tmp.str();
-}
-
-bool is_mu_type_literal(reagent r) {
-  return is_literal(r) && r.type && r.type->name == "type";
-}
-
-bool is_shared_address_of_array_of_numbers(reagent product) {
-  canonize_type(product);
-  if (!product.type || product.type->value != get(Type_ordinal, "address")) return false;
-  drop_from_type(product, "address");
-  if (!product.type || product.type->value != get(Type_ordinal, "shared")) return false;
-  drop_from_type(product, "shared");
-  if (!product.type || product.type->value != get(Type_ordinal, "array")) return false;
-  drop_from_type(product, "array");
-  if (!product.type || product.type->value != get(Type_ordinal, "number")) return false;
-  return true;
-}
-
-void test_brace_conversion() {
-  Trace_file = "brace_conversion";
-  transform("recipe main [\n  {\n    break\n    1:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: jump 1:offsettransform: copy ...");
-}
-void transform_braces(const recipe_ordinal r) {
-  const int OPEN = 0, CLOSE = 1;
-  // use signed integer for step index because we'll be doing arithmetic on it
-  list<pair<int/*OPEN/CLOSE*/, /*step*/long long int> > braces;
-  trace(9991, "transform") << "--- transform braces for recipe " << get(Recipe, r).name << end();
-//?   cerr << "--- transform braces for recipe " << get(Recipe, r).name << '\n';
-  for (long long int index = 0; index < SIZE(get(Recipe, r).steps); ++index) {
-    const instruction& inst = get(Recipe, r).steps.at(index);
-    if (inst.label == "{") {
-      trace(9993, "transform") << maybe(get(Recipe, r).name) << "push (open, " << index << ")" << end();
-      braces.push_back(pair<int,long long int>(OPEN, index));
-    }
-    if (inst.label == "}") {
-      trace(9993, "transform") << "push (close, " << index << ")" << end();
-      braces.push_back(pair<int,long long int>(CLOSE, index));
-    }
-  }
-  stack</*step*/long long int> open_braces;
-  for (long long int index = 0; index < SIZE(get(Recipe, r).steps); ++index) {
-    instruction& inst = get(Recipe, r).steps.at(index);
-    if (inst.label == "{") {
-      open_braces.push(index);
-      continue;
-    }
-    if (inst.label == "}") {
-      if (open_braces.empty()) {
-        raise << "missing '{' in " << get(Recipe, r).name << '\n';
-        return;
-      }
-      open_braces.pop();
-      continue;
-    }
-    if (inst.is_label) continue;
-    if (inst.old_name != "loop"
-         && inst.old_name != "loop-if"
-         && inst.old_name != "loop-unless"
-         && inst.old_name != "break"
-         && inst.old_name != "break-if"
-         && inst.old_name != "break-unless") {
-      trace(9992, "transform") << inst.old_name << " ..." << end();
-      continue;
-    }
-    // check for errors
-    if (inst.old_name.find("-if") != string::npos || inst.old_name.find("-unless") != string::npos) {
-      if (inst.ingredients.empty()) {
-        raise_error << inst.old_name << " expects 1 or 2 ingredients, but got none\n" << end();
-        continue;
-      }
-    }
-    // update instruction operation
-    if (inst.old_name.find("-if") != string::npos) {
-      inst.name = "jump-if";
-      inst.operation = JUMP_IF;
-    }
-    else if (inst.old_name.find("-unless") != string::npos) {
-      inst.name = "jump-unless";
-      inst.operation = JUMP_UNLESS;
-    }
-    else {
-      inst.name = "jump";
-      inst.operation = JUMP;
-    }
-    // check for explicitly provided targets
-    if (inst.old_name.find("-if") != string::npos || inst.old_name.find("-unless") != string::npos) {
-      // conditional branches check arg 1
-      if (SIZE(inst.ingredients) > 1 && is_literal(inst.ingredients.at(1))) {
-        trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(1).name << ":offset" << end();
-        continue;
-      }
-    }
-    else {
-      // unconditional branches check arg 0
-      if (!inst.ingredients.empty() && is_literal(inst.ingredients.at(0))) {
-        trace(9992, "transform") << "jump " << inst.ingredients.at(0).name << ":offset" << end();
-        continue;
-      }
-    }
-    // if implicit, compute target
-    reagent target;
-    target.type = new type_tree("offset", get(Type_ordinal, "offset"));
-    target.set_value(0);
-    if (open_braces.empty())
-      raise_error << inst.old_name << " needs a '{' before\n" << end();
-    else if (inst.old_name.find("loop") != string::npos)
-      target.set_value(open_braces.top()-index);
-    else  // break instruction
-      target.set_value(matching_brace(open_braces.top(), braces, r) - index - 1);
-    inst.ingredients.push_back(target);
-    // log computed target
-    if (inst.name == "jump")
-      trace(9992, "transform") << "jump " << no_scientific(target.value) << ":offset" << end();
-    else
-      trace(9992, "transform") << inst.name << ' ' << inst.ingredients.at(0).name << ", " << no_scientific(target.value) << ":offset" << end();
-  }
-}
-
-// returns a signed integer not just so that we can return -1 but also to
-// enable future signed arithmetic
-long long int matching_brace(long long int index, const list<pair<int, long long int> >& braces, recipe_ordinal r) {
-  int stacksize = 0;
-  for (list<pair<int, long long int> >::const_iterator p = braces.begin(); p != braces.end(); ++p) {
-    if (p->second < index) continue;
-    stacksize += (p->first ? 1 : -1);
-    if (stacksize == 0) return p->second;
-  }
-  raise_error << maybe(get(Recipe, r).name) << "unbalanced '{'\n" << end();
-  return SIZE(get(Recipe, r).steps);  // exit current routine
-}
-
-void test_loop() {
-  Trace_file = "loop";
-  transform("recipe main [\n  1:number <- copy 0\n  2:number <- copy 0\n  {\n    3:number <- copy 0\n    loop\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: copy ...transform: jump -2:offset");
-}
-void test_break_empty_block() {
-  Trace_file = "break_empty_block";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    break\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: jump 0:offset");
-}
-void test_break_cascading() {
-  Trace_file = "break_cascading";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    break\n  }\n  {\n    break\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: jump 0:offsettransform: jump 0:offset");
-}
-void test_break_cascading_2() {
-  Trace_file = "break_cascading_2";
-  transform("recipe main [\n  1:number <- copy 0\n  2:number <- copy 0\n  {\n    break\n    3:number <- copy 0\n  }\n  {\n    break\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump 1:offsettransform: copy ...transform: jump 0:offset");
-}
-void test_break_if() {
-  Trace_file = "break_if";
-  transform("recipe main [\n  1:number <- copy 0\n  2:number <- copy 0\n  {\n    break-if 2:number\n    3:number <- copy 0\n  }\n  {\n    break\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-if 2, 1:offsettransform: copy ...transform: jump 0:offset");
-}
-void test_break_nested() {
-  Trace_file = "break_nested";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    2:number <- copy 0\n    break\n    {\n      3:number <- copy 0\n    }\n    4:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: jump 4:offset");
-}
-void test_break_nested_degenerate() {
-  Trace_file = "break_nested_degenerate";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    2:number <- copy 0\n    break\n    {\n    }\n    4:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: jump 3:offset");
-}
-void test_break_nested_degenerate_2() {
-  Trace_file = "break_nested_degenerate_2";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    2:number <- copy 0\n    break\n    {\n    }\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: jump 2:offset");
-}
-void test_break_label() {
-  Trace_file = "break_label";
-  Hide_errors = true;
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    break +foo:offset\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: jump +foo:offset");
-}
-void test_break_unless() {
-  Trace_file = "break_unless";
-  transform("recipe main [\n  1:number <- copy 0\n  2:number <- copy 0\n  {\n    break-unless 2:number\n    3:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-unless 2, 1:offsettransform: copy ...");
-}
-void test_loop_unless() {
-  Trace_file = "loop_unless";
-  transform("recipe main [\n  1:number <- copy 0\n  2:number <- copy 0\n  {\n    loop-unless 2:number\n    3:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...transform: jump-unless 2, -1:offsettransform: copy ...");
-}
-void test_loop_nested() {
-  Trace_file = "loop_nested";
-  transform("recipe main [\n  1:number <- copy 0\n  {\n    2:number <- copy 0\n    {\n      3:number <- copy 0\n    }\n    loop-if 4:boolean\n    5:number <- copy 0\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: jump-if 4, -5:offset");
-}
-void test_loop_label() {
-  Trace_file = "loop_label";
-  transform("recipe main [\n  1:number <- copy 0\n  +foo\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("transform: --- transform braces for recipe maintransform: copy ...transform: copy ...");
-}
-void test_brace_conversion_and_run() {
-  Trace_file = "brace_conversion_and_run";
-  run("recipe test-factorial [\n  1:number <- copy 5\n  2:number <- copy 1\n  {\n    3:boolean <- equal 1:number, 1\n    break-if 3:boolean\n#    $print 1:number\n    2:number <- multiply 2:number, 1:number\n    1:number <- subtract 1:number, 1\n    loop\n  }\n  4:number <- copy 2:number  # trigger a read\n]\n");
-  CHECK_TRACE_CONTENTS("mem: location 2 is 120");
-}
-void test_break_outside_braces_fails() {
-  Trace_file = "break_outside_braces_fails";
-  Hide_errors = true;
-  run("recipe main [\n  break\n]\n");
-  CHECK_TRACE_CONTENTS("error: break needs a '{' before");
-}
-void test_break_conditional_without_ingredient_fails() {
-  Trace_file = "break_conditional_without_ingredient_fails";
-  Hide_errors = true;
-  run("recipe main [\n  {\n    break-if\n  }\n]\n");
-  CHECK_TRACE_CONTENTS("error: break-if expects 1 or 2 ingredients, but got none");
-}
-
-void test_jump_to_label() {
-  Trace_file = "jump_to_label";
-  run("recipe main [\n  jump +target:label\n  1:number <- copy 0\n  +target\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1");
-}
-void transform_labels(const recipe_ordinal r) {
-  map<string, long long int> offset;
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    const instruction& inst = get(Recipe, r).steps.at(i);
-    if (!inst.label.empty() && inst.label.at(0) == '+') {
-      if (!contains_key(offset, inst.label)) {
-        put(offset, inst.label, i);
-      }
-      else {
-        raise_error << maybe(get(Recipe, r).name) << "duplicate label '" << inst.label << "'" << end();
-        // have all jumps skip some random but noticeable and deterministic amount of code
-        put(offset, inst.label, 9999);
-      }
-    }
-  }
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    instruction& inst = get(Recipe, r).steps.at(i);
-    if (inst.name == "jump") {
-      replace_offset(inst.ingredients.at(0), offset, i, r);
-    }
-    if (inst.name == "jump-if" || inst.name == "jump-unless") {
-      replace_offset(inst.ingredients.at(1), offset, i, r);
-    }
-    if ((inst.name == "loop" || inst.name == "break")
-        && SIZE(inst.ingredients) == 1) {
-      replace_offset(inst.ingredients.at(0), offset, i, r);
-    }
-    if ((inst.name == "loop-if" || inst.name == "loop-unless"
-            || inst.name == "break-if" || inst.name == "break-unless")
-        && SIZE(inst.ingredients) == 2) {
-      replace_offset(inst.ingredients.at(1), offset, i, r);
-    }
-  }
-}
-
-void replace_offset(reagent& x, /*const*/ map<string, long long int>& offset, const long long int current_offset, const recipe_ordinal r) {
-  if (!is_literal(x)) {
-    raise_error << maybe(get(Recipe, r).name) << "jump target must be offset or label but is " << x.original_string << '\n' << end();
-    x.set_value(0);  // no jump by default
-    return;
-  }
-  if (x.initialized) return;
-  if (is_integer(x.name)) return;  // non-labels will be handled like other number operands
-  if (!is_jump_target(x.name)) {
-    raise_error << maybe(get(Recipe, r).name) << "can't jump to label " << x.name << '\n' << end();
-    x.set_value(0);  // no jump by default
-    return;
-  }
-  if (!contains_key(offset, x.name)) {
-    raise_error << maybe(get(Recipe, r).name) << "can't find label " << x.name << '\n' << end();
-    x.set_value(0);  // no jump by default
-    return;
-  }
-  x.set_value(get(offset, x.name) - current_offset);
-}
-
-bool is_jump_target(string label) {
-  return label.at(0) == '+';
-}
-
-void test_break_to_label() {
-  Trace_file = "break_to_label";
-  run("recipe main [\n  {\n    {\n      break +target:label\n      1:number <- copy 0\n    }\n  }\n  +target\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1");
-}
-void test_jump_if_to_label() {
-  Trace_file = "jump_if_to_label";
-  run("recipe main [\n  {\n    {\n      jump-if 1, +target:label\n      1:number <- copy 0\n    }\n  }\n  +target\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1");
-}
-void test_loop_unless_to_label() {
-  Trace_file = "loop_unless_to_label";
-  run("recipe main [\n  {\n    {\n      loop-unless 0, +target:label  # loop/break with a label don't care about braces\n      1:number <- copy 0\n    }\n  }\n  +target\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 1");
-}
-void test_jump_runs_code_after_label() {
-  Trace_file = "jump_runs_code_after_label";
-  run("recipe main [\n  # first a few lines of padding to exercise the offset computation\n  1:number <- copy 0\n  2:number <- copy 0\n  3:number <- copy 0\n  jump +target:label\n  4:number <- copy 0\n  +target\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 5");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 4");
-}
-void test_recipe_fails_on_duplicate_jump_target() {
-  Trace_file = "recipe_fails_on_duplicate_jump_target";
-  Hide_errors = true;
-  run("recipe main [\n  +label\n  1:number <- copy 0\n  +label\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: duplicate label '+label'");
-}
-void test_jump_ignores_nontarget_label() {
-  Trace_file = "jump_ignores_nontarget_label";
-  Hide_errors = true;
-  run("recipe main [\n  # first a few lines of padding to exercise the offset computation\n  1:number <- copy 0\n  2:number <- copy 0\n  3:number <- copy 0\n  jump $target:label\n  4:number <- copy 0\n  $target\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't jump to label $target");
-}
-
-void test_transform_names() {
-  Trace_file = "transform_names";
-  run("recipe main [\n  x:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("name: assign x 1mem: storing 0 in location 1");
-}
-void test_transform_names_fails_on_use_before_define() {
-  Trace_file = "transform_names_fails_on_use_before_define";
-  Hide_errors = true;
-  transform("recipe main [\n  x:number <- copy y:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: use before set: y");
-}
-void transform_names(const recipe_ordinal r) {
-  recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- transform names for recipe " << caller.name << end();
-//?   cerr << "--- transform names for recipe " << caller.name << '\n';
-  bool names_used = false;
-  bool numeric_locations_used = false;
-  map<string, long long int>& names = Name[r];
-  // store the indices 'used' so far in the map
-  long long int& curr_idx = names[""];
-  ++curr_idx;  // avoid using index 0, benign skip in some other cases
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    instruction& inst = caller.steps.at(i);
-    // replace element names of containers with offsets
-    if (inst.name == "get" || inst.name == "get-address") {
-      if (SIZE(inst.ingredients) != 2) {
-        raise_error << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_string(inst) << "'\n" << end();
-        break;
-      }
-      if (!is_literal(inst.ingredients.at(1)))
-        raise_error << maybe(get(Recipe, r).name) << "expected ingredient 1 of " << (inst.name == "get" ? "'get'" : "'get-address'") << " to have type 'offset'; got " << inst.ingredients.at(1).original_string << '\n' << end();
-      if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
-        // since first non-address in base type must be a container, we don't have to canonize
-        type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type, get(Recipe, r).name);
-        if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
-          inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
-          trace(9993, "name") << "element " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " is at offset " << no_scientific(inst.ingredients.at(1).value) << end();
-        }
-      }
-    }
-
-    // convert variant names of exclusive containers
-    if (inst.name == "maybe-convert") {
-      if (SIZE(inst.ingredients) != 2) {
-        raise_error << maybe(get(Recipe, r).name) << "exactly 2 ingredients expected in '" << to_string(inst) << "'\n" << end();
-        break;
-      }
-      assert(is_literal(inst.ingredients.at(1)));
-      if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
-        // since first non-address in base type must be an exclusive container, we don't have to canonize
-        type_ordinal base_type = skip_addresses(inst.ingredients.at(0).type, get(Recipe, r).name);
-        if (contains_key(Type, base_type)) {  // otherwise we'll raise an error elsewhere
-          inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name, get(Recipe, r).name));
-          trace(9993, "name") << "variant " << inst.ingredients.at(1).name << " of type " << get(Type, base_type).name << " has tag " << no_scientific(inst.ingredients.at(1).value) << end();
-        }
-      }
-    }
-
-    // End transform_names(inst) Special-cases
-    // map names to addresses
-    for (long long int in = 0; in < SIZE(inst.ingredients); ++in) {
-      if (is_disqualified(inst.ingredients.at(in), inst, caller.name)) continue;
-      if (is_numeric_location(inst.ingredients.at(in))) numeric_locations_used = true;
-      if (is_named_location(inst.ingredients.at(in))) names_used = true;
-      if (is_integer(inst.ingredients.at(in).name)) continue;
-      if (!already_transformed(inst.ingredients.at(in), names)) {
-        raise_error << maybe(caller.name) << "use before set: " << inst.ingredients.at(in).name << '\n' << end();
-      }
-      inst.ingredients.at(in).set_value(lookup_name(inst.ingredients.at(in), r));
-    }
-    for (long long int out = 0; out < SIZE(inst.products); ++out) {
-      if (is_disqualified(inst.products.at(out), inst, caller.name)) continue;
-      if (is_numeric_location(inst.products.at(out))) numeric_locations_used = true;
-      if (is_named_location(inst.products.at(out))) names_used = true;
-      if (is_integer(inst.products.at(out).name)) continue;
-      if (names.find(inst.products.at(out).name) == names.end()) {
-        trace(9993, "name") << "assign " << inst.products.at(out).name << " " << curr_idx << end();
-        names[inst.products.at(out).name] = curr_idx;
-        curr_idx += size_of(inst.products.at(out));
-      }
-      inst.products.at(out).set_value(lookup_name(inst.products.at(out), r));
-    }
-  }
-  if (names_used && numeric_locations_used)
-    raise_error << maybe(caller.name) << "mixing variable names and numeric addresses\n" << end();
-}
-
-bool is_disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) {
-  if (!x.type) {
-    if (!x.type && contains_key(Recipe_ordinal, x.name)) {
-      x.type = new type_tree("recipe-literal", get(Type_ordinal, "recipe-literal"));
-      x.set_value(get(Recipe_ordinal, x.name));
-      return true;
-    }
-
-    // End Null-type is_disqualified Exceptions
-    raise_error << maybe(recipe_name) << "missing type for " << x.original_string << " in '" << to_string(inst) << "'\n" << end();
-    return true;
-  }
-  if (is_raw(x)) return true;
-  if (is_literal(x)) return true;
-  if (x.name == "default-space")
-    x.initialized = true;
-  if (x.name == "number-of-locals")
-    x.initialized = true;
-  if (x.name == "global-space")
-    x.initialized = true;
-  // End is_disqualified Cases
-  if (x.initialized) return true;
-  return false;
-}
-
-bool already_transformed(const reagent& r, const map<string, long long int>& names) {
-  if (has_property(r, "space")) {
-    string_tree* p = property(r, "space");
-    if (!p || p->right) {
-      raise_error << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end();
-      return false;
-    }
-    if (p->value != "0") return true;
-  }
-  return contains_key(names, r.name);
-}
-
-
-long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe) {
-  if (!has_property(x, "space")) {
-    if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end();
-    return Name[default_recipe][x.name];
-  }
-  string_tree* p = property(x, "space");
-  if (!p || p->right) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end();
-  long long int n = to_integer(p->value);
-  assert(n >= 0);
-  recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n);
-  set<recipe_ordinal> done;
-  vector<recipe_ordinal> path;
-  return lookup_name(x, surrounding_recipe, done, path);
-}
-
-// If the recipe we need to lookup this name in doesn't have names done yet,
-// recursively call transform_names on it.
-long long int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_ordinal>& done, vector<recipe_ordinal>& path) {
-  if (!Name[r].empty()) return Name[r][x.name];
-  if (contains_key(done, r)) {
-    raise_error << "can't compute address of " << to_string(x) << " because " << end();
-    for (long long int i = 1; i < SIZE(path); ++i) {
-      raise_error << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end();
-    }
-    raise_error << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end();
-    return 0;
-  }
-  done.insert(r);
-  path.push_back(r);
-  transform_names(r);  // Not passing 'done' through. Might this somehow cause an infinite loop?
-  assert(!Name[r].empty());
-  return Name[r][x.name];
-}
-
-recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n) {
-  if (n == 0) return r;
-  if (!contains_key(Surrounding_space, r)) {
-    raise_error << "don't know surrounding recipe of " << get(Recipe, r).name << '\n' << end();
-    return 0;
-  }
-  assert(contains_key(Surrounding_space, r));
-  return lookup_surrounding_recipe(get(Surrounding_space, r), n-1);
-}
-
-
-type_ordinal skip_addresses(type_tree* type, const string& recipe_name) {
-  type_ordinal address = get(Type_ordinal, "address");
-  type_ordinal shared = get(Type_ordinal, "shared");
-  for (; type; type = type->right) {
-    if (type->value != address && type->value != shared)
-      return type->value;
-  }
-  raise_error << maybe(recipe_name) << "expected a container" << '\n' << end();
-  return -1;
-}
-
-int find_element_name(const type_ordinal t, const string& name, const string& recipe_name) {
-  const type_info& container = get(Type, t);
-  for (long long int i = 0; i < SIZE(container.elements); ++i)
-    if (container.elements.at(i).name == name) return i;
-  raise_error << maybe(recipe_name) << "unknown element " << name << " in container " << get(Type, t).name << '\n' << end();
-  return -1;
-}
-
-bool is_numeric_location(const reagent& x) {
-  if (is_global(x)) return false;
-
-
-  if (is_literal(x)) return false;
-  if (is_raw(x)) return false;
-  if (x.name == "0") return false;  // used for chaining lexical scopes
-  return is_integer(x.name);
-}
-
-bool is_named_location(const reagent& x) {
-  if (is_literal(x)) return false;
-  if (is_raw(x)) return false;
-  if (is_special_name(x.name)) return false;
-  return !is_integer(x.name);
-}
-
-bool is_special_name(const string& s) {
-  if (s == "_") return true;
-  if (s == "0") return true;
-  if (s == "default-space") return true;
-
-  if (s == "number-of-locals") return true;
-
-  if (s == "global-space") return true;
-
-  if (s == "screen") return true;
-
-  if (s == "console") return true;
-
-  // End is_special_name Cases
-  return false;
-}
-
-void test_transform_names_passes_dummy() {
-  Trace_file = "transform_names_passes_dummy";
-  transform("# _ is just a dummy result that never gets consumed\nrecipe main [\n  _, x:number <- copy 0, 1\n]\n");
-  CHECK_TRACE_CONTENTS("name: assign x 1");
-  CHECK_TRACE_DOESNT_CONTAIN("name: assign _ 1");
-}
-void test_transform_names_passes_raw() {
-  Trace_file = "transform_names_passes_raw";
-  Hide_errors = true;
-  run("recipe main [\n  x:number/raw <- copy 0\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("name: assign x 1");
-  run("");
-  CHECK_TRACE_CONTENTS("error: can't write to location 0 in 'x:number/raw <- copy 0'");
-}
-void test_transform_names_fails_when_mixing_names_and_numeric_locations() {
-  Trace_file = "transform_names_fails_when_mixing_names_and_numeric_locations";
-  Hide_errors = true;
-  transform("recipe main [\n  x:number <- copy 1:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: mixing variable names and numeric addresses");
-}
-void test_transform_names_fails_when_mixing_names_and_numeric_locations_2() {
-  Trace_file = "transform_names_fails_when_mixing_names_and_numeric_locations_2";
-  Hide_errors = true;
-  transform("recipe main [\n  x:number <- copy 1\n  1:number <- copy x:number\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: mixing variable names and numeric addresses");
-}
-void test_transform_names_does_not_fail_when_mixing_names_and_raw_locations() {
-  Trace_file = "transform_names_does_not_fail_when_mixing_names_and_raw_locations";
-  Hide_errors = true;
-  transform("recipe main [\n  x:number <- copy 1:number/raw\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("error: main: mixing variable names and numeric addresses");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_transform_names_does_not_fail_when_mixing_names_and_literals() {
-  Trace_file = "transform_names_does_not_fail_when_mixing_names_and_literals";
-  Hide_errors = true;
-  transform("recipe main [\n  x:number <- copy 1\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("error: main: mixing variable names and numeric addresses");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_transform_names_transforms_container_elements() {
-  Trace_file = "transform_names_transforms_container_elements";
-  transform("recipe main [\n  p:address:point <- copy 0\n  a:number <- get *p:address:point, y:offset\n  b:number <- get *p:address:point, x:offset\n]\n");
-  CHECK_TRACE_CONTENTS("name: element y of type point is at offset 1name: element x of type point is at offset 0");
-}
-void test_transform_names_handles_containers() {
-  Trace_file = "transform_names_handles_containers";
-  transform("recipe main [\n  a:point <- copy 0/unsafe\n  b:number <- copy 0/unsafe\n]\n");
-  CHECK_TRACE_CONTENTS("name: assign a 1name: assign b 3");
-}
-void test_transform_names_handles_exclusive_containers() {
-  Trace_file = "transform_names_handles_exclusive_containers";
-  run("recipe main [\n  12:number <- copy 1\n  13:number <- copy 35\n  14:number <- copy 36\n  20:address:point <- maybe-convert 12:number-or-point/unsafe, p:variant\n]\n");
-  CHECK_TRACE_CONTENTS("name: variant p of type number-or-point has tag 1mem: storing 13 in location 20");
-}
-
-void test_set_default_space() {
-  Trace_file = "set_default_space";
-  run("# if default-space is 10, and if an array of 5 locals lies from location 12 to 16 (inclusive),\n# then local 0 is really location 12, local 1 is really location 13, and so on.\nrecipe main [\n  # pretend shared:array:location; in practice we'll use new\n  10:number <- copy 0  # refcount\n  11:number <- copy 5  # length\n  default-space:address:shared:array:location <- copy 10/unsafe\n  1:number <- copy 23\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 23 in location 13");
-}
-void test_lookup_sidesteps_default_space() {
-  Trace_file = "lookup_sidesteps_default_space";
-  run("recipe main [\n  # pretend pointer from outside\n  3:number <- copy 34\n  # pretend shared:array:location; in practice we'll use new\n  1000:number <- copy 0  # refcount\n  1001:number <- copy 5  # length\n  # actual start of this recipe\n  default-space:address:shared:array:location <- copy 1000/unsafe\n  1:address:number <- copy 3/unsafe\n  8:number/raw <- copy *1:address:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 8");
-}
-void test_convert_names_passes_default_space() {
-  Trace_file = "convert_names_passes_default_space";
-  Hide_errors = true;
-  run("recipe main [\n  default-space:number, x:number <- copy 0, 1\n]\n");
-  CHECK_TRACE_CONTENTS("name: assign x 1");
-  CHECK_TRACE_DOESNT_CONTAIN("name: assign default-space 1");
-}
-void absolutize(reagent& x) {
-  if (is_raw(x) || is_dummy(x)) return;
-  if (x.name == "default-space") return;
-  if (!x.initialized) {
-    raise_error << to_string(current_instruction()) << ": reagent not initialized: " << x.original_string << '\n' << end();
-  }
-  x.set_value(address(x.value, space_base(x)));
-  x.properties.push_back(pair<string, string_tree*>("raw", NULL));
-  assert(is_raw(x));
-}
-
-long long int space_base(const reagent& x) {
-  if (is_global(x)) {
-    if (!Current_routine->global_space)
-      raise_error << "routine has no global space\n" << end();
-    return Current_routine->global_space + /*skip refcount*/1;
-  }
-
-
-  long long int base = current_call().default_space ? (current_call().default_space+/*skip refcount*/1) : 0;
-  return space_base(x, space_index(x), base);
-}
-
-long long int space_base(const reagent& x, long long int space_index, long long int base) {
-//?   trace(9999, "space") << "space-base: " << space_index << " " << base << end();
-  if (space_index == 0) {
-    return base;
-  }
-  long long int result = space_base(x, space_index-1, get_or_insert(Memory, base+/*skip length*/1))+/*skip refcount*/1;
-//?   trace(9999, "space") << "space-base: " << space_index << " " << base << " => " << result << end();
-  return result;
-}
-
-long long int space_index(const reagent& x) {
-  for (long long int i = 0; i < SIZE(x.properties); ++i) {
-    if (x.properties.at(i).first == "space") {
-      if (!x.properties.at(i).second || x.properties.at(i).second->right)
-        raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end();
-      return to_integer(x.properties.at(i).second->value);
-    }
-  }
-  return 0;
-}
-
-
-long long int address(long long int offset, long long int base) {
-  if (base == 0) return offset;  // raw
-  long long int size = get_or_insert(Memory, base);
-  if (offset >= size) {
-    // todo: test
-    raise_error << "location " << offset << " is out of bounds " << size << " at " << base << '\n' << end();
-    return 0;
-  }
-  return base + /*skip length*/1 + offset;
-}
-
-
-void test_get_default_space() {
-  Trace_file = "get_default_space";
-  run("recipe main [\n  default-space:address:shared:array:location <- copy 10/unsafe\n  1:address:shared:array:location/raw <- copy default-space:address:shared:array:location\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1");
-}
-void test_lookup_sidesteps_default_space_in_get() {
-  Trace_file = "lookup_sidesteps_default_space_in_get";
-  run("recipe main [\n  # pretend pointer to container from outside\n  12:number <- copy 34\n  13:number <- copy 35\n  # pretend shared:array:location; in practice we'll use new\n  1000:number <- copy 0  # refcount\n  1001:number <- copy 5  # length\n  # actual start of this recipe\n  default-space:address:shared:array:location <- copy 1000/unsafe\n  1:address:point <- copy 12/unsafe\n  9:number/raw <- get *1:address:point, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 9");
-}
-void test_lookup_sidesteps_default_space_in_index() {
-  Trace_file = "lookup_sidesteps_default_space_in_index";
-  run("recipe main [\n  # pretend pointer to array from outside\n  12:number <- copy 2\n  13:number <- copy 34\n  14:number <- copy 35\n  # pretend shared:array:location; in practice we'll use new\n  1000:number <- copy 0  # refcount\n  1001:number <- copy 5  # length\n  # actual start of this recipe\n  default-space:address:shared:array:location <- copy 1000/unsafe\n  1:address:array:number <- copy 12/unsafe\n  9:number/raw <- index *1:address:array:number, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 9");
-}
-void test_new_default_space() {
-  Trace_file = "new_default_space";
-  run("recipe main [\n  new-default-space\n  x:number <- copy 0\n  y:number <- copy 3\n]\n# allocate space for x and y, as well as the chaining slot at 0\n");
-  CHECK_TRACE_CONTENTS("mem: array size is 3");
-}
-void test_local_scope() {
-  Trace_file = "local_scope";
-  run("recipe main [\n  1:address <- foo\n  2:address <- foo\n  3:boolean <- equal 1:address, 2:address\n]\nrecipe foo [\n  local-scope\n  x:number <- copy 34\n  reply default-space:address:shared:array:location\n]\n# both calls to foo should have received the same default-space\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void try_reclaim_locals() {
-  // only reclaim routines starting with 'local-scope'
-  const recipe_ordinal r = get(Recipe_ordinal, current_recipe_name());
-  if (get(Recipe, r).steps.empty()) return;
-  const instruction& inst = get(Recipe, r).steps.at(0);
-  if (inst.old_name != "local-scope") return;
-  abandon(current_call().default_space,
-          /*refcount*/1 + /*array length*/1 + /*number-of-locals*/Name[r][""]);
-}
-
-void rewrite_default_space_instruction(instruction& curr) {
-  if (!curr.ingredients.empty())
-    raise_error << to_string(curr) << " can't take any ingredients\n" << end();
-  curr.name = "new";
-  curr.ingredients.push_back(reagent("location:type"));
-  curr.ingredients.push_back(reagent("number-of-locals:literal"));
-  if (!curr.products.empty())
-    raise_error << "new-default-space can't take any results\n" << end();
-  curr.products.push_back(reagent("default-space:address:shared:array:location"));
-}
-
-
-void check_default_space(const recipe_ordinal r) {
-  if (!Warn_on_missing_default_space) return;  // skip previous core tests; this is only for mu code
-  const recipe& caller = get(Recipe, r);
-  // skip scenarios (later layer)
-  // user code should never create recipes with underscores in their names
-  if (caller.name.find("scenario_") == 0) return;  // skip mu scenarios which will use raw memory locations
-  if (caller.name.find("run_") == 0) return;  // skip calls to 'run', which should be in scenarios and will also use raw memory locations
-  // assume recipes with only numeric addresses know what they're doing (usually tests)
-  if (!contains_non_special_name(r)) return;
-  trace(9991, "transform") << "--- check that recipe " << caller.name << " sets default-space" << end();
-  if (caller.steps.empty()) return;
-  if (caller.steps.at(0).products.empty()
-      || caller.steps.at(0).products.at(0).name != "default-space") {
-    raise << maybe(caller.name) << " does not seem to start with default-space or local-scope\n" << end();
-//?     cerr << maybe(caller.name) << " does not seem to start with default-space or local-scope\n" << '\n';
-  }
-}
-bool contains_non_special_name(const recipe_ordinal r) {
-  for (map<string, long long int>::iterator p = Name[r].begin(); p != Name[r].end(); ++p) {
-    if (p->first.empty()) continue;
-    if (p->first.find("stash_") == 0) continue;  // generated by rewrite_stashes_to_text (cross-layer)
-    if (!is_special_name(p->first)) {
-//?       cerr << "  " << get(Recipe, r).name << ": " << p->first << '\n';
-      return true;
-    }
-  }
-  return false;
-}
-
-
-void test_surrounding_space() {
-  Trace_file = "surrounding_space";
-  run("# location 1 in space 1 refers to the space surrounding the default space, here 20.\nrecipe main [\n  # pretend shared:array:location; in practice we'll use new\n  10:number <- copy 0  # refcount\n  11:number <- copy 5  # length\n  # pretend shared:array:location; in practice we'll use new\n  20:number <- copy 0  # refcount\n  21:number <- copy 5  # length\n  # actual start of this recipe\n  default-space:address:shared:array:location <- copy 10/unsafe\n  0:address:shared:array:location/names:dummy <- copy 20/unsafe  # later layers will explain the /names: property\n  1:number <- copy 32\n  1:number/space:1 <- copy 33\n]\nrecipe dummy [  # just for the /names: property above\n]\n# chain space: 10 + /*skip refcount*/1 + /*skip length*/1\n");
-  CHECK_TRACE_CONTENTS("mem: storing 20 in location 12mem: storing 32 in location 13mem: storing 33 in location 23");
-}
-void test_permit_space_as_variable_name() {
-  Trace_file = "permit_space_as_variable_name";
-  run("recipe main [\n  space:number <- copy 0\n]\n");
-}
-
-void test_closure() {
-  Trace_file = "closure";
-  run("recipe main [\n  default-space:address:shared:array:location <- new location:type, 30\n  1:address:shared:array:location/names:new-counter <- new-counter\n  2:number/raw <- increment-counter 1:address:shared:array:location/names:new-counter\n  3:number/raw <- increment-counter 1:address:shared:array:location/names:new-counter\n]\nrecipe new-counter [\n  default-space:address:shared:array:location <- new location:type, 30\n  x:number <- copy 23\n  y:number <- copy 3  # variable that will be incremented\n  reply default-space:address:shared:array:location\n]\nrecipe increment-counter [\n  default-space:address:shared:array:location <- new location:type, 30\n  0:address:shared:array:location/names:new-counter <- next-ingredient  # outer space must be created by 'new-counter' above\n  y:number/space:1 <- add y:number/space:1, 1  # increment\n  y:number <- copy 234  # dummy\n  reply y:number/space:1\n]\n");
-  CHECK_TRACE_CONTENTS("name: lexically surrounding space for recipe increment-counter comes from new-countermem: storing 5 in location 3");
-}
-void collect_surrounding_spaces(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << end();
-//?   cerr << "--- collect surrounding spaces for recipe " << get(Recipe, r).name << '\n';
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    const instruction& inst = get(Recipe, r).steps.at(i);
-    if (inst.is_label) continue;
-    for (long long int j = 0; j < SIZE(inst.products); ++j) {
-      if (is_literal(inst.products.at(j))) continue;
-      if (inst.products.at(j).name != "0") continue;
-      type_tree* type = inst.products.at(j).type;
-      if (!type
-          || type->value != get(Type_ordinal, "address")
-          || !type->right
-          || type->right->value != get(Type_ordinal, "shared")
-          || !type->right->right
-          || type->right->right->value != get(Type_ordinal, "array")
-          || !type->right->right->right
-          || type->right->right->right->value != get(Type_ordinal, "location")
-          || type->right->right->right->right) {
-        raise_error << "slot 0 should always have type address:shared:array:location, but is " << to_string(inst.products.at(j)) << '\n' << end();
-        continue;
-      }
-      string_tree* s = property(inst.products.at(j), "names");
-      if (!s) {
-        raise_error << "slot 0 requires a /names property in recipe " << get(Recipe, r).name << end();
-        continue;
-      }
-      if (s->right) raise_error << "slot 0 should have a single value in /names, but got " << to_string(inst.products.at(j)) << '\n' << end();
-      const string& surrounding_recipe_name = s->value;
-      if (surrounding_recipe_name.empty()) {
-        raise_error << "slot 0 doesn't initialize its /names property in recipe " << get(Recipe, r).name << end();
-        continue;
-      }
-      if (contains_key(Surrounding_space, r)
-          && get(Surrounding_space, r) != get(Recipe_ordinal, surrounding_recipe_name)) {
-        raise_error << "recipe " << get(Recipe, r).name << " can have only one 'surrounding' recipe but has " << get(Recipe, get(Surrounding_space, r)).name << " and " << surrounding_recipe_name << '\n' << end();
-        continue;
-      }
-      trace(9993, "name") << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << end();
-//?       cerr << "lexically surrounding space for recipe " << get(Recipe, r).name << " comes from " << surrounding_recipe_name << '\n';
-      if (!contains_key(Recipe_ordinal, surrounding_recipe_name)) {
-        raise << "can't find recipe providing surrounding space for " << get(Recipe, r).name << ": " << surrounding_recipe_name << '\n' << end();
-        continue;
-      }
-      put(Surrounding_space, r, get(Recipe_ordinal, surrounding_recipe_name));
-    }
-  }
-}
-
-
-// So far we have local variables, and we can nest local variables of short
-// lifetimes inside longer ones. Now let's support 'global' variables that
-// last for the life of a routine. If we create multiple routines they won't
-// have access to each other's globals.
-
-void test_global_space() {
-  Trace_file = "global_space";
-  run("recipe main [\n  # pretend shared:array:location; in practice we'll use new\n  10:number <- copy 0  # refcount\n  11:number <- copy 5  # length\n  # pretend shared:array:location; in practice we'll use new\n  20:number <- copy 0  # refcount\n  21:number <- copy 5  # length\n  # actual start of this recipe\n  global-space:address:shared:array:location <- copy 20/unsafe\n  default-space:address:shared:array:location <- copy 10/unsafe\n  1:number <- copy 23\n  1:number/space:global <- copy 24\n]\n# store to default space: 10 + /*skip refcount*/1 + /*skip length*/1 + /*index*/1\n");
-  CHECK_TRACE_CONTENTS("mem: storing 23 in location 13mem: storing 24 in location 23");
-}
-void test_global_space_with_names() {
-  Trace_file = "global_space_with_names";
-  Hide_errors = true;
-  run("recipe main [\n  global-space:address:shared:array:location <- new location:type, 10\n  x:number <- copy 23\n  1:number/space:global <- copy 24\n]\n# don't complain about mixing numeric addresses and names\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-bool is_global(const reagent& x) {
-  for (long long int i = 0; i < SIZE(x.properties); ++i) {
-    if (x.properties.at(i).first == "space")
-      return x.properties.at(i).second && x.properties.at(i).second->value == "global";
-  }
-  return false;
-}
-
-
-void test_transform_fails_on_reusing_name_with_different_type() {
-  Trace_file = "transform_fails_on_reusing_name_with_different_type";
-  Hide_errors = true;
-  run("recipe main [\n  x:number <- copy 1\n  x:boolean <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: x used with multiple types");
-}
-void check_or_set_types_by_name(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- deduce types for recipe " << get(Recipe, r).name << end();
-  map<string, type_tree*> type;
-  for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-    instruction& inst = get(Recipe, r).steps.at(i);
-    for (long long int in = 0; in < SIZE(inst.ingredients); ++in) {
-      deduce_missing_type(type, inst.ingredients.at(in));
-      check_type(type, inst.ingredients.at(in), r);
-    }
-    for (long long int out = 0; out < SIZE(inst.products); ++out) {
-      deduce_missing_type(type, inst.products.at(out));
-      check_type(type, inst.products.at(out), r);
-    }
-  }
-}
-
-void deduce_missing_type(map<string, type_tree*>& type, reagent& x) {
-  if (x.type) return;
-  if (!contains_key(type, x.name)) return;
-  x.type = new type_tree(*get(type, x.name));
-  trace(9992, "transform") << x.name << " <= " << names_to_string(x.type) << end();
-}
-
-void check_type(map<string, type_tree*>& type, const reagent& x, const recipe_ordinal r) {
-  if (is_literal(x)) return;
-  if (is_integer(x.name)) return;  // if you use raw locations you're probably doing something unsafe
-  if (!x.type) return;  // might get filled in by other logic later
-  if (!contains_key(type, x.name)) {
-    trace(9992, "transform") << x.name << " => " << names_to_string(x.type) << end();
-    put(type, x.name, x.type);
-  }
-  if (!types_strictly_match(get(type, x.name), x.type)) {
-    raise_error << maybe(get(Recipe, r).name) << x.name << " used with multiple types\n" << end();
-    return;
-  }
-  if (get(type, x.name)->name == "array") {
-    if (!get(type, x.name)->right) {
-      raise_error << maybe(get(Recipe, r).name) << x.name << " can't be just an array. What is it an array of?\n" << end();
-      return;
-    }
-    if (!get(type, x.name)->right->right) {
-      raise_error << get(Recipe, r).name << " can't determine the size of array variable " << x.name << ". Either allocate it separately and make the type of " << x.name << " address:shared:..., or specify the length of the array in the type of " << x.name << ".\n" << end();
-      return;
-    }
-  }
-}
-
-void test_transform_fills_in_missing_types() {
-  Trace_file = "transform_fills_in_missing_types";
-  run("recipe main [\n  x:number <- copy 1\n  y:number <- add x, 1\n]\n");
-}
-void test_transform_fills_in_missing_types_in_product() {
-  Trace_file = "transform_fills_in_missing_types_in_product";
-  run("recipe main [\n  x:number <- copy 1\n  x <- copy 2\n]\n");
-}
-void test_transform_fills_in_missing_types_in_product_and_ingredient() {
-  Trace_file = "transform_fills_in_missing_types_in_product_and_ingredient";
-  run("recipe main [\n  x:number <- copy 1\n  x <- add x, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_transform_fails_on_missing_types_in_first_mention() {
-  Trace_file = "transform_fails_on_missing_types_in_first_mention";
-  Hide_errors = true;
-  run("recipe main [\n  x <- copy 1\n  x:number <- copy 2\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: missing type for x in 'x <- copy 1'");
-}
-void test_typo_in_address_type_fails() {
-  Trace_file = "typo_in_address_type_fails";
-  Hide_errors = true;
-  run("recipe main [\n  y:address:shared:charcter <- new character:type\n  *y <- copy 67\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: unknown type charcter in 'y:address:shared:charcter <- new character:type'");
-}
-void test_array_type_without_size_fails() {
-  Trace_file = "array_type_without_size_fails";
-  Hide_errors = true;
-  run("recipe main [\n  x:array:number <- merge 2, 12, 13\n]\n");
-  CHECK_TRACE_CONTENTS("error: main can't determine the size of array variable x. Either allocate it separately and make the type of x address:shared:..., or specify the length of the array in the type of x.");
-}
-
-scenario parse_scenario(istream& in) {
-  scenario result;
-  result.name = next_word(in);
-  if (contains_key(Scenario_names, result.name))
-    raise_error << "duplicate scenario name: " << result.name << '\n' << end();
-  Scenario_names.insert(result.name);
-  skip_whitespace_and_comments(in);
-  assert(in.peek() == '[');
-  // scenarios are take special 'code' strings so we need to ignore brackets
-  // inside comments
-  result.to_run = slurp_quoted(in);
-  // delete [] delimiters
-  assert(result.to_run.at(0) == '[');
-  result.to_run.erase(0, 1);
-  assert(result.to_run.at(SIZE(result.to_run)-1) == ']');
-  result.to_run.erase(SIZE(result.to_run)-1);
-  return result;
-}
-
-void test_warn_on_redefine_scenario() {
-  Trace_file = "warn_on_redefine_scenario";
-  Hide_warnings = true;
-  Disable_redefine_warnings = true;
-  run("recipe scenario-foo [\n  1:number <- copy 34\n]\nrecipe scenario-foo [\n  1:number <- copy 35\n]\n");
-  CHECK_TRACE_CONTENTS("warn: redefining recipe scenario-foo");
-}
-void test_run() {
-  Trace_file = "run";
-  run("recipe main [\n  run [\n    1:number <- copy 13\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 13 in location 1");
-}
-void bind_special_scenario_names(recipe_ordinal r) {
-  // Special Scenario Variable Names(r)
-  Name[r]["screen"] = SCREEN;
-
-  Name[r]["console"] = CONSOLE;
-
-  // End Special Scenario Variable Names(r)
-}
-
-void test_run_multiple() {
-  Trace_file = "run_multiple";
-  run("recipe main [\n  run [\n    1:number <- copy 13\n  ]\n  run [\n    2:number <- copy 13\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 13 in location 1mem: storing 13 in location 2");
-}
-void test_memory_check() {
-  Trace_file = "memory_check";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  memory-should-contain [\n    1 <- 13\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("run: checking location 1error: expected location 1 to contain 13 but saw 0");
-}
-void check_memory(const string& s) {
-  istringstream in(s);
-  in >> std::noskipws;
-  set<long long int> locations_checked;
-  while (true) {
-    skip_whitespace_and_comments(in);
-    if (!has_data(in)) break;
-    string lhs = next_word(in);
-    if (!is_integer(lhs)) {
-      check_type(lhs, in);
-      continue;
-    }
-    long long int address = to_integer(lhs);
-    skip_whitespace_and_comments(in);
-    string _assign;  in >> _assign;  assert(_assign == "<-");
-    skip_whitespace_and_comments(in);
-    double value = 0;  in >> value;
-    if (contains_key(locations_checked, address))
-      raise_error << "duplicate expectation for location " << address << '\n' << end();
-    trace(9999, "run") << "checking location " << address << end();
-    if (get_or_insert(Memory, address) != value) {
-      if (Current_scenario && !Scenario_testing_scenario) {
-        // genuine test in a mu file
-        raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-      }
-      else {
-        // just testing scenario support
-        raise_error << "expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-      }
-      if (!Scenario_testing_scenario) {
-        Passed = false;
-        ++Num_failures;
-      }
-      return;
-    }
-    locations_checked.insert(address);
-  }
-}
-
-void check_type(const string& lhs, istream& in) {
-  reagent x(lhs);
-  if (x.type->name == "array"
-      && x.type->right && x.type->right->name == "character"
-      && !x.type->right->right) {
-    x.set_value(to_integer(x.name));
-    skip_whitespace_and_comments(in);
-    string _assign = next_word(in);
-    assert(_assign == "<-");
-    skip_whitespace_and_comments(in);
-    string literal = next_word(in);
-    long long int address = x.value;
-    // exclude quoting brackets
-    assert(*literal.begin() == '[');  literal.erase(literal.begin());
-    assert(*--literal.end() == ']');  literal.erase(--literal.end());
-    check_string(address, literal);
-    return;
-  }
-  raise_error << "don't know how to check memory for " << lhs << '\n' << end();
-}
-
-void check_string(long long int address, const string& literal) {
-  trace(9999, "run") << "checking string length at " << address << end();
-  if (get_or_insert(Memory, address) != SIZE(literal)) {
-    if (Current_scenario && !Scenario_testing_scenario)
-      raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << " (" << read_mu_string(address) << ")\n" << end();
-    else
-      raise_error << "expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-    if (!Scenario_testing_scenario) {
-      Passed = false;
-      ++Num_failures;
-    }
-    return;
-  }
-  ++address;  // now skip length
-  for (long long int i = 0; i < SIZE(literal); ++i) {
-    trace(9999, "run") << "checking location " << address+i << end();
-    if (get_or_insert(Memory, address+i) != literal.at(i)) {
-      if (Current_scenario && !Scenario_testing_scenario) {
-        // genuine test in a mu file
-        raise_error << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << " ('" << static_cast<char>(get_or_insert(Memory, address+i)) << "')\n" << end();
-      }
-      else {
-        // just testing scenario support
-        raise_error << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << '\n' << end();
-      }
-      if (!Scenario_testing_scenario) {
-        Passed = false;
-        ++Num_failures;
-      }
-      return;
-    }
-  }
-}
-
-void test_memory_check_multiple() {
-  Trace_file = "memory_check_multiple";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  memory-should-contain [\n    1 <- 0\n    1 <- 0\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: duplicate expectation for location 1");
-}
-void test_memory_check_string_length() {
-  Trace_file = "memory_check_string_length";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy 3\n  2:number <- copy 97  # 'a'\n  3:number <- copy 98  # 'b'\n  4:number <- copy 99  # 'c'\n  memory-should-contain [\n    1:array:character <- [ab]\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: expected location 1 to contain length 2 of string [ab] but saw 3");
-}
-void test_memory_check_string() {
-  Trace_file = "memory_check_string";
-  run("recipe main [\n  1:number <- copy 3\n  2:number <- copy 97  # 'a'\n  3:number <- copy 98  # 'b'\n  4:number <- copy 99  # 'c'\n  memory-should-contain [\n    1:array:character <- [abc]\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("run: checking string length at 1run: checking location 2run: checking location 3run: checking location 4");
-}
-// Like runs of contiguous '+' lines, order is important. The trace checks
-// that the lines are present *and* in the specified sequence. (There can be
-// other lines in between.)
-
-void test_trace_check_fails() {
-  Trace_file = "trace_check_fails";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  trace-should-contain [\n    a: b\n    a: d\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: missing [b] in trace with label a");
-}
-// simplified version of check_trace_contents() that emits errors rather
-// than just printing to stderr
-void check_trace(const string& expected) {
-  Trace_stream->newline();
-  vector<trace_line> expected_lines = parse_trace(expected);
-  if (expected_lines.empty()) return;
-  long long int curr_expected_line = 0;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (expected_lines.at(curr_expected_line).label != p->label) continue;
-    if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue;
-    // match
-    ++curr_expected_line;
-    if (curr_expected_line == SIZE(expected_lines)) return;
-  }
-
-  raise_error << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
-              << "in trace with label " << expected_lines.at(curr_expected_line).label << '\n' << end();
-  Passed = false;
-}
-
-vector<trace_line> parse_trace(const string& expected) {
-  vector<string> buf = split(expected, "\n");
-  vector<trace_line> result;
-  for (long long int i = 0; i < SIZE(buf); ++i) {
-    buf.at(i) = trim(buf.at(i));
-    if (buf.at(i).empty()) continue;
-    long long int delim = buf.at(i).find(": ");
-    result.push_back(trace_line(trim(buf.at(i).substr(0, delim)),  trim(buf.at(i).substr(delim+2))));
-  }
-  return result;
-}
-
-void test_trace_check_fails_in_nonfirst_line() {
-  Trace_file = "trace_check_fails_in_nonfirst_line";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  run [\n    trace 1, [a], [b]\n  ]\n  trace-should-contain [\n    a: b\n    a: d\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: missing [d] in trace with label a");
-}
-void test_trace_check_passes_silently() {
-  Trace_file = "trace_check_passes_silently";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  run [\n    trace 1, [a], [b]\n  ]\n  trace-should-contain [\n    a: b\n  ]\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("error: missing [b] in trace with label a");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_trace_negative_check_fails() {
-  Trace_file = "trace_negative_check_fails";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  run [\n    trace 1, [a], [b]\n  ]\n  trace-should-not-contain [\n    a: b\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: unexpected [b] in trace with label a");
-}
-// simplified version of check_trace_contents() that emits errors rather
-// than just printing to stderr
-bool check_trace_missing(const string& in) {
-  Trace_stream->newline();
-  vector<trace_line> lines = parse_trace(in);
-  for (long long int i = 0; i < SIZE(lines); ++i) {
-    if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) {
-      raise_error << "unexpected [" << lines.at(i).contents << "] in trace with label " << lines.at(i).label << '\n' << end();
-      Passed = false;
-      return false;
-    }
-  }
-  return true;
-}
-
-void test_trace_negative_check_passes_silently() {
-  Trace_file = "trace_negative_check_passes_silently";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  trace-should-not-contain [\n    a: b\n  ]\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("error: unexpected [b] in trace with label a");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_trace_negative_check_fails_on_any_unexpected_line() {
-  Trace_file = "trace_negative_check_fails_on_any_unexpected_line";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  run [\n    trace 1, [a], [d]\n  ]\n  trace-should-not-contain [\n    a: b\n    a: d\n  ]\n]\n");
-  CHECK_TRACE_CONTENTS("error: unexpected [d] in trace with label a");
-}
-void test_trace_count_check() {
-  Trace_file = "trace_count_check";
-  run("recipe main [\n  run [\n    trace 1, [a], [foo]\n  ]\n  check-trace-count-for-label 1, [a]\n]\n");
-}
-void test_trace_count_check_2() {
-  Trace_file = "trace_count_check_2";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  run [\n    trace 1, [a], [foo]\n  ]\n  check-trace-count-for-label 2, [a]\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: expected 2 lines in trace with label a in trace");
-}
-
-void test_tangle_before() {
-  Trace_file = "tangle_before";
-  run("recipe main [\n  1:number <- copy 0\n  <label1>\n  3:number <- copy 0\n]\nbefore <label1> [\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3");
-  CHECK_TRACE_COUNT("mem", 3);
-}
-void insert_fragments(const recipe_ordinal r) {
-  bool made_progress = true;
-  long long int pass = 0;
-  while (made_progress) {
-    made_progress = false;
-    // create a new vector because insertions invalidate iterators
-    vector<instruction> result;
-    for (long long int i = 0; i < SIZE(get(Recipe, r).steps); ++i) {
-      const instruction& inst = get(Recipe, r).steps.at(i);
-      if (!inst.is_label || !is_waypoint(inst.label) || inst.tangle_done) {
-        result.push_back(inst);
-        continue;
-      }
-      inst.tangle_done = true;
-      made_progress = true;
-      Fragments_used.insert(inst.label);
-      ostringstream prefix;
-      prefix << '+' << get(Recipe, r).name << '_' << pass << '_' << i;
-      // ok to use contains_key even though Before_fragments uses [],
-      // because appending an empty recipe is a noop
-      if (contains_key(Before_fragments, inst.label))
-        append_fragment(result, Before_fragments[inst.label].steps, prefix.str());
-      result.push_back(inst);
-      if (contains_key(After_fragments, inst.label))
-        append_fragment(result, After_fragments[inst.label].steps, prefix.str());
-    }
-    get(Recipe, r).steps.swap(result);
-    ++pass;
-  }
-}
-
-void append_fragment(vector<instruction>& base, const vector<instruction>& patch, const string prefix) {
-  // append 'patch' to 'base' while keeping 'base' oblivious to any new jump
-  // targets in 'patch' oblivious to 'base' by prepending 'prefix' to them.
-  // we might tangle the same fragment at multiple points in a single recipe,
-  // and we need to avoid duplicate jump targets.
-  // so we'll keep jump targets local to the specific before/after fragment
-  // that introduces them.
-  set<string> jump_targets;
-  for (long long int i = 0; i < SIZE(patch); ++i) {
-    const instruction& inst = patch.at(i);
-    if (inst.is_label && is_jump_target(inst.label))
-      jump_targets.insert(inst.label);
-  }
-  for (long long int i = 0; i < SIZE(patch); ++i) {
-    instruction inst = patch.at(i);
-    if (inst.is_label) {
-      if (contains_key(jump_targets, inst.label))
-        inst.label = prefix+inst.label;
-      base.push_back(inst);
-      continue;
-    }
-    for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
-      reagent& x = inst.ingredients.at(j);
-      if (!is_literal(x)) continue;
-      if (x.type->name == "label" && contains_key(jump_targets, x.name))
-        x.name = prefix+x.name;
-    }
-    base.push_back(inst);
-  }
-}
-
-bool is_waypoint(string label) {
-  return *label.begin() == '<' && *label.rbegin() == '>';
-}
-
-void check_insert_fragments(unused recipe_ordinal) {
-  if (Transform_check_insert_fragments_Ran) return;
-  Transform_check_insert_fragments_Ran = true;
-  for (map<string, recipe>::iterator p = Before_fragments.begin(); p != Before_fragments.end(); ++p) {
-    if (!contains_key(Fragments_used, p->first))
-      raise_error << "could not locate insert before " << p->first << '\n' << end();
-  }
-  for (map<string, recipe>::iterator p = After_fragments.begin(); p != After_fragments.end(); ++p) {
-    if (!contains_key(Fragments_used, p->first))
-      raise_error << "could not locate insert after " << p->first << '\n' << end();
-  }
-}
-
-void test_tangle_before_and_after() {
-  Trace_file = "tangle_before_and_after";
-  run("recipe main [\n  1:number <- copy 0\n  <label1>\n  4:number <- copy 0\n]\nbefore <label1> [\n  2:number <- copy 0\n]\nafter <label1> [\n  3:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4");
-  CHECK_TRACE_COUNT("mem", 4);
-}
-void test_tangle_ignores_jump_target() {
-  Trace_file = "tangle_ignores_jump_target";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy 0\n  +label1\n  4:number <- copy 0\n]\nbefore +label1 [\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("error: can't tangle before label +label1");
-}
-void test_tangle_keeps_labels_separate() {
-  Trace_file = "tangle_keeps_labels_separate";
-  run("recipe main [\n  1:number <- copy 0\n  <label1>\n  <label2>\n  6:number <- copy 0\n]\nbefore <label1> [\n  2:number <- copy 0\n]\nafter <label1> [\n  3:number <- copy 0\n]\nbefore <label2> [\n  4:number <- copy 0\n]\nafter <label2> [\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 6");
-  CHECK_TRACE_COUNT("mem", 6);
-}
-void test_tangle_stacks_multiple_fragments() {
-  Trace_file = "tangle_stacks_multiple_fragments";
-  run("recipe main [\n  1:number <- copy 0\n  <label1>\n  6:number <- copy 0\n]\nbefore <label1> [\n  2:number <- copy 0\n]\nafter <label1> [\n  3:number <- copy 0\n]\nbefore <label1> [\n  4:number <- copy 0\n]\nafter <label1> [\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 3mem: storing 0 in location 6");
-  CHECK_TRACE_COUNT("mem", 6);
-}
-void test_tangle_supports_fragments_with_multiple_instructions() {
-  Trace_file = "tangle_supports_fragments_with_multiple_instructions";
-  run("recipe main [\n  1:number <- copy 0\n  <label1>\n  6:number <- copy 0\n]\nbefore <label1> [\n  2:number <- copy 0\n  3:number <- copy 0\n]\nafter <label1> [\n  4:number <- copy 0\n  5:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 0 in location 2mem: storing 0 in location 3mem: storing 0 in location 4mem: storing 0 in location 5mem: storing 0 in location 6");
-  CHECK_TRACE_COUNT("mem", 6);
-}
-void test_tangle_tangles_into_all_labels_with_same_name() {
-  Trace_file = "tangle_tangles_into_all_labels_with_same_name";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  4:number <- copy 10\n  recipe2\n]\nrecipe recipe2 [\n  1:number <- copy 11\n  <label1>\n  4:number <- copy 11\n]\nbefore <label1> [\n  2:number <- copy 12\n]\nafter <label1> [\n  3:number <- copy 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4mem: storing 11 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 11 in location 4");
-  CHECK_TRACE_COUNT("mem", 8);
-}
-void test_tangle_tangles_into_all_labels_with_same_name_2() {
-  Trace_file = "tangle_tangles_into_all_labels_with_same_name_2";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  <label1>\n  4:number <- copy 10\n]\nbefore <label1> [\n  2:number <- copy 12\n]\nafter <label1> [\n  3:number <- copy 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4");
-  CHECK_TRACE_COUNT("mem", 6);
-}
-void test_tangle_tangles_into_all_labels_with_same_name_3() {
-  Trace_file = "tangle_tangles_into_all_labels_with_same_name_3";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  <foo>\n  4:number <- copy 10\n]\nbefore <label1> [\n  2:number <- copy 12\n]\nafter <label1> [\n  3:number <- copy 12\n]\nafter <foo> [\n  <label1>\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 12 in location 2mem: storing 12 in location 3mem: storing 10 in location 4");
-  CHECK_TRACE_COUNT("mem", 6);
-}
-void test_tangle_handles_jump_target_inside_fragment() {
-  Trace_file = "tangle_handles_jump_target_inside_fragment";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  4:number <- copy 10\n]\nbefore <label1> [\n  jump +label2:label\n  2:number <- copy 12\n  +label2\n  3:number <- copy 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 3mem: storing 10 in location 4");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2");
-  CHECK_TRACE_COUNT("mem", 3);
-}
-void test_tangle_renames_jump_target() {
-  Trace_file = "tangle_renames_jump_target";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  +label2\n  4:number <- copy 10\n]\nbefore <label1> [\n  jump +label2:label\n  2:number <- copy 12\n  +label2  # renamed\n  3:number <- copy 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 12 in location 3mem: storing 10 in location 4");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2");
-  CHECK_TRACE_COUNT("mem", 3);
-}
-void test_tangle_jump_to_base_recipe() {
-  Trace_file = "tangle_jump_to_base_recipe";
-  run("recipe main [\n  1:number <- copy 10\n  <label1>\n  +label2\n  4:number <- copy 10\n]\nbefore <label1> [\n  jump +label2:label\n  2:number <- copy 12\n  3:number <- copy 12\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 10 in location 1mem: storing 10 in location 4");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in label 2");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 12 in location 3");
-  CHECK_TRACE_COUNT("mem", 2);
-}
-
-void rewrite_stashes_to_text(recipe_ordinal r) {
-  recipe& caller = get(Recipe, r);
-  trace(9991, "transform") << "--- rewrite 'stash' instructions in recipe " << caller.name << end();
-  if (contains_named_locations(caller))
-    rewrite_stashes_to_text_named(caller);
-  // in recipes without named locations, 'stash' is still not extensible
-}
-
-bool contains_named_locations(const recipe& caller) {
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    const instruction& inst = caller.steps.at(i);
-    for (long long int in = 0; in < SIZE(inst.ingredients); ++in)
-      if (is_named_location(inst.ingredients.at(in)))
-        return true;
-    for (long long int out = 0; out < SIZE(inst.products); ++out)
-      if (is_named_location(inst.products.at(out)))
-        return true;
-  }
-  return false;
-}
-
-void rewrite_stashes_to_text_named(recipe& caller) {
-  static long long int stash_instruction_idx = 0;
-  vector<instruction> new_instructions;
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    instruction& inst = caller.steps.at(i);
-    if (inst.name == "stash") {
-      for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
-        if (is_literal(inst.ingredients.at(j))) continue;
-        if (is_mu_string(inst.ingredients.at(j))) continue;
-        instruction def;
-        def.name = "to-text-line";
-        def.ingredients.push_back(inst.ingredients.at(j));
-        ostringstream ingredient_name;
-        ingredient_name << "stash_" << stash_instruction_idx << '_' << j << ":address:shared:array:character";
-        def.products.push_back(reagent(ingredient_name.str()));
-        new_instructions.push_back(def);
-        inst.ingredients.at(j).clear();  // reclaim old memory
-        inst.ingredients.at(j) = reagent(ingredient_name.str());
-      }
-    }
-    new_instructions.push_back(inst);
-  }
-  new_instructions.swap(caller.steps);
-}
-
-
-void test_dilated_reagent() {
-  Trace_file = "dilated_reagent";
-  load("recipe main [\n  {1: number, foo: bar} <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   product: 1: \"number\", {\"foo\": \"bar\"}");
-}
-void test_load_trailing_space_after_curly_bracket() {
-  Trace_file = "load_trailing_space_after_curly_bracket";
-  load("recipe main [\n  # line below has a space at the end\n  { \n]\n# successfully parsed\n");
-}
-void test_dilated_reagent_with_comment() {
-  Trace_file = "dilated_reagent_with_comment";
-  Hide_errors = true;
-  run("recipe main [\n  {1: number, foo: bar} <- copy 34  # test comment\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   product: 1: \"number\", {\"foo\": \"bar\"}");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_dilated_reagent_with_comment_immediately_following() {
-  Trace_file = "dilated_reagent_with_comment_immediately_following";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy {34: literal}  # test comment\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-// A curly is considered a label if it's the last thing on a line. Dilated
-// reagents should remain all on one line.
-bool start_of_dilated_reagent(istream& in) {
-  if (in.peek() != '{') return false;
-  long long int pos = in.tellg();
-  in.get();  // slurp '{'
-  skip_whitespace_but_not_newline(in);
-  char next = in.peek();
-  in.seekg(pos);
-  return next != '\n';
-}
-
-// Assume the first letter is an open bracket, and read everything until the
-// matching close bracket.
-// We balance {} () and []. And we skip one character after '\'.
-string slurp_balanced_bracket(istream& in) {
-  ostringstream result;
-  char c;
-  list<char> open_brackets;
-  while (in >> c) {
-    if (c == '\\') {
-      // always silently skip the next character
-      result << c;
-      if (!(in >> c)) break;
-      result << c;
-      continue;
-    }
-    if (c == '(') open_brackets.push_back(c);
-    if (c == ')') {
-      assert(open_brackets.back() == '(');
-      open_brackets.pop_back();
-    }
-    if (c == '[') open_brackets.push_back(c);
-    if (c == ']') {
-      assert(open_brackets.back() == '[');
-      open_brackets.pop_back();
-    }
-    if (c == '{') open_brackets.push_back(c);
-    if (c == '}') {
-      assert(open_brackets.back() == '{');
-      open_brackets.pop_back();
-    }
-    result << c;
-    if (open_brackets.empty()) break;
-  }
-  skip_whitespace_and_comments_but_not_newline(in);
-  return result.str();
-}
-
-string slurp_key(istream& in) {
-  string result = next_word(in);
-  while (!result.empty() && *result.rbegin() == ':')
-    strip_last(result);
-  while (isspace(in.peek()) || in.peek() == ':')
-    in.get();
-  return result;
-}
-
-// So far instructions can only contain linear lists of properties. Now we add
-// support for more complex trees of properties in dilated reagents. This will
-// come in handy later for expressing complex types, like "a dictionary from
-// (address to array of charaters) to (list of numbers)".
-
-void test_dilated_reagent_with_nested_brackets() {
-  Trace_file = "dilated_reagent_with_nested_brackets";
-  run("recipe main [\n  {1: number, foo: (bar (baz quux))} <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   product: 1: \"number\", {\"foo\": (\"bar\" (\"baz\" \"quux\"))}");
-}
-string_tree* parse_string_tree(string_tree* s) {
-  assert(!s->left && !s->right);
-  if (s->value.at(0) != '(') return s;
-  string_tree* result = parse_string_tree(s->value);
-  delete s;
-  return result;
-}
-
-string_tree* parse_string_tree(const string& s) {
-  istringstream in(s);
-  in >> std::noskipws;
-  return parse_string_tree(in);
-}
-
-string_tree* parse_string_tree(istream& in) {
-  skip_whitespace_but_not_newline(in);
-  if (!has_data(in)) return NULL;
-  if (in.peek() == ')') {
-    in.get();
-    return NULL;
-  }
-  if (in.peek() != '(') {
-    string_tree* result = new string_tree(next_word(in));
-    return result;
-  }
-  in.get();  // skip '('
-  string_tree* result = NULL;
-  string_tree** curr = &result;
-  while (in.peek() != ')') {
-    assert(has_data(in));
-    *curr = new string_tree("");
-    skip_whitespace_but_not_newline(in);
-    if (in.peek() == '(')
-      (*curr)->left = parse_string_tree(in);
-    else
-      (*curr)->value = next_word(in);
-    curr = &(*curr)->right;
-  }
-  in.get();  // skip ')'
-  return result;
-}
-
-void test_dilated_reagent_with_type_tree() {
-  Trace_file = "dilated_reagent_with_type_tree";
-  Hide_errors = true;  // 'map' isn't defined yet
-  run("recipe main [\n  {1: (foo (address array character) (bar number))} <- copy 34\n]\n# just to avoid errors\ncontainer foo [\n]\ncontainer bar [\n]\n");
-  CHECK_TRACE_CONTENTS("parse:   product: 1: (\"foo\" (\"address\" \"array\" \"character\") (\"bar\" \"number\"))");
-}
-void test_dilated_reagent_with_new() {
-  Trace_file = "dilated_reagent_with_new";
-  run("recipe main [\n  x:address:shared:address:number <- new {(address number): type}\n]\n");
-  CHECK_TRACE_CONTENTS("new: size of (\"address\" \"number\") is 1");
-}
-
-void test_recipe_with_header() {
-  Trace_file = "recipe_with_header";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z:number <- add x, y\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 8 in location 1");
-}
-void load_recipe_header(istream& in, recipe& result) {
-  result.has_header = true;
-  while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
-    string s = next_word(in);
-    if (s == "->") break;
-    result.ingredients.push_back(reagent(s));
-    trace(9999, "parse") << "header ingredient: " << result.ingredients.back().original_string << end();
-    skip_whitespace_but_not_newline(in);
-  }
-  while (has_data(in) && in.peek() != '[' && in.peek() != '\n') {
-    string s = next_word(in);
-    result.products.push_back(reagent(s));
-    trace(9999, "parse") << "header product: " << result.products.back().original_string << end();
-    skip_whitespace_but_not_newline(in);
-  }
-  // there can only ever be one variant for main
-  if (result.name != "main" && contains_key(Recipe_ordinal, result.name)) {
-    const recipe_ordinal r = get(Recipe_ordinal, result.name);
-  //?   cerr << result.name << ": " << contains_key(Recipe, r) << (contains_key(Recipe, r) ? get(Recipe, r).has_header : 0) << matching_variant_name(result) << '\n';
-    if (!contains_key(Recipe, r) || get(Recipe, r).has_header) {
-      string new_name = matching_variant_name(result);
-      if (new_name.empty()) {
-        // variant doesn't already exist
-        new_name = next_unused_recipe_name(result.name);
-        put(Recipe_ordinal, new_name, Next_recipe_ordinal++);
-        get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, new_name));
-      }
-      trace(9999, "load") << "switching " << result.name << " to " << new_name << end();
-      result.name = new_name;
-  //?     cerr << "=> " << new_name << '\n';
-    }
-  }
-  else {
-    // save first variant
-    put(Recipe_ordinal, result.name, Next_recipe_ordinal++);
-    get_or_insert(Recipe_variants, result.name).push_back(get(Recipe_ordinal, result.name));
-  }
-
-  // End Load Recipe Header(result)
-}
-
-void test_recipe_handles_stray_comma() {
-  Trace_file = "recipe_handles_stray_comma";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number, [\n  local-scope\n  load-ingredients\n  z:number <- add x, y\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 8 in location 1");
-}
-void test_recipe_handles_stray_comma_2() {
-  Trace_file = "recipe_handles_stray_comma_2";
-  run("recipe main [\n  foo\n]\nrecipe foo, [\n  1:number/raw <- add 2, 2\n]\nrecipe bar [\n  1:number/raw <- add 2, 3\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_recipe_handles_missing_bracket() {
-  Trace_file = "recipe_handles_missing_bracket";
-  Hide_errors = true;
-  run("recipe main\n]\n");
-  CHECK_TRACE_CONTENTS("error: recipe body must begin with '['");
-}
-void test_recipe_handles_missing_bracket_2() {
-  Trace_file = "recipe_handles_missing_bracket_2";
-  Hide_errors = true;
-  run("recipe main\n  local-scope\n  {\n  }\n]\n# doesn't overflow line when reading header\n");
-  CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope");
-  run("");
-  CHECK_TRACE_CONTENTS("error: recipe body must begin with '['");
-}
-void test_recipe_handles_missing_bracket_3() {
-  Trace_file = "recipe_handles_missing_bracket_3";
-  Hide_errors = true;
-  run("recipe main  # comment\n  local-scope\n  {\n  }\n]\n# doesn't overflow line when reading header\n");
-  CHECK_TRACE_DOESNT_CONTAIN("parse: header ingredient: local-scope");
-  run("");
-  CHECK_TRACE_CONTENTS("error: recipe body must begin with '['");
-}
-void test_recipe_without_ingredients_or_products_has_header() {
-  Trace_file = "recipe_without_ingredients_or_products_has_header";
-  run("recipe test [\n  1:number <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("parse: recipe test has a header");
-}
-void test_show_clear_error_on_bad_call() {
-  Trace_file = "show_clear_error_on_bad_call";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- foo 34\n]\nrecipe foo x:boolean -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number <- foo 34'");
-}
-void test_show_clear_error_on_bad_call_2() {
-  Trace_file = "show_clear_error_on_bad_call_2";
-  Hide_errors = true;
-  run("recipe main [\n  1:boolean <- foo 34\n]\nrecipe foo x:number -> y:number [\n  local-scope\n  load-ingredients\n  reply x\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: product 0 has the wrong type at '1:boolean <- foo 34'");
-}
-void check_calls_against_header(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- type-check calls inside recipe " << get(Recipe, r).name << end();
-  const recipe& caller = get(Recipe, r);
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    const instruction& inst = caller.steps.at(i);
-    if (inst.operation < MAX_PRIMITIVE_RECIPES) continue;
-    const recipe& callee = get(Recipe, inst.operation);
-    if (!callee.has_header) continue;
-    for (long int i = 0; i < min(SIZE(inst.ingredients), SIZE(callee.ingredients)); ++i) {
-      // ingredients coerced from call to callee
-      if (!types_coercible(callee.ingredients.at(i), inst.ingredients.at(i)))
-        raise_error << maybe(caller.name) << "ingredient " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end();
-      if (is_unique_address(inst.ingredients.at(i)))
-        raise << maybe(caller.name) << "try to avoid passing non-shared addresses into calls, like ingredient " << i << " at '" << to_string(inst) << "'\n" << end();
-    }
-    for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee.products)); ++i) {
-      if (is_dummy(inst.products.at(i))) continue;
-      // products coerced from callee to call
-      if (!types_coercible(inst.products.at(i), callee.products.at(i)))
-        raise_error << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end();
-      if (is_unique_address(inst.products.at(i)))
-        raise << maybe(caller.name) << "try to avoid getting non-shared addresses out of calls, like product " << i << " at '" << to_string(inst) << "'\n" << end();
-    }
-  }
-}
-
-bool is_unique_address(reagent x) {
-  if (!canonize_type(x)) return false;
-  if (!x.type) return false;
-  if (x.type->value != get(Type_ordinal, "address")) return false;
-  if (!x.type->right) return true;
-  return x.type->right->value != get(Type_ordinal, "shared");
-}
-
-
-void test_warn_on_calls_with_addresses() {
-  Trace_file = "warn_on_calls_with_addresses";
-  Hide_warnings= true;
-  run("recipe main [\n  1:address:number <- copy 3/unsafe\n  foo 1:address:number\n]\nrecipe foo x:address:number [\n  local-scope\n  load-ingredients\n]\n");
-  CHECK_TRACE_CONTENTS("warn: main: try to avoid passing non-shared addresses into calls, like ingredient 0 at 'foo 1:address:number'");
-}
-void test_warn_on_calls_with_addresses_2() {
-  Trace_file = "warn_on_calls_with_addresses_2";
-  Hide_warnings= true;
-  run("recipe main [\n  1:address:number <- foo\n]\nrecipe foo -> x:address:number [\n  local-scope\n  load-ingredients\n  x <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("warn: main: try to avoid getting non-shared addresses out of calls, like product 0 at '1:address:number <- foo '");
-}
-void test_recipe_headers_are_checked() {
-  Trace_file = "recipe_headers_are_checked";
-  Hide_errors = true;
-  transform("recipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z:address:number <- copy 0/unsafe\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("error: add2: replied with the wrong type at 'reply z'");
-}
-void check_reply_instructions_against_header(const recipe_ordinal r) {
-  const recipe& caller_recipe = get(Recipe, r);
-  if (!caller_recipe.has_header) return;
-  trace(9991, "transform") << "--- checking reply instructions against header for " << caller_recipe.name << end();
-  for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) {
-    const instruction& inst = caller_recipe.steps.at(i);
-    if (inst.name != "reply") continue;
-    if (SIZE(caller_recipe.products) != SIZE(inst.ingredients)) {
-      raise_error << maybe(caller_recipe.name) << "replied with the wrong number of products at '" << to_string(inst) << "'\n" << end();
-      continue;
-    }
-    for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) {
-      if (!types_match(caller_recipe.products.at(i), inst.ingredients.at(i)))
-        raise_error << maybe(caller_recipe.name) << "replied with the wrong type at '" << to_string(inst) << "'\n" << end();
-    }
-  }
-}
-
-void test_recipe_headers_are_checked_2() {
-  Trace_file = "recipe_headers_are_checked_2";
-  Hide_errors = true;
-  transform("recipe add2 x:number, y:number [\n  local-scope\n  load-ingredients\n  z:address:number <- copy 0/unsafe\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("error: add2: replied with the wrong number of products at 'reply z'");
-}
-void test_recipe_headers_check_for_duplicate_names() {
-  Trace_file = "recipe_headers_check_for_duplicate_names";
-  Hide_errors = true;
-  transform("recipe add2 x:number, x:number -> z:number [\n  local-scope\n  load-ingredients\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("error: add2: x can't repeat in the ingredients");
-}
-void check_header_ingredients(const recipe_ordinal r) {
-  recipe& caller_recipe = get(Recipe, r);
-  if (caller_recipe.products.empty()) return;
-  caller_recipe.ingredient_index.clear();
-  trace(9991, "transform") << "--- checking reply instructions against header for " << caller_recipe.name << end();
-  for (long long int i = 0; i < SIZE(caller_recipe.ingredients); ++i) {
-    if (contains_key(caller_recipe.ingredient_index, caller_recipe.ingredients.at(i).name))
-      raise_error << maybe(caller_recipe.name) << caller_recipe.ingredients.at(i).name << " can't repeat in the ingredients\n" << end();
-    put(caller_recipe.ingredient_index, caller_recipe.ingredients.at(i).name, i);
-  }
-}
-
-
-void test_deduce_instruction_types_from_recipe_header() {
-  Trace_file = "deduce_instruction_types_from_recipe_header";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z <- add x, y  # no type for z\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 8 in location 1");
-}
-void deduce_types_from_header(const recipe_ordinal r) {
-  recipe& caller_recipe = get(Recipe, r);
-  if (caller_recipe.products.empty()) return;
-  trace(9991, "transform") << "--- deduce types from header for " << caller_recipe.name << end();
-  map<string, const type_tree*> header_type;
-  for (long long int i = 0; i < SIZE(caller_recipe.ingredients); ++i) {
-    put(header_type, caller_recipe.ingredients.at(i).name, caller_recipe.ingredients.at(i).type);
-    //TODO: replace to_string with names_to_string
-    trace(9993, "transform") << "type of " << caller_recipe.ingredients.at(i).name << " is " << to_string(caller_recipe.ingredients.at(i).type) << end();
-  }
-  for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) {
-    put(header_type, caller_recipe.products.at(i).name, caller_recipe.products.at(i).type);
-    trace(9993, "transform") << "type of " << caller_recipe.products.at(i).name << " is " << to_string(caller_recipe.products.at(i).type) << end();
-  }
-  for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) {
-    instruction& inst = caller_recipe.steps.at(i);
-    trace(9992, "transform") << "instruction: " << to_string(inst) << end();
-    for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-      if (inst.ingredients.at(i).type) continue;
-      if (header_type.find(inst.ingredients.at(i).name) == header_type.end())
-        continue;
-      if (!inst.ingredients.at(i).type)
-        inst.ingredients.at(i).type = new type_tree(*get(header_type, inst.ingredients.at(i).name));
-      trace(9993, "transform") << "type of " << inst.ingredients.at(i).name << " is " << to_string(inst.ingredients.at(i).type) << end();
-    }
-    for (long long int i = 0; i < SIZE(inst.products); ++i) {
-      trace(9993, "transform") << "  product: " << to_string(inst.products.at(i)) << end();
-      if (inst.products.at(i).type) continue;
-      if (header_type.find(inst.products.at(i).name) == header_type.end())
-        continue;
-      if (!inst.products.at(i).type)
-        inst.products.at(i).type = new type_tree(*get(header_type, inst.products.at(i).name));
-      trace(9993, "transform") << "type of " << inst.products.at(i).name << " is " << to_string(inst.products.at(i).type) << end();
-    }
-  }
-}
-
-
-void test_reply_based_on_header() {
-  Trace_file = "reply_based_on_header";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z <- add x, y\n  reply\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 8 in location 1");
-}
-void fill_in_reply_ingredients(recipe_ordinal r) {
-  recipe& caller_recipe = get(Recipe, r);
-  if (!caller_recipe.has_header) return;
-  trace(9991, "transform") << "--- fill in reply ingredients from header for recipe " << caller_recipe.name << end();
-  for (long long int i = 0; i < SIZE(caller_recipe.steps); ++i) {
-    instruction& inst = caller_recipe.steps.at(i);
-    if (inst.name == "reply")
-      add_header_products(inst, caller_recipe);
-  }
-  // fall through reply
-  if (caller_recipe.steps.at(SIZE(caller_recipe.steps)-1).name != "reply") {
-    instruction inst;
-    inst.name = "reply";
-    add_header_products(inst, caller_recipe);
-    caller_recipe.steps.push_back(inst);
-  }
-}
-
-void add_header_products(instruction& inst, const recipe& caller_recipe) {
-  assert(inst.name == "reply");
-  // collect any products with the same names as ingredients
-  for (long long int i = 0; i < SIZE(caller_recipe.products); ++i) {
-    // if the ingredient is missing, add it from the header
-    if (SIZE(inst.ingredients) == i)
-      inst.ingredients.push_back(caller_recipe.products.at(i));
-    // if it's missing /same_as_ingredient, try to fill it in
-    if (contains_key(caller_recipe.ingredient_index, caller_recipe.products.at(i).name) && !has_property(inst.ingredients.at(i), "same_as_ingredient")) {
-      ostringstream same_as_ingredient;
-      same_as_ingredient << get(caller_recipe.ingredient_index, caller_recipe.products.at(i).name);
-      inst.ingredients.at(i).properties.push_back(pair<string, string_tree*>("same-as-ingredient", new string_tree(same_as_ingredient.str())));
-    }
-  }
-}
-
-void test_explicit_reply_ignores_header() {
-  Trace_file = "explicit_reply_ignores_header";
-  run("recipe main [\n  1:number/raw, 2:number/raw <- add2 3, 5\n]\nrecipe add2 a:number, b:number -> y:number, z:number [\n  local-scope\n  load-ingredients\n  y <- add a, b\n  z <- subtract a, b\n  reply a, z\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 3 in location 1mem: storing -2 in location 2");
-}
-void test_reply_on_fallthrough_based_on_header() {
-  Trace_file = "reply_on_fallthrough_based_on_header";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z <- add x, y\n]\n");
-  CHECK_TRACE_CONTENTS("transform: instruction: reply z:numbermem: storing 8 in location 1");
-}
-void test_reply_on_fallthrough_already_exists() {
-  Trace_file = "reply_on_fallthrough_already_exists";
-  run("recipe main [\n  1:number/raw <- add2 3, 5\n]\nrecipe add2 x:number, y:number -> z:number [\n  local-scope\n  load-ingredients\n  z <- add x, y  # no type for z\n  reply z\n]\n");
-  CHECK_TRACE_CONTENTS("transform: instruction: reply z");
-  CHECK_TRACE_DOESNT_CONTAIN("transform: instruction: reply z:number");
-  run("");
-  CHECK_TRACE_CONTENTS("mem: storing 8 in location 1");
-}
-void test_recipe_headers_perform_same_ingredient_check() {
-  Trace_file = "recipe_headers_perform_same_ingredient_check";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 34\n  3:number <- add2 1:number, 2:number\n]\nrecipe add2 x:number, y:number -> x:number [\n  local-scope\n  load-ingredients\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: '3:number <- add2 1:number, 2:number' should write to 1:number rather than 3:number");
-}
-
-void test_static_dispatch() {
-  Trace_file = "static_dispatch";
-  run("recipe main [\n  7:number/raw <- test 3\n]\nrecipe test a:number -> z:number [\n  z <- copy 1\n]\nrecipe test a:number, b:number -> z:number [\n  z <- copy 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 7");
-}
-string matching_variant_name(const recipe& rr) {
-  const vector<recipe_ordinal>& variants = get_or_insert(Recipe_variants, rr.name);
-  for (long long int i = 0; i < SIZE(variants); ++i) {
-    if (!contains_key(Recipe, variants.at(i))) continue;
-    const recipe& candidate = get(Recipe, variants.at(i));
-    if (!all_reagents_match(rr, candidate)) continue;
-    return candidate.name;
-  }
-  return "";
-}
-
-bool all_reagents_match(const recipe& r1, const recipe& r2) {
-  if (SIZE(r1.ingredients) != SIZE(r2.ingredients)) return false;
-  if (SIZE(r1.products) != SIZE(r2.products)) return false;
-  for (long long int i = 0; i < SIZE(r1.ingredients); ++i) {
-    if (!deeply_equal_type_names(r1.ingredients.at(i), r2.ingredients.at(i))) {
-      return false;
-    }
-  }
-  for (long long int i = 0; i < SIZE(r1.products); ++i) {
-    if (!deeply_equal_type_names(r1.products.at(i), r2.products.at(i))) {
-      return false;
-    }
-  }
-  return true;
-}
-
-bool deeply_equal_type_names(const reagent& a, const reagent& b) {
-  return deeply_equal_type_names(a.type, b.type);
-}
-bool deeply_equal_type_names(const type_tree* a, const type_tree* b) {
-  if (!a) return !b;
-  if (!b) return !a;
-  if (a->name == "literal" && b->name == "literal")
-    return true;
-  if (a->name == "literal")
-    return Literal_type_names.find(b->name) != Literal_type_names.end();
-  if (b->name == "literal")
-    return Literal_type_names.find(a->name) != Literal_type_names.end();
-  return a->name == b->name
-      && deeply_equal_type_names(a->left, b->left)
-      && deeply_equal_type_names(a->right, b->right);
-}
-
-string next_unused_recipe_name(const string& recipe_name) {
-  for (long long int i = 2; ; ++i) {
-    ostringstream out;
-    out << recipe_name << '_' << i;
-    if (!contains_key(Recipe_ordinal, out.str()))
-      return out.str();
-  }
-}
-
-
-void test_static_dispatch_picks_most_similar_variant() {
-  Trace_file = "static_dispatch_picks_most_similar_variant";
-  run("recipe main [\n  7:number/raw <- test 3, 4, 5\n]\nrecipe test a:number -> z:number [\n  z <- copy 1\n]\nrecipe test a:number, b:number -> z:number [\n  z <- copy 2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 7");
-}
-void resolve_ambiguous_calls(recipe_ordinal r) {
-  cerr << "DDD " << get(Recipe, r).name << ": " << contains_key(Type_ordinal, "_elem") << '\n';
-  recipe& caller_recipe = get(Recipe, r);
-  trace(9991, "transform") << "--- resolve ambiguous calls for recipe " << caller_recipe.name << end();
-  for (long long int index = 0; index < SIZE(caller_recipe.steps); ++index) {
-    instruction& inst = caller_recipe.steps.at(index);
-    if (inst.is_label) continue;
-    if (non_ghost_size(get_or_insert(Recipe_variants, inst.name)) == 0) continue;
-    trace(9992, "transform") << "instruction " << inst.original_string << end();
-    resolve_stack.push_front(call(r));
-    resolve_stack.front().running_step_index = index;
-    cerr << "EEE " << contains_key(Type_ordinal, "_elem") << '\n';
-    string new_name = best_variant(inst, caller_recipe);
-    if (!new_name.empty())
-      inst.name = new_name;
-    assert(resolve_stack.front().running_recipe == r);
-    assert(resolve_stack.front().running_step_index == index);
-    resolve_stack.pop_front();
-  }
-  if (contains_key(Type_ordinal, "_elem")) {
-    cerr << "ZZZ " << get(Recipe, r).name << '\n';
-    exit(0);
-  }
-}
-
-string best_variant(instruction& inst, const recipe& caller_recipe) {
-  cerr << "FFF " << contains_key(Type_ordinal, "_elem") << '\n';
-  vector<recipe_ordinal>& variants = get(Recipe_variants, inst.name);
-  vector<recipe_ordinal> candidates;
-
-  // Static Dispatch Phase 1
-  candidates = strictly_matching_variants(inst, variants);
-  if (!candidates.empty()) return best_variant(inst, candidates).name;
-
-  // Static Dispatch Phase 2 (shape-shifting recipes in a later layer)
-  candidates = strictly_matching_shape_shifting_variants(inst, variants);
-  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();
-    cerr << "GGG " << contains_key(Type_ordinal, "_elem") << '\n';
-    recipe_ordinal new_recipe_ordinal = new_variant(exemplar, inst, caller_recipe);
-    cerr << "XXX " << contains_key(Type_ordinal, "_elem") << '\n';
-    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
-    cerr << "YYY " << contains_key(Type_ordinal, "_elem") << '\n';
-    if (!variant.steps.empty()) {
-      trace(9992, "transform") << "transforming new specialization: " << variant.name << end();
-      for (long long int t = 0; t < SIZE(Transform); ++t) {
-        (*Transform.at(t))(new_recipe_ordinal);
-      }
-    }
-    variant.transformed_until = SIZE(Transform)-1;
-    trace(9992, "transform") << "new specialization: " << variant.name << end();
-    return variant.name;
-  }
-  skip_shape_shifting_variants:;
-
-
-  // End Static Dispatch Phase 2
-
-  // Static Dispatch Phase 3
-  candidates = strictly_matching_variants_except_literal_against_boolean(inst, variants);
-  if (!candidates.empty()) return best_variant(inst, candidates).name;
-
-  // Static Dispatch Phase 4
-  candidates = matching_variants(inst, variants);
-  if (!candidates.empty()) return best_variant(inst, candidates).name;
-
-  // error messages
-  if (get(Recipe_ordinal, inst.name) >= MAX_PRIMITIVE_RECIPES) {  // we currently don't check types for primitive variants
-    raise_error << maybe(caller_recipe.name) << "failed to find a matching call for '" << to_string(inst) << "'\n" << end();
-    for (list<call>::iterator p = /*skip*/++resolve_stack.begin(); p != resolve_stack.end(); ++p) {
-      const recipe& specializer_recipe = get(Recipe, p->running_recipe);
-      const instruction& specializer_inst = specializer_recipe.steps.at(p->running_step_index);
-      if (specializer_recipe.name != "interactive")
-        raise_error << "  (from '" << to_string(specializer_inst) << "' in " << specializer_recipe.name << ")\n" << end();
-      else
-        raise_error << "  (from '" << to_string(specializer_inst) << "')\n" << end();
-      // One special-case to help with the rewrite_stash transform. (cross-layer)
-      if (specializer_inst.products.at(0).name.find("stash_") == 0) {
-        instruction stash_inst;
-        if (next_stash(*p, &stash_inst)) {
-          if (specializer_recipe.name != "interactive")
-            raise_error << "  (part of '" << stash_inst.original_string << "' in " << specializer_recipe.name << ")\n" << end();
-          else
-            raise_error << "  (part of '" << stash_inst.original_string << "')\n" << end();
-        }
-      }
-    }
-  }
-  return "";
-}
-
-// phase 1
-vector<recipe_ordinal> strictly_matching_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;
-    trace(9992, "transform") << "checking variant (strict) " << i << ": " << header_label(variants.at(i)) << end();
-    if (all_header_reagents_strictly_match(inst, get(Recipe, variants.at(i))))
-      result.push_back(variants.at(i));
-  }
-  return result;
-}
-
-bool all_header_reagents_strictly_match(const instruction& inst, const recipe& variant) {
-  for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) {
-    if (!types_strictly_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "strict match failed: ingredient " << i << end();
-      return false;
-    }
-  }
-  for (long long int i = 0; i < min(SIZE(inst.products), SIZE(variant.products)); ++i) {
-    if (is_dummy(inst.products.at(i))) continue;
-    if (!types_strictly_match(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "strict match failed: product " << i << end();
-      return false;
-    }
-  }
-  return true;
-}
-
-// phase 3
-vector<recipe_ordinal> strictly_matching_variants_except_literal_against_boolean(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;
-    trace(9992, "transform") << "checking variant (strict except literals-against-booleans) " << i << ": " << header_label(variants.at(i)) << end();
-    if (all_header_reagents_strictly_match_except_literal_against_boolean(inst, get(Recipe, variants.at(i))))
-      result.push_back(variants.at(i));
-  }
-  return result;
-}
-
-bool all_header_reagents_strictly_match_except_literal_against_boolean(const instruction& inst, const recipe& variant) {
-  for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) {
-    if (!types_strictly_match_except_literal_against_boolean(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "strict match failed: ingredient " << i << end();
-      return false;
-    }
-  }
-  for (long long int i = 0; i < min(SIZE(variant.products), SIZE(inst.products)); ++i) {
-    if (is_dummy(inst.products.at(i))) continue;
-    if (!types_strictly_match_except_literal_against_boolean(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "strict match failed: product " << i << end();
-      return false;
-    }
-  }
-  return true;
-}
-
-// phase 4
-vector<recipe_ordinal> matching_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;
-    trace(9992, "transform") << "checking variant " << i << ": " << header_label(variants.at(i)) << end();
-    if (all_header_reagents_match(inst, get(Recipe, variants.at(i))))
-      result.push_back(variants.at(i));
-  }
-  return result;
-}
-
-bool all_header_reagents_match(const instruction& inst, const recipe& variant) {
-  for (long long int i = 0; i < min(SIZE(inst.ingredients), SIZE(variant.ingredients)); ++i) {
-    if (!types_match(variant.ingredients.at(i), inst.ingredients.at(i))) {
-      trace(9993, "transform") << "strict match failed: ingredient " << i << end();
-      return false;
-    }
-  }
-  for (long long int i = 0; i < min(SIZE(variant.products), SIZE(inst.products)); ++i) {
-    if (is_dummy(inst.products.at(i))) continue;
-    if (!types_match(variant.products.at(i), inst.products.at(i))) {
-      trace(9993, "transform") << "strict match failed: product " << i << end();
-      return false;
-    }
-  }
-  return true;
-}
-
-// tie-breaker for each phase
-const recipe& best_variant(const instruction& inst, vector<recipe_ordinal>& candidates) {
-  assert(!candidates.empty());
-  long long int min_score = 999;
-  long long int min_index = 0;
-  for (long long int i = 0; i < SIZE(candidates); ++i) {
-    const recipe& candidate = get(Recipe, candidates.at(i));
-    long long int score = abs(SIZE(candidate.products)-SIZE(inst.products))
-                          + abs(SIZE(candidate.ingredients)-SIZE(inst.ingredients));
-    assert(score < 999);
-    if (score < min_score) {
-      min_score = score;
-      min_index = i;
-    }
-  }
-  return get(Recipe, candidates.at(min_index));
-}
-
-long long int non_ghost_size(vector<recipe_ordinal>& variants) {
-  long long int result = 0;
-  for (long long int i = 0; i < SIZE(variants); ++i)
-    if (variants.at(i) != -1) ++result;
-  return result;
-}
-
-bool next_stash(const call& c, instruction* stash_inst) {
-  const recipe& specializer_recipe = get(Recipe, c.running_recipe);
-  long long int index = c.running_step_index;
-  for (++index; index < SIZE(specializer_recipe.steps); ++index) {
-    const instruction& inst = specializer_recipe.steps.at(index);
-    if (inst.name == "stash") {
-      *stash_inst = inst;
-      return true;
-    }
-  }
-  return false;
-}
-
-void test_static_dispatch_disabled_in_recipe_without_variants() {
-  Trace_file = "static_dispatch_disabled_in_recipe_without_variants";
-  run("recipe main [\n  1:number <- test 3\n]\nrecipe test [\n  2:number <- next-ingredient  # ensure no header\n  reply 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-void test_static_dispatch_disabled_on_headerless_definition() {
-  Trace_file = "static_dispatch_disabled_on_headerless_definition";
-  Hide_warnings = true;
-  run("recipe test a:number -> z:number [\n  z <- copy 1\n]\nrecipe test [\n  reply 34\n]\n");
-  CHECK_TRACE_CONTENTS("warn: redefining recipe test");
-}
-void test_static_dispatch_disabled_on_headerless_definition_2() {
-  Trace_file = "static_dispatch_disabled_on_headerless_definition_2";
-  Hide_warnings = true;
-  run("recipe test [\n  reply 34\n]\nrecipe test a:number -> z:number [\n  z <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("warn: redefining recipe test");
-}
-void test_static_dispatch_on_primitive_names() {
-  Trace_file = "static_dispatch_on_primitive_names";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- copy 34\n  3:boolean <- equal 1:number, 2:number\n  4:boolean <- copy 0/false\n  5:boolean <- copy 0/false\n  6:boolean <- equal 4:boolean, 5:boolean\n]\n# temporarily hardcode number equality to always fail\nrecipe equal x:number, y:number -> z:boolean [\n  local-scope\n  load-ingredients\n  z <- copy 0/false\n]\n# comparing numbers used overload\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3mem: storing 1 in location 6");
-}
-void test_static_dispatch_works_with_dummy_results_for_containers() {
-  Trace_file = "static_dispatch_works_with_dummy_results_for_containers";
-  Hide_errors = true;
-  run("recipe main [\n  _ <- test 3, 4\n]\nrecipe test a:number -> z:point [\n  local-scope\n  load-ingredients\n  z <- merge a, 0\n]\nrecipe test a:number, b:number -> z:point [\n  local-scope\n  load-ingredients\n  z <- merge a, b\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_static_dispatch_works_with_compound_type_containing_container_defined_after_first_use() {
-  Trace_file = "static_dispatch_works_with_compound_type_containing_container_defined_after_first_use";
-  Hide_errors = true;
-  run("recipe main [\n  x:address:shared:foo <- new foo:type\n  test x\n]\ncontainer foo [\n  x:number\n]\nrecipe test a:address:shared:foo -> z:number [\n  local-scope\n  load-ingredients\n  z:number <- get *a, x:offset\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_static_dispatch_works_with_compound_type_containing_container_defined_after_second_use() {
-  Trace_file = "static_dispatch_works_with_compound_type_containing_container_defined_after_second_use";
-  Hide_errors = true;
-  run("recipe main [\n  x:address:shared:foo <- new foo:type\n  test x\n]\nrecipe test a:address:shared:foo -> z:number [\n  local-scope\n  load-ingredients\n  z:number <- get *a, x:offset\n]\ncontainer foo [\n  x:number\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses() {
-  Trace_file = "static_dispatch_prefers_literals_to_be_numbers_rather_than_addresses";
-  run("recipe main [\n  1:number <- foo 0\n]\nrecipe foo x:address:number -> y:number [\n  reply 34\n]\nrecipe foo x:number -> y:number [\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 1");
-}
-void test_static_dispatch_on_non_literal_character_ignores_variant_with_numbers() {
-  Trace_file = "static_dispatch_on_non_literal_character_ignores_variant_with_numbers";
-  Hide_errors = true;
-  run("recipe main [\n  local-scope\n  x:character <- copy 10/newline\n  1:number/raw <- foo x\n]\nrecipe foo x:number -> y:number [\n  load-ingredients\n  reply 34\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number/raw <- foo x'");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 34 in location 1");
-}
-void test_static_dispatch_dispatches_literal_to_boolean_before_character() {
-  Trace_file = "static_dispatch_dispatches_literal_to_boolean_before_character";
-  run("recipe main [\n  1:number/raw <- foo 0  # valid literal for boolean\n]\nrecipe foo x:character -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:boolean -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# boolean variant is preferred\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 1");
-}
-void test_static_dispatch_dispatches_literal_to_character_when_out_of_boolean_range() {
-  Trace_file = "static_dispatch_dispatches_literal_to_character_when_out_of_boolean_range";
-  run("recipe main [\n  1:number/raw <- foo 97  # not a valid literal for boolean\n]\nrecipe foo x:character -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:boolean -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# character variant is preferred\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-void test_static_dispatch_dispatches_literal_to_number_if_at_all_possible() {
-  Trace_file = "static_dispatch_dispatches_literal_to_number_if_at_all_possible";
-  run("recipe main [\n  1:number/raw <- foo 97\n]\nrecipe foo x:character -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:number -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# number variant is preferred\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 1");
-}
-string header_label(recipe_ordinal r) {
-  const recipe& caller = get(Recipe, r);
-  ostringstream out;
-  out << "recipe " << caller.name;
-  for (long long int i = 0; i < SIZE(caller.ingredients); ++i)
-    out << ' ' << to_string(caller.ingredients.at(i));
-  if (!caller.products.empty()) out << " ->";
-  for (long long int i = 0; i < SIZE(caller.products); ++i)
-    out << ' ' << to_string(caller.products.at(i));
-  return out.str();
-}
-
-void test_reload_variant_retains_other_variants() {
-  Trace_file = "reload_variant_retains_other_variants";
-  run("recipe main [\n  1:number <- copy 34\n  2:number <- foo 1:number\n]\nrecipe foo x:number -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:address:number -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\nrecipe! foo x:address:number -> y:number [\n  local-scope\n  load-ingredients\n  reply 36\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-  CHECK_TRACE_COUNT("error", 0);
-  run("");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-void test_dispatch_errors_come_after_unknown_name_errors() {
-  Trace_file = "dispatch_errors_come_after_unknown_name_errors";
-  Hide_errors = true;
-  run("recipe main [\n  y:number <- foo x\n]\nrecipe foo a:number -> b:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo a:boolean -> b:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: missing type for x in 'y:number <- foo x'error: main: failed to find a matching call for 'y:number <- foo x'");
-}
-
-void test_size_of_shape_shifting_container() {
-  Trace_file = "size_of_shape_shifting_container";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  1:foo:number <- merge 12, 13\n  3:foo:point <- merge 14, 15, 16\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 1mem: storing 13 in location 2mem: storing 14 in location 3mem: storing 15 in location 4mem: storing 16 in location 5");
-}
-void test_size_of_shape_shifting_container_2() {
-  Trace_file = "size_of_shape_shifting_container_2";
-  Hide_errors = true;
-  run("# multiple type ingredients\ncontainer foo:_a:_b [\n  x:_a\n  y:_b\n]\nrecipe main [\n  1:foo:number:boolean <- merge 34, 1/true\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_size_of_shape_shifting_container_3() {
-  Trace_file = "size_of_shape_shifting_container_3";
-  Hide_errors = true;
-  run("container foo:_a:_b [\n  x:_a\n  y:_b\n]\nrecipe main [\n  1:address:shared:array:character <- new [abc]\n  # compound types for type ingredients\n  {2: (foo number (address shared array character))} <- merge 34/x, 1:address:shared:array:character/y\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_size_of_shape_shifting_container_4() {
-  Trace_file = "size_of_shape_shifting_container_4";
-  Hide_errors = true;
-  run("container foo:_a:_b [\n  x:_a\n  y:_b\n]\ncontainer bar:_a:_b [\n  # dilated element\n  {data: (foo _a (address shared _b))}\n]\nrecipe main [\n  1:address:shared:array:character <- new [abc]\n  2:bar:number:array:character <- merge 34/x, 1:address:shared:array:character/y\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void read_type_ingredients(string& name) {
-  string save_name = name;
-  istringstream in(save_name);
-  name = slurp_until(in, ':');
-  if (!contains_key(Type_ordinal, name) || get(Type_ordinal, name) == 0)
-    put(Type_ordinal, name, Next_type_ordinal++);
-  type_info& info = get_or_insert(Type, get(Type_ordinal, name));
-  long long int next_type_ordinal = START_TYPE_INGREDIENTS;
-  while (has_data(in)) {
-    string curr = slurp_until(in, ':');
-    if (info.type_ingredient_names.find(curr) != info.type_ingredient_names.end()) {
-      raise_error << "can't repeat type ingredient names in a single container definition\n" << end();
-      return;
-    }
-    put(info.type_ingredient_names, curr, next_type_ordinal++);
-  }
-}
-
-bool is_type_ingredient_name(const string& type) {
-  return !type.empty() && type.at(0) == '_';
-}
-
-void test_size_of_shape_shifting_exclusive_container() {
-  Trace_file = "size_of_shape_shifting_exclusive_container";
-  run("exclusive-container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  1:foo:number <- merge 0/x, 34\n  3:foo:point <- merge 0/x, 15, 16\n  6:foo:point <- merge 1/y, 23\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1mem: storing 34 in location 2mem: storing 0 in location 3mem: storing 15 in location 4mem: storing 16 in location 5mem: storing 1 in location 6mem: storing 23 in location 7");
-  CHECK_TRACE_COUNT("mem", 7);
-}
-// shape-shifting version of size_of
-long long int size_of_type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) {
-  type_tree* element_type = type_ingredient(element_template, rest_of_use);
-  if (!element_type) return 0;
-  long long int result = size_of(element_type);
-  delete element_type;
-  return result;
-}
-
-type_tree* type_ingredient(const type_tree* element_template, const type_tree* rest_of_use) {
-  long long int type_ingredient_index = element_template->value - START_TYPE_INGREDIENTS;
-  const type_tree* curr = rest_of_use;
-  if (!curr) return NULL;
-  while (type_ingredient_index > 0) {
-    --type_ingredient_index;
-    curr = curr->right;
-    if (!curr) return NULL;
-  }
-  assert(curr);
-  if (curr->left) curr = curr->left;
-  assert(curr->value > 0);
-  trace(9999, "type") << "type deduced to be " << get(Type, curr->value).name << "$" << end();
-  return new type_tree(*curr);
-}
-
-void test_get_on_shape_shifting_container() {
-  Trace_file = "get_on_shape_shifting_container";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  1:foo:point <- merge 14, 15, 16\n  2:number <- get 1:foo:point, y:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 16 in location 2");
-}
-void test_get_on_shape_shifting_container_2() {
-  Trace_file = "get_on_shape_shifting_container_2";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  1:foo:point <- merge 14, 15, 16\n  2:point <- get 1:foo:point, x:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 2mem: storing 15 in location 3");
-}
-void test_get_on_shape_shifting_container_3() {
-  Trace_file = "get_on_shape_shifting_container_3";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  1:foo:address:point <- merge 34/unsafe, 48\n  2:address:point <- get 1:foo:address:point, x:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-}
-void test_get_on_shape_shifting_container_inside_container() {
-  Trace_file = "get_on_shape_shifting_container_inside_container";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\ncontainer bar [\n  x:foo:point\n  y:number\n]\nrecipe main [\n  1:bar <- merge 14, 15, 16, 17\n  2:number <- get 1:bar, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 17 in location 2");
-}
-void test_get_on_complex_shape_shifting_container() {
-  Trace_file = "get_on_complex_shape_shifting_container";
-  run("container foo:_a:_b [\n  x:_a\n  y:_b\n]\nrecipe main [\n  1:address:shared:array:character <- new [abc]\n  {2: (foo number (address shared array character))} <- merge 34/x, 1:address:shared:array:character/y\n  3:address:shared:array:character <- get {2: (foo number (address shared array character))}, y:offset\n  4:boolean <- equal 1:address:shared:array:character, 3:address:shared:array:character\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 4");
-}
-bool contains_type_ingredient(const reagent& x) {
-  return contains_type_ingredient(x.type);
-}
-
-bool contains_type_ingredient(const type_tree* type) {
-  if (!type) return false;
-  if (type->value >= START_TYPE_INGREDIENTS) return true;
-  return contains_type_ingredient(type->left) || contains_type_ingredient(type->right);
-}
-
-// todo: too complicated and likely incomplete; maybe avoid replacing in place? Maybe process element_type and element_type_name in separate functions?
-void replace_type_ingredients(type_tree* element_type, const type_tree* callsite_type, const type_info& container_info) {
-  if (!callsite_type) return;  // error but it's already been raised above
-  if (!element_type) return;
-
-  // A. recurse first to avoid nested replaces (which I can't reason about yet)
-  replace_type_ingredients(element_type->left, callsite_type, container_info);
-  replace_type_ingredients(element_type->right, callsite_type, container_info);
-  if (element_type->value < START_TYPE_INGREDIENTS) return;
-
-  const long long int type_ingredient_index = element_type->value-START_TYPE_INGREDIENTS;
-  if (!has_nth_type(callsite_type, type_ingredient_index)) {
-    raise_error << "illegal type " << names_to_string(callsite_type) << " seems to be missing a type ingredient or three\n" << end();
-    return;
-  }
-
-  // B. replace the current location
-  const type_tree* replacement = NULL;
-  bool splice_right = true ;
-  {
-    const type_tree* curr = callsite_type;
-    for (long long int i = 0; i < type_ingredient_index; ++i)
-      curr = curr->right;
-    if (curr && curr->left) {
-      replacement = curr->left;
-    }
-    else {
-      // We want foo:_t to be used like foo:number, which expands to {foo: number}
-      // rather than {foo: (number)}
-      // We'd also like to use it with multiple types: foo:address:number.
-      replacement = curr;
-      if (!final_type_ingredient(type_ingredient_index, container_info)) {
-        splice_right = false;
-      }
-    }
-  }
-  element_type->name = replacement->name;
-  element_type->value = replacement->value;
-  assert(!element_type->left);  // since value is set
-  element_type->left = replacement->left ? new type_tree(*replacement->left) : NULL;
-  if (splice_right) {
-    type_tree* old_right = element_type->right;
-    element_type->right = replacement->right ? new type_tree(*replacement->right) : NULL;
-    append(element_type->right, old_right);
-  }
-}
-
-bool final_type_ingredient(long long int type_ingredient_index, const type_info& container_info) {
-  for (map<string, type_ordinal>::const_iterator p = container_info.type_ingredient_names.begin();
-       p != container_info.type_ingredient_names.end();
-       ++p) {
-    if (p->second > START_TYPE_INGREDIENTS+type_ingredient_index) return false;
-  }
-  return true;
-}
-
-void append(type_tree*& base, type_tree* extra) {
-  if (!base) {
-    base = extra;
-    return;
-  }
-  type_tree* curr = base;
-  while (curr->right) curr = curr->right;
-  curr->right = extra;
-}
-
-void append(string_tree*& base, string_tree* extra) {
-  if (!base) {
-    base = extra;
-    return;
-  }
-  string_tree* curr = base;
-  while (curr->right) curr = curr->right;
-  curr->right = extra;
-}
-
-void test_replace_type_ingredients_entire() {
-  run("container foo:_elem [\n"
-      "  x:_elem\n"
-      "  y:number\n"
-      "]\n");
-  reagent callsite("x:foo:point");
-  reagent element = element_type(callsite, 0);
-  cerr << debug_string(element) << '\n';
-//?   DUMP("");
-  CHECK_EQ(element.name, "x");
-  cerr << get(Type_ordinal, "point") << '\n';
-  cerr << contains_key(Type, 23) << '\n';
-  for (map<string, type_ordinal>::iterator p = Type_ordinal.begin(); p != Type_ordinal.end(); ++p)
-    if (p->second == 23) cerr << p->first << '\n';
-  CHECK_EQ(element.type->name, "point");
-  CHECK(!element.type->right);
-}
-
-void test_replace_type_ingredients_tail() {
-  exit(0);
-  run("container foo:_elem [\n"
-      "  x:_elem\n"
-      "]\n"
-      "container bar:_elem [\n"
-      "  x:foo:_elem\n"
-      "]\n");
-  reagent callsite("x:bar:point");
-  reagent element = element_type(callsite, 0);
-  CHECK_EQ(element.name, "x");
-  CHECK_EQ(element.type->name, "foo");
-  CHECK_EQ(element.type->right->name, "point");
-  CHECK(!element.type->right->right);
-}
-
-void test_replace_type_ingredients_head_tail_multiple() {
-  run("container foo:_elem [\n"
-      "  x:_elem\n"
-      "]\n"
-      "container bar:_elem [\n"
-      "  x:foo:_elem\n"
-      "]\n");
-  reagent callsite("x:bar:address:shared:array:character");
-  reagent element = element_type(callsite, 0);
-  CHECK_EQ(element.name, "x");
-  CHECK_EQ(element.type->name, "foo");
-  CHECK_EQ(element.type->right->name, "address");
-  CHECK_EQ(element.type->right->right->name, "shared");
-  CHECK_EQ(element.type->right->right->right->name, "array");
-  CHECK_EQ(element.type->right->right->right->right->name, "character");
-  CHECK(!element.type->right->right->right->right->right);
-}
-
-void test_replace_type_ingredients_head_middle() {
-  run("container foo:_elem [\n"
-      "  x:_elem\n"
-      "]\n"
-      "container bar:_elem [\n"
-      "  x:foo:_elem:number\n"
-      "]\n");
-  reagent callsite("x:bar:address");
-  reagent element = element_type(callsite, 0);
-  CHECK_EQ(element.name, "x");
-  CHECK(element.type)
-  CHECK_EQ(element.type->name, "foo");
-  CHECK(element.type->right)
-  CHECK_EQ(element.type->right->name, "address");
-  CHECK(element.type->right->right)
-  CHECK_EQ(element.type->right->right->name, "number");
-  CHECK(!element.type->right->right->right);
-}
-
-void test_replace_last_type_ingredient_with_multiple() {
-  run("container foo:_a:_b [\n"
-      "  x:_a\n"
-      "  y:_b\n"
-      "]\n");
-  reagent callsite("{f: (foo number (address shared array character))}");
-  reagent element1 = element_type(callsite, 0);
-  CHECK_EQ(element1.name, "x");
-  CHECK_EQ(element1.type->name, "number");
-  CHECK(!element1.type->right);
-  reagent element2 = element_type(callsite, 1);
-  CHECK_EQ(element2.name, "y");
-  CHECK_EQ(element2.type->name, "address");
-  CHECK_EQ(element2.type->right->name, "shared");
-  CHECK_EQ(element2.type->right->right->name, "array");
-  CHECK_EQ(element2.type->right->right->right->name, "character");
-  CHECK(!element2.type->right->right->right->right);
-}
-
-void test_replace_middle_type_ingredient_with_multiple() {
-  run("container foo:_a:_b:_c [\n"
-      "  x:_a\n"
-      "  y:_b\n"
-      "  z:_c\n"
-      "]\n");
-  reagent callsite("{f: (foo number (address shared array character) boolean)}");
-  reagent element1 = element_type(callsite, 0);
-  CHECK_EQ(element1.name, "x");
-  CHECK_EQ(element1.type->name, "number");
-  CHECK(!element1.type->right);
-  reagent element2 = element_type(callsite, 1);
-  CHECK_EQ(element2.name, "y");
-  CHECK_EQ(element2.type->name, "address");
-  CHECK_EQ(element2.type->right->name, "shared");
-  CHECK_EQ(element2.type->right->right->name, "array");
-  CHECK_EQ(element2.type->right->right->right->name, "character");
-  CHECK(!element2.type->right->right->right->right);
-  reagent element3 = element_type(callsite, 2);
-  CHECK_EQ(element3.name, "z");
-  CHECK_EQ(element3.type->name, "boolean");
-  CHECK(!element3.type->right);
-}
-
-bool has_nth_type(const type_tree* base, long long int n) {
-  assert(n >= 0);
-  if (base == NULL) return false;
-  if (n == 0) return true;
-  return has_nth_type(base->right, n-1);
-}
-
-void test_get_on_shape_shifting_container_error() {
-  Trace_file = "get_on_shape_shifting_container_error";
-  Hide_errors = true;
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  10:foo:point <- merge 14, 15, 16\n  1:number <- get 10:foo, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("error: illegal type \"foo\" seems to be missing a type ingredient or three");
-}
-void test_get_address_on_shape_shifting_container() {
-  Trace_file = "get_address_on_shape_shifting_container";
-  run("container foo:_t [\n  x:_t\n  y:number\n]\nrecipe main [\n  10:foo:point <- merge 14, 15, 16\n  1:address:number <- get-address 10:foo:point, 1:offset\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 1");
-}
-void test_merge_check_shape_shifting_container_containing_exclusive_container() {
-  Trace_file = "merge_check_shape_shifting_container_containing_exclusive_container";
-  Hide_errors = true;
-  run("container foo:_elem [\n  x:number\n  y:_elem\n]\nexclusive-container bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo:bar <- merge 23, 1/y, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 23 in location 1mem: storing 1 in location 2mem: storing 34 in location 3");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_shape_shifting_container_containing_exclusive_container_2() {
-  Trace_file = "merge_check_shape_shifting_container_containing_exclusive_container_2";
-  Hide_errors = true;
-  run("container foo:_elem [\n  x:number\n  y:_elem\n]\nexclusive-container bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo:bar <- merge 23, 1/y, 34, 35\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too many ingredients in '1:foo:bar <- merge 23, 1/y, 34, 35'");
-}
-void test_merge_check_shape_shifting_exclusive_container_containing_container() {
-  Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container";
-  Hide_errors = true;
-  run("exclusive-container foo:_elem [\n  x:number\n  y:_elem\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo:bar <- merge 1/y, 23, 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 1mem: storing 23 in location 2mem: storing 34 in location 3");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_shape_shifting_exclusive_container_containing_container_2() {
-  Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container_2";
-  Hide_errors = true;
-  run("exclusive-container foo:_elem [\n  x:number\n  y:_elem\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo:bar <- merge 0/x, 23\n]\n");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_merge_check_shape_shifting_exclusive_container_containing_container_3() {
-  Trace_file = "merge_check_shape_shifting_exclusive_container_containing_container_3";
-  Hide_errors = true;
-  run("exclusive-container foo:_elem [\n  x:number\n  y:_elem\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo:bar <- merge 1/y, 23\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: too few ingredients in '1:foo:bar <- merge 1/y, 23'");
-}
-
-void test_shape_shifting_recipe() {
-  Trace_file = "shape_shifting_recipe";
-  run("recipe main [\n  10:point <- merge 14, 15\n  11:point <- foo 10:point\n]\n# non-matching variant\nrecipe foo a:number -> result:number [\n  local-scope\n  load-ingredients\n  result <- copy 34\n]\n# matching shape-shifting variant\nrecipe foo a:_t -> result:_t [\n  local-scope\n  load-ingredients\n  result <- copy a\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 11mem: storing 15 in location 12");
-}
-// phase 2 of static dispatch
-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))))
-      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)) {
-    trace(9993, "transform") << "too few ingredients" << end();
-    return false;
-  }
-  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))) {
-      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))) {
-      trace(9993, "transform") << "strict match failed: product " << i << end();
-      return false;
-    }
-  }
-  return true;
-}
-
-// tie-breaker for phase 2
-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));
-    assert(score > -1);
-    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));
-    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))
-                           + (SIZE(inst.ingredients)-SIZE(candidate.ingredients));
-    assert(score2 < 999);
-    if (score2 < min_score2) {
-      min_score2 = score2;
-      best_index = i;
-    }
-  }
-  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)))
-      return true;
-  }
-  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) {
-  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)
-    result += number_of_concrete_type_names(caller.ingredients.at(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) {
-  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))
-    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")
-    return true;
-  if (to->name == "literal"
-      && Literal_type_names.find(from->name) != Literal_type_names.end())
-    return true;
-  if (from->name == "literal"
-      && Literal_type_names.find(to->name) != Literal_type_names.end())
-    return true;
-  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) {
-  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;
-  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) {
-  cerr << "HHH " << contains_key(Type_ordinal, "_elem") << '\n';
-  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++);
-  // make a copy
-  assert(contains_key(Recipe, exemplar));
-  assert(!contains_key(Recipe, new_recipe_ordinal));
-  Recently_added_recipes.push_back(new_recipe_ordinal);
-  Recently_added_shape_shifting_recipes.push_back(new_recipe_ordinal);
-  put(Recipe, new_recipe_ordinal, get(Recipe, exemplar));
-  recipe& new_recipe = get(Recipe, new_recipe_ordinal);
-  new_recipe.name = new_name;
-  trace(9993, "transform") << "switching " << inst.name << " to specialized " << header_label(new_recipe_ordinal) << end();
-  // Since the exemplar never ran any transforms, we have to redo some of the
-  // work of the check_types_by_name transform while supporting type-ingredients.
-  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;
-  cerr << "III " << contains_key(Type_ordinal, "_elem") << '\n';
-    compute_type_ingredient_mappings(get(Recipe, exemplar), inst, mappings, caller_recipe, &error);
-  cerr << "JJJ " << contains_key(Type_ordinal, "_elem") << '\n';
-    if (!error) replace_type_ingredients(new_recipe, mappings);
-  cerr << "VVV " << contains_key(Type_ordinal, "_elem") << '\n';
-//?     if (!is_type_ingredient_name(x.type->name) && contains_key(Type_ordinal, x.type->name)) {
-//?       x.type->value = get(Type_ordinal, x.type->name);
-//?       return;
-//?     }
-    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));
-  cerr << "WWW " << contains_key(Type_ordinal, "_elem") << '\n';
-  return new_recipe_ordinal;
-}
-
-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)
-    save_or_deduce_type_name(variant.ingredients.at(i), type_names, variant);
-  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) {
-    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)
-      save_or_deduce_type_name(inst.ingredients.at(in), type_names, variant);
-    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) {
-  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));
-    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();
-    return;
-  }
-  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);
-    reagent ingredient = inst.ingredients.at(i);
-    canonize_type(ingredient);
-    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);
-    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) {
-  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) {
-  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) {
-    // 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;
-  }
-  if (is_type_ingredient_name(exemplar_type->name)) {
-    assert(!refinement_type->name.empty());
-    if (exemplar_type->right) {
-      raise_error << "type_ingredients in non-last position not currently supported\n" << end();
-      return;
-    }
-    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));
-    }
-    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();
-        *error = true;
-        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));
-      }
-    }
-  }
-  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) {
-  cerr << "KKK " << contains_key(Type_ordinal, "_elem") << '\n';
-  // update its header
-  if (mappings.empty()) return;
-  cerr << "LLL " << contains_key(Type_ordinal, "_elem") << '\n';
-  trace(9993, "transform") << "replacing in recipe header ingredients" << end();
-  for (long long int i = 0; i < SIZE(new_recipe.ingredients); ++i)
-    replace_type_ingredients(new_recipe.ingredients.at(i), mappings, new_recipe);
-  cerr << "MMM " << contains_key(Type_ordinal, "_elem") << '\n';
-  trace(9993, "transform") << "replacing in recipe header products" << end();
-  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
-  cerr << "NNN " << contains_key(Type_ordinal, "_elem") << '\n';
-  for (long long int i = 0; i < SIZE(new_recipe.steps); ++i) {
-    cerr << "OOO " << i << ' ' << contains_key(Type_ordinal, "_elem") << ' ' << to_string(new_recipe.steps.at(i)) << '\n';
-    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)
-      replace_type_ingredients(inst.ingredients.at(j), mappings, new_recipe);
-    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") {
-    cerr << "PPP " << contains_key(Type_ordinal, "_elem") << '\n';
-      type_tree* type = parse_type_tree(inst.ingredients.at(0).name);
-    cerr << "QQQ " << contains_key(Type_ordinal, "_elem") << '\n';
-      replace_type_ingredients(type, mappings);
-    cerr << "RRR " << contains_key(Type_ordinal, "_elem") << '\n';
-      inst.ingredients.at(0).name = inspect(type);
-      delete type;
-    cerr << "SSS " << contains_key(Type_ordinal, "_elem") << '\n';
-    }
-  }
-  cerr << "TTT " << contains_key(Type_ordinal, "_elem") << '\n';
-}
-
-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();
-    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))
-    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);
-    //TODO names_to_string
-    trace(9993, "transform") << type->name << " => " << to_string(replacement) << end();
-    if (replacement->name == "literal") {
-      type->name = "number";
-      type->value = get(Type_ordinal, "number");
-    }
-    else {
-      type->name = replacement->name;
-      type->value = replacement->value;
-    }
-    if (replacement->left) type->left = new type_tree(*replacement->left);
-    if (replacement->right) 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) {
-  istringstream in(s);
-  in >> std::noskipws;
-  return parse_type_tree(in);
-}
-
-type_tree* parse_type_tree(istream& in) {
-  skip_whitespace_but_not_newline(in);
-  if (!has_data(in)) return NULL;
-  if (in.peek() == ')') {
-    in.get();
-    return NULL;
-  }
-  if (in.peek() != '(') {
-    string type_name = next_word(in);
-    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));
-    return result;
-  }
-  in.get();  // skip '('
-  type_tree* result = NULL;
-  type_tree** curr = &result;
-  while (in.peek() != ')') {
-    assert(has_data(in));
-    *curr = new type_tree("", 0);
-    skip_whitespace_but_not_newline(in);
-    if (in.peek() == '(')
-      (*curr)->left = parse_type_tree(in);
-    else {
-      (*curr)->name = next_word(in);
-      if (!is_type_ingredient_name((*curr)->name)) {
-        if (!contains_key(Type_ordinal, (*curr)->name)) {
-          cerr << "RRR aaa " << contains_key(Type_ordinal, "_elem") << '\n';
-          put(Type_ordinal, (*curr)->name, Next_type_ordinal++);
-          cerr << ">>> RRR zzz " << contains_key(Type_ordinal, "_elem") << '\n';
-        }
-        (*curr)->value = get(Type_ordinal, (*curr)->name);
-      }
-    }
-    curr = &(*curr)->right;
-  }
-  in.get();  // skip ')'
-  return result;
-}
-
-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) {
-    out << x->name;
-    return;
-  }
-  out << '(';
-  for (const type_tree* curr = x; curr; curr = curr->right) {
-    if (curr != x) out << ' ';
-    if (curr->left)
-      dump_inspect(curr->left, out);
-    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)
-    ensure_all_concrete_types(new_recipe.ingredients.at(i), exemplar);
-  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) {
-    instruction& inst = new_recipe.steps.at(i);
-    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)
-      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();
-    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();
-    return;
-  }
-}
-
-void test_shape_shifting_recipe_2() {
-  Trace_file = "shape_shifting_recipe_2";
-  run("recipe main [\n  10:point <- merge 14, 15\n  11:point <- foo 10:point\n]\n# non-matching shape-shifting variant\nrecipe foo a:_t, b:_t -> result:number [\n  local-scope\n  load-ingredients\n  result <- copy 34\n]\n# matching shape-shifting variant\nrecipe foo a:_t -> result:_t [\n  local-scope\n  load-ingredients\n  result <- copy a\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 11mem: storing 15 in location 12");
-}
-void test_shape_shifting_recipe_nonroot() {
-  Trace_file = "shape_shifting_recipe_nonroot";
-  run("recipe main [\n  10:foo:point <- merge 14, 15, 16\n  20:point/raw <- bar 10:foo:point\n]\n# shape-shifting recipe with type ingredient following some other type\nrecipe bar a:foo:_t -> result:_t [\n  local-scope\n  load-ingredients\n  result <- get a, x:offset\n]\ncontainer foo:_t [\n  x:_t\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 20mem: storing 15 in location 21");
-}
-void test_shape_shifting_recipe_type_deduction_ignores_offsets() {
-  Trace_file = "shape_shifting_recipe_type_deduction_ignores_offsets";
-  run("recipe main [\n  10:foo:point <- merge 14, 15, 16\n  20:point/raw <- bar 10:foo:point\n]\nrecipe bar a:foo:_t -> result:_t [\n  local-scope\n  load-ingredients\n  x:number <- copy 1\n  result <- get a, x:offset  # shouldn't collide with other variable\n]\ncontainer foo:_t [\n  x:_t\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 14 in location 20mem: storing 15 in location 21");
-}
-void test_shape_shifting_recipe_empty() {
-  Trace_file = "shape_shifting_recipe_empty";
-  run("recipe main [\n  foo 1\n]\n# shape-shifting recipe with no body\nrecipe foo a:_t [\n]\n# shouldn't crash\n");
-}
-void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient() {
-  Trace_file = "shape_shifting_recipe_handles_shape_shifting_new_ingredient";
-  run("recipe main [\n  1:address:shared:foo:point <- bar 3\n  11:foo:point <- copy *1:address:shared:foo:point\n]\ncontainer foo:_t [\n  x:_t\n  y:number\n]\nrecipe bar x:number -> result:address:shared:foo:_t [\n  local-scope\n  load-ingredients\n  # new refers to _t in its ingredient *value*\n  result <- new {(foo _t) : type}\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 11mem: storing 0 in location 12mem: storing 0 in location 13");
-}
-void test_shape_shifting_recipe_handles_shape_shifting_new_ingredient_2() {
-  Trace_file = "shape_shifting_recipe_handles_shape_shifting_new_ingredient_2";
-  run("recipe main [\n  1:address:shared:foo:point <- bar 3\n  11:foo:point <- copy *1:address:shared:foo:point\n]\nrecipe bar x:number -> result:address:shared:foo:_t [\n  local-scope\n  load-ingredients\n  # new refers to _t in its ingredient *value*\n  result <- new {(foo _t) : type}\n]\n# container defined after use\ncontainer foo:_t [\n  x:_t\n  y:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 11mem: storing 0 in location 12mem: storing 0 in location 13");
-}
-void test_shape_shifting_recipe_supports_compound_types() {
-  Trace_file = "shape_shifting_recipe_supports_compound_types";
-  run("recipe main [\n  1:address:shared:point <- new point:type\n  2:address:number <- get-address *1:address:shared:point, y:offset\n  *2:address:number <- copy 34\n  3:address:shared:point <- bar 1:address:shared:point  # specialize _t to address:shared:point\n  4:point <- copy *3:address:shared:point\n]\nrecipe bar a:_t -> result:_t [\n  local-scope\n  load-ingredients\n  result <- copy a\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 5");
-}
-void test_shape_shifting_recipe_error() {
-  Trace_file = "shape_shifting_recipe_error";
-  Hide_errors = true;
-  run("recipe main [\n  a:number <- copy 3\n  b:address:shared:number <- foo a\n]\nrecipe foo a:_t -> b:_t [\n  load-ingredients\n  b <- copy a\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: no call found for 'b:address:shared:number <- foo a'");
-}
-void test_specialize_inside_recipe_without_header() {
-  Trace_file = "specialize_inside_recipe_without_header";
-  run("recipe main [\n  foo 3\n]\nrecipe foo [\n  local-scope\n  x:number <- next-ingredient  # ensure no header\n  1:number/raw <- bar x  # call a shape-shifting recipe\n]\nrecipe bar x:_elem -> y:_elem [\n  local-scope\n  load-ingredients\n  y <- add x, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_specialize_with_literal() {
-  Trace_file = "specialize_with_literal";
-  run("recipe main [\n  local-scope\n  # permit literal to map to number\n  1:number/raw <- foo 3\n]\nrecipe foo x:_elem -> y:_elem [\n  local-scope\n  load-ingredients\n  y <- add x, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_specialize_with_literal_2() {
-  Trace_file = "specialize_with_literal_2";
-  run("recipe main [\n  local-scope\n  # permit literal to map to character\n  1:character/raw <- foo 3\n]\nrecipe foo x:_elem -> y:_elem [\n  local-scope\n  load-ingredients\n  y <- add x, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 1");
-}
-void test_specialize_with_literal_3() {
-  Trace_file = "specialize_with_literal_3";
-  Hide_errors = true;
-  run("recipe main [\n  local-scope\n  # permit '0' to map to address to shape-shifting type-ingredient\n  1:address:shared:character/raw <- foo 0\n]\nrecipe foo x:address:_elem -> y:address:_elem [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 1");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void test_specialize_with_literal_4() {
-  Trace_file = "specialize_with_literal_4";
-  Hide_errors = true;
-  run("recipe main [\n  local-scope\n  # ambiguous call: what's the type of its ingredient?!\n  foo 0\n]\nrecipe foo x:address:_elem -> y:address:_elem [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("error: foo: failed to map a type to xerror: foo: failed to map a type to y");
-}
-void test_specialize_with_literal_5() {
-  Trace_file = "specialize_with_literal_5";
-  run("recipe main [\n  foo 3, 4  # recipe mapping two variables to literals\n]\nrecipe foo x:_elem, y:_elem [\n  local-scope\n  load-ingredients\n  1:number/raw <- add x, y\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 7 in location 1");
-}
-void test_multiple_shape_shifting_variants() {
-  Trace_file = "multiple_shape_shifting_variants";
-  run("# try to call two different shape-shifting recipes with the same name\nrecipe main [\n  e1:d1:number <- merge 3\n  e2:d2:number <- merge 4, 5\n  1:number/raw <- foo e1\n  2:number/raw <- foo e2\n]\n# the two shape-shifting definitions\nrecipe foo a:d1:_elem -> b:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo a:d2:_elem -> b:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# the shape-shifting containers they use\ncontainer d1:_elem [\n  x:_elem\n]\ncontainer d2:_elem [\n  x:number\n  y:_elem\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2");
-}
-void test_multiple_shape_shifting_variants_2() {
-  Trace_file = "multiple_shape_shifting_variants_2";
-  run("# static dispatch between shape-shifting variants, _including pointer lookups_\nrecipe main [\n  e1:d1:number <- merge 3\n  e2:address:shared:d2:number <- new {(d2 number): type}\n  1:number/raw <- foo e1\n  2:number/raw <- foo *e2  # different from previous scenario\n]\nrecipe foo a:d1:_elem -> b:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo a:d2:_elem -> b:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\ncontainer d1:_elem [\n  x:_elem\n]\ncontainer d2:_elem [\n  x:number\n  y:_elem\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1mem: storing 35 in location 2");
-}
-void test_missing_type_in_shape_shifting_recipe() {
-  Trace_file = "missing_type_in_shape_shifting_recipe";
-  Hide_errors = true;
-  run("recipe main [\n  a:d1:number <- merge 3\n  foo a\n]\nrecipe foo a:d1:_elem -> b:number [\n  local-scope\n  load-ingredients\n  copy e  # no such variable\n  reply 34\n]\ncontainer d1:_elem [\n  x:_elem\n]\n");
-  CHECK_TRACE_CONTENTS("error: foo: unknown type for e (check the name for typos)error: specializing foo: missing type for e");
-}
-void test_missing_type_in_shape_shifting_recipe_2() {
-  Trace_file = "missing_type_in_shape_shifting_recipe_2";
-  Hide_errors = true;
-  run("recipe main [\n  a:d1:number <- merge 3\n  foo a\n]\nrecipe foo a:d1:_elem -> b:number [\n  local-scope\n  load-ingredients\n  get e, x:offset  # unknown variable in a 'get', which does some extra checking\n  reply 34\n]\ncontainer d1:_elem [\n  x:_elem\n]\n");
-  CHECK_TRACE_CONTENTS("error: foo: unknown type for e (check the name for typos)error: specializing foo: missing type for e");
-}
-void test_specialize_recursive_shape_shifting_recipe() {
-  Trace_file = "specialize_recursive_shape_shifting_recipe";
-  transform("recipe main [\n  1:number <- copy 34\n  2:number <- foo 1:number\n]\nrecipe foo x:_elem -> y:number [\n  local-scope\n  load-ingredients\n  {\n    break\n    y:number <- foo x\n  }\n  reply y\n]\n");
-  CHECK_TRACE_CONTENTS("transform: new specialization: foo_2");
-}
-void test_specialize_most_similar_variant() {
-  Trace_file = "specialize_most_similar_variant";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  2:number <- foo 1:address:shared:number\n]\nrecipe foo x:_elem -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:address:shared:_elem -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 35 in location 2");
-}
-void test_specialize_most_similar_variant_2() {
-  Trace_file = "specialize_most_similar_variant_2";
-  run("# version with headers padded with lots of unrelated concrete types\nrecipe main [\n  1:number <- copy 23\n  2:address:shared:array:number <- copy 0\n  3:number <- foo 2:address:shared:array:number, 1:number\n]\n# variant with concrete type\nrecipe foo dummy:address:shared:array:number, x:number -> y:number, dummy:address:shared:array:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\n# shape-shifting variant\nrecipe foo dummy:address:shared:array:number, x:_elem -> y:number, dummy:address:shared:array:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# prefer the concrete variant\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 3");
-}
-void test_specialize_most_similar_variant_3() {
-  Trace_file = "specialize_most_similar_variant_3";
-  run("recipe main [\n  1:address:shared:array:character <- new [abc]\n  foo 1:address:shared:array:character\n]\nrecipe foo x:address:shared:array:character [\n  2:number <- copy 34\n]\nrecipe foo x:address:_elem [\n  2:number <- copy 35\n]\n# make sure the more precise version was used\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-}
-void test_specialize_literal_as_number() {
-  Trace_file = "specialize_literal_as_number";
-  run("recipe main [\n  1:number <- foo 23\n]\nrecipe foo x:_elem -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\nrecipe foo x:character -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-void test_specialize_literal_as_number_2() {
-  Trace_file = "specialize_literal_as_number_2";
-  run("# version calling with literal\nrecipe main [\n  1:number <- foo 0\n]\n# variant with concrete type\nrecipe foo x:number -> y:number [\n  local-scope\n  load-ingredients\n  reply 34\n]\n# shape-shifting variant\nrecipe foo x:address:shared:_elem -> y:number [\n  local-scope\n  load-ingredients\n  reply 35\n]\n# prefer the concrete variant, ignore concrete types in scoring the shape-shifting variant\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-
-void test_can_modify_value_ingredients() {
-  Trace_file = "can_modify_value_ingredients";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:point <- new point:type\n  foo *p\n]\nrecipe foo p:point [\n  local-scope\n  load-ingredients\n  x:address:number <- get-address p, x:offset\n  *x <- copy 34\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-void test_can_modify_ingredients_that_are_also_products() {
-  Trace_file = "can_modify_ingredients_that_are_also_products";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:point <- new point:type\n  p <- foo p\n]\nrecipe foo p:address:shared:point -> p:address:shared:point [\n  local-scope\n  load-ingredients\n  x:address:number <- get-address *p, x:offset\n  *x <- copy 34\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-void test_ignore_literal_ingredients_for_immutability_checks() {
-  Trace_file = "ignore_literal_ingredients_for_immutability_checks";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:d1 <- new d1:type\n  q:number <- foo p\n]\nrecipe foo p:address:shared:d1 -> q:number [\n  local-scope\n  load-ingredients\n  x:address:shared:d1 <- new d1:type\n  y:address:number <- get-address *x, p:offset  # ignore this 'p'\n  q <- copy 34\n]\ncontainer d1 [\n  p:number\n  q:number\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-void test_cannot_take_address_inside_immutable_ingredients() {
-  Trace_file = "cannot_take_address_inside_immutable_ingredients";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:point <- new point:type\n  foo p\n]\nrecipe foo p:address:shared:point [\n  local-scope\n  load-ingredients\n  x:address:number <- get-address *p, x:offset\n  *x <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("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");
-}
-void test_cannot_call_mutating_recipes_on_immutable_ingredients() {
-  Trace_file = "cannot_call_mutating_recipes_on_immutable_ingredients";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:point <- new point:type\n  foo p\n]\nrecipe foo p:address:shared:point [\n  local-scope\n  load-ingredients\n  bar p\n]\nrecipe bar p:address:shared:point -> p:address:shared:point [\n  local-scope\n  load-ingredients\n  x:address:number <- get-address *p, x:offset\n  *x <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("warn: foo: cannot modify ingredient p at instruction 'bar p' because it's not also a product of foo");
-}
-void test_cannot_modify_copies_of_immutable_ingredients() {
-  Trace_file = "cannot_modify_copies_of_immutable_ingredients";
-  Hide_warnings = true;
-  run("recipe main [\n  local-scope\n  p:address:shared:point <- new point:type\n  foo p\n]\nrecipe foo p:address:shared:point [\n  local-scope\n  load-ingredients\n  q:address:shared:point <- copy p\n  x:address:number <- get-address *q, x:offset\n]\n");
-  CHECK_TRACE_CONTENTS("warn: foo: cannot modify q after instruction 'x:address:number <- get-address *q, x:offset' because that would modify ingredient p which is not also a product of foo");
-}
-void test_can_traverse_immutable_ingredients() {
-  Trace_file = "can_traverse_immutable_ingredients";
-  Hide_warnings = true;
-  run("container test-list [\n  next:address:shared:test-list\n]\nrecipe main [\n  local-scope\n  p:address:shared:test-list <- new test-list:type\n  foo p\n]\nrecipe foo p:address:shared:test-list [\n  local-scope\n  load-ingredients\n  p2:address:shared:test-list <- bar p\n]\nrecipe bar x:address:shared:test-list -> y:address:shared:test-list [\n  local-scope\n  load-ingredients\n  y <- get *x, next:offset\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-void test_handle_optional_ingredients_in_immutability_checks() {
-  Trace_file = "handle_optional_ingredients_in_immutability_checks";
-  Hide_warnings = true;
-  run("recipe main [\n  k:address:shared:number <- new number:type\n  test k\n]\n# recipe taking an immutable address ingredient\nrecipe test k:address:shared:number [\n  local-scope\n  load-ingredients\n  foo k\n]\n# ..calling a recipe with an optional address ingredient\nrecipe foo -> [\n  local-scope\n  load-ingredients\n  k:address:shared:number, found?:boolean <- next-ingredient\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-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);
-  trace(9991, "transform") << "--- check mutability of ingredients in recipe " << caller.name << end();
-  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
-    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 " << to_string(property(current_ingredient, "contained-in")) << '\n' << end();
-      continue;
-    }
-
-    // End Immutable Ingredients Special-cases
-    set<string> immutable_vars;
-    immutable_vars.insert(current_ingredient.name);
-    for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-      const instruction& inst = caller.steps.at(i);
-      check_immutable_ingredient_in_instruction(inst, immutable_vars, current_ingredient.name, caller);
-      update_aliases(inst, immutable_vars);
-    }
-  }
-}
-
-void update_aliases(const instruction& inst, set<string>& current_ingredient_and_aliases) {
-  set<long long int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
-  if (!contains_key(Recipe, inst.operation)) {
-    // primitive recipe
-    if (inst.operation == COPY) {
-      for (set<long long int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
-        current_ingredient_and_aliases.insert(inst.products.at(*p).name);
-      }
-    }
-  }
-  else {
-    // defined recipe
-    set<long long int> contained_in_product_indices = scan_contained_in_product_indices(inst, current_ingredient_indices);
-    for (set<long long int>::iterator p = contained_in_product_indices.begin(); p != contained_in_product_indices.end(); ++p) {
-      if (*p < SIZE(inst.products))
-        current_ingredient_and_aliases.insert(inst.products.at(*p).name);
-    }
-  }
-}
-
-set<long long int> scan_contained_in_product_indices(const instruction& inst, set<long long int>& ingredient_indices) {
-  set<string> selected_ingredient_names;
-  const recipe& callee = get(Recipe, inst.operation);
-  for (set<long long int>::iterator p = ingredient_indices.begin(); p != ingredient_indices.end(); ++p) {
-    if (*p >= SIZE(callee.ingredients)) continue;  // optional immutable ingredient
-    selected_ingredient_names.insert(callee.ingredients.at(*p).name);
-  }
-  set<long long int> result;
-  for (long long int i = 0; i < SIZE(callee.products); ++i) {
-    const reagent& current_product = callee.products.at(i);
-    const string_tree* contained_in_name = property(current_product, "contained-in");
-    if (contained_in_name && selected_ingredient_names.find(contained_in_name->value) != selected_ingredient_names.end())
-      result.insert(i);
-  }
-  return result;
-}
-
-void test_immutability_infects_contained_in_variables() {
-  Trace_file = "immutability_infects_contained_in_variables";
-  Hide_warnings = true;
-  transform("container test-list [\n  next:address:shared:test-list\n]\nrecipe main [\n  local-scope\n  p:address:shared:test-list <- new test-list:type\n  foo p\n]\nrecipe foo p:address:shared:test-list [  # p is immutable\n  local-scope\n  load-ingredients\n  p2:address:shared:test-list <- test-next p  # p2 is immutable\n  p3:address:address:shared:test-list <- get-address *p2, next:offset  # signal modification of p2\n]\nrecipe test-next x:address:shared:test-list -> y:address:shared:test-list/contained-in:x [\n  local-scope\n  load-ingredients\n  y <- get *x, next:offset\n]\n");
-  CHECK_TRACE_CONTENTS("warn: foo: cannot modify p2 after instruction 'p3:address:address:shared:test-list <- get-address *p2, next:offset' because that would modify ingredient p which is not also a product of foo");
-}
-void check_immutable_ingredient_in_instruction(const instruction& inst, const set<string>& current_ingredient_and_aliases, const string& original_ingredient_name, const recipe& caller) {
-  set<long long int> current_ingredient_indices = ingredient_indices(inst, current_ingredient_and_aliases);
-  if (current_ingredient_indices.empty()) return;  // ingredient not found in call
-  for (set<long long int>::iterator p = current_ingredient_indices.begin(); p != current_ingredient_indices.end(); ++p) {
-    const long long int current_ingredient_index = *p;
-    reagent current_ingredient = inst.ingredients.at(current_ingredient_index);
-    canonize_type(current_ingredient);
-    const string& current_ingredient_name = current_ingredient.name;
-    if (!contains_key(Recipe, inst.operation)) {
-      // primitive recipe
-      if (inst.operation == GET_ADDRESS || inst.operation == INDEX_ADDRESS) {
-        if (current_ingredient_name == original_ingredient_name)
-          raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because it's not also a product of " << caller.name << '\n' << end();
-        else
-          raise << maybe(caller.name) << "cannot modify " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because that would modify ingredient " << original_ingredient_name << " which is 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)) {
-        if (current_ingredient_name == original_ingredient_name)
-          raise << maybe(caller.name) << "cannot modify ingredient " << current_ingredient_name << " at instruction '" << to_string(inst) << "' because it's not also a product of " << caller.name << '\n' << end();
-        else
-          raise << maybe(caller.name) << "cannot modify " << current_ingredient_name << " after instruction '" << to_string(inst) << "' because that would modify ingredient " << original_ingredient_name << " which is 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;
-  }
-  if (ingredient_index >= SIZE(callee.ingredients)) return false;  // optional immutable ingredient
-  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;
-}
-
-set<long long int> ingredient_indices(const instruction& inst, const set<string>& ingredient_names) {
-  set<long long int> result;
-  for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
-    if (is_literal(inst.ingredients.at(i))) continue;
-    if (ingredient_names.find(inst.ingredients.at(i).name) != ingredient_names.end())
-      result.insert(i);
-  }
-  return result;
-}
-
-
-void test_can_modify_contained_in_addresses() {
-  Trace_file = "can_modify_contained_in_addresses";
-  Hide_warnings = true;
-  transform("container test-list [\n  next:address:shared:test-list\n]\nrecipe main [\n  local-scope\n  p:address:shared:test-list <- new test-list:type\n  foo p\n]\nrecipe foo p:address:shared:test-list -> p:address:shared:test-list [\n  local-scope\n  load-ingredients\n  p2:address:shared:test-list <- test-next p\n  p <- test-remove p2, p\n]\nrecipe test-next x:address:shared:test-list -> y:address:shared:test-list [\n  local-scope\n  load-ingredients\n  y <- get *x, next:offset\n]\nrecipe test-remove x:address:shared:test-list/contained-in:from, from:address:shared:test-list -> from:address:shared:test-list [\n  local-scope\n  load-ingredients\n  x2:address:address:shared:test-list <- get-address *x, next:offset  # pretend modification\n]\n");
-  CHECK_TRACE_COUNT("warn", 0);
-}
-
-void test_call_literal_recipe() {
-  Trace_file = "call_literal_recipe";
-  run("recipe main [\n  1:number <- call f, 34\n]\nrecipe f x:number -> y:number [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-void test_call_variable() {
-  Trace_file = "call_variable";
-  run("recipe main [\n  {1: (recipe number -> number)} <- copy f\n  2:number <- call {1: (recipe number -> number)}, 34\n]\nrecipe f x:number -> y:number [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-}
-void test_call_check_literal_recipe() {
-  Trace_file = "call_check_literal_recipe";
-  Hide_errors = true;
-  run("recipe main [\n  1:number <- call f, 34\n]\nrecipe f x:boolean -> y:boolean [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '1:number <- call f, 34'error: main: product 0 has the wrong type at '1:number <- call f, 34'");
-}
-void test_call_check_variable_recipe() {
-  Trace_file = "call_check_variable_recipe";
-  Hide_errors = true;
-  run("recipe main [\n  {1: (recipe boolean -> boolean)} <- copy f\n  2:number <- call {1: (recipe boolean -> boolean)}, 34\n]\nrecipe f x:boolean -> y:boolean [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: ingredient 0 has the wrong type at '2:number <- call {1: (recipe boolean -> boolean)}, 34'error: main: product 0 has the wrong type at '2:number <- call {1: (recipe boolean -> boolean)}, 34'");
-}
-void check_indirect_calls_against_header(const recipe_ordinal r) {
-  trace(9991, "transform") << "--- type-check 'call' instructions inside recipe " << get(Recipe, r).name << end();
-  const recipe& caller = get(Recipe, r);
-  for (long long int i = 0; i < SIZE(caller.steps); ++i) {
-    const instruction& inst = caller.steps.at(i);
-    if (inst.operation != CALL) continue;
-    if (inst.ingredients.empty()) continue;  // error raised above
-    const reagent& callee = inst.ingredients.at(0);
-    if (!is_mu_recipe(callee)) continue;  // error raised above
-    const recipe callee_header = is_literal(callee) ? get(Recipe, callee.value) : from_reagent(inst.ingredients.at(0));
-    if (!callee_header.has_header) continue;
-    for (long int i = /*skip callee*/1; i < min(SIZE(inst.ingredients), SIZE(callee_header.ingredients)+/*skip callee*/1); ++i) {
-      if (!types_coercible(callee_header.ingredients.at(i-/*skip callee*/1), inst.ingredients.at(i)))
-        raise_error << maybe(caller.name) << "ingredient " << i-/*skip callee*/1 << " has the wrong type at '" << to_string(inst) << "'\n" << end();
-    }
-    for (long int i = 0; i < min(SIZE(inst.products), SIZE(callee_header.products)); ++i) {
-      if (is_dummy(inst.products.at(i))) continue;
-      if (!types_coercible(callee_header.products.at(i), inst.products.at(i)))
-        raise_error << maybe(caller.name) << "product " << i << " has the wrong type at '" << to_string(inst) << "'\n" << end();
-    }
-  }
-}
-
-recipe from_reagent(const reagent& r) {
-  assert(r.type->name == "recipe");
-  recipe result_header;  // will contain only ingredients and products, nothing else
-  result_header.has_header = true;
-  const type_tree* curr = r.type->right;
-  for (; curr; curr=curr->right) {
-    if (curr->name == "->") {
-      curr = curr->right;  // skip delimiter
-      break;
-    }
-    result_header.ingredients.push_back("recipe:"+curr->name);
-  }
-  for (; curr; curr=curr->right)
-    result_header.products.push_back("recipe:"+curr->name);
-  return result_header;
-}
-
-bool is_mu_recipe(reagent r) {
-  if (!r.type) return false;
-  if (r.type->name == "recipe") return true;
-  if (r.type->name == "recipe-literal") return true;
-  // End is_mu_recipe Cases
-  return false;
-}
-
-void test_copy_typecheck_recipe_variable() {
-  Trace_file = "copy_typecheck_recipe_variable";
-  Hide_errors = true;
-  run("recipe main [\n  3:number <- copy 34  # abc def\n  {1: (recipe number -> number)} <- copy f  # store literal in a matching variable\n  {2: (recipe boolean -> boolean)} <- copy {1: (recipe number -> number)}  # mismatch between recipe variables\n]\nrecipe f x:number -> y:number [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy {1: (recipe number -> number)} to {2: (recipe boolean -> boolean)}; types don't match");
-}
-void test_copy_typecheck_recipe_variable_2() {
-  Trace_file = "copy_typecheck_recipe_variable_2";
-  Hide_errors = true;
-  run("recipe main [\n  {1: (recipe number -> number)} <- copy f  # mismatch with a recipe literal\n]\nrecipe f x:boolean -> y:boolean [\n  local-scope\n  load-ingredients\n  y <- copy x\n]\n");
-  CHECK_TRACE_CONTENTS("error: main: can't copy f to {1: (recipe number -> number)}; types don't match");
-}
-
-void test_scheduler() {
-  Trace_file = "scheduler";
-  run("recipe f1 [\n  start-running f2\n  # wait for f2 to run\n  {\n    jump-unless 1:number, -1\n  }\n]\nrecipe f2 [\n  1:number <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("schedule: f1schedule: f2");
-}
-void run(routine* rr) {
-  Routines.push_back(rr);
-  Current_routine_index = 0, Current_routine = Routines.at(0);
-  while (!all_routines_done()) {
-    skip_to_next_routine();
-    assert(Current_routine);
-    assert(Current_routine->state == RUNNING);
-    trace(9990, "schedule") << current_routine_label() << end();
-    run_current_routine(Scheduling_interval);
-    // Scheduler State Transitions
-    if (Current_routine->completed())
-      Current_routine->state = COMPLETED;
-    if (Current_routine->limit >= 0) {
-      if (Current_routine->limit <= Scheduling_interval) {
-        trace(9999, "schedule") << "discontinuing routine " << Current_routine->id << end();
-        Current_routine->state = DISCONTINUED;
-        Current_routine->limit = 0;
-      }
-      else {
-        Current_routine->limit -= Scheduling_interval;
-      }
-    }
-
-    for (long long int i = 0; i < SIZE(Routines); ++i) {
-      if (Routines.at(i)->state != WAITING) continue;
-      if (Routines.at(i)->waiting_on_location &&
-          get_or_insert(Memory, Routines.at(i)->waiting_on_location) != Routines.at(i)->old_value_of_waiting_location) {
-        trace(9999, "schedule") << "waking up routine\n" << end();
-        Routines.at(i)->state = RUNNING;
-        Routines.at(i)->waiting_on_location = Routines.at(i)->old_value_of_waiting_location = 0;
-      }
-    }
-
-
-    // Wake up any routines waiting for other routines to go to sleep.
-    // Important: this must come after the scheduler loop above giving routines
-    // waiting for locations to change a chance to wake up.
-    for (long long int i = 0; i < SIZE(Routines); ++i) {
-      if (Routines.at(i)->state != WAITING) continue;
-      if (!Routines.at(i)->waiting_on_routine) continue;
-      long long int id = Routines.at(i)->waiting_on_routine;
-      assert(id != Routines.at(i)->id);  // routine can't wait on itself
-      for (long long int j = 0; j < SIZE(Routines); ++j) {
-        if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) {
-          trace(9999, "schedule") << "waking up routine " << Routines.at(i)->id << end();
-          Routines.at(i)->state = RUNNING;
-          Routines.at(i)->waiting_on_routine = 0;
-        }
-      }
-    }
-
-    // End Scheduler State Transitions
-
-    // Scheduler Cleanup
-    for (long long int i = 0; i < SIZE(Routines); ++i) {
-      if (Routines.at(i)->state == COMPLETED) continue;
-      if (Routines.at(i)->parent_index < 0) continue;  // root thread
-      if (has_completed_parent(i)) {
-        Routines.at(i)->state = COMPLETED;
-      }
-    }
-
-    // End Scheduler Cleanup
-  }
-}
-
-bool all_routines_done() {
-  for (long long int i = 0; i < SIZE(Routines); ++i) {
-    if (Routines.at(i)->state == RUNNING) {
-      return false;
-    }
-  }
-  return true;
-}
-
-// skip Current_routine_index past non-RUNNING routines
-void skip_to_next_routine() {
-  assert(!Routines.empty());
-  assert(Current_routine_index < SIZE(Routines));
-  for (long long int i = (Current_routine_index+1)%SIZE(Routines);  i != Current_routine_index;  i = (i+1)%SIZE(Routines)) {
-    if (Routines.at(i)->state == RUNNING) {
-      Current_routine_index = i;
-      Current_routine = Routines.at(i);
-      return;
-    }
-  }
-}
-
-string current_routine_label() {
-  ostringstream result;
-  const call_stack& calls = Current_routine->calls;
-  for (call_stack::const_iterator p = calls.begin(); p != calls.end(); ++p) {
-    if (p != calls.begin()) result << '/';
-    result << get(Recipe, p->running_recipe).name;
-  }
-  return result.str();
-}
-
-void test_scheduler_runs_single_routine() {
-  Trace_file = "scheduler_runs_single_routine";
-  Scheduling_interval = 1;
-  run("recipe f1 [\n  1:number <- copy 0\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("schedule: f1run: 1:number <- copy 0schedule: f1run: 2:number <- copy 0");
-}
-void test_scheduler_interleaves_routines() {
-  Trace_file = "scheduler_interleaves_routines";
-  Scheduling_interval = 1;
-  run("recipe f1 [\n  start-running f2\n  1:number <- copy 0\n  2:number <- copy 0\n]\nrecipe f2 [\n  3:number <- copy 0\n  4:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("schedule: f1run: start-running f2schedule: f2run: 3:number <- copy 0schedule: f1run: 1:number <- copy 0schedule: f2run: 4:number <- copy 0schedule: f1run: 2:number <- copy 0");
-}
-void test_start_running_takes_ingredients() {
-  Trace_file = "start_running_takes_ingredients";
-  run("recipe f1 [\n  start-running f2, 3\n  # wait for f2 to run\n  {\n    jump-unless 1:number, -1\n  }\n]\nrecipe f2 [\n  1:number <- next-ingredient\n  2:number <- add 1:number, 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 4 in location 2");
-}
-void test_start_running_returns_routine_id() {
-  Trace_file = "start_running_returns_routine_id";
-  run("recipe f1 [\n  1:number <- start-running f2\n]\nrecipe f2 [\n  12:number <- copy 44\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 2 in location 1");
-}
-void test_scheduler_skips_completed_routines() {
-  Trace_file = "scheduler_skips_completed_routines";
-  recipe_ordinal f1 = load("recipe f1 [\n1:number <- copy 0\n]\n").front();
-  recipe_ordinal f2 = load("recipe f2 [\n2:number <- copy 0\n]\n").front();
-  Routines.push_back(new routine(f1));  // f1 meant to run
-  Routines.push_back(new routine(f2));
-  Routines.back()->state = COMPLETED;  // f2 not meant to run
-  run("# must have at least one routine without escaping\nrecipe f3 [\n  3:number <- copy 0\n]\n# by interleaving '+' lines with '-' lines, we allow f1 and f3 to run in any order\n");
-  CHECK_TRACE_CONTENTS("schedule: f1mem: storing 0 in location 1");
-  CHECK_TRACE_DOESNT_CONTAIN("schedule: f2");
-  CHECK_TRACE_DOESNT_CONTAIN("mem: storing 0 in location 2");
-  run("");
-  CHECK_TRACE_CONTENTS("schedule: f3mem: storing 0 in location 3");
-}
-void test_scheduler_starts_at_middle_of_routines() {
-  Trace_file = "scheduler_starts_at_middle_of_routines";
-  Routines.push_back(new routine(COPY));
-  Routines.back()->state = COMPLETED;
-  run("recipe f1 [\n  1:number <- copy 0\n  2:number <- copy 0\n]\n");
-  CHECK_TRACE_CONTENTS("schedule: f1");
-  CHECK_TRACE_DOESNT_CONTAIN("run: idle");
-}
-void test_scheduler_terminates_routines_after_errors() {
-  Trace_file = "scheduler_terminates_routines_after_errors";
-  Hide_errors = true;
-  Scheduling_interval = 2;
-  run("recipe f1 [\n  start-running f2\n  1:number <- copy 0\n  2:number <- copy 0\n]\nrecipe f2 [\n  # divide by 0 twice\n  3:number <- divide-with-remainder 4, 0\n  4:number <- divide-with-remainder 4, 0\n]\n# f2 should stop after first divide by 0\n");
-  CHECK_TRACE_CONTENTS("error: f2: divide by zero in '3:number <- divide-with-remainder 4, 0'");
-  CHECK_TRACE_DOESNT_CONTAIN("error: f2: divide by zero in '4:number <- divide-with-remainder 4, 0'");
-}
-void test_scheduler_kills_orphans() {
-  Trace_file = "scheduler_kills_orphans";
-  run("recipe main [\n  start-running f1\n  # f1 never actually runs because its parent completes without waiting for it\n]\nrecipe f1 [\n  1:number <- copy 0\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("schedule: f1");
-}
-bool has_completed_parent(long long int routine_index) {
-  for (long long int j = routine_index; j >= 0; j = Routines.at(j)->parent_index) {
-    if (Routines.at(j)->state == COMPLETED)
-      return true;
-  }
-  return false;
-}
-
-
-void test_routine_state_test() {
-  Trace_file = "routine_state_test";
-  Scheduling_interval = 2;
-  run("recipe f1 [\n  1:number/child-id <- start-running f2\n  12:number <- copy 0  # race condition since we don't care about location 12\n  # thanks to Scheduling_interval, f2's one instruction runs in between here and completes\n  2:number/state <- routine-state 1:number/child-id\n]\nrecipe f2 [\n  12:number <- copy 0\n  # trying to run a second instruction marks routine as completed\n]\n# recipe f2 should be in state COMPLETED\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 2");
-}
-void test_routine_discontinues_past_limit() {
-  Trace_file = "routine_discontinues_past_limit";
-  Scheduling_interval = 2;
-  run("recipe f1 [\n  1:number/child-id <- start-running f2\n  limit-time 1:number/child-id, 10\n  # padding loop just to make sure f2 has time to completed\n  2:number <- copy 20\n  2:number <- subtract 2:number, 1\n  jump-if 2:number, -2:offset\n]\nrecipe f2 [\n  jump -1:offset  # run forever\n  $print [should never get here], 10/newline\n]\n# f2 terminates\n");
-  CHECK_TRACE_CONTENTS("schedule: discontinuing routine 2");
-}
-void test_new_concurrent() {
-  Trace_file = "new_concurrent";
-  run("recipe f1 [\n  start-running f2\n  1:address:shared:number/raw <- new number:type\n  # wait for f2 to complete\n  {\n    loop-unless 4:number/raw\n  }\n]\nrecipe f2 [\n  2:address:shared:number/raw <- new number:type\n  # hack: assumes scheduler implementation\n  3:boolean/raw <- equal 1:address:shared:number/raw, 2:address:shared:number/raw\n  # signal f2 complete\n  4:number/raw <- copy 1\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-
-void test_wait_for_location() {
-  Trace_file = "wait_for_location";
-  run("recipe f1 [\n  1:number <- copy 0\n  start-running f2\n  wait-for-location 1:number\n  # now wait for f2 to run and modify location 1 before using its value\n  2:number <- copy 1:number\n]\nrecipe f2 [\n  1:number <- copy 34\n]\n# if we got the synchronization wrong we'd be storing 0 in location 2\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 2");
-}
-void test_wait_for_routine() {
-  Trace_file = "wait_for_routine";
-  run("recipe f1 [\n  1:number <- copy 0\n  12:number/routine <- start-running f2\n  wait-for-routine 12:number/routine\n  # now wait for f2 to run and modify location 1 before using its value\n  3:number <- copy 1:number\n]\nrecipe f2 [\n  1:number <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("schedule: f1run: waiting for routine 2schedule: f2schedule: waking up routine 1schedule: f1mem: storing 34 in location 3");
-}
-long long int some_other_running_routine() {
-  for (long long int i = 0; i < SIZE(Routines); ++i) {
-    if (i == Current_routine_index) continue;
-    assert(Routines.at(i) != Current_routine);
-    assert(Routines.at(i)->id != Current_routine->id);
-    if (Routines.at(i)->state == RUNNING)
-      return Routines.at(i)->id;
-  }
-  return 0;
-}
-
-void test_round_to_nearest_integer() {
-  Trace_file = "round_to_nearest_integer";
-  run("recipe main [\n  1:number <- round 12.2\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 12 in location 1");
-}
-// A universal hash function that can handle objects of any type.
-//
-// The way it's currently implemented, two objects will have the same hash if
-// all their non-address fields (all the way down) expand to the same sequence
-// of scalar values. In particular, a container with all zero addresses hashes
-// to 0. Hopefully this won't be an issue because we are usually hashing
-// objects of a single type in any given hash table.
-//
-// Based on http://burtleburtle.net/bob/hash/hashfaq.html
-
-size_t hash(size_t h, reagent& r) {
-//?   cerr << debug_string(r) << '\n';
-  canonize(r);
-  if (is_mu_string(r))  // optimization
-    return hash_mu_string(h, r);
-  else if (is_mu_address(r))
-    return hash_mu_address(h, r);
-  else if (is_mu_scalar(r))
-    return hash_mu_scalar(h, r);
-  else if (is_mu_array(r))
-    return hash_mu_array(h, r);
-  else if (is_mu_container(r))
-    return hash_mu_container(h, r);
-  else if (is_mu_exclusive_container(r))
-    return hash_mu_exclusive_container(h, r);
-  assert(false);
-}
-
-size_t hash_mu_scalar(size_t h, const reagent& r) {
-  double input = is_literal(r) ? r.value : get_or_insert(Memory, r.value);
-  return hash_iter(h, static_cast<size_t>(input));
-}
-
-size_t hash_mu_address(size_t h, reagent& r) {
-  if (r.value == 0) return 0;
-  r.value = get_or_insert(Memory, r.value);
-  drop_from_type(r, "address");
-  if (r.type->name == "shared") {
-    ++r.value;
-    drop_from_type(r, "shared");
-  }
-  return hash(h, r);
-}
-
-size_t hash_mu_string(size_t h, const reagent& r) {
-  string input = read_mu_string(get_or_insert(Memory, r.value));
-  for (long long int i = 0; i < SIZE(input); ++i) {
-    h = hash_iter(h, static_cast<size_t>(input.at(i)));
-//?     cerr << i << ": " << h << '\n';
-  }
-  return h;
-}
-
-size_t hash_mu_array(size_t h, const reagent& r) {
-  long long int size = get_or_insert(Memory, r.value);
-  reagent elem = r;
-  delete elem.type;
-  elem.type = new type_tree(*array_element(r.type));
-  for (long long int i=0, address = r.value+1; i < size; ++i, address += size_of(elem)) {
-    reagent tmp = elem;
-    tmp.value = address;
-    h = hash(h, tmp);
-//?     cerr << i << " (" << address << "): " << h << '\n';
-  }
-  return h;
-}
-
-bool is_mu_container(const reagent& r) {
-  if (r.type->value == 0) return false;
-  type_info& info = get(Type, r.type->value);
-  return info.kind == CONTAINER;
-}
-
-size_t hash_mu_container(size_t h, const reagent& r) {
-  assert(r.type->value);
-  type_info& info = get(Type, r.type->value);
-  long long int address = r.value;
-  long long int offset = 0;
-  for (long long int i = 0; i < SIZE(info.elements); ++i) {
-    reagent element = element_type(r, i);
-    if (has_property(element, "ignore-for-hash")) continue;
-    element.set_value(address+offset);
-    h = hash(h, element);
-//?     cerr << i << ": " << h << '\n';
-    offset += size_of(info.elements.at(i).type);
-  }
-  return h;
-}
-
-bool is_mu_exclusive_container(const reagent& r) {
-  if (r.type->value == 0) return false;
-  type_info& info = get(Type, r.type->value);
-  return info.kind == EXCLUSIVE_CONTAINER;
-}
-
-size_t hash_mu_exclusive_container(size_t h, const reagent& r) {
-  assert(r.type->value);
-  long long int tag = get(Memory, r.value);
-  reagent variant = variant_type(r, tag);
-  // todo: move this warning to container definition time
-  if (has_property(variant, "ignore-for-hash"))
-    raise << get(Type, r.type->value).name << ": /ignore-for-hash won't work in exclusive containers\n";
-  variant.set_value(r.value + /*skip tag*/1);
-  h = hash(h, variant);
-  return h;
-}
-
-size_t hash_iter(size_t h, size_t input) {
-  h += input;
-  h += (h<<10);
-  h ^= (h>>6);
-
-  h += (h<<3);
-  h ^= (h>>11);
-  h += (h<<15);
-  return h;
-}
-
-void test_hash_container_checks_all_elements() {
-  Trace_file = "hash_container_checks_all_elements";
-  run("container foo [\n  x:number\n  y:character\n]\nrecipe main [\n  1:foo <- merge 34, 97/a\n  3:number <- hash 1:foo\n  reply-unless 3:number\n  4:foo <- merge 34, 98/a\n  6:number <- hash 4:foo\n  reply-unless 6:number\n  7:boolean <- equal 3:number, 6:number\n]\n# hash on containers includes all elements\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 7");
-}
-void test_hash_exclusive_container_checks_all_elements() {
-  Trace_file = "hash_exclusive_container_checks_all_elements";
-  run("exclusive-container foo [\n  x:bar\n  y:number\n]\ncontainer bar [\n  a:number\n  b:number\n]\nrecipe main [\n  1:foo <- merge 0/x, 34, 35\n  4:number <- hash 1:foo\n  reply-unless 4:number\n  5:foo <- merge 0/x, 34, 36\n  8:number <- hash 5:foo\n  reply-unless 8:number\n  9:boolean <- equal 4:number, 8:number\n]\n# hash on containers includes all elements\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 9");
-}
-void test_hash_can_ignore_container_elements() {
-  Trace_file = "hash_can_ignore_container_elements";
-  run("container foo [\n  x:number\n  y:character/ignore-for-hash\n]\nrecipe main [\n  1:foo <- merge 34, 97/a\n  3:number <- hash 1:foo\n  reply-unless 3:number\n  4:foo <- merge 34, 98/a\n  6:number <- hash 4:foo\n  reply-unless 6:number\n  7:boolean <- equal 3:number, 6:number\n]\n# hashes match even though y is different\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 7");
-}
-void test_hash_of_zero_address() {
-  Trace_file = "hash_of_zero_address";
-  run("recipe main [\n  1:address:number <- copy 0\n  2:number <- hash 1:address:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2");
-}
-void test_hash_of_numbers_ignores_fractional_part() {
-  Trace_file = "hash_of_numbers_ignores_fractional_part";
-  run("recipe main [\n  1:number <- hash 1.5\n  2:number <- hash 1\n  3:boolean <- equal 1:number, 2:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 3");
-}
-void test_hash_of_array_same_as_string() {
-  Trace_file = "hash_of_array_same_as_string";
-  run("recipe main [\n  10:number <- copy 3\n  11:number <- copy 97\n  12:number <- copy 98\n  13:number <- copy 99\n  2:number <- hash 10:array:number/unsafe\n  reply-unless 2:number\n  3:address:shared:array:character <- new [abc]\n  4:number <- hash 3:address:shared:array:character\n  reply-unless 4:number\n  5:boolean <- equal 2:number, 4:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 5");
-}
-void test_hash_ignores_address_value() {
-  Trace_file = "hash_ignores_address_value";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  *1:address:shared:number <- copy 34\n  2:number <- hash 1:address:shared:number\n  3:address:shared:number <- new number:type\n  *3:address:shared:number <- copy 34\n  4:number <- hash 3:address:shared:number\n  5:boolean <- equal 2:number, 4:number\n]\n# different addresses hash to the same result as long as the values the point to do so\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 5");
-}
-void test_hash_ignores_address_refcount() {
-  Trace_file = "hash_ignores_address_refcount";
-  run("recipe main [\n  1:address:shared:number <- new number:type\n  *1:address:shared:number <- copy 34\n  2:number <- hash 1:address:shared:number\n  reply-unless 2:number\n  # increment refcount\n  3:address:shared:number <- copy 1:address:shared:number\n  4:number <- hash 3:address:shared:number\n  reply-unless 4:number\n  5:boolean <- equal 2:number, 4:number\n]\n# hash doesn't change when refcount changes\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 5");
-}
-void test_hash_container_depends_only_on_elements() {
-  Trace_file = "hash_container_depends_only_on_elements";
-  run("container foo [\n  x:number\n  y:character\n]\ncontainer bar [\n  x:number\n  y:character\n]\nrecipe main [\n  1:foo <- merge 34, 97/a\n  3:number <- hash 1:foo\n  reply-unless 3:number\n  4:bar <- merge 34, 97/a\n  6:number <- hash 4:bar\n  reply-unless 6:number\n  7:boolean <- equal 3:number, 6:number\n]\n# containers with identical elements return identical hashes\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 7");
-}
-void test_hash_container_depends_only_on_elements_2() {
-  Trace_file = "hash_container_depends_only_on_elements_2";
-  run("container foo [\n  x:number\n  y:character\n  z:address:shared:number\n]\nrecipe main [\n  1:address:shared:number <- new number:type\n  *1:address:shared:number <- copy 34\n  2:foo <- merge 34, 97/a, 1:address:shared:number\n  5:number <- hash 2:foo\n  reply-unless 5:number\n  6:address:shared:number <- new number:type\n  *6:address:shared:number <- copy 34\n  7:foo <- merge 34, 97/a, 6:address:shared:number\n  10:number <- hash 7:foo\n  reply-unless 10:number\n  11:boolean <- equal 5:number, 10:number\n]\n# containers with identical 'leaf' elements return identical hashes\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 11");
-}
-void test_hash_container_depends_only_on_elements_3() {
-  Trace_file = "hash_container_depends_only_on_elements_3";
-  run("container foo [\n  x:number\n  y:character\n  z:bar\n]\ncontainer bar [\n  x:number\n  y:number\n]\nrecipe main [\n  1:foo <- merge 34, 97/a, 47, 48\n  6:number <- hash 1:foo\n  reply-unless 6:number\n  7:foo <- merge 34, 97/a, 47, 48\n  12:number <- hash 7:foo\n  reply-unless 12:number\n  13:boolean <- equal 6:number, 12:number\n]\n# containers with identical 'leaf' elements return identical hashes\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 13");
-}
-void test_hash_exclusive_container_ignores_tag() {
-  Trace_file = "hash_exclusive_container_ignores_tag";
-  run("exclusive-container foo [\n  x:bar\n  y:number\n]\ncontainer bar [\n  a:number\n  b:number\n]\nrecipe main [\n  1:foo <- merge 0/x, 34, 35\n  4:number <- hash 1:foo\n  reply-unless 4:number\n  5:bar <- merge 34, 35\n  7:number <- hash 5:bar\n  reply-unless 7:number\n  8:boolean <- equal 4:number, 7:number\n]\n# hash on containers includes all elements\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 8");
-}
-void test_hash_matches_old_version() {
-  Trace_file = "hash_matches_old_version";
-  run("recipe main [\n  1:address:shared:array:character <- new [abc]\n  2:number <- hash 1:address:shared:array:character\n  3:number <- hash_old 1:address:shared:array:character\n  4:number <- equal 2:number, 3:number\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 1 in location 4");
-}
-
-
-
-void test_convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations() {
-  Trace_file = "convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations";
-  Scenario_testing_scenario = true;
-  Hide_errors = true;
-  run("recipe main [\n  screen:number <- copy 1:number\n]\n");
-  CHECK_TRACE_DOESNT_CONTAIN("error: mixing variable names and numeric addresses in main");
-  CHECK_TRACE_COUNT("error", 0);
-}
-void check_screen(const string& expected_contents, const int color) {
-  assert(!current_call().default_space);  // not supported
-  long long int screen_location = get_or_insert(Memory, SCREEN)+/*skip refcount*/1;
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  long long int screen_data_location = screen_location+data_offset;  // type: address:shared:array:character
-  long long int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
-  raw_string_stream cursor(expected_contents);
-  // todo: too-long expected_contents should fail
-  long long int addr = screen_data_start+/*skip length*/1;
-  for (long long int row = 0; row < screen_height; ++row) {
-    cursor.skip_whitespace_and_comments();
-    if (cursor.at_end()) break;
-    assert(cursor.get() == '.');
-    for (long long int column = 0;  column < screen_width;  ++column, addr+= /*size of screen-cell*/2) {
-      const int cell_color_offset = 1;
-      uint32_t curr = cursor.get();
-      if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue;
-      if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) {
-        // filter out other colors
-        continue;
-      }
-      if (get_or_insert(Memory, addr) != 0 && get_or_insert(Memory, addr) == curr) {
-        if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue;
-        // contents match but color is off
-        if (Current_scenario && !Scenario_testing_scenario) {
-          // genuine test in a mu file
-          raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end();
-        }
-        else {
-          // just testing check_screen
-          raise_error << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << '\n' << end();
-        }
-        if (!Scenario_testing_scenario) {
-          Passed = false;
-          ++Num_failures;
-        }
-        return;
-      }
-
-      // really a mismatch
-      // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
-      char expected_pretty[10] = {0};
-      if (curr < 256 && !iscntrl(curr)) {
-        // " ('<curr>')"
-        expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
-      }
-      char actual_pretty[10] = {0};
-      if (get_or_insert(Memory, addr) < 256 && !iscntrl(get_or_insert(Memory, addr))) {
-        // " ('<curr>')"
-        actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
-      }
-
-      ostringstream color_phrase;
-      if (color != -1) color_phrase << " in color " << color;
-      if (Current_scenario && !Scenario_testing_scenario) {
-        // genuine test in a mu file
-        raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
-        dump_screen();
-      }
-      else {
-        // just testing check_screen
-        raise_error << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
-      }
-      if (!Scenario_testing_scenario) {
-        Passed = false;
-        ++Num_failures;
-      }
-      return;
-    }
-    assert(cursor.get() == '.');
-  }
-  cursor.skip_whitespace_and_comments();
-  assert(cursor.at_end());
-}
-
-raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {}
-
-bool raw_string_stream::at_end() const {
-  if (index >= max) return true;
-  if (tb_utf8_char_length(buf[index]) > max-index) {
-    raise_error << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
-    return true;
-  }
-  return false;
-}
-
-uint32_t raw_string_stream::get() {
-  assert(index < max);  // caller must check bounds before calling 'get'
-  uint32_t result = 0;
-  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
-  assert(length != TB_EOF);
-  index += length;
-  return result;
-}
-
-uint32_t raw_string_stream::peek() {
-  assert(index < max);  // caller must check bounds before calling 'get'
-  uint32_t result = 0;
-  int length = tb_utf8_char_to_unicode(&result, &buf[index]);
-  assert(length != TB_EOF);
-  return result;
-}
-
-void raw_string_stream::skip_whitespace_and_comments() {
-  while (!at_end()) {
-    if (isspace(peek())) get();
-    else if (peek() == '#') {
-      // skip comment
-      get();
-      while (peek() != '\n') get();  // implicitly also handles CRLF
-    }
-    else break;
-  }
-}
-
-void dump_screen() {
-  assert(!current_call().default_space);  // not supported
-  long long int screen_location = get_or_insert(Memory, SCREEN) + /*skip refcount*/1;
-  int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
-  long long int screen_width = get_or_insert(Memory, screen_location+width_offset);
-  int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
-  long long int screen_height = get_or_insert(Memory, screen_location+height_offset);
-  int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
-  assert(data_offset >= 0);
-  long long int screen_data_location = screen_location+data_offset;  // type: address:shared:array:character
-  long long int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
-  assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
-  long long int curr = screen_data_start+1;  // skip length
-  for (long long int row = 0; row < screen_height; ++row) {
-    cerr << '.';
-    for (long long int col = 0; col < screen_width; ++col) {
-      if (get_or_insert(Memory, curr))
-        cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
-      else
-        cerr << ' ';
-      curr += /*size of screen-cell*/2;
-    }
-    cerr << ".\n";
-  }
-}
-
-
-void initialize_key_names() {
-  Key["F1"] = TB_KEY_F1;
-  Key["F2"] = TB_KEY_F2;
-  Key["F3"] = TB_KEY_F3;
-  Key["F4"] = TB_KEY_F4;
-  Key["F5"] = TB_KEY_F5;
-  Key["F6"] = TB_KEY_F6;
-  Key["F7"] = TB_KEY_F7;
-  Key["F8"] = TB_KEY_F8;
-  Key["F9"] = TB_KEY_F9;
-  Key["F10"] = TB_KEY_F10;
-  Key["F11"] = TB_KEY_F11;
-  Key["F12"] = TB_KEY_F12;
-  Key["insert"] = TB_KEY_INSERT;
-  Key["delete"] = TB_KEY_DELETE;
-  Key["home"] = TB_KEY_HOME;
-  Key["end"] = TB_KEY_END;
-  Key["page-up"] = TB_KEY_PGUP;
-  Key["page-down"] = TB_KEY_PGDN;
-  Key["up-arrow"] = TB_KEY_ARROW_UP;
-  Key["down-arrow"] = TB_KEY_ARROW_DOWN;
-  Key["left-arrow"] = TB_KEY_ARROW_LEFT;
-  Key["right-arrow"] = TB_KEY_ARROW_RIGHT;
-  Key["ctrl-a"] = TB_KEY_CTRL_A;
-  Key["ctrl-b"] = TB_KEY_CTRL_B;
-  Key["ctrl-c"] = TB_KEY_CTRL_C;
-  Key["ctrl-d"] = TB_KEY_CTRL_D;
-  Key["ctrl-e"] = TB_KEY_CTRL_E;
-  Key["ctrl-f"] = TB_KEY_CTRL_F;
-  Key["ctrl-g"] = TB_KEY_CTRL_G;
-  Key["backspace"] = TB_KEY_BACKSPACE;
-  Key["ctrl-h"] = TB_KEY_CTRL_H;
-  Key["tab"] = TB_KEY_TAB;
-  Key["ctrl-i"] = TB_KEY_CTRL_I;
-  Key["ctrl-j"] = TB_KEY_CTRL_J;
-  Key["enter"] = TB_KEY_NEWLINE;  // ignore CR/LF distinction; there is only 'enter'
-  Key["ctrl-k"] = TB_KEY_CTRL_K;
-  Key["ctrl-l"] = TB_KEY_CTRL_L;
-  Key["ctrl-m"] = TB_KEY_CTRL_M;
-  Key["ctrl-n"] = TB_KEY_CTRL_N;
-  Key["ctrl-o"] = TB_KEY_CTRL_O;
-  Key["ctrl-p"] = TB_KEY_CTRL_P;
-  Key["ctrl-q"] = TB_KEY_CTRL_Q;
-  Key["ctrl-r"] = TB_KEY_CTRL_R;
-  Key["ctrl-s"] = TB_KEY_CTRL_S;
-  Key["ctrl-t"] = TB_KEY_CTRL_T;
-  Key["ctrl-u"] = TB_KEY_CTRL_U;
-  Key["ctrl-v"] = TB_KEY_CTRL_V;
-  Key["ctrl-w"] = TB_KEY_CTRL_W;
-  Key["ctrl-x"] = TB_KEY_CTRL_X;
-  Key["ctrl-y"] = TB_KEY_CTRL_Y;
-  Key["ctrl-z"] = TB_KEY_CTRL_Z;
-  Key["escape"] = TB_KEY_ESC;
-}
-
-long long int count_events(const recipe& r) {
-  long long int result = 0;
-  for (long long int i = 0; i < SIZE(r.steps); ++i) {
-    const instruction& curr = r.steps.at(i);
-    if (curr.name == "type")
-      result += unicode_length(curr.ingredients.at(0).name);
-    else
-      result++;
-  }
-  return result;
-}
-
-long long int size_of_event() {
-  // memoize result if already computed
-  static long long int result = 0;
-  if (result) return result;
-  type_tree* type = new type_tree("event", get(Type_ordinal, "event"));
-  result = size_of(type);
-  delete type;
-  return result;
-}
-
-long long int size_of_console() {
-  // memoize result if already computed
-  static long long int result = 0;
-  if (result) return result;
-  assert(get(Type_ordinal, "console"));
-  type_tree* type = new type_tree("console", get(Type_ordinal, "console"));
-  result = size_of(type)+/*refcount*/1;
-  delete type;
-  return result;
-}
-
-
-void start_trace_browser() {
-  if (!Trace_stream) return;
-  cerr << "computing min depth to display\n";
-  long long int min_depth = 9999;
-  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    trace_line& curr_line = Trace_stream->past_lines.at(i);
-    if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-  }
-  cerr << "min depth is " << min_depth << '\n';
-  cerr << "computing lines to display\n";
-  for (long long int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    if (Trace_stream->past_lines.at(i).depth == min_depth)
-      Visible.insert(i);
-  }
-  tb_init();
-  Display_row = Display_column = 0;
-  tb_event event;
-  Top_of_screen = 0;
-  refresh_screen_rows();
-  while (true) {
-    render();
-    do {
-      tb_poll_event(&event);
-    } while (event.type != TB_EVENT_KEY);
-    long long int key = event.key ? event.key : event.ch;
-    if (key == 'q' || key == 'Q') break;
-    if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
-      // move cursor one line down
-      if (Display_row < Last_printed_row) ++Display_row;
-    }
-    if (key == 'k' || key == TB_KEY_ARROW_UP) {
-      // move cursor one line up
-      if (Display_row > 0) --Display_row;
-    }
-    if (key == 'H') {
-      // move cursor to top of screen
-      Display_row = 0;
-    }
-    if (key == 'M') {
-      // move cursor to center of screen
-      Display_row = tb_height()/2;
-    }
-    if (key == 'L') {
-      // move cursor to bottom of screen
-      Display_row = tb_height()-1;
-    }
-    if (key == 'J' || key == TB_KEY_PGDN) {
-      // page-down
-      if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
-        Top_of_screen = get(Trace_index, tb_height()-1) + 1;
-        refresh_screen_rows();
-      }
-    }
-    if (key == 'K' || key == TB_KEY_PGUP) {
-      // page-up is more convoluted
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      if (Top_of_screen >= 0)
-        refresh_screen_rows();
-    }
-    if (key == 'G') {
-      // go to bottom of screen; largely like page-up, interestingly
-      Top_of_screen = SIZE(Trace_stream->past_lines)-1;
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      refresh_screen_rows();
-      // move cursor to bottom
-      Display_row = Last_printed_row;
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_CARRIAGE_RETURN) {
-      // expand lines under current by one level
-      assert(contains_key(Trace_index, Display_row));
-      long long int start_index = get(Trace_index, Display_row);
-      long long int index = 0;
-      // simultaneously compute end_index and min_depth
-      int min_depth = 9999;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (contains_key(Visible, index)) break;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
-        if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-      }
-      long long int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index; index < end_index; ++index) {
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth == min_depth) {
-          Visible.insert(index);
-        }
-      }
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
-      // collapse all lines under current
-      assert(contains_key(Trace_index, Display_row));
-      long long int start_index = get(Trace_index, Display_row);
-      long long int index = 0;
-      // end_index is the next line at a depth same as or lower than start_index
-      int initial_depth = Trace_stream->past_lines.at(start_index).depth;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (!contains_key(Visible, index)) continue;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth <= initial_depth) break;
-      }
-      long long int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index+1; index < end_index; ++index) {
-        Visible.erase(index);
-      }
-      refresh_screen_rows();
-    }
-  }
-  tb_shutdown();
-}
-
-// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
-void refresh_screen_rows() {
-  long long int screen_row = 0, index = 0;
-  Trace_index.clear();
-  for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
-    // skip lines without depth for now
-    while (!contains_key(Visible, index)) {
-      ++index;
-      if (index >= SIZE(Trace_stream->past_lines)) goto done;
-    }
-    assert(index < SIZE(Trace_stream->past_lines));
-    put(Trace_index, screen_row, index);
-  }
-done:;
-}
-
-void render() {
-  long long int screen_row = 0;
-  for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
-    if (!contains_key(Trace_index, screen_row)) break;
-    trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
-    ostringstream out;
-    out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
-    if (screen_row < tb_height()-1) {
-      long long int delta = lines_hidden(screen_row);
-      // home-brew escape sequence for red
-      if (delta > 999) out << "{";
-      out << " (" << delta << ")";
-      if (delta > 999) out << "}";
-    }
-    render_line(screen_row, out.str());
-  }
-  // clear rest of screen
-  Last_printed_row = screen_row-1;
-  for (; screen_row < tb_height(); ++screen_row) {
-    render_line(screen_row, "~");
-  }
-  // move cursor back to display row at the end
-  tb_set_cursor(0, Display_row);
-  tb_present();
-}
-
-long long int lines_hidden(long long int screen_row) {
-  assert(contains_key(Trace_index, screen_row));
-  if (!contains_key(Trace_index, screen_row+1))
-    return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
-  else
-    return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
-}
-
-void render_line(int screen_row, const string& s) {
-  long long int col = 0;
-  int color = TB_WHITE;
-  for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
-    char c = s.at(col);  // todo: unicode
-    if (c == '\n') c = ';';  // replace newlines with semi-colons
-    // escapes. hack: can't start a line with them.
-    if (c == '{') { color = /*red*/1; c = ' '; }
-    if (c == '}') { color = TB_WHITE; c = ' '; }
-    tb_change_cell(col, screen_row, c, color, TB_BLACK);
-  }
-  for (; col < tb_width(); ++col) {
-    tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
-  }
-}
-
-void load_trace(const char* filename) {
-  ifstream tin(filename);
-  if (!tin) {
-    cerr << "no such file: " << filename << '\n';
-    exit(1);
-  }
-  Trace_stream = new trace_stream;
-  while (has_data(tin)) {
-    tin >> std::noskipws;
-      skip_whitespace_but_not_newline(tin);
-      if (!isdigit(tin.peek())) {
-        string dummy;
-        getline(tin, dummy);
-        continue;
-      }
-    tin >> std::skipws;
-    int depth;
-    tin >> depth;
-    string label;
-    tin >> label;
-    if (*--label.end() == ':') label.erase(--label.end());
-    string line;
-    getline(tin, line);
-    Trace_stream->past_lines.push_back(trace_line(depth, label, line));
-  }
-  cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
-}
-
-
-void test_run_interactive_code() {
-  Trace_file = "run_interactive_code";
-  run("recipe main [\n  1:number/raw <- copy 0\n  2:address:shared:array:character <- new [1:number/raw <- copy 34]\n  run-interactive 2:address:shared:array:character\n  3:number/raw <- copy 1:number/raw\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 3");
-}
-void test_run_interactive_empty() {
-  Trace_file = "run_interactive_empty";
-  run("recipe main [\n  1:address:shared:array:character <- copy 0/unsafe\n  2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n]\n# result is null\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 2");
-}
-// reads a string, tries to call it as code (treating it as a test), saving
-// all warnings.
-// returns true if successfully called (no errors found during load and transform)
-bool run_interactive(long long int address) {
-  assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
-  // try to sandbox the run as best you can
-  // todo: test this
-  if (!Current_scenario) {
-    for (long long int i = 1; i < Reserved_for_tests; ++i)
-      Memory.erase(i);
-  }
-  string command = trim(strip_comments(read_mu_string(address)));
-  if (command.empty()) return false;
-  Name[get(Recipe_ordinal, "interactive")].clear();
-  run_code_begin(/*snapshot_recently_added_recipes*/true);
-  // don't kill the current routine on parse errors
-  routine* save_current_routine = Current_routine;
-  Current_routine = NULL;
-  // call run(string) but without the scheduling
-  load(string("recipe! interactive [\n") +
-          "local-scope\n" +
-          "screen:address:shared:screen <- next-ingredient\n" +
-          "$start-tracking-products\n" +
-          command + "\n" +
-          "$stop-tracking-products\n" +
-          "reply screen\n" +
-       "]\n");
-  transform_all();
-  Current_routine = save_current_routine;
-  if (trace_count("error") > 0) return false;
-  // now call 'sandbox' which will run 'interactive' in a separate routine,
-  // and wait for it
-  if (Save_trace_stream) {
-    ++Save_trace_stream->callstack_depth;
-    trace(9999, "trace") << "run-interactive: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
-    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
-  Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
-  return true;
-}
-
-void run_code_begin(bool snapshot_recently_added_recipes) {
-//?   cerr << "loading new trace\n";
-  // stuff to undo later, in run_code_end()
-  Hide_warnings = true;
-  Hide_errors = true;
-  Disable_redefine_warnings = true;
-  if (snapshot_recently_added_recipes) {
-    Save_recently_added_recipes = Recently_added_recipes;
-    Recently_added_recipes.clear();
-    Save_recently_added_shape_shifting_recipes = Recently_added_shape_shifting_recipes;
-    Recently_added_shape_shifting_recipes.clear();
-  }
-  Save_trace_stream = Trace_stream;
-  Save_trace_file = Trace_file;
-  Trace_file = "";
-  Trace_stream = new trace_stream;
-  Trace_stream->collect_depth = App_depth;
-}
-
-void run_code_end() {
-//?   cerr << "back to old trace\n";
-  Hide_warnings = false;
-  Hide_errors = false;
-  Disable_redefine_warnings = false;
-  delete Trace_stream;
-  Trace_stream = Save_trace_stream;
-  Save_trace_stream = NULL;
-  Trace_file = Save_trace_file;
-  Save_trace_file.clear();
-  Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
-  if (!Save_recently_added_recipes.empty()) {
-    clear_recently_added_recipes();
-    Recently_added_recipes = Save_recently_added_recipes;
-    Save_recently_added_recipes.clear();
-    Recently_added_shape_shifting_recipes = Save_recently_added_shape_shifting_recipes;
-    Save_recently_added_shape_shifting_recipes.clear();
-  }
-}
-
-void test_run_interactive_comments() {
-  Trace_file = "run_interactive_comments";
-  run("recipe main [\n  1:address:shared:array:character <- new [# ab\nadd 2, 2]\n  2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n  3:array:character <- copy *2:address:shared:array:character\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 52 in location 4");
-}
-void test_run_interactive_converts_result_to_text() {
-  Trace_file = "run_interactive_converts_result_to_text";
-  run("recipe main [\n  # try to interactively add 2 and 2\n  1:address:shared:array:character <- new [add 2, 2]\n  2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n  10:array:character <- copy 2:address:shared:array:character/lookup\n]\n# first letter in the output should be '4' in unicode\n");
-  CHECK_TRACE_CONTENTS("mem: storing 52 in location 11");
-}
-void test_run_interactive_returns_text() {
-  Trace_file = "run_interactive_returns_text";
-  run("recipe main [\n  # try to interactively add 2 and 2\n  1:address:shared:array:character <- new [\n    x:address:shared:array:character <- new [a]\n    y:address:shared:array:character <- new [b]\n    z:address:shared:array:character <- append x:address:shared:array:character, y:address:shared:array:character\n  ]\n  2:address:shared:array:character <- run-interactive 1:address:shared:array:character\n  10:array:character <- copy 2:address:shared:array:character/lookup\n]\n# output contains \"ab\"\n");
-  CHECK_TRACE_CONTENTS("mem: storing 97 in location 11mem: storing 98 in location 12");
-}
-void test_run_interactive_returns_errors() {
-  Trace_file = "run_interactive_returns_errors";
-  run("recipe main [\n  # run a command that generates an error\n  1:address:shared:array:character <- new [x:number <- copy 34\nget x:number, foo:offset]\n  2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character\n  10:array:character <- copy 3:address:shared:array:character/lookup\n]\n# error should be \"unknown element foo in container number\"\n");
-  CHECK_TRACE_CONTENTS("mem: storing 117 in location 11mem: storing 110 in location 12mem: storing 107 in location 13mem: storing 110 in location 14");
-}
-void test_run_interactive_with_comment() {
-  Trace_file = "run_interactive_with_comment";
-  run("recipe main [\n  # 2 instructions, with a comment after the first\n  1:address:shared:array:number <- new [a:number <- copy 0  # abc\nb:number <- copy 0\n]\n  2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character\n]\n# no errors\n");
-  CHECK_TRACE_CONTENTS("mem: storing 0 in location 3");
-}
-void test_run_interactive_cleans_up_any_created_specializations() {
-  // define a generic recipe
-  assert(!contains_key(Recipe_ordinal, "foo"));
-  load("recipe foo x:_elem -> n:number [\n"
-       "  reply 34\n"
-       "]\n");
-  assert(SIZE(Recently_added_recipes) == 1);  // foo
-  assert(variant_count("foo") == 1);
-  // run-interactive a call that specializes this recipe
-  run("recipe main [\n"
-       "  1:number/raw <- copy 0\n"
-       "  2:address:shared:array:character <- new [foo 1:number/raw]\n"
-       "  run-interactive 2:address:shared:array:character\n"
-       "]\n");
-  assert(SIZE(Recently_added_recipes) == 2);  // foo, main
-  // check that number of variants doesn't change
-  CHECK_EQ(variant_count("foo"), 1);
-}
-
-long long int variant_count(string recipe_name) {
-  if (!contains_key(Recipe_variants, recipe_name)) return 0;
-  return non_ghost_size(get(Recipe_variants, recipe_name));
-}
-
-void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
-  ostringstream out;
-  for (long long int i = 0; i < SIZE(products); ++i) {
-    // string
-    if (i < SIZE(instruction.products)) {
-      if (is_mu_string(instruction.products.at(i))) {
-        if (!scalar(products.at(i))) {
-          tb_shutdown();
-          cerr << read_mu_string(trace_error_warning_contents()) << '\n';
-          cerr << SIZE(products.at(i)) << ": ";
-          for (long long int j = 0; j < SIZE(products.at(i)); ++j)
-            cerr << no_scientific(products.at(i).at(j)) << ' ';
-          cerr << '\n';
-        }
-        assert(scalar(products.at(i)));
-        out << read_mu_string(products.at(i).at(0)) << '\n';
-        continue;
-      }
-      // End Record Product Special-cases
-    }
-    for (long long int j = 0; j < SIZE(products.at(i)); ++j)
-      out << no_scientific(products.at(i).at(j)) << ' ';
-    out << '\n';
-  }
-  Most_recent_products = out.str();
-}
-
-string strip_comments(string in) {
-  ostringstream result;
-  for (long long int i = 0; i < SIZE(in); ++i) {
-    if (in.at(i) != '#') {
-      result << in.at(i);
-    }
-    else {
-      while (i+1 < SIZE(in) && in.at(i+1) != '\n')
-        ++i;
-    }
-  }
-  return result.str();
-}
-
-long long int stringified_value_of_location(long long int address) {
-  // convert to string
-  ostringstream out;
-  out << no_scientific(get_or_insert(Memory, address));
-  return new_mu_string(out.str());
-}
-
-long long int trace_error_warning_contents() {
-  if (!Trace_stream) return 0;
-  ostringstream out;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (p->depth > Warning_depth) continue;
-    out << p->contents;
-    if (*--p->contents.end() != '\n') out << '\n';
-  }
-  string result = out.str();
-  if (result.empty()) return 0;
-  truncate(result);
-  return new_mu_string(result);
-}
-
-long long int trace_app_contents() {
-  if (!Trace_stream) return 0;
-  ostringstream out;
-  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
-    if (p->depth != App_depth) continue;
-    out << p->contents;
-    if (*--p->contents.end() != '\n') out << '\n';
-  }
-  string result = out.str();
-  if (result.empty()) return 0;
-  truncate(result);
-  return new_mu_string(result);
-}
-
-void truncate(string& x) {
-  if (SIZE(x) > 512) {
-    x.erase(512);
-    *x.rbegin() = '\n';
-    *++x.rbegin() = '.';
-    *++++x.rbegin() = '.';
-  }
-}
-
-
-void test_reload_continues_past_error() {
-  Trace_file = "reload_continues_past_error";
-  run("recipe main [\n  local-scope\n  x:address:shared:array:character <- new [recipe foo [\n  get 1234:number, foo:offset\n]]\n  reload x\n  1:number/raw <- copy 34\n]\n");
-  CHECK_TRACE_CONTENTS("mem: storing 34 in location 1");
-}
-void test_reload_cleans_up_any_created_specializations() {
-  // define a generic recipe and a call to it
-  assert(!contains_key(Recipe_ordinal, "foo"));
-  assert(variant_count("foo") == 0);
-  // a call that specializes this recipe
-  run("recipe main [\n"
-      "  local-scope\n"
-      "  x:address:shared:array:character <- new [recipe foo x:_elem -> n:number [\n"
-      "local-scope\n"
-      "load-ingredients\n"
-      "reply 34\n"
-      "]\n"
-      "recipe main2 [\n"
-      "local-scope\n"
-      "load-ingredients\n"
-      "x:number <- copy 34\n"
-      "foo x:number\n"
-      "]]\n"
-      "  reload x\n"
-      "]\n");
-  // check that number of variants includes specialization
-  assert(SIZE(Recently_added_recipes) == 4);  // foo, main, main2, foo specialization
-  CHECK_EQ(variant_count("foo"), 2);
-}
-
-
-string slurp(const string& filename) {
-  ostringstream result;
-  ifstream fin(filename.c_str());
-  fin.peek();
-  if (!fin) return result.str();  // don't bother checking errno
-  const int N = 1024;
-  char buf[N];
-  while (has_data(fin)) {
-    bzero(buf, N);
-    fin.read(buf, N-1);  // leave at least one null
-    result << buf;
-  }
-  fin.close();
-  return result.str();
-}
-
-bool exists(const string& filename) {
-  struct stat dummy;
-  return 0 == stat(filename.c_str(), &dummy);
-}
-
-//? :(before "End Transform All")
-//? check_type_pointers();
-//? 
-//? :(code)
-//? void check_type_pointers() {
-//?   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
-//?     if (any_type_ingredient_in_header(p->first)) continue;
-//?     const recipe& r = p->second;
-//?     for (long long int i = 0; i < SIZE(r.steps); ++i) {
-//?       const instruction& inst = r.steps.at(i);
-//?       for (long long int j = 0; j < SIZE(inst.ingredients); ++j) {
-//?         if (!inst.ingredients.at(j).type) {
-//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type\n" << end();
-//?           return;
-//?         }
-//?         if (!inst.ingredients.at(j).properties.at(0).second) {
-//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.ingredients.at(j).to_string() << " has no type name\n" << end();
-//?           return;
-//?         }
-//?       }
-//?       for (long long int j = 0; j < SIZE(inst.products); ++j) {
-//?         if (!inst.products.at(j).type) {
-//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type\n" << end();
-//?           return;
-//?         }
-//?         if (!inst.products.at(j).properties.at(0).second) {
-//?           raise_error << maybe(r.name) << " '" << inst.to_string() << "' -- " << inst.products.at(j).to_string() << " has no type name\n" << end();
-//?           return;
-//?         }
-//?       }
-//?     }
-//?   }
-//? }
-