about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-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, [
 ]
 ]