about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-05-07 15:06:53 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-05-07 15:29:13 -0700
commit0487a30e7078861ed7de42bdb21b5c71fb9b54a1 (patch)
treef7ccc4040b510403da90477947c1cf07ea91b627
parent94fa5c95ad9c8beead183bb7c4b88c7c2c7ca6ec (diff)
downloadmu-0487a30e7078861ed7de42bdb21b5c71fb9b54a1.tar.gz
1298 - better ingredient/product handling
All primitives now always write to all their products. If a product is
not used that's fine, but if an instruction seems to expect too many
products mu will complain.

In the process, many primitives can operate on more than two ingredients
where it seems intuitive. You can add or divide more than two numbers
together, copy or negate multiple corresponding locations, etc.

There's one remaining bit of ugliness. Some instructions like
get/get-address, index/index-address, wait-for-location, these can
unnecessarily load values from memory when they don't need to.

Useful vim commands:
  %s/ingredients\[\([^\]]*\)\]/ingredients.at(\1)/gc
  %s/products\[\([^\]]*\)\]/products.at(\1)/gc
  .,$s/\[\(.\)]/.at(\1)/gc
-rw-r--r--020run.cc38
-rw-r--r--021arithmetic.cc139
-rw-r--r--022boolean.cc112
-rw-r--r--023jump.cc33
-rw-r--r--024compare.cc186
-rw-r--r--026assert.cc11
-rw-r--r--027debug.cc22
-rw-r--r--030container.cc82
-rw-r--r--031address.cc6
-rw-r--r--032array.cc47
-rw-r--r--033length.cc10
-rw-r--r--034exclusive_container.cc25
-rw-r--r--036call_ingredient.cc50
-rw-r--r--037call_reply.cc44
-rw-r--r--038scheduler.cc25
-rw-r--r--039wait.cc4
-rw-r--r--040brace.cc41
-rw-r--r--041name.cc82
-rw-r--r--042new.cc79
-rw-r--r--043space.cc4
-rw-r--r--044space_surround.cc6
-rw-r--r--045closure_name.cc28
-rw-r--r--046tangle.cc4
-rw-r--r--050scenario.cc38
-rw-r--r--060string.mu32
-rw-r--r--070display.cc53
-rw-r--r--071print.mu24
-rw-r--r--072scenario_screen.cc12
-rw-r--r--075scenario_keyboard.cc6
-rwxr-xr-xbuild_and_test_until2
-rw-r--r--channel.mu8
-rw-r--r--counters.mu6
-rw-r--r--factorial.mu4
-rw-r--r--fork.mu6
-rw-r--r--tangle.mu4
35 files changed, 687 insertions, 586 deletions
diff --git a/020run.cc b/020run.cc
index d8fdcf55..7dadff6d 100644
--- a/020run.cc
+++ b/020run.cc
@@ -27,6 +27,15 @@ recipe main [
 +mem: location 1 is 23
 +mem: storing 23 in location 2
 
+:(scenario copy_multiple)
+recipe main [
+  1:integer, 2:integer <- copy 23:literal, 24:literal
+]
++run: ingredient 0 is 23
++run: ingredient 1 is 24
++mem: storing 23 in location 1
++mem: storing 24 in location 2
+
 :(before "End Types")
 // Book-keeping while running a recipe.
 //: Later layers will change this.
@@ -55,14 +64,20 @@ void run_current_routine()
     if (current_instruction().is_label) { ++current_step_index(); continue; }
     trace("run") << "instruction " << current_recipe_name() << '/' << current_step_index();
     trace("run") << current_instruction().to_string();
-//?     trace("run") << Memory[1033] << '\n'; //? 1
-//?     cout << "operation " << current_instruction().operation << '\n'; //? 3
+    // Read all ingredients.
+    vector<vector<long long int> > ingredients;
+    for (index_t i = 0; i < current_instruction().ingredients.size(); ++i) {
+      trace("run") << "ingredient " << i << " is " << current_instruction().ingredients.at(i).name;
+      ingredients.push_back(read_memory(current_instruction().ingredients.at(i)));
+    }
+    // Instructions below will write to 'products' or to 'instruction_counter'.
+    vector<vector<long long int> > products;
+    index_t instruction_counter = current_step_index();
+//?     cout << "AAA: " << current_instruction().to_string() << '\n'; //? 1
     switch (current_instruction().operation) {
       // Primitive Recipe Implementations
       case COPY: {
-        trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-        vector<long long int> data = read_memory(current_instruction().ingredients[0]);
-        write_memory(current_instruction().products[0], data);
+        copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
         break;
       }
       // End Primitive Recipe Implementations
@@ -70,7 +85,16 @@ void run_current_routine()
         cout << "not a primitive op: " << current_instruction().operation << '\n';
       }
     }
-    ++current_step_index();
+//?     cout << "BBB: " << current_instruction().to_string() << '\n'; //? 1
+    if (products.size() < current_instruction().products.size())
+      raise << "failed to write to all products! " << current_instruction().to_string();
+//?     cout << "CCC: " << current_instruction().to_string() << '\n'; //? 1
+    for (index_t i = 0; i < current_instruction().products.size(); ++i) {
+      trace("run") << "product " << i << " is " << current_instruction().products.at(i).name;
+      write_memory(current_instruction().products.at(i), products.at(i));
+    }
+//?     cout << "DDD: " << current_instruction().to_string() << '\n'; //? 1
+    current_step_index() = instruction_counter+1;
   }
 }
 
@@ -146,7 +170,7 @@ void run(string form) {
 //:: Reading from memory, writing to memory.
 
 vector<long long int> read_memory(reagent x) {
-//?   cout << "read_memory: " << x.to_string() << '\n'; //? 1
+//?   cout << "read_memory: " << x.to_string() << '\n'; //? 2
   vector<long long int> result;
   if (isa_literal(x)) {
     result.push_back(x.value);
diff --git a/021arithmetic.cc b/021arithmetic.cc
index e9496248..307bcb18 100644
--- a/021arithmetic.cc
+++ b/021arithmetic.cc
@@ -6,16 +6,13 @@ ADD,
 Recipe_number["add"] = ADD;
 :(before "End Primitive Recipe Implementations")
 case ADD: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] + arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  long long int result = 0;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+    result += ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
@@ -26,7 +23,7 @@ recipe main [
 +run: instruction main/0
 +run: ingredient 0 is 23
 +run: ingredient 1 is 34
-+run: product 0 is 57
++run: product 0 is 1
 +mem: storing 57 in location 1
 
 :(scenario add)
@@ -40,25 +37,29 @@ recipe main [
 +mem: location 1 is 23
 +run: ingredient 1 is 2
 +mem: location 2 is 34
-+run: product 0 is 57
++run: product 0 is 3
 +mem: storing 57 in location 3
 
+:(scenario add_multiple)
+recipe main [
+  1:integer <- add 3:literal, 4:literal, 5:literal
+]
++mem: storing 12 in location 1
+
 :(before "End Primitive Recipe Declarations")
 SUBTRACT,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["subtract"] = SUBTRACT;
 :(before "End Primitive Recipe Implementations")
 case SUBTRACT: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] - arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  assert(ingredients.at(0).size() == 1);  // scalar
+  long long int result = ingredients.at(0).at(0);
+  for (index_t i = 1; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+    result -= ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
@@ -69,7 +70,7 @@ recipe main [
 +run: instruction main/0
 +run: ingredient 0 is 5
 +run: ingredient 1 is 2
-+run: product 0 is 3
++run: product 0 is 1
 +mem: storing 3 in location 1
 
 :(scenario subtract)
@@ -83,26 +84,28 @@ recipe main [
 +mem: location 1 is 23
 +run: ingredient 1 is 2
 +mem: location 2 is 34
-+run: product 0 is -11
++run: product 0 is 3
 +mem: storing -11 in location 3
 
+:(scenario subtract_multiple)
+recipe main [
+  1:integer <- subtract 6:literal, 3:literal, 2:literal
+]
++mem: storing 1 in location 1
+
 :(before "End Primitive Recipe Declarations")
 MULTIPLY,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["multiply"] = MULTIPLY;
 :(before "End Primitive Recipe Implementations")
 case MULTIPLY: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  trace("run") << "ingredient 1 is " << arg1[0];
-  vector<long long int> result;
-  result.push_back(arg0[0] * arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  long long int result = 1;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+    result *= ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
@@ -113,7 +116,7 @@ recipe main [
 +run: instruction main/0
 +run: ingredient 0 is 2
 +run: ingredient 1 is 3
-+run: product 0 is 6
++run: product 0 is 1
 +mem: storing 6 in location 1
 
 :(scenario multiply)
@@ -127,26 +130,29 @@ recipe main [
 +mem: location 1 is 4
 +run: ingredient 1 is 2
 +mem: location 2 is 6
-+run: product 0 is 24
++run: product 0 is 3
 +mem: storing 24 in location 3
 
+:(scenario multiply_multiple)
+recipe main [
+  1:integer <- multiply 2:literal, 3:literal, 4:literal
+]
++mem: storing 24 in location 1
+
 :(before "End Primitive Recipe Declarations")
 DIVIDE,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["divide"] = DIVIDE;
 :(before "End Primitive Recipe Implementations")
 case DIVIDE: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  trace("run") << "ingredient 1 is " << arg1[0];
-  vector<long long int> result;
-  result.push_back(arg0[0] / arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  assert(ingredients.at(0).size() == 1);  // scalar
+  long long int result = ingredients.at(0).at(0);
+  for (index_t i = 1; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+    result /= ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
@@ -157,7 +163,7 @@ recipe main [
 +run: instruction main/0
 +run: ingredient 0 is 8
 +run: ingredient 1 is 2
-+run: product 0 is 4
++run: product 0 is 1
 +mem: storing 4 in location 1
 
 :(scenario divide)
@@ -171,29 +177,26 @@ recipe main [
 +mem: location 1 is 27
 +run: ingredient 1 is 2
 +mem: location 2 is 3
-+run: product 0 is 9
++run: product 0 is 3
 +mem: storing 9 in location 3
 
+:(scenario divide_multiple)
+recipe main [
+  1:integer <- divide 12:literal, 3:literal, 2:literal
+]
++mem: storing 2 in location 1
+
 :(before "End Primitive Recipe Declarations")
 DIVIDE_WITH_REMAINDER,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["divide-with-remainder"] = DIVIDE_WITH_REMAINDER;
 :(before "End Primitive Recipe Implementations")
 case DIVIDE_WITH_REMAINDER: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result0;
-  result0.push_back(arg0[0] / arg1[0]);
-  trace("run") << "product 0 is " << result0[0];
-  write_memory(current_instruction().products[0], result0);
-  vector<long long int> result1;
-  result1.push_back(arg0[0] % arg1[0]);
-  trace("run") << "product 1 is " << result1[0];
-  write_memory(current_instruction().products[1], result1);
+  long long int quotient = ingredients.at(0).at(0) / ingredients.at(1).at(0);
+  long long int remainder = ingredients.at(0).at(0) % ingredients.at(1).at(0);
+  products.resize(2);
+  products.at(0).push_back(quotient);
+  products.at(1).push_back(remainder);
   break;
 }
 
@@ -204,9 +207,9 @@ recipe main [
 +run: instruction main/0
 +run: ingredient 0 is 9
 +run: ingredient 1 is 2
-+run: product 0 is 4
++run: product 0 is 1
 +mem: storing 4 in location 1
-+run: product 1 is 1
++run: product 1 is 2
 +mem: storing 1 in location 2
 
 :(scenario divide_with_remainder)
@@ -220,7 +223,7 @@ recipe main [
 +mem: location 1 is 27
 +run: ingredient 1 is 2
 +mem: location 2 is 11
-+run: product 0 is 2
++run: product 0 is 3
 +mem: storing 2 in location 3
-+run: product 1 is 5
++run: product 1 is 4
 +mem: storing 5 in location 4
diff --git a/022boolean.cc b/022boolean.cc
index 013519fd..e91dc209 100644
--- a/022boolean.cc
+++ b/022boolean.cc
@@ -6,89 +6,125 @@ AND,
 Recipe_number["and"] = AND;
 :(before "End Primitive Recipe Implementations")
 case AND: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] && arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = true;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // must be a scalar
+    result = result && ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
 :(scenario and)
 recipe main [
-  1:integer <- copy 1:literal
-  2:integer <- copy 0:literal
-  3:integer <- and 1:integer, 2:integer
+  1:boolean <- copy 1:literal
+  2:boolean <- copy 0:literal
+  3:boolean <- and 1:boolean, 2:boolean
 ]
 +run: instruction main/2
 +run: ingredient 0 is 1
 +mem: location 1 is 1
 +run: ingredient 1 is 2
 +mem: location 2 is 0
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
 
+:(scenario and2)
+recipe main [
+  1:boolean <- and 1:literal, 1:literal
+]
++mem: storing 1 in location 1
+
+:(scenario and_multiple)
+recipe main [
+  1:boolean <- and 1:literal, 1:literal, 0:literal
+]
++mem: storing 0 in location 1
+
+:(scenario and_multiple2)
+recipe main [
+  1:boolean <- and 1:literal, 1:literal, 1:literal
+]
++mem: storing 1 in location 1
+
 :(before "End Primitive Recipe Declarations")
 OR,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["or"] = OR;
 :(before "End Primitive Recipe Implementations")
 case OR: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] || arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = false;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // must be a scalar
+    result = result || ingredients.at(i).at(0);
+  }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
 
 :(scenario or)
 recipe main [
-  1:integer <- copy 1:literal
-  2:integer <- copy 0:literal
-  3:integer <- or 1:integer, 2:integer
+  1:boolean <- copy 1:literal
+  2:boolean <- copy 0:literal
+  3:boolean <- or 1:boolean, 2:boolean
 ]
 +run: instruction main/2
 +run: ingredient 0 is 1
 +mem: location 1 is 1
 +run: ingredient 1 is 2
 +mem: location 2 is 0
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
+:(scenario or2)
+recipe main [
+  1:boolean <- or 0:literal, 0:literal
+]
++mem: storing 0 in location 1
+
+:(scenario or_multiple)
+recipe main [
+  1:boolean <- and 0:literal, 0:literal, 0:literal
+]
++mem: storing 0 in location 1
+
+:(scenario or_multiple2)
+recipe main [
+  1:boolean <- or 0:literal, 0:literal, 1:literal
+]
++mem: storing 1 in location 1
+
 :(before "End Primitive Recipe Declarations")
 NOT,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["not"] = NOT;
 :(before "End Primitive Recipe Implementations")
 case NOT: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  vector<long long int> result;
-  result.push_back(!arg0[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  products.resize(ingredients.size());
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // must be a scalar
+    products.at(i).push_back(!ingredients.at(i).at(0));
+  }
   break;
 }
 
 :(scenario not)
 recipe main [
-  1:integer <- copy 1:literal
-  2:integer <- not 1:integer
+  1:boolean <- copy 1:literal
+  2:boolean <- not 1:boolean
 ]
 +run: instruction main/1
 +run: ingredient 0 is 1
 +mem: location 1 is 1
-+run: product 0 is 0
++run: product 0 is 2
 +mem: storing 0 in location 2
+
+:(scenario not_multiple)
+recipe main [
+  1:boolean, 2:boolean, 3:boolean <- not 1:literal, 0:literal, 1:literal
+]
++mem: storing 0 in location 1
++mem: storing 1 in location 2
++mem: storing 0 in location 3
diff --git a/023jump.cc b/023jump.cc
index 7e649ddd..e9bd5011 100644
--- a/023jump.cc
+++ b/023jump.cc
@@ -6,9 +6,10 @@ JUMP,
 Recipe_number["jump"] = JUMP;
 :(before "End Primitive Recipe Implementations")
 case JUMP: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].value;
-  current_step_index() += current_instruction().ingredients[0].value;
-  trace("run") << "jumping to instruction " << current_step_index()+1;
+  assert(ingredients.size() == 1);
+  assert(ingredients.at(0).size() == 1);  // scalar
+  instruction_counter += ingredients.at(0).at(0);
+  trace("run") << "jumping to instruction " << instruction_counter+1;
   break;
 }
 
@@ -39,16 +40,15 @@ JUMP_IF,
 Recipe_number["jump-if"] = JUMP_IF;
 :(before "End Primitive Recipe Implementations")
 case JUMP_IF: {
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 0 is " << arg0[0];
-  if (!arg0[0]) {
+  assert(ingredients.size() == 2);
+  assert(ingredients.at(0).size() == 1);  // scalar
+  if (!ingredients.at(0).at(0)) {
     trace("run") << "jump-if fell through";
     break;
   }
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  current_step_index() += current_instruction().ingredients[1].value;
-  trace("run") << "jumping to instruction " << current_step_index()+1;
+  assert(ingredients.at(1).size() == 1);  // scalar
+  instruction_counter += ingredients.at(1).at(0);
+  trace("run") << "jumping to instruction " << instruction_counter+1;
   break;
 }
 
@@ -79,16 +79,15 @@ JUMP_UNLESS,
 Recipe_number["jump-unless"] = JUMP_UNLESS;
 :(before "End Primitive Recipe Implementations")
 case JUMP_UNLESS: {
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 0 is " << arg0[0];
-  if (arg0[0]) {
+  assert(ingredients.size() == 2);
+  assert(ingredients.at(0).size() == 1);  // scalar
+  if (ingredients.at(0).at(0)) {
     trace("run") << "jump-unless fell through";
     break;
   }
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  current_step_index() += current_instruction().ingredients[1].value;
-  trace("run") << "jumping to instruction " << current_step_index()+1;
+  assert(ingredients.at(1).size() == 1);  // scalar
+  instruction_counter += ingredients.at(1).at(0);
+  trace("run") << "jumping to instruction " << instruction_counter+1;
   break;
 }
 
diff --git a/024compare.cc b/024compare.cc
index 91cea1e6..92d87e5c 100644
--- a/024compare.cc
+++ b/024compare.cc
@@ -6,14 +6,16 @@ EQUAL,
 Recipe_number["equal"] = EQUAL;
 :(before "End Primitive Recipe Implementations")
 case EQUAL: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  vector<long long int> result;
-  result.push_back(equal(arg0.begin(), arg0.end(), arg1.begin()));
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  vector<long long int>& exemplar = ingredients.at(0);
+  bool result = true;
+  for (index_t i = 1; i < ingredients.size(); ++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;
 }
 
@@ -28,7 +30,7 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
 
 :(scenario equal2)
@@ -42,25 +44,38 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 34
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
+:(scenario equal_multiple)
+recipe main [
+  1:integer <- equal 34:literal, 34:literal, 34:literal
+]
++mem: storing 1 in location 1
+
+:(scenario equal_multiple2)
+recipe main [
+  1:integer <- equal 34:literal, 34:literal, 35:literal
+]
++mem: storing 0 in location 1
+
 :(before "End Primitive Recipe Declarations")
 GREATER_THAN,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["greater-than"] = GREATER_THAN;
 :(before "End Primitive Recipe Implementations")
 case GREATER_THAN: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] > arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = true;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+  }
+  for (index_t i = /**/1; i < ingredients.size(); ++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;
 }
 
@@ -75,7 +90,7 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario greater_than2)
@@ -89,25 +104,38 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 34
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
 
+:(scenario greater_than_multiple)
+recipe main [
+  1:integer <- greater-than 36:literal, 35:literal, 34:literal
+]
++mem: storing 1 in location 1
+
+:(scenario greater_than_multiple2)
+recipe main [
+  1:integer <- greater-than 36:literal, 35:literal, 35:literal
+]
++mem: storing 0 in location 1
+
 :(before "End Primitive Recipe Declarations")
 LESSER_THAN,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["lesser-than"] = LESSER_THAN;
 :(before "End Primitive Recipe Implementations")
 case LESSER_THAN: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] < arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = true;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+  }
+  for (index_t i = /**/1; i < ingredients.size(); ++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;
 }
 
@@ -122,7 +150,7 @@ recipe main [
 +mem: location 1 is 32
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario lesser_than2)
@@ -136,25 +164,38 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
 
+:(scenario lesser_than_multiple)
+recipe main [
+  1:integer <- lesser-than 34:literal, 35:literal, 36:literal
+]
++mem: storing 1 in location 1
+
+:(scenario lesser_than_multiple2)
+recipe main [
+  1:integer <- lesser-than 34:literal, 35:literal, 35:literal
+]
++mem: storing 0 in location 1
+
 :(before "End Primitive Recipe Declarations")
 GREATER_OR_EQUAL,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["greater-or-equal"] = GREATER_OR_EQUAL;
 :(before "End Primitive Recipe Implementations")
 case GREATER_OR_EQUAL: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] >= arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = true;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+  }
+  for (index_t i = /**/1; i < ingredients.size(); ++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;
 }
 
@@ -169,7 +210,7 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario greater_or_equal2)
@@ -183,7 +224,7 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 34
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario greater_or_equal3)
@@ -197,25 +238,38 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 35
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
 
+:(scenario greater_or_equal_multiple)
+recipe main [
+  1:integer <- greater-or-equal 36:literal, 35:literal, 35:literal
+]
++mem: storing 1 in location 1
+
+:(scenario greater_or_equal_multiple2)
+recipe main [
+  1:integer <- greater-or-equal 36:literal, 35:literal, 36:literal
+]
++mem: storing 0 in location 1
+
 :(before "End Primitive Recipe Declarations")
 LESSER_OR_EQUAL,
 :(before "End Primitive Recipe Numbers")
 Recipe_number["lesser-or-equal"] = LESSER_OR_EQUAL;
 :(before "End Primitive Recipe Implementations")
 case LESSER_OR_EQUAL: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  vector<long long int> arg1 = read_memory(current_instruction().ingredients[1]);
-  assert(arg1.size() == 1);
-  vector<long long int> result;
-  result.push_back(arg0[0] <= arg1[0]);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  bool result = true;
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    assert(ingredients.at(i).size() == 1);  // scalar
+  }
+  for (index_t i = /**/1; i < ingredients.size(); ++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;
 }
 
@@ -230,7 +284,7 @@ recipe main [
 +mem: location 1 is 32
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario lesser_or_equal2)
@@ -244,7 +298,7 @@ recipe main [
 +mem: location 1 is 33
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 1
++run: product 0 is 3
 +mem: storing 1 in location 3
 
 :(scenario lesser_or_equal3)
@@ -258,5 +312,17 @@ recipe main [
 +mem: location 1 is 34
 +run: ingredient 1 is 2
 +mem: location 2 is 33
-+run: product 0 is 0
++run: product 0 is 3
 +mem: storing 0 in location 3
+
+:(scenario lesser_or_equal_multiple)
+recipe main [
+  1:integer <- lesser-or-equal 34:literal, 35:literal, 35:literal
+]
++mem: storing 1 in location 1
+
+:(scenario lesser_or_equal_multiple2)
+recipe main [
+  1:integer <- lesser-or-equal 34:literal, 35:literal, 34:literal
+]
++mem: storing 0 in location 1
diff --git a/026assert.cc b/026assert.cc
index 6ab49db2..301aec58 100644
--- a/026assert.cc
+++ b/026assert.cc
@@ -11,10 +11,11 @@ ASSERT,
 Recipe_number["assert"] = ASSERT;
 :(before "End Primitive Recipe Implementations")
 case ASSERT: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  vector<long long int> arg0 = read_memory(current_instruction().ingredients[0]);
-  assert(arg0.size() == 1);
-  if (arg0[0] == 0)
-    raise << current_instruction().ingredients[1].name << '\n';
+  assert(ingredients.size() == 2);
+  assert(ingredients.at(0).size() == 1);
+  if (!ingredients.at(0).at(0)) {
+    assert(isa_literal(current_instruction().ingredients.at(1)));
+    raise << current_instruction().ingredients.at(1).name << '\n';
+  }
   break;
 }
diff --git a/027debug.cc b/027debug.cc
index 27ac8ce6..c4d39104 100644
--- a/027debug.cc
+++ b/027debug.cc
@@ -6,16 +6,18 @@ _PRINT,
 Recipe_number["$print"] = _PRINT;
 :(before "End Primitive Recipe Implementations")
 case _PRINT: {
-  if (isa_literal(current_instruction().ingredients[0])) {
-    trace("run") << "$print: " << current_instruction().ingredients[0].name;
-    cout << current_instruction().ingredients[0].name;
-    break;
-  }
-  vector<long long int> result(read_memory(current_instruction().ingredients[0]));
-  for (index_t i = 0; i < result.size(); ++i) {
-    trace("run") << "$print: " << result[i];
-    if (i > 0) cout << " ";
-    cout << result[i];
+  for (index_t i = 0; i < ingredients.size(); ++i) {
+    if (isa_literal(current_instruction().ingredients.at(i))) {
+      trace("run") << "$print: " << current_instruction().ingredients.at(i).name;
+      cout << current_instruction().ingredients.at(i).name;
+    }
+    else {
+      for (index_t j = 0; j < ingredients.at(i).size(); ++j) {
+        trace("run") << "$print: " << ingredients.at(i).at(j);
+        if (j > 0) cout << " ";
+        cout << ingredients.at(i).at(j);
+      }
+    }
   }
   break;
 }
diff --git a/030container.cc b/030container.cc
index 9de3aad7..071e3ea5 100644
--- a/030container.cc
+++ b/030container.cc
@@ -11,9 +11,10 @@ i.push_back(integer);
 Type[point].elements.push_back(i);
 Type[point].elements.push_back(i);
 
+//: Containers can be copied around with a single instruction just like
+//: integers, no matter how large they are.
+
 :(scenario copy_multiple_locations)
-# Containers can be copied around with a single instruction just like integers,
-# no matter how large they are.
 recipe main [
   1:integer <- copy 34:literal
   2:integer <- copy 35:literal
@@ -48,13 +49,40 @@ recipe main [
 ]
 +mem: storing 36 in location 17
 
+//: Containers can be checked for equality with a single instruction just like
+//: integers, no matter how large they are.
+
+:(scenario compare_multiple_locations)
+recipe main [
+  1:integer <- copy 34:literal  # first
+  2:integer <- copy 35:literal
+  3:integer <- copy 36:literal
+  4:integer <- copy 34:literal  # second
+  5:integer <- copy 35:literal
+  6:integer <- copy 36:literal
+  7:boolean <- equal 1:point-integer, 4:point-integer
+]
++mem: storing 1 in location 7
+
+:(scenario compare_multiple_locations2)
+recipe main [
+  1:integer <- copy 34:literal  # first
+  2:integer <- copy 35:literal
+  3:integer <- copy 36:literal
+  4:integer <- copy 34:literal  # second
+  5:integer <- copy 35:literal
+  6:integer <- copy 37:literal  # different
+  7:boolean <- equal 1:point-integer, 4:point-integer
+]
++mem: storing 0 in location 7
+
 :(before "End size_of(types) Cases")
-type_info t = Type[types[0]];
+type_info t = Type[types.at(0)];
 if (t.kind == container) {
   // size of a container is the sum of the sizes of its elements
   size_t result = 0;
   for (index_t i = 0; i < t.elements.size(); ++i) {
-    result += size_of(t.elements[i]);
+    result += size_of(t.elements.at(i));
   }
   return result;
 }
@@ -72,7 +100,7 @@ recipe main [
 +run: address to copy is 13
 +run: its type is 1
 +mem: location 13 is 35
-+run: product 0 is 35
++run: product 0 is 15
 +mem: storing 35 in location 15
 
 :(before "End Primitive Recipe Declarations")
@@ -81,29 +109,26 @@ GET,
 Recipe_number["get"] = GET;
 :(before "End Primitive Recipe Implementations")
 case GET: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  reagent base = current_instruction().ingredients[0];
+  reagent base = current_instruction().ingredients.at(0);
   index_t base_address = base.value;
-  type_number base_type = base.types[0];
+  type_number base_type = base.types.at(0);
   assert(Type[base_type].kind == container);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  assert(isa_literal(current_instruction().ingredients[1]));
-  index_t offset = current_instruction().ingredients[1].value;
+  assert(isa_literal(current_instruction().ingredients.at(1)));
+  assert(ingredients.at(1).size() == 1);  // scalar
+  index_t offset = ingredients.at(1).at(0);
   index_t src = base_address;
   for (index_t i = 0; i < offset; ++i) {
-    src += size_of(Type[base_type].elements[i]);
+    src += size_of(Type[base_type].elements.at(i));
   }
   trace("run") << "address to copy is " << src;
   assert(Type[base_type].kind == container);
   assert(Type[base_type].elements.size() > offset);
-  type_number src_type = Type[base_type].elements[offset][0];
+  type_number src_type = Type[base_type].elements[offset].at(0);
   trace("run") << "its type is " << src_type;
   reagent tmp;
   tmp.set_value(src);
   tmp.types.push_back(src_type);
-  vector<long long int> result(read_memory(tmp));
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  products.push_back(read_memory(tmp));
   break;
 }
 
@@ -125,7 +150,7 @@ recipe main [
 +run: address to copy is 14
 +run: its type is 1
 +mem: location 14 is 36
-+run: product 0 is 36
++run: product 0 is 15
 +mem: storing 36 in location 15
 
 //:: To write to elements of containers, you need their address.
@@ -148,22 +173,19 @@ GET_ADDRESS,
 Recipe_number["get-address"] = GET_ADDRESS;
 :(before "End Primitive Recipe Implementations")
 case GET_ADDRESS: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  reagent base = current_instruction().ingredients[0];
+  reagent base = current_instruction().ingredients.at(0);
   index_t base_address = base.value;
-  type_number base_type = base.types[0];
+  type_number base_type = base.types.at(0);
   assert(Type[base_type].kind == container);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  assert(isa_literal(current_instruction().ingredients[1]));
-  index_t offset = current_instruction().ingredients[1].value;
-  index_t src = base_address;
+  assert(isa_literal(current_instruction().ingredients.at(1)));
+  assert(ingredients.at(1).size() == 1);  // scalar
+  index_t offset = ingredients.at(1).at(0);
+  index_t result = base_address;
   for (index_t i = 0; i < offset; ++i) {
-    src += size_of(Type[base_type].elements[i]);
+    result += size_of(Type[base_type].elements.at(i));
   }
-  trace("run") << "address to copy is " << src;
-  vector<long long int> result;
-  result.push_back(src);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  trace("run") << "address to copy is " << result;
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
diff --git a/031address.cc b/031address.cc
index 1c830c74..f6ad1f47 100644
--- a/031address.cc
+++ b/031address.cc
@@ -78,7 +78,7 @@ recipe main [
 ]
 +run: instruction main/3
 +run: address to copy is 2
-+run: product 0 is 34
++run: product 0 is 4
 +mem: storing 34 in location 4
 
 :(scenario include_nonderef_properties)
@@ -90,7 +90,7 @@ recipe main [
 ]
 +run: instruction main/3
 +run: address to copy is 2
-+run: product 0 is 34
++run: product 0 is 4
 +mem: storing 34 in location 4
 
 :(after "reagent base = " following "case GET:")
@@ -106,7 +106,7 @@ recipe main [
 ]
 +run: instruction main/3
 +run: address to copy is 2
-+run: product 0 is 2
++run: product 0 is 4
 
 :(after "reagent base = " following "case GET_ADDRESS:")
 base = canonize(base);
diff --git a/032array.cc b/032array.cc
index de19b719..69641aaa 100644
--- a/032array.cc
+++ b/032array.cc
@@ -48,10 +48,9 @@ recipe main [
 
 //: disable the size mismatch check since the destination array need not be initialized
 :(replace "if (size_of(x) != data.size())" following "void write_memory(reagent x, vector<long long int> data)")
-if (x.types[0] != Type_number["array"] && size_of(x) != data.size())
+if (x.types.at(0) != Type_number["array"] && size_of(x) != data.size())
 :(after "size_t size_of(const reagent& r)")
-  static const type_number ARRAY = Type_number["array"];
-  if (r.types[0] == ARRAY) {
+  if (r.types.at(0) == Type_number["array"]) {
     assert(r.types.size() > 1);
     // skip the 'array' type to get at the element type
     return 1 + Memory[r.value]*size_of(array_element(r.types));
@@ -71,7 +70,7 @@ recipe main [
 +run: address to copy is 2
 +run: its type is 1
 +mem: location 2 is 14
-+run: product 0 is 14
++run: product 0 is 5
 +mem: storing 14 in location 5
 
 :(scenario index_direct_offset)
@@ -87,7 +86,7 @@ recipe main [
 +run: address to copy is 2
 +run: its type is 1
 +mem: location 2 is 14
-+run: product 0 is 14
++run: product 0 is 6
 +mem: storing 14 in location 6
 
 :(before "End Primitive Recipe Declarations")
@@ -96,31 +95,24 @@ INDEX,
 Recipe_number["index"] = INDEX;
 :(before "End Primitive Recipe Implementations")
 case INDEX: {
-  static const type_number ARRAY = Type_number["array"];
 //?   if (Trace_stream) Trace_stream->dump_layer = "run"; //? 1
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].to_string();
-  reagent base = canonize(current_instruction().ingredients[0]);
+  reagent base = canonize(current_instruction().ingredients.at(0));
 //?   trace("run") << "ingredient 0 after canonize: " << base.to_string(); //? 1
   index_t base_address = base.value;
-  assert(base.types[0] == ARRAY);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].to_string();
-  reagent offset = canonize(current_instruction().ingredients[1]);
+  assert(base.types.at(0) == Type_number["array"]);
+  reagent offset = canonize(current_instruction().ingredients.at(1));
 //?   trace("run") << "ingredient 1 after canonize: " << offset.to_string(); //? 1
   vector<long long int> offset_val(read_memory(offset));
   vector<type_number> element_type = array_element(base.types);
 //?   trace("run") << "offset: " << offset_val[0]; //? 1
 //?   trace("run") << "size of elements: " << size_of(element_type); //? 1
-  index_t src = base_address + 1 + offset_val[0]*size_of(element_type);
+  index_t src = base_address + 1 + offset_val.at(0)*size_of(element_type);
   trace("run") << "address to copy is " << src;
-  trace("run") << "its type is " << element_type[0];
+  trace("run") << "its type is " << element_type.at(0);
   reagent tmp;
   tmp.set_value(src);
   copy(element_type.begin(), element_type.end(), inserter(tmp.types, tmp.types.begin()));
-//?   trace("run") << "AAA: " << tmp.to_string() << '\n'; //? 3
-  vector<long long int> result(read_memory(tmp));
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
-//?   if (Trace_stream) Trace_stream->dump_layer = ""; //? 1
+  products.push_back(read_memory(tmp));
   break;
 }
 
@@ -138,7 +130,6 @@ recipe main [
   5:integer <- index-address 1:array:integer, 0:literal
 ]
 +run: instruction main/4
-+run: address to copy is 2
 +mem: storing 2 in location 5
 
 //:: To write to elements of containers, you need their address.
@@ -161,20 +152,14 @@ INDEX_ADDRESS,
 Recipe_number["index-address"] = INDEX_ADDRESS;
 :(before "End Primitive Recipe Implementations")
 case INDEX_ADDRESS: {
-  static const type_number ARRAY = Type_number["array"];
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  reagent base = canonize(current_instruction().ingredients[0]);
+  reagent base = canonize(current_instruction().ingredients.at(0));
   index_t base_address = base.value;
-  assert(base.types[0] == ARRAY);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].to_string();
-  reagent offset = canonize(current_instruction().ingredients[1]);
+  assert(base.types.at(0) == Type_number["array"]);
+  reagent offset = canonize(current_instruction().ingredients.at(1));
   vector<long long int> offset_val(read_memory(offset));
   vector<type_number> element_type = array_element(base.types);
-  index_t src = base_address + 1 + offset_val[0]*size_of(element_type);
-  trace("run") << "address to copy is " << src;
-  vector<long long int> result;
-  result.push_back(src);
-  trace("run") << "product 0 is " << result[0];
-  write_memory(current_instruction().products[0], result);
+  index_t result = base_address + 1 + offset_val.at(0)*size_of(element_type);
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
diff --git a/033length.cc b/033length.cc
index 126a14e7..2268611a 100644
--- a/033length.cc
+++ b/033length.cc
@@ -17,14 +17,12 @@ LENGTH,
 Recipe_number["length"] = LENGTH;
 :(before "End Primitive Recipe Implementations")
 case LENGTH: {
-  reagent x = canonize(current_instruction().ingredients[0]);
-  if (x.types[0] != Type_number["array"]) {
+  reagent x = canonize(current_instruction().ingredients.at(0));
+  if (x.types.at(0) != Type_number["array"]) {
     raise << "tried to calculate length of non-array " << x.to_string() << '\n';
     break;
   }
-  vector<long long int> result;
-//?   cout << "length: " << x.value << '\n'; //? 1
-  result.push_back(Memory[x.value]);
-  write_memory(current_instruction().products[0], result);
+  products.resize(1);
+  products.at(0).push_back(Memory[x.value]);
   break;
 }
diff --git a/034exclusive_container.cc b/034exclusive_container.cc
index 45164c35..b2855f79 100644
--- a/034exclusive_container.cc
+++ b/034exclusive_container.cc
@@ -41,13 +41,13 @@ recipe main [
 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.)
-//?   cout << "--- " << types[0] << ' ' << t.size << '\n'; //? 1
+//?   cout << "--- " << types.at(0) << ' ' << t.size << '\n'; //? 1
 //?   cout << "point: " << Type_number["point"] << " " << Type[Type_number["point"]].name << " " << Type[Type_number["point"]].size << '\n'; //? 1
 //?   cout << t.name << ' ' << t.size << ' ' << t.elements.size() << '\n'; //? 1
   size_t result = 0;
   for (index_t i = 0; i < t.size; ++i) {
-    size_t tmp = size_of(t.elements[i]);
-//?     cout << i << ": " << t.elements[i][0] << ' ' << tmp << ' ' << result << '\n'; //? 1
+    size_t tmp = size_of(t.elements.at(i));
+//?     cout << i << ": " << t.elements.at(i).at(0) << ' ' << tmp << ' ' << result << '\n'; //? 1
     if (tmp > result) result = tmp;
   }
   // ...+1 for its tag.
@@ -88,21 +88,20 @@ MAYBE_CONVERT,
 Recipe_number["maybe-convert"] = MAYBE_CONVERT;
 :(before "End Primitive Recipe Implementations")
 case MAYBE_CONVERT: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  reagent base = canonize(current_instruction().ingredients[0]);
+  reagent base = canonize(current_instruction().ingredients.at(0));
   index_t base_address = base.value;
-  type_number base_type = base.types[0];
+  type_number base_type = base.types.at(0);
   assert(Type[base_type].kind == exclusive_container);
-  trace("run") << "ingredient 1 is " << current_instruction().ingredients[1].name;
-  assert(isa_literal(current_instruction().ingredients[1]));
-  index_t tag = current_instruction().ingredients[1].value;
-  vector<long long int> result;
+  assert(isa_literal(current_instruction().ingredients.at(1)));
+  index_t tag = current_instruction().ingredients.at(1).value;
+  long long int result;
   if (tag == static_cast<index_t>(Memory[base_address])) {
-    result.push_back(base_address+1);
+    result = base_address+1;
   }
   else {
-    result.push_back(0);
+    result = 0;
   }
-  write_memory(current_instruction().products[0], result);
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
diff --git a/036call_ingredient.cc b/036call_ingredient.cc
index 6224f8df..5c321ef9 100644
--- a/036call_ingredient.cc
+++ b/036call_ingredient.cc
@@ -30,8 +30,8 @@ call(recipe_number r) :running_recipe(r), running_step_index(0), next_ingredient
 
 :(replace "Current_routine->calls.push(call(current_instruction().operation))" following "End Primitive Recipe Implementations")
 call callee(current_instruction().operation);
-for (size_t i = 0; i < current_instruction().ingredients.size(); ++i) {
-  callee.ingredient_atoms.push_back(read_memory(current_instruction().ingredients[i]));
+for (size_t i = 0; i < ingredients.size(); ++i) {
+  callee.ingredient_atoms.push_back(ingredients.at(i));
 }
 Current_routine->calls.push(callee);
 
@@ -42,23 +42,16 @@ Recipe_number["next-ingredient"] = NEXT_INGREDIENT;
 :(before "End Primitive Recipe Implementations")
 case NEXT_INGREDIENT: {
   if (Current_routine->calls.top().next_ingredient_to_process < Current_routine->calls.top().ingredient_atoms.size()) {
-    trace("run") << "product 0 is "
-        << Current_routine->calls.top().ingredient_atoms[Current_routine->calls.top().next_ingredient_to_process][0];
-    write_memory(current_instruction().products[0],
-        Current_routine->calls.top().ingredient_atoms[Current_routine->calls.top().next_ingredient_to_process]);
-    if (current_instruction().products.size() > 1) {
-      vector<long long int> ingredient_exists;
-      ingredient_exists.push_back(1);
-      write_memory(current_instruction().products[1], ingredient_exists);
-    }
+    products.push_back(
+        Current_routine->calls.top().ingredient_atoms.at(Current_routine->calls.top().next_ingredient_to_process));
+    assert(products.size() == 1);  products.resize(2);  // push a new vector
+    products.at(1).push_back(1);
     ++Current_routine->calls.top().next_ingredient_to_process;
   }
   else {
-    if (current_instruction().products.size() > 1) {
-      vector<long long int> no_ingredient;
-      no_ingredient.push_back(0);
-      write_memory(current_instruction().products[1], no_ingredient);
-    }
+    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;
 }
@@ -105,24 +98,21 @@ INGREDIENT,
 Recipe_number["ingredient"] = INGREDIENT;
 :(before "End Primitive Recipe Implementations")
 case INGREDIENT: {
-  if (static_cast<index_t>(current_instruction().ingredients[0].value) < Current_routine->calls.top().ingredient_atoms.size()) {
-    Current_routine->calls.top().next_ingredient_to_process = current_instruction().ingredients[0].value;
-    trace("run") << "product 0 is "
-        << Current_routine->calls.top().ingredient_atoms[Current_routine->calls.top().next_ingredient_to_process][0];
-    write_memory(current_instruction().products[0],
-        Current_routine->calls.top().ingredient_atoms[Current_routine->calls.top().next_ingredient_to_process]);
-    if (current_instruction().products.size() > 1) {
-      vector<long long int> ingredient_exists;
-      ingredient_exists.push_back(1);
-      write_memory(current_instruction().products[1], ingredient_exists);
-    }
+  assert(isa_literal(current_instruction().ingredients.at(0)));
+  assert(ingredients.at(0).size() == 1);  // scalar
+  if (static_cast<index_t>(ingredients.at(0).at(0)) < Current_routine->calls.top().ingredient_atoms.size()) {
+    Current_routine->calls.top().next_ingredient_to_process = ingredients.at(0).at(0);
+    products.push_back(
+        Current_routine->calls.top().ingredient_atoms.at(Current_routine->calls.top().next_ingredient_to_process));
+    assert(products.size() == 1);  products.resize(2);  // push a new vector
+    products.at(1).push_back(1);
     ++Current_routine->calls.top().next_ingredient_to_process;
   }
   else {
     if (current_instruction().products.size() > 1) {
-      vector<long long int> no_ingredient;
-      no_ingredient.push_back(0);
-      write_memory(current_instruction().products[1], no_ingredient);
+      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;
diff --git a/037call_reply.cc b/037call_reply.cc
index 697c6b84..da1cf1f1 100644
--- a/037call_reply.cc
+++ b/037call_reply.cc
@@ -2,7 +2,7 @@
 
 :(scenario reply)
 recipe main [
-  3:integer, 4:integer <- f 2:literal
+  1:integer, 2:integer <- f 34:literal
 ]
 recipe f [
   12:integer <- next-ingredient
@@ -10,10 +10,8 @@ recipe f [
   reply 12:integer, 13:integer
 ]
 +run: instruction main/0
-+run: result 0 is 2
-+mem: storing 2 in location 3
-+run: result 1 is 3
-+mem: storing 3 in location 4
++mem: storing 34 in location 1
++mem: storing 35 in location 2
 
 :(before "End Primitive Recipe Declarations")
 REPLY,
@@ -21,29 +19,27 @@ REPLY,
 Recipe_number["reply"] = REPLY;
 :(before "End Primitive Recipe Implementations")
 case REPLY: {
-  vector<vector<long long int> > callee_results;
-  for (index_t i = 0; i < current_instruction().ingredients.size(); ++i) {
-    callee_results.push_back(read_memory(current_instruction().ingredients[i]));
-  }
   const instruction& reply_inst = current_instruction();  // save pointer into recipe before pop
   Current_routine->calls.pop();
   assert(!Current_routine->calls.empty());
   const instruction& caller_instruction = current_instruction();
-  assert(caller_instruction.products.size() <= callee_results.size());
+  // make reply results available to caller
+  copy(ingredients.begin(), ingredients.end(), inserter(products, products.begin()));
+  // check that any reply ingredients with /same-as-ingredient connect up
+  // the corresponding ingredient and product in the caller.
   for (index_t i = 0; i < caller_instruction.products.size(); ++i) {
-    trace("run") << "result " << i << " is " << to_string(callee_results[i]);
-    // check that any reply ingredients with /same-as-ingredient connect up
-    // the corresponding ingredient and product in the caller.
-    if (has_property(reply_inst.ingredients[i], "same-as-ingredient")) {
-      vector<string> tmp = property(reply_inst.ingredients[i], "same-as-ingredient");
+    trace("run") << "result " << i << " is " << to_string(ingredients.at(i));
+    if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) {
+      vector<string> tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
       assert(tmp.size() == 1);
-      long long int ingredient_index = to_int(tmp[0]);
-      if (caller_instruction.products[i].value != caller_instruction.ingredients[ingredient_index].value)
-        raise << "'same-as-ingredient' result " << caller_instruction.products[i].value << " must be location " << caller_instruction.ingredients[ingredient_index].value << '\n';
+      long long int ingredient_index = to_int(tmp.at(0));
+      if (caller_instruction.products.at(i).value != caller_instruction.ingredients[ingredient_index].value)
+        raise << "'same-as-ingredient' result " << caller_instruction.products.at(i).value << " must be location " << caller_instruction.ingredients[ingredient_index].value << '\n';
     }
-    write_memory(caller_instruction.products[i], callee_results[i]);
   }
-  break;  // instruction loop will increment caller's step_index
+  // refresh instruction_counter to caller's step_index
+  instruction_counter = current_step_index();
+  break;
 }
 
 //: Products can include containers and exclusive containers, addresses and arrays.
@@ -68,8 +64,8 @@ recipe f [
 :(scenario reply_same_as_ingredient)
 % Hide_warnings = true;
 recipe main [
-  1:address:integer <- new integer:type
-  2:address:integer <- test1 1:address:integer  # call with different ingredient and product
+  1:integer <- copy 0:literal
+  2:integer <- test1 1:integer  # call with different ingredient and product
 ]
 recipe test1 [
   10:address:integer <- next-ingredient
@@ -82,13 +78,13 @@ string to_string(const vector<long long int>& in) {
   if (in.empty()) return "[]";
   ostringstream out;
   if (in.size() == 1) {
-    out << in[0];
+    out << in.at(0);
     return out.str();
   }
   out << "[";
   for (index_t i = 0; i < in.size(); ++i) {
     if (i > 0) out << ", ";
-    out << in[i];
+    out << in.at(i);
   }
   out << "]";
   return out.str();
diff --git a/038scheduler.cc b/038scheduler.cc
index bc04d91c..ebc690a0 100644
--- a/038scheduler.cc
+++ b/038scheduler.cc
@@ -109,18 +109,15 @@ START_RUNNING,
 Recipe_number["start-running"] = START_RUNNING;
 :(before "End Primitive Recipe Implementations")
 case START_RUNNING: {
-  trace("run") << "ingredient 0 is " << current_instruction().ingredients[0].name;
-  assert(!current_instruction().ingredients[0].initialized);
-  routine* new_routine = new routine(Recipe_number[current_instruction().ingredients[0].name]);
+  assert(isa_literal(current_instruction().ingredients.at(0)));
+  assert(!current_instruction().ingredients.at(0).initialized);
+  routine* new_routine = new routine(Recipe_number[current_instruction().ingredients.at(0).name]);
   // populate ingredients
   for (index_t i = 1; i < current_instruction().ingredients.size(); ++i)
-    new_routine->calls.top().ingredient_atoms.push_back(read_memory(current_instruction().ingredients[i]));
+    new_routine->calls.top().ingredient_atoms.push_back(ingredients.at(i));
   Routines.push_back(new_routine);
-  if (!current_instruction().products.empty()) {
-    vector<long long int> result;
-    result.push_back(new_routine->id);
-    write_memory(current_instruction().products[0], result);
-  }
+  products.resize(1);
+  products.at(0).push_back(new_routine->id);
   break;
 }
 
@@ -230,14 +227,16 @@ ROUTINE_STATE,
 Recipe_number["routine-state"] = ROUTINE_STATE;
 :(before "End Primitive Recipe Implementations")
 case ROUTINE_STATE: {
-  vector<long long int> result;
-  index_t id = read_memory(current_instruction().ingredients[0])[0];
+  assert(ingredients.at(0).size() == 1);  // routine id must be scalar
+  index_t id = ingredients.at(0).at(0);
+  long long int result = -1;
   for (index_t i = 0; i < Routines.size(); ++i) {
     if (Routines[i]->id == id) {
-      result.push_back(Routines[i]->state);
-      write_memory(current_instruction().products[0], result);
+      result = Routines[i]->state;
       break;
     }
   }
+  products.resize(1);
+  products.at(0).push_back(result);
   break;
 }
diff --git a/039wait.cc b/039wait.cc
index 940cba89..2055a5b0 100644
--- a/039wait.cc
+++ b/039wait.cc
@@ -36,7 +36,7 @@ WAIT_FOR_LOCATION,
 Recipe_number["wait-for-location"] = WAIT_FOR_LOCATION;
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_LOCATION: {
-  reagent loc = canonize(current_instruction().ingredients[0]);
+  reagent loc = canonize(current_instruction().ingredients.at(0));
   Current_routine->state = WAITING;
   Current_routine->waiting_on_location = loc.value;
   Current_routine->old_value_of_wating_location = Memory[loc.value];
@@ -85,7 +85,7 @@ WAIT_FOR_ROUTINE,
 Recipe_number["wait-for-routine"] = WAIT_FOR_ROUTINE;
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_ROUTINE: {
-  reagent loc = canonize(current_instruction().ingredients[0]);
+  reagent loc = canonize(current_instruction().ingredients.at(0));
   Current_routine->state = WAITING;
   Current_routine->waiting_on_routine = loc.value;
   trace("run") << "waiting for routine " << loc.value;
diff --git a/040brace.cc b/040brace.cc
index 0b3f6f1b..90826e2e 100644
--- a/040brace.cc
+++ b/040brace.cc
@@ -63,83 +63,89 @@ void transform_braces(const recipe_number r) {
       ;  // do nothing
     else if (inst.operation == Recipe_number["loop"]) {
       inst.operation = Recipe_number["jump"];
-      if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) {
+      if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients.at(0))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(0).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(open_braces.top()-index);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
         trace("after-brace") << "jump " << ing.value << ":offset";
         trace("after-brace") << index << ": " << ing.to_string();
-        trace("after-brace") << index << ": " << Recipe[r].steps[index].ingredients[0].to_string();
+        trace("after-brace") << index << ": " << Recipe[r].steps[index].ingredients.at(0).to_string();
       }
     }
     else if (inst.operation == Recipe_number["break"]) {
       inst.operation = Recipe_number["jump"];
-      if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients[0])) {
+      if (inst.ingredients.size() > 0 && isa_literal(inst.ingredients.at(0))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[0].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(0).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(matching_brace(open_braces.top(), braces) - index - 1);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
         trace("after-brace") << "jump " << ing.value << ":offset";
       }
     }
     else if (inst.operation == Recipe_number["loop-if"]) {
       inst.operation = Recipe_number["jump-if"];
-      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) {
+      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients.at(1))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(1).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(open_braces.top()-index);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
-        trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset";
+        trace("after-brace") << "jump-if " << inst.ingredients.at(0).name << ", " << ing.value << ":offset";
       }
     }
     else if (inst.operation == Recipe_number["break-if"]) {
       inst.operation = Recipe_number["jump-if"];
-      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) {
+      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients.at(1))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(1).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(matching_brace(open_braces.top(), braces) - index - 1);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
-        trace("after-brace") << "jump-if " << inst.ingredients[0].name << ", " << ing.value << ":offset";
+        trace("after-brace") << "jump-if " << inst.ingredients.at(0).name << ", " << ing.value << ":offset";
       }
     }
     else if (inst.operation == Recipe_number["loop-unless"]) {
       inst.operation = Recipe_number["jump-unless"];
-      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) {
+      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients.at(1))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(1).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(open_braces.top()-index);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
-        trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset";
+        trace("after-brace") << "jump-unless " << inst.ingredients.at(0).name << ", " << ing.value << ":offset";
       }
     }
     else if (inst.operation == Recipe_number["break-unless"]) {
 //?       cout << "AAA break-unless\n"; //? 1
       inst.operation = Recipe_number["jump-unless"];
-      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients[1])) {
+      if (inst.ingredients.size() > 1 && isa_literal(inst.ingredients.at(1))) {
         // explicit target; a later phase will handle it
-        trace("after-brace") << "jump " << inst.ingredients[1].name << ":offset";
+        trace("after-brace") << "jump " << inst.ingredients.at(1).name << ":offset";
       }
       else {
         reagent ing;
         ing.set_value(matching_brace(open_braces.top(), braces) - index - 1);
+        ing.types.push_back(Type_number["offset"]);
         inst.ingredients.push_back(ing);
-        trace("after-brace") << "jump-unless " << inst.ingredients[0].name << ", " << ing.value << ":offset";
+        trace("after-brace") << "jump-unless " << inst.ingredients.at(0).name << ", " << ing.value << ":offset";
       }
     }
     else {
@@ -368,6 +374,7 @@ recipe main [
 //: test how things actually run
 :(scenarios run)
 :(scenario brace_conversion_and_run)
+#? % Trace_stream->dump_layer = "run";
 recipe test-factorial [
   1:integer <- copy 5:literal
   2:integer <- copy 1:literal
diff --git a/041name.cc b/041name.cc
index b54a1e78..d73e5139 100644
--- a/041name.cc
+++ b/041name.cc
@@ -42,41 +42,41 @@ void transform_names(const recipe_number r) {
     // map names to addresses
     for (index_t in = 0; in < inst.ingredients.size(); ++in) {
 //?       cout << "ingredients\n"; //? 2
-      if (is_raw(inst.ingredients[in])) continue;
-//?       cout << "ingredient " << inst.ingredients[in].name << '\n'; //? 3
-//?       cout << "ingredient " << inst.ingredients[in].to_string() << '\n'; //? 1
-      if (inst.ingredients[in].name == "default-space")
-        inst.ingredients[in].initialized = true;
-      if (inst.ingredients[in].types.empty())
+      if (is_raw(inst.ingredients.at(in))) continue;
+//?       cout << "ingredient " << inst.ingredients.at(in).name << '\n'; //? 3
+//?       cout << "ingredient " << inst.ingredients.at(in).to_string() << '\n'; //? 1
+      if (inst.ingredients.at(in).name == "default-space")
+        inst.ingredients.at(in).initialized = true;
+      if (inst.ingredients.at(in).types.empty())
         raise << "missing type in " << inst.to_string() << '\n';
-      assert(!inst.ingredients[in].types.empty());
-      if (inst.ingredients[in].types[0]  // not a literal
-          && !inst.ingredients[in].initialized
-          && !is_number(inst.ingredients[in].name)) {
-        if (!already_transformed(inst.ingredients[in], names)) {
-          raise << "use before set: " << inst.ingredients[in].name << " in " << Recipe[r].name << '\n';
+      assert(!inst.ingredients.at(in).types.empty());
+      if (inst.ingredients.at(in).types[0]  // not a literal
+          && !inst.ingredients.at(in).initialized
+          && !is_number(inst.ingredients.at(in).name)) {
+        if (!already_transformed(inst.ingredients.at(in), names)) {
+          raise << "use before set: " << inst.ingredients.at(in).name << " in " << Recipe[r].name << '\n';
         }
-        inst.ingredients[in].set_value(lookup_name(inst.ingredients[in], r));
-//?         cout << "lookup ingredient " << Recipe[r].name << "/" << i << ": " << inst.ingredients[in].to_string() << '\n'; //? 1
+        inst.ingredients.at(in).set_value(lookup_name(inst.ingredients.at(in), r));
+//?         cout << "lookup ingredient " << Recipe[r].name << "/" << i << ": " << inst.ingredients.at(in).to_string() << '\n'; //? 1
       }
     }
     for (index_t out = 0; out < inst.products.size(); ++out) {
 //?       cout << "products\n"; //? 1
-      if (is_raw(inst.products[out])) continue;
-//?       cout << "product " << out << '/' << inst.products.size() << " " << inst.products[out].name << '\n'; //? 4
-//?       cout << inst.products[out].types[0] << '\n'; //? 1
-      if (inst.products[out].name == "default-space")
-        inst.products[out].initialized = true;
-      if (inst.products[out].types[0]  // not a literal
-          && !inst.products[out].initialized
-          && !is_number(inst.products[out].name)) {
-        if (names.find(inst.products[out].name) == names.end()) {
-          trace("name") << "assign " << inst.products[out].name << " " << curr_idx;
-          names[inst.products[out].name] = curr_idx;
-          curr_idx += size_of(inst.products[out]);
+      if (is_raw(inst.products.at(out))) continue;
+//?       cout << "product " << out << '/' << inst.products.size() << " " << inst.products.at(out).name << '\n'; //? 4
+//?       cout << inst.products.at(out).types[0] << '\n'; //? 1
+      if (inst.products.at(out).name == "default-space")
+        inst.products.at(out).initialized = true;
+      if (inst.products.at(out).types[0]  // not a literal
+          && !inst.products.at(out).initialized
+          && !is_number(inst.products.at(out).name)) {
+        if (names.find(inst.products.at(out).name) == names.end()) {
+          trace("name") << "assign " << inst.products.at(out).name << " " << curr_idx;
+          names[inst.products.at(out).name] = curr_idx;
+          curr_idx += size_of(inst.products.at(out));
         }
-        inst.products[out].set_value(lookup_name(inst.products[out], r));
-//?         cout << "lookup product " << Recipe[r].name << "/" << i << ": " << inst.products[out].to_string() << '\n'; //? 1
+        inst.products.at(out).set_value(lookup_name(inst.products.at(out), r));
+//?         cout << "lookup product " << Recipe[r].name << "/" << i << ": " << inst.products.at(out).to_string() << '\n'; //? 1
       }
     }
   }
@@ -118,7 +118,7 @@ bool is_raw(const reagent& r) {
 :(scenario convert_names_passes_dummy)
 # _ is just a dummy result that never gets consumed
 recipe main [
-  _, x:integer <- copy 0:literal
+  _, x:integer <- copy 0:literal, 1:literal
 ]
 +name: assign x 1
 -name: assign _ 1
@@ -126,7 +126,7 @@ recipe main [
 //: one reserved word that we'll need later
 :(scenario convert_names_passes_default_space)
 recipe main [
-  default-space:integer, x:integer <- copy 0:literal
+  default-space:integer, x:integer <- copy 0:literal, 1:literal
 ]
 +name: assign x 1
 -name: assign default-space 1
@@ -158,13 +158,13 @@ if (inst.operation == Recipe_number["get"]
     || inst.operation == Recipe_number["get-address"]) {
   // at least 2 args, and second arg is offset
   assert(inst.ingredients.size() >= 2);
-//?   cout << inst.ingredients[1].to_string() << '\n'; //? 1
-  assert(isa_literal(inst.ingredients[1]));
-  if (inst.ingredients[1].name.find_first_not_of("0123456789") == string::npos) continue;
+//?   cout << inst.ingredients.at(1).to_string() << '\n'; //? 1
+  assert(isa_literal(inst.ingredients.at(1)));
+  if (inst.ingredients.at(1).name.find_first_not_of("0123456789") == string::npos) continue;
   // since first non-address in base type must be a container, we don't have to canonize
-  type_number base_type = skip_addresses(inst.ingredients[0].types);
-  inst.ingredients[1].set_value(find_element_name(base_type, inst.ingredients[1].name));
-  trace("name") << "element " << inst.ingredients[1].name << " of type " << Type[base_type].name << " is at offset " << inst.ingredients[1].value;
+  type_number base_type = skip_addresses(inst.ingredients.at(0).types);
+  inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name));
+  trace("name") << "element " << inst.ingredients.at(1).name << " of type " << Type[base_type].name << " is at offset " << inst.ingredients.at(1).value;
 }
 
 //: this test is actually illegal so can't call run
@@ -195,10 +195,10 @@ recipe main [
 if (inst.operation == Recipe_number["maybe-convert"]) {
   // at least 2 args, and second arg is offset
   assert(inst.ingredients.size() >= 2);
-  assert(isa_literal(inst.ingredients[1]));
-  if (inst.ingredients[1].name.find_first_not_of("0123456789") == string::npos) continue;
+  assert(isa_literal(inst.ingredients.at(1)));
+  if (inst.ingredients.at(1).name.find_first_not_of("0123456789") == string::npos) continue;
   // since first non-address in base type must be an exclusive container, we don't have to canonize
-  type_number base_type = skip_addresses(inst.ingredients[0].types);
-  inst.ingredients[1].set_value(find_element_name(base_type, inst.ingredients[1].name));
-  trace("name") << "variant " << inst.ingredients[1].name << " of type " << Type[base_type].name << " has tag " << inst.ingredients[1].value;
+  type_number base_type = skip_addresses(inst.ingredients.at(0).types);
+  inst.ingredients.at(1).set_value(find_element_name(base_type, inst.ingredients.at(1).name));
+  trace("name") << "variant " << inst.ingredients.at(1).name << " of type " << Type[base_type].name << " has tag " << inst.ingredients.at(1).value;
 }
diff --git a/042new.cc b/042new.cc
index 81a4528a..375b4ce4 100644
--- a/042new.cc
+++ b/042new.cc
@@ -34,12 +34,12 @@ Type_number["type"] = 0;
 if (inst.operation == Recipe_number["new"]) {
   // first arg must be of type 'type'
   assert(inst.ingredients.size() >= 1);
-//?   cout << inst.ingredients[0].to_string() << '\n'; //? 1
-  assert(isa_literal(inst.ingredients[0]));
-  if (inst.ingredients[0].properties[0].second[0] == "type") {
-    inst.ingredients[0].set_value(Type_number[inst.ingredients[0].name]);
+//?   cout << inst.ingredients.at(0).to_string() << '\n'; //? 1
+  assert(isa_literal(inst.ingredients.at(0)));
+  if (inst.ingredients.at(0).properties.at(0).second.at(0) == "type") {
+    inst.ingredients.at(0).set_value(Type_number[inst.ingredients.at(0).name]);
   }
-  trace("new") << inst.ingredients[0].name << " -> " << inst.ingredients[0].value;
+  trace("new") << inst.ingredients.at(0).name << " -> " << inst.ingredients.at(0).value;
 }
 
 //:: Now implement the primitive recipe.
@@ -55,11 +55,11 @@ case NEW: {
   size_t array_length = 0;
   {
     vector<type_number> type;
-    type.push_back(current_instruction().ingredients[0].value);
+    assert(isa_literal(current_instruction().ingredients.at(0)));
+    type.push_back(current_instruction().ingredients.at(0).value);
     if (current_instruction().ingredients.size() > 1) {
       // array
-      vector<long long int> capacity = read_memory(current_instruction().ingredients[1]);
-      array_length = capacity[0];
+      array_length = ingredients.at(1).at(0);
       trace("mem") << "array size is " << array_length;
       size = array_length*size_of(type) + /*space for length*/1;
     }
@@ -70,24 +70,16 @@ case NEW: {
   }
   // compute the resulting location
   // really crappy at the moment
-  assert(size <= Initial_memory_per_routine);
-  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("new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max;
-  }
+  ensure_space(size);
   const index_t result = Current_routine->alloc;
   trace("mem") << "new alloc: " << result;
+  // save result
+  products.resize(1);
+  products.at(0).push_back(result);
+  // initialize array if necessary
   if (current_instruction().ingredients.size() > 1) {
-    // initialize array
     Memory[result] = array_length;
   }
-  // write result to memory
-  vector<long long int> tmp;
-  tmp.push_back(Current_routine->alloc);
-  write_memory(current_instruction().products[0], tmp);
   // bump
   Current_routine->alloc += size;
   // no support for reclaiming memory
@@ -95,6 +87,19 @@ case NEW: {
   break;
 }
 
+:(code)
+void ensure_space(size_t size) {
+  assert(size <= Initial_memory_per_routine);
+//?   cout << Current_routine->alloc << " " << Current_routine->alloc_max << " " << size << '\n'; //? 1
+  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("new") << "routine allocated memory from " << Current_routine->alloc << " to " << Current_routine->alloc_max;
+  }
+}
+
 :(scenario new_array)
 recipe main [
   1:address:array:integer/raw <- new integer:type, 5:literal
@@ -105,6 +110,7 @@ recipe main [
 +mem: array size is 5
 +run: instruction main/1
 +run: instruction main/2
+# don't forget the extra location for array size
 +mem: storing 6 in location 3
 
 //: Make sure that each routine gets a different alloc to start.
@@ -141,17 +147,30 @@ recipe main [
 +mem: storing 101 in location 2
 
 :(after "case NEW" following "Primitive Recipe Implementations")
-if (current_instruction().ingredients[0].properties[0].second[0] == "literal-string") {
+if (isa_literal(current_instruction().ingredients.at(0))
+    && current_instruction().ingredients.at(0).properties.at(0).second.at(0) == "literal-string") {
   // allocate an array just large enough for it
-  vector<long long int> result;
-  result.push_back(Current_routine->alloc);
-  write_memory(current_instruction().products[0], result);
-  // assume that all characters fit in a single location
-//?   cout << "new string literal: " << current_instruction().ingredients[0].name << '\n'; //? 1
-  Memory[Current_routine->alloc++] = current_instruction().ingredients[0].name.size();
-  for (index_t i = 0; i < current_instruction().ingredients[0].name.size(); ++i) {
-    Memory[Current_routine->alloc++] = current_instruction().ingredients[0].name[i];
+  size_t string_length = current_instruction().ingredients.at(0).name.size();
+//?   cout << "string_length is " << string_length << '\n'; //? 1
+  ensure_space(string_length+1);  // don't forget the extra location for array size
+  products.resize(1);
+  products.at(0).push_back(Current_routine->alloc);
+  // initialize string
+//?   cout << "new string literal: " << current_instruction().ingredients.at(0).name << '\n'; //? 1
+  Memory[Current_routine->alloc++] = string_length;
+  for (index_t i = 0; i < string_length; ++i) {
+    Memory[Current_routine->alloc++] = current_instruction().ingredients.at(0).name.at(i);
   }
   // mu strings are not null-terminated in memory
   break;
 }
+
+//: Allocate more to routine when initializing a literal string
+:(scenario new_string_overflow)
+% Initial_memory_per_routine = 2;
+recipe main [
+  1:address:integer/raw <- new integer:type
+  2:address:array:character/raw <- new [a]  # not enough room in initial page, if you take the array size into account
+]
++new: routine allocated memory from 1000 to 1002
++new: routine allocated memory from 1002 to 1004
diff --git a/043space.cc b/043space.cc
index 4230468f..b0ee7f85 100644
--- a/043space.cc
+++ b/043space.cc
@@ -35,7 +35,7 @@ reagent r = absolutize(x);
 :(code)
 reagent absolutize(reagent x) {
 //?   if (Recipe_number.find("increment-counter") != Recipe_number.end()) //? 1
-//?     cout << "AAA " << "increment-counter/2: " << Recipe[Recipe_number["increment-counter"]].steps[2].products[0].to_string() << '\n'; //? 1
+//?     cout << "AAA " << "increment-counter/2: " << Recipe[Recipe_number["increment-counter"]].steps.at(2).products.at(0).to_string() << '\n'; //? 1
 //?   cout << "absolutize " << x.to_string() << '\n'; //? 4
 //?   cout << is_raw(x) << '\n'; //? 1
   if (is_raw(x) || is_dummy(x)) return x;
@@ -110,7 +110,7 @@ index_t address(index_t offset, index_t base) {
 :(after "void write_memory(reagent x, vector<long long int> data)")
   if (x.name == "default-space") {
     assert(data.size() == 1);
-    Current_routine->calls.top().default_space = data[0];
+    Current_routine->calls.top().default_space = data.at(0);
 //?     cout << "AAA " << Current_routine->calls.top().default_space << '\n'; //? 1
     return;
   }
diff --git a/044space_surround.cc b/044space_surround.cc
index dfa6c9ef..16aef4dc 100644
--- a/044space_surround.cc
+++ b/044space_surround.cc
@@ -43,8 +43,10 @@ index_t space_base(const reagent& x, index_t space_index, index_t base) {
 
 index_t space_index(const reagent& x) {
   for (index_t i = 0; i < x.properties.size(); ++i) {
-    if (x.properties[i].first == "space")
-      return to_int(x.properties[i].second[0]);
+    if (x.properties.at(i).first == "space") {
+      assert(x.properties.at(i).second.size() == 1);
+      return to_int(x.properties.at(i).second.at(0));
+    }
   }
   return 0;
 }
diff --git a/045closure_name.cc b/045closure_name.cc
index 3f90f308..6273e880 100644
--- a/045closure_name.cc
+++ b/045closure_name.cc
@@ -46,20 +46,20 @@ void collect_surrounding_spaces(const recipe_number r) {
     const instruction& inst = Recipe[r].steps[i];
     if (inst.is_label) continue;
     for (index_t j = 0; j < inst.products.size(); ++j) {
-      if (isa_literal(inst.products[j])) continue;
-      if (inst.products[j].name != "0") continue;
-      if (inst.products[j].types.size() != 3
-          || inst.products[j].types[0] != Type_number["address"]
-          || inst.products[j].types[1] != Type_number["array"]
-          || inst.products[j].types[2] != Type_number["location"]) {
-        raise << "slot 0 should always have type address:array:location, but is " << inst.products[j].to_string() << '\n';
+      if (isa_literal(inst.products.at(j))) continue;
+      if (inst.products.at(j).name != "0") continue;
+      if (inst.products.at(j).types.size() != 3
+          || inst.products.at(j).types.at(0) != Type_number["address"]
+          || inst.products.at(j).types.at(1) != Type_number["array"]
+          || inst.products.at(j).types.at(2) != Type_number["location"]) {
+        raise << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n';
         continue;
       }
-      vector<string> s = property(inst.products[j], "names");
+      vector<string> s = property(inst.products.at(j), "names");
       if (s.empty())
         raise << "slot 0 requires a /names property in recipe " << Recipe[r].name << die();
-      if (s.size() > 1) raise << "slot 0 should have a single value in /names, got " << inst.products[j].to_string() << '\n';
-      string surrounding_recipe_name = s[0];
+      if (s.size() > 1) raise << "slot 0 should have a single value in /names, got " << inst.products.at(j).to_string() << '\n';
+      string surrounding_recipe_name = s.at(0);
       if (Surrounding_space.find(r) != Surrounding_space.end()
           && Surrounding_space[r] != Recipe_number[surrounding_recipe_name]) {
         raise << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n';
@@ -84,7 +84,7 @@ index_t lookup_name(const reagent& x, const recipe_number default_recipe) {
   }
   vector<string> p = property(x, "space");
   if (p.size() != 1) raise << "/space property should have exactly one (non-negative integer) value\n";
-  int n = to_int(p[0]);
+  int n = to_int(p.at(0));
   assert(n >= 0);
   recipe_number surrounding_recipe = lookup_surrounding_recipe(default_recipe, n);
   set<recipe_number> done;
@@ -99,9 +99,9 @@ index_t lookup_name(const reagent& x, const recipe_number r, set<recipe_number>&
   if (done.find(r) != done.end()) {
     raise << "can't compute address of " << x.to_string() << " because ";
     for (index_t i = 1; i < path.size(); ++i) {
-      raise << path[i-1] << " requires computing names of " << path[i] << '\n';
+      raise << path.at(i-1) << " requires computing names of " << path.at(i) << '\n';
     }
-    raise << path[path.size()-1] << " requires computing names of " << r << "..ad infinitum\n" << die();
+    raise << path.at(path.size()-1) << " requires computing names of " << r << "..ad infinitum\n" << die();
     return 0;
   }
   done.insert(r);
@@ -127,7 +127,7 @@ bool already_transformed(const reagent& r, const map<string, index_t>& names) {
   if (has_property(r, "space")) {
     vector<string> p = property(r, "space");
     assert(p.size() == 1);
-    if (p[0] != "0") return true;
+    if (p.at(0) != "0") return true;
   }
   return names.find(r.name) != names.end();
 }
diff --git a/046tangle.cc b/046tangle.cc
index 9ec03f68..0c417322 100644
--- a/046tangle.cc
+++ b/046tangle.cc
@@ -49,7 +49,7 @@ void insert_fragments(const recipe_number r) {
   // But this way we can't insert into labels created inside before/after.
   vector<instruction> result;
   for (index_t i = 0; i < Recipe[r].steps.size(); ++i) {
-    const instruction inst = Recipe[r].steps[i];
+    const instruction inst = Recipe[r].steps.at(i);
     if (!inst.is_label) {
       result.push_back(inst);
       continue;
@@ -63,7 +63,7 @@ void insert_fragments(const recipe_number r) {
     }
   }
 //?   for (index_t i = 0; i < result.size(); ++i) { //? 1
-//?     cout << result[i].to_string() << '\n'; //? 1
+//?     cout << result.at(i).to_string() << '\n'; //? 1
 //?   } //? 1
   Recipe[r].steps.swap(result);
 }
diff --git a/050scenario.cc b/050scenario.cc
index 07279d39..6c048dfd 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -88,7 +88,7 @@ time_t mu_time; time(&mu_time);
 cerr << "\nMu tests: " << ctime(&mu_time);
 for (index_t i = 0; i < Scenarios.size(); ++i) {
 //?   cerr << Passed << '\n'; //? 1
-  run_mu_scenario(Scenarios[i]);
+  run_mu_scenario(Scenarios.at(i));
   if (Passed) cerr << ".";
 }
 
@@ -137,16 +137,16 @@ RUN,
 Recipe_number["run"] = RUN;
 :(before "End Primitive Recipe Implementations")
 case RUN: {
-//?   cout << "recipe " << current_instruction().ingredients[0].name << '\n'; //? 1
+//?   cout << "recipe " << current_instruction().ingredients.at(0).name << '\n'; //? 1
   ostringstream tmp;
-  tmp << "recipe run" << Next_recipe_number << " [ " << current_instruction().ingredients[0].name << " ]";
+  tmp << "recipe run" << Next_recipe_number << " [ " << current_instruction().ingredients.at(0).name << " ]";
 //?   Show_rest_of_stream = true; //? 1
   vector<recipe_number> tmp_recipe = load(tmp.str());
   // Predefined Scenario Locals In Run.
   // End Predefined Scenario Locals In Run.
   transform_all();
-//?   cout << tmp_recipe[0] << ' ' << Recipe_number["main"] << '\n'; //? 1
-  Current_routine->calls.push(call(tmp_recipe[0]));
+//?   cout << tmp_recipe.at(0) << ' ' << Recipe_number["main"] << '\n'; //? 1
+  Current_routine->calls.push(call(tmp_recipe.at(0)));
   continue;  // not done with caller; don't increment current_step_index()
 }
 
@@ -181,8 +181,8 @@ MEMORY_SHOULD_CONTAIN,
 Recipe_number["memory-should-contain"] = MEMORY_SHOULD_CONTAIN;
 :(before "End Primitive Recipe Implementations")
 case MEMORY_SHOULD_CONTAIN: {
-//?   cout << current_instruction().ingredients[0].name << '\n'; //? 1
-  check_memory(current_instruction().ingredients[0].name);
+//?   cout << current_instruction().ingredients.at(0).name << '\n'; //? 1
+  check_memory(current_instruction().ingredients.at(0).name);
   break;
 }
 
@@ -221,7 +221,7 @@ void check_memory(const string& s) {
 
 void check_type(const string& lhs, istream& in) {
   reagent x(lhs);
-  if (x.properties[0].second[0] == "string") {
+  if (x.properties.at(0).second.at(0) == "string") {
     x.set_value(to_int(x.name));
     skip_whitespace_and_comments(in);
     string _assign = next_word(in);
@@ -230,7 +230,7 @@ void check_type(const string& lhs, istream& in) {
     string literal = next_word(in);
     index_t address = x.value;
     // exclude quoting brackets
-    assert(literal[0] == '[');  literal.erase(0, 1);
+    assert(literal.at(0) == '[');  literal.erase(0, 1);
     assert(literal[literal.size()-1] == ']');  literal.erase(literal.size()-1);
     check_string(address, literal);
     return;
@@ -245,8 +245,8 @@ void check_string(index_t address, const string& literal) {
   ++address;  // now skip length
   for (index_t i = 0; i < literal.size(); ++i) {
     trace("run") << "checking location " << address+i;
-    if (Memory[address+i] != literal[i])
-      raise << "expected location " << (address+i) << " to contain " << literal[i] << " but saw " << Memory[address+i] << '\n';
+    if (Memory[address+i] != literal.at(i))
+      raise << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << Memory[address+i] << '\n';
   }
 }
 
@@ -310,7 +310,7 @@ TRACE_SHOULD_CONTAIN,
 Recipe_number["trace-should-contain"] = TRACE_SHOULD_CONTAIN;
 :(before "End Primitive Recipe Implementations")
 case TRACE_SHOULD_CONTAIN: {
-  check_trace(current_instruction().ingredients[0].name);
+  check_trace(current_instruction().ingredients.at(0).name);
   break;
 }
 
@@ -340,10 +340,10 @@ vector<pair<string, string> > parse_trace(const string& expected) {
   vector<string> buf = split(expected, "\n");
   vector<pair<string, string> > result;
   for (index_t i = 0; i < buf.size(); ++i) {
-    buf[i] = trim(buf[i]);
-    if (buf[i].empty()) continue;
-    index_t delim = buf[i].find(": ");
-    result.push_back(pair<string, string>(buf[i].substr(0, delim), buf[i].substr(delim+2)));
+    buf.at(i) = trim(buf.at(i));
+    if (buf.at(i).empty()) continue;
+    index_t delim = buf.at(i).find(": ");
+    result.push_back(pair<string, string>(buf.at(i).substr(0, delim), buf.at(i).substr(delim+2)));
   }
   return result;
 }
@@ -395,7 +395,7 @@ TRACE_SHOULD_NOT_CONTAIN,
 Recipe_number["trace-should-not-contain"] = TRACE_SHOULD_NOT_CONTAIN;
 :(before "End Primitive Recipe Implementations")
 case TRACE_SHOULD_NOT_CONTAIN: {
-  check_trace_missing(current_instruction().ingredients[0].name);
+  check_trace_missing(current_instruction().ingredients.at(0).name);
   break;
 }
 
@@ -406,8 +406,8 @@ bool check_trace_missing(const string& in) {
   Trace_stream->newline();
   vector<pair<string, string> > lines = parse_trace(in);
   for (index_t i = 0; i < lines.size(); ++i) {
-    if (trace_count(lines[i].first, lines[i].second) != 0) {
-      raise << "unexpected [" << lines[i].second << "] in trace layer " << lines[i].first << '\n';
+    if (trace_count(lines.at(i).first, lines.at(i).second) != 0) {
+      raise << "unexpected [" << lines.at(i).second << "] in trace layer " << lines.at(i).first << '\n';
       Passed = false;
       return false;
     }
diff --git a/060string.mu b/060string.mu
index e6705a65..6b38803b 100644
--- a/060string.mu
+++ b/060string.mu
@@ -105,8 +105,7 @@ container buffer [
 
 recipe init-buffer [
   default-space:address:array:location <- new location:type, 30:literal
-#?   $print default-space:address:array:location
-#?   $print [
+#?   $print default-space:address:array:location, [
 #? ]
   result:address:buffer <- new buffer:type
   len:address:integer <- get-address result:address:buffer/deref, length:offset
@@ -114,8 +113,7 @@ recipe init-buffer [
   s:address:address:array:character <- get-address result:address:buffer/deref, data:offset
   capacity:integer <- next-ingredient
   s:address:address:array:character/deref <- new character:type, capacity:integer
-#?   $print s:address:address:array:character/deref
-#?   $print [
+#?   $print s:address:address:array:character/deref, [
 #? ]
   reply result:address:buffer
 ]
@@ -182,26 +180,19 @@ scenario buffer-append-works [
     x:address:buffer <- buffer-append x:address:buffer, 99:literal  # 'c'
     s2:address:array:character <- get x:address:buffer/deref, data:offset
     1:boolean/raw <- equal s1:address:array:character, s2:address:array:character
-#?     $print s2:address:array:character
-#?     $print [
+#?     $print s2:address:array:character, [
 #? ]
-#?     $print 1060:integer/raw
-#?     $print [
+#?     $print 1060:integer/raw, [
 #? ]
-#?     $print 1061:integer/raw
-#?     $print [
+#?     $print 1061:integer/raw, [
 #? ]
-#?     $print 1062:integer/raw
-#?     $print [
+#?     $print 1062:integer/raw, [
 #? ]
-#?     $print 1063:integer/raw
-#?     $print [
+#?     $print 1063:integer/raw, [
 #? ]
-#?     $print 1064:integer/raw
-#?     $print [
+#?     $print 1064:integer/raw, [
 #? ]
-#?     $print 1065:integer/raw
-#?     $print [
+#?     $print 1065:integer/raw, [
 #? ]
     2:array:character/raw <- copy s2:address:array:character/deref
     +buffer-filled
@@ -395,10 +386,7 @@ recipe interpolate [
     result-len:integer <- subtract result-len:integer, 1:literal
     loop
   }
-#?   $print tem-len:integer #? 1
-#?   $print [ ] #? 1
-#?   $print result-len:integer #? 1
-#?   $print [ #? 1
+#?   $print tem-len:integer, [ ], $result-len:integer, [ #? 1
 #? ] #? 1
   rewind-ingredients
   _ <- next-ingredient  # skip template
diff --git a/070display.cc b/070display.cc
index aa2c31b8..b4e354f8 100644
--- a/070display.cc
+++ b/070display.cc
@@ -62,11 +62,12 @@ PRINT_CHARACTER_TO_DISPLAY,
 Recipe_number["print-character-to-display"] = PRINT_CHARACTER_TO_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case PRINT_CHARACTER_TO_DISPLAY: {
-  vector<long long int> arg = read_memory(current_instruction().ingredients[0]);
   int h=tb_height(), w=tb_width();
   size_t height = (h >= 0) ? h : 0;
   size_t width = (w >= 0) ? w : 0;
-  if (arg[0] == '\n') {
+  assert(ingredients.at(0).size() == 1);  // scalar
+  long long int c = ingredients.at(0).at(0);
+  if (c == '\n') {
     if (Display_row < height) {
       Display_column = 0;
       ++Display_row;
@@ -75,7 +76,7 @@ case PRINT_CHARACTER_TO_DISPLAY: {
     }
     break;
   }
-  tb_change_cell(Display_column, Display_row, arg[0], TB_WHITE, TB_DEFAULT);
+  tb_change_cell(Display_column, Display_row, c, TB_WHITE, TB_DEFAULT);
   if (Display_column < width) {
     Display_column++;
     tb_set_cursor(Display_column, Display_row);
@@ -90,12 +91,9 @@ CURSOR_POSITION_ON_DISPLAY,
 Recipe_number["cursor-position-on-display"] = CURSOR_POSITION_ON_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case CURSOR_POSITION_ON_DISPLAY: {
-  vector<long long int> row;
-  row.push_back(Display_row);
-  write_memory(current_instruction().products[0], row);
-  vector<long long int> column;
-  column.push_back(Display_column);
-  write_memory(current_instruction().products[1], column);
+  products.resize(2);
+  products.at(0).push_back(Display_row);
+  products.at(1).push_back(Display_column);
   break;
 }
 
@@ -105,10 +103,10 @@ MOVE_CURSOR_ON_DISPLAY,
 Recipe_number["move-cursor-on-display"] = MOVE_CURSOR_ON_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case MOVE_CURSOR_ON_DISPLAY: {
-  vector<long long int> row = read_memory(current_instruction().ingredients[0]);
-  vector<long long int> column = read_memory(current_instruction().ingredients[1]);
-  Display_row = row[0];
-  Display_column = column[0];
+  assert(ingredients.at(0).size() == 1);  // scalar
+  Display_row = ingredients.at(0).at(0);
+  assert(ingredients.at(1).size() == 1);  // scalar
+  Display_column = ingredients.at(1).at(0);
   tb_set_cursor(Display_column, Display_row);
   tb_present();
   break;
@@ -170,18 +168,12 @@ WAIT_FOR_KEY_FROM_KEYBOARD,
 Recipe_number["wait-for-key-from-keyboard"] = WAIT_FOR_KEY_FROM_KEYBOARD;
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_KEY_FROM_KEYBOARD: {
-//? LOG << "AAA\n";  LOG.flush();
   struct tb_event event;
   do {
     tb_poll_event(&event);
   } while (event.type != TB_EVENT_KEY);
-//? LOG << "AAA 2\n";  LOG.flush();
-  vector<long long int> result;
-  result.push_back(event.ch);
-//? LOG << "AAA 3\n";  LOG.flush();
-  if (!current_instruction().products.empty())
-    write_memory(current_instruction().products[0], result);
-//? LOG << "AAA 9\n";  LOG.flush();
+  products.resize(1);
+  products.at(0).push_back(event.ch);
   break;
 }
 
@@ -193,18 +185,15 @@ Recipe_number["read-key-from-keyboard"] = READ_KEY_FROM_KEYBOARD;
 case READ_KEY_FROM_KEYBOARD: {
   struct tb_event event;
   int event_type = tb_peek_event(&event, 5/*ms*/);
-  vector<long long int> result;
-  vector<long long int> found;
-  if (event_type != TB_EVENT_KEY) {
-    result.push_back(0);
-    found.push_back(false);
+  long long int result = 0;
+  long long int found = false;
+  if (event_type == TB_EVENT_KEY) {
+    result = event.ch;
+    found = true;
   }
-  else {
-    result.push_back(event.ch);
-    found.push_back(true);
-  }
-  write_memory(current_instruction().products[0], result);
-  write_memory(current_instruction().products[1], found);
+  products.resize(2);
+  products.at(0).push_back(result);
+  products.at(1).push_back(found);
   break;
 }
 
diff --git a/071print.mu b/071print.mu
index e02e8483..c78bc069 100644
--- a/071print.mu
+++ b/071print.mu
@@ -56,8 +56,7 @@ recipe print-character [
   default-space:address:array:location <- new location:type, 30:literal
   x:address:screen <- next-ingredient
   c:character <- next-ingredient
-#?   $print x:address:character #? 1
-#?   $print [ print-character #? 1
+#?   $print x:address:character, [ print-character #? 1
 #? ] #? 1
   {
     # if x exists
@@ -66,11 +65,7 @@ recipe print-character [
 #? ] #? 1
     # save character in fake screen
     row:address:integer <- get-address x:address:screen/deref, cursor-row:offset
-#?     $print [CCC: ] #? 1
-#?     $print row:address:integer #? 1
-#?     $print [ -> ] #? 1
-#?     $print row:address:integer/deref #? 1
-#?     $print [ #? 1
+#?     $print [CCC: ], row:address:integer, [ -> ], row:address:integer/deref, [ #? 1
 #? ] #? 1
 #?     $stop-tracing #? 1
     column:address:integer <- get-address x:address:screen/deref, cursor-column:offset
@@ -79,8 +74,7 @@ recipe print-character [
     index:integer <- add index:integer, column:address:integer/deref
     buf:address:array:character <- get x:address:screen/deref, data:offset
     cursor:address:character <- index-address buf:address:array:character/deref, index:integer
-#?     $print cursor:address:character #? 1
-#?     $print [ #? 1
+#?     $print cursor:address:character, [ #? 1
 #? ] #? 1
     cursor:address:character/deref <- copy c:character  # todo: newline, etc.
     # increment column unless it's already all the way to the right
@@ -207,18 +201,10 @@ recipe cursor-down [
       at-bottom?:boolean <- greater-or-equal row:address:integer/deref, height:integer
       break-if at-bottom?:boolean
       # row = row+1
-#?       $print [AAA: ] #? 1
-#?       $print row:address:integer #? 1
-#?       $print [ -> ] #? 1
-#?       $print row:address:integer/deref #? 1
-#?       $print [ #? 1
+#?       $print [AAA: ], row:address:integer, [ -> ], row:address:integer/deref, [ #? 1
 #? ] #? 1
       row:address:integer/deref <- add row:address:integer/deref, 1:literal
-#?       $print [BBB: ] #? 1
-#?       $print row:address:integer #? 1
-#?       $print [ -> ] #? 1
-#?       $print row:address:integer/deref #? 1
-#?       $print [ #? 1
+#?       $print [BBB: ], row:address:integer, [ -> ], row:address:integer/deref, [ #? 1
 #? ] #? 1
 #?       $start-tracing #? 1
     }
diff --git a/072scenario_screen.cc b/072scenario_screen.cc
index 993639dd..e2a70726 100644
--- a/072scenario_screen.cc
+++ b/072scenario_screen.cc
@@ -66,7 +66,7 @@ if (curr.name == "assume-screen") {
   curr.operation = Recipe_number["init-fake-screen"];
   assert(curr.products.empty());
   curr.products.push_back(reagent("screen:address"));
-  curr.products[0].set_value(SCREEN);
+  curr.products.at(0).set_value(SCREEN);
 //? cout << "after: " << curr.to_string() << '\n'; //? 1
 //? cout << "AAA " << Recipe_number["init-fake-screen"] << '\n'; //? 1
 }
@@ -79,7 +79,7 @@ Recipe_number["screen-should-contain"] = SCREEN_SHOULD_CONTAIN;
 :(before "End Primitive Recipe Implementations")
 case SCREEN_SHOULD_CONTAIN: {
 //?   cout << "AAA\n"; //? 1
-  check_screen(current_instruction().ingredients[0].name);
+  check_screen(current_instruction().ingredients.at(0).name);
   break;
 }
 
@@ -117,11 +117,11 @@ void check_screen(const string& contents) {
   ++screen_data_start;  // now skip length
   for (index_t i = 0; i < expected_contents.size(); ++i) {
     trace("run") << "checking location " << screen_data_start+i;
-//?     cerr << "comparing " << i/screen_width << ", " << i%screen_width << ": " << Memory[screen_data_start+i] << " vs " << (int)expected_contents[i] << '\n'; //? 1
-    if ((!Memory[screen_data_start+i] && !isspace(expected_contents[i]))  // uninitialized memory => spaces
-        || (Memory[screen_data_start+i] && Memory[screen_data_start+i] != expected_contents[i])) {
+//?     cerr << "comparing " << i/screen_width << ", " << i%screen_width << ": " << Memory[screen_data_start+i] << " vs " << (int)expected_contents.at(i) << '\n'; //? 1
+    if ((!Memory[screen_data_start+i] && !isspace(expected_contents.at(i)))  // uninitialized memory => spaces
+        || (Memory[screen_data_start+i] && Memory[screen_data_start+i] != expected_contents.at(i))) {
 //?       cerr << "CCC " << Trace_stream << " " << Hide_warnings << '\n'; //? 1
-      raise << "expected screen location (" << i/screen_width << ", " << i%screen_width << ") to contain '" << expected_contents[i] << "' instead of '" << static_cast<char>(Memory[screen_data_start+i]) << "'\n";
+      raise << "expected screen location (" << i/screen_width << ", " << i%screen_width << ") to contain '" << expected_contents.at(i) << "' instead of '" << static_cast<char>(Memory[screen_data_start+i]) << "'\n";
       Passed = false;
       return;
     }
diff --git a/075scenario_keyboard.cc b/075scenario_keyboard.cc
index 92e5a7a8..388df7c7 100644
--- a/075scenario_keyboard.cc
+++ b/075scenario_keyboard.cc
@@ -41,15 +41,15 @@ if (curr.name == "assume-keyboard") {
   curr.operation = Recipe_number["new"];
   assert(curr.products.empty());
   curr.products.push_back(reagent("keyboard:address"));
-  curr.products[0].set_value(KEYBOARD);
+  curr.products.at(0).set_value(KEYBOARD);
   result.steps.push_back(curr);  // hacky that "Rewrite Instruction" is converting to multiple instructions
   // leave second instruction in curr
   curr.clear();
   curr.operation = Recipe_number["init-fake-keyboard"];
   assert(curr.ingredients.empty());
   curr.ingredients.push_back(reagent("keyboard:address"));
-  curr.ingredients[0].set_value(KEYBOARD);
+  curr.ingredients.at(0).set_value(KEYBOARD);
   assert(curr.products.empty());
   curr.products.push_back(reagent("keyboard:address"));
-  curr.products[0].set_value(KEYBOARD);
+  curr.products.at(0).set_value(KEYBOARD);
 }
diff --git a/build_and_test_until b/build_and_test_until
index 916e93e8..25db949f 100755
--- a/build_and_test_until
+++ b/build_and_test_until
@@ -8,4 +8,4 @@ make tangle/tangle
 make enumerate/enumerate
 ./tangle/tangle $(./enumerate/enumerate --until $* |grep -v '.mu$') |grep -v "^\s*//:" > mu.cc
 cat /dev/null $(./enumerate/enumerate --until $* |grep '.mu$') > core.mu
-make valgrind
+make test
diff --git a/channel.mu b/channel.mu
index c20fa804..a0c2b6dd 100644
--- a/channel.mu
+++ b/channel.mu
@@ -8,9 +8,7 @@ recipe producer [
     done?:boolean <- lesser-than n:integer, 5:literal
     break-unless done?:boolean
     # other threads might get between these prints
-    $print [produce: ]
-    $print n:integer
-    $print [
+    $print [produce: ], n:integer, [
 ]
     chan:address:channel <- write chan:address:channel, n:integer
     n:integer <- add n:integer, 1:literal
@@ -26,9 +24,7 @@ recipe consumer [
     # read an integer from the channel
     n:integer, chan:address:channel <- read chan:address:channel
     # other threads might get between these prints
-    $print [consume: ]
-    $print n:integer
-    $print [
+    $print [consume: ], n:integer, [
 ]
     loop
   }
diff --git a/counters.mu b/counters.mu
index 4662b833..98199072 100644
--- a/counters.mu
+++ b/counters.mu
@@ -26,10 +26,6 @@ recipe main [
   bres:integer <- increment-counter b:address:space, 2:literal
   ares:integer <- increment-counter a:address:space, 1:literal
   # check results
-  $print [Contents of counters a: ]
-  $print ares:integer
-  $print [ b: ]
-  $print bres:integer
-  $print [
+  $print [Contents of counters a: ], ares:integer, [ b: ], bres:integer, [
 ]
 ]
diff --git a/factorial.mu b/factorial.mu
index 8dd2f23f..4b2126a8 100644
--- a/factorial.mu
+++ b/factorial.mu
@@ -3,9 +3,7 @@
 recipe main [
   default-space:address:space <- new location:type, 30:literal
   x:integer <- factorial 5:literal
-  $print [result: ]
-  $print x:integer
-  $print [
+  $print [result: ], x:integer, [
 ]
 ]
 
diff --git a/fork.mu b/fork.mu
index eb1a2ca1..1e3bac99 100644
--- a/fork.mu
+++ b/fork.mu
@@ -3,7 +3,8 @@ recipe main [
   default-space:address:array:location <- new location:type, 2:literal
   x:integer <- copy 34:literal
   {
-    $print x:integer
+    $print x:integer, [
+]
     loop
   }
 ]
@@ -12,7 +13,8 @@ recipe thread2 [
   default-space:address:array:location <- new location:type, 2:literal
   y:integer <- copy 35:literal
   {
-    $print y:integer
+    $print y:integer, [
+]
     loop
   }
 ]
diff --git a/tangle.mu b/tangle.mu
index db890bc8..10829ca5 100644
--- a/tangle.mu
+++ b/tangle.mu
@@ -29,8 +29,6 @@ after +recursive-case [
 
 recipe main [
   1:integer <- factorial 5:literal
-  $print [result: ]
-  $print 1:integer
-  $print [
+  $print [result: ], 1:integer, [
 ]
 ]