about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-07-25 14:19:28 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-07-25 17:14:58 -0700
commite46306432ddb75a89f69d92ccc175a23f0b72072 (patch)
tree48ed3828064f29cefaf14e3fe61d7dc02cac0e80
parente83602d3917eba137cd8fb37605076fff5a746b1 (diff)
downloadmu-e46306432ddb75a89f69d92ccc175a23f0b72072.tar.gz
1848 - core instructions now check for ingredients
Also standardized warnings.
-rw-r--r--021arithmetic.cc12
-rw-r--r--022boolean.cc2
-rw-r--r--023jump.cc40
-rw-r--r--024compare.cc44
-rw-r--r--027trace.cc4
-rw-r--r--028assert.cc15
-rw-r--r--030container.cc10
-rw-r--r--031address.cc10
-rw-r--r--032array.cc19
-rw-r--r--033exclusive_container.cc16
-rw-r--r--034call.cc2
-rw-r--r--035call_ingredient.cc15
-rw-r--r--036call_reply.cc69
-rw-r--r--037recipe.cc12
-rw-r--r--038scheduler.cc27
-rw-r--r--039wait.cc22
-rw-r--r--041jump_label.cc12
-rw-r--r--042name.cc13
-rw-r--r--043new.cc34
-rw-r--r--044space.cc12
-rw-r--r--045space_surround.cc3
-rw-r--r--046closure_name.cc9
-rw-r--r--047global.cc4
-rw-r--r--049continuation.cc24
-rw-r--r--064random.cc9
-rw-r--r--070display.cc40
-rw-r--r--081run_interactive.cc19
-rw-r--r--082persist.cc14
28 files changed, 358 insertions, 154 deletions
diff --git a/021arithmetic.cc b/021arithmetic.cc
index 0701e82a..51df6f46 100644
--- a/021arithmetic.cc
+++ b/021arithmetic.cc
@@ -43,6 +43,10 @@ SUBTRACT,
 Recipe_ordinal["subtract"] = SUBTRACT;
 :(before "End Primitive Recipe Implementations")
 case SUBTRACT: {
+  if (ingredients.empty()) {
+    raise << current_recipe_name() << ": 'subtract' has no ingredients\n" << end();
+    break;
+  }
   assert(scalar(ingredients.at(0)));
   double result = ingredients.at(0).at(0);
   for (long long int i = 1; i < SIZE(ingredients); ++i) {
@@ -116,6 +120,10 @@ DIVIDE,
 Recipe_ordinal["divide"] = DIVIDE;
 :(before "End Primitive Recipe Implementations")
 case DIVIDE: {
+  if (ingredients.empty()) {
+    raise << current_recipe_name() << ": 'divide' has no ingredients\n" << end();
+    break;
+  }
   assert(scalar(ingredients.at(0)));
   double result = ingredients.at(0).at(0);
   for (long long int i = 1; i < SIZE(ingredients); ++i) {
@@ -155,6 +163,10 @@ DIVIDE_WITH_REMAINDER,
 Recipe_ordinal["divide-with-remainder"] = DIVIDE_WITH_REMAINDER;
 :(before "End Primitive Recipe Implementations")
 case DIVIDE_WITH_REMAINDER: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'divide-with-remainder' requires exactly two ingredients, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
   long long int quotient = ingredients.at(0).at(0) / ingredients.at(1).at(0);
   long long int remainder = static_cast<long long int>(ingredients.at(0).at(0)) % static_cast<long long int>(ingredients.at(1).at(0));
   products.resize(2);
diff --git a/022boolean.cc b/022boolean.cc
index 86b2f16d..bebe2204 100644
--- a/022boolean.cc
+++ b/022boolean.cc
@@ -90,7 +90,7 @@ NOT,
 Recipe_ordinal["not"] = NOT;
 :(before "End Primitive Recipe Implementations")
 case NOT: {
-  products.resize(ingredients.size());
+  products.resize(SIZE(ingredients));
   for (long long int i = 0; i < SIZE(ingredients); ++i) {
     assert(scalar(ingredients.at(i)));
     products.at(i).push_back(!ingredients.at(i).at(0));
diff --git a/023jump.cc b/023jump.cc
index edccf207..af713628 100644
--- a/023jump.cc
+++ b/023jump.cc
@@ -16,9 +16,15 @@ JUMP,
 Recipe_ordinal["jump"] = JUMP;
 :(before "End Primitive Recipe Implementations")
 case JUMP: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'jump' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'jump' should be a label or offset, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   assert(current_instruction().ingredients.at(0).initialized);
-  assert(SIZE(ingredients) == 1);
-  assert(scalar(ingredients.at(0)));
   current_step_index() += ingredients.at(0).at(0)+1;
   trace(Primitive_recipe_depth, "run") << "jumping to instruction " << current_step_index() << end();
   continue;  // skip rest of this instruction
@@ -45,14 +51,23 @@ JUMP_IF,
 Recipe_ordinal["jump-if"] = JUMP_IF;
 :(before "End Primitive Recipe Implementations")
 case JUMP_IF: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'jump-if' requires exactly two ingredients, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": 'jump-if' requires a boolean for its first ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(1))) {
+    raise << current_recipe_name() << ": 'jump-if' requires a label or offset for its second ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   assert(current_instruction().ingredients.at(1).initialized);
-  assert(SIZE(ingredients) == 2);
-  assert(scalar(ingredients.at(0)));
   if (!ingredients.at(0).at(0)) {
     trace(Primitive_recipe_depth, "run") << "jump-if fell through" << end();
     break;
   }
-  assert(scalar(ingredients.at(1)));
   current_step_index() += ingredients.at(1).at(0)+1;
   trace(Primitive_recipe_depth, "run") << "jumping to instruction " << current_step_index() << end();
   continue;  // skip rest of this instruction
@@ -84,14 +99,23 @@ JUMP_UNLESS,
 Recipe_ordinal["jump-unless"] = JUMP_UNLESS;
 :(before "End Primitive Recipe Implementations")
 case JUMP_UNLESS: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'jump-unless' requires exactly two ingredients, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": 'jump-unless' requires a boolean for its first ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(1))) {
+    raise << current_recipe_name() << ": 'jump-unless' requires a label or offset for its second ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   assert(current_instruction().ingredients.at(1).initialized);
-  assert(SIZE(ingredients) == 2);
-  assert(scalar(ingredients.at(0)));
   if (ingredients.at(0).at(0)) {
     trace(Primitive_recipe_depth, "run") << "jump-unless fell through" << end();
     break;
   }
-  assert(scalar(ingredients.at(1)));
   current_step_index() += ingredients.at(1).at(0)+1;
   trace(Primitive_recipe_depth, "run") << "jumping to instruction " << current_step_index() << end();
   continue;  // skip rest of this instruction
diff --git a/024compare.cc b/024compare.cc
index c037c381..cd31065b 100644
--- a/024compare.cc
+++ b/024compare.cc
@@ -6,6 +6,10 @@ EQUAL,
 Recipe_ordinal["equal"] = EQUAL;
 :(before "End Primitive Recipe Implementations")
 case EQUAL: {
+  if (SIZE(ingredients) <= 1) {
+    raise << current_recipe_name() << ": 'equal' needs at least two ingredients to compare in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   vector<double>& exemplar = ingredients.at(0);
   bool result = true;
   for (long long int i = 1; i < SIZE(ingredients); ++i) {
@@ -58,14 +62,22 @@ Recipe_ordinal["greater-than"] = GREATER_THAN;
 :(before "End Primitive Recipe Implementations")
 case GREATER_THAN: {
   bool result = true;
+  if (SIZE(ingredients) <= 1) {
+    raise << current_recipe_name() << ": 'greater-than' needs at least two ingredients to compare in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   for (long long int i = 0; i < SIZE(ingredients); ++i) {
-    assert(scalar(ingredients.at(i)));
+    if (!scalar(ingredients.at(i))) {
+      raise << current_recipe_name() << ": 'greater-than' can only compare numbers; got " << current_instruction().ingredients.at(i).original_string << '\n' << end();
+      goto finish_greater_than;
+    }
   }
   for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
     if (ingredients.at(i-1).at(0) <= ingredients.at(i).at(0)) {
       result = false;
     }
   }
+  finish_greater_than:
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -106,14 +118,22 @@ Recipe_ordinal["lesser-than"] = LESSER_THAN;
 :(before "End Primitive Recipe Implementations")
 case LESSER_THAN: {
   bool result = true;
+  if (SIZE(ingredients) <= 1) {
+    raise << current_recipe_name() << ": 'lesser-than' needs at least two ingredients to compare in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   for (long long int i = 0; i < SIZE(ingredients); ++i) {
-    assert(scalar(ingredients.at(i)));
+    if (!scalar(ingredients.at(i))) {
+      raise << current_recipe_name() << ": 'lesser-than' can only compare numbers; got " << current_instruction().ingredients.at(i).original_string << '\n' << end();
+      goto finish_lesser_than;
+    }
   }
   for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
     if (ingredients.at(i-1).at(0) >= ingredients.at(i).at(0)) {
       result = false;
     }
   }
+  finish_lesser_than:
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -154,14 +174,22 @@ Recipe_ordinal["greater-or-equal"] = GREATER_OR_EQUAL;
 :(before "End Primitive Recipe Implementations")
 case GREATER_OR_EQUAL: {
   bool result = true;
+  if (SIZE(ingredients) <= 1) {
+    raise << current_recipe_name() << ": 'greater-or-equal' needs at least two ingredients to compare in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   for (long long int i = 0; i < SIZE(ingredients); ++i) {
-    assert(scalar(ingredients.at(i)));
+    if (!scalar(ingredients.at(i))) {
+      raise << current_recipe_name() << ": 'greater-or-equal' can only compare numbers; got " << current_instruction().ingredients.at(i).original_string << '\n' << end();
+      goto finish_greater_or_equal;
+    }
   }
   for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
     if (ingredients.at(i-1).at(0) < ingredients.at(i).at(0)) {
       result = false;
     }
   }
+  finish_greater_or_equal:
   products.resize(1);
   products.at(0).push_back(result);
   break;
@@ -210,14 +238,22 @@ Recipe_ordinal["lesser-or-equal"] = LESSER_OR_EQUAL;
 :(before "End Primitive Recipe Implementations")
 case LESSER_OR_EQUAL: {
   bool result = true;
+  if (SIZE(ingredients) <= 1) {
+    raise << current_recipe_name() << ": 'lesser-or-equal' needs at least two ingredients to compare in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   for (long long int i = 0; i < SIZE(ingredients); ++i) {
-    assert(scalar(ingredients.at(i)));
+    if (!scalar(ingredients.at(i))) {
+      raise << current_recipe_name() << ": 'lesser-or-equal' can only compare numbers; got " << current_instruction().ingredients.at(i).original_string << '\n' << end();
+      goto finish_lesser_or_equal;
+    }
   }
   for (long long int i = /**/1; i < SIZE(ingredients); ++i) {
     if (ingredients.at(i-1).at(0) > ingredients.at(i).at(0)) {
       result = false;
     }
   }
+  finish_lesser_or_equal:
   products.resize(1);
   products.at(0).push_back(result);
   break;
diff --git a/027trace.cc b/027trace.cc
index 12cc7ca3..742432fa 100644
--- a/027trace.cc
+++ b/027trace.cc
@@ -12,6 +12,10 @@ TRACE,
 Recipe_ordinal["trace"] = TRACE;
 :(before "End Primitive Recipe Implementations")
 case TRACE: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'trace' takes exactly two ingredients rather than '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   assert(is_literal(current_instruction().ingredients.at(0)));
   string label = current_instruction().ingredients.at(0).name;
   assert(is_literal(current_instruction().ingredients.at(1)));
diff --git a/028assert.cc b/028assert.cc
index 07f68cc8..6cdb2ba0 100644
--- a/028assert.cc
+++ b/028assert.cc
@@ -11,10 +11,19 @@ ASSERT,
 Recipe_ordinal["assert"] = ASSERT;
 :(before "End Primitive Recipe Implementations")
 case ASSERT: {
-  assert(SIZE(ingredients) == 2);
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'assert' takes exactly two ingredients rather than '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": 'assert' requires a boolean for its first ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(1))) {
+    raise << current_recipe_name() << ": 'assert' requires a literal string for its second ingredient, but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
+    break;
+  }
   if (!ingredients.at(0).at(0)) {
-    assert(is_literal(current_instruction().ingredients.at(1)));
     raise << current_instruction().ingredients.at(1).name << '\n' << end();
   }
   break;
diff --git a/030container.cc b/030container.cc
index 79ab71e1..76f05d05 100644
--- a/030container.cc
+++ b/030container.cc
@@ -107,7 +107,7 @@ GET,
 Recipe_ordinal["get"] = GET;
 :(before "End Primitive Recipe Implementations")
 case GET: {
-  if (ingredients.size() != 2) {
+  if (SIZE(ingredients) != 2) {
     raise << current_recipe_name() << ": 'get' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
@@ -115,11 +115,11 @@ case GET: {
   long long int base_address = base.value;
   type_ordinal base_type = base.types.at(0);
   if (Type[base_type].kind != container) {
-    raise << current_recipe_name () << ": 'get' on a non-container " << base.original_string << '\n' << end();
+    raise << current_recipe_name () << ": first ingredient of 'get' should be a container, but got " << base.original_string << '\n' << end();
     break;
   }
   if (!is_literal(current_instruction().ingredients.at(1))) {
-    raise << current_recipe_name() << ": expected ingredient 1 of 'get' to have type 'offset', got '" << current_instruction().ingredients.at(1).original_string << "'\n" << end();
+    raise << current_recipe_name() << ": second ingredient of 'get' should have type 'offset', but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
     break;
   }
   assert(scalar(ingredients.at(1)));
@@ -192,11 +192,11 @@ case GET_ADDRESS: {
   long long int base_address = base.value;
   type_ordinal base_type = base.types.at(0);
   if (Type[base_type].kind != container) {
-    raise << current_recipe_name () << ": 'get-address' on a non-container " << base.original_string << '\n' << end();
+    raise << current_recipe_name () << ": first ingredient of 'get-address' should be a container, but got " << base.original_string << '\n' << end();
     break;
   }
   if (!is_literal(current_instruction().ingredients.at(1))) {
-    raise << current_recipe_name() << ": expected ingredient 1 of 'get-address' to have type 'offset', got '" << current_instruction().ingredients.at(1).original_string << "'\n" << end();
+    raise << current_recipe_name() << ": second ingredient of 'get-address' should have type 'offset', but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
     break;
   }
   assert(scalar(ingredients.at(1)));
diff --git a/031address.cc b/031address.cc
index 0e2a4f0f..51a6a960 100644
--- a/031address.cc
+++ b/031address.cc
@@ -40,9 +40,15 @@ reagent deref(reagent x) {
 //?   cout << "deref: " << x.to_string() << "\n"; //? 2
   static const type_ordinal ADDRESS = Type_ordinal["address"];
   reagent result;
-  assert(x.types.at(0) == ADDRESS);
-
+  if (x.types.at(0) != ADDRESS) {
+    raise << current_recipe_name() << ": tried to /deref " << x.original_string << " but it isn't an address\n" << end();
+    return result;
+  }
   // compute value
+  if (x.value == 0) {
+    raise << current_recipe_name() << ": tried to /deref 0\n" << end();
+    return result;
+  }
   result.set_value(Memory[x.value]);
   trace(Primitive_recipe_depth, "mem") << "location " << x.value << " is " << result.value << end();
 
diff --git a/032array.cc b/032array.cc
index f9134e87..a9274b49 100644
--- a/032array.cc
+++ b/032array.cc
@@ -79,20 +79,19 @@ INDEX,
 Recipe_ordinal["index"] = INDEX;
 :(before "End Primitive Recipe Implementations")
 case INDEX: {
-//?   if (Trace_stream) Trace_stream->dump_layer = "run"; //? 1
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'index' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   reagent base = canonize(current_instruction().ingredients.at(0));
-//?   trace(Primitive_recipe_depth, "run") << "ingredient 0 after canonize: " << base.to_string() << end(); //? 1
   long long int base_address = base.value;
   if (base.types.at(0) != Type_ordinal["array"]) {
     raise << current_recipe_name () << ": 'index' on a non-array " << base.original_string << '\n' << end();
     break;
   }
   reagent offset = canonize(current_instruction().ingredients.at(1));
-//?   trace(Primitive_recipe_depth, "run") << "ingredient 1 after canonize: " << offset.to_string() << end(); //? 1
   vector<double> offset_val(read_memory(offset));
   vector<type_ordinal> element_type = array_element(base.types);
-//?   trace(Primitive_recipe_depth, "run") << "offset: " << offset_val.at(0) << end(); //? 1
-//?   trace(Primitive_recipe_depth, "run") << "size of elements: " << size_of(element_type) << end(); //? 1
   if (offset_val.at(0) < 0 || offset_val.at(0) >= Memory[base_address]) {
     raise << current_recipe_name() << ": invalid index " << offset_val.at(0) << '\n' << end();
     products.resize(1);
@@ -172,6 +171,10 @@ INDEX_ADDRESS,
 Recipe_ordinal["index-address"] = INDEX_ADDRESS;
 :(before "End Primitive Recipe Implementations")
 case INDEX_ADDRESS: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'index-address' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   reagent base = canonize(current_instruction().ingredients.at(0));
   long long int base_address = base.value;
   if (base.types.at(0) != Type_ordinal["array"]) {
@@ -240,9 +243,13 @@ LENGTH,
 Recipe_ordinal["length"] = LENGTH;
 :(before "End Primitive Recipe Implementations")
 case LENGTH: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'length' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   reagent x = canonize(current_instruction().ingredients.at(0));
   if (x.types.at(0) != Type_ordinal["array"]) {
-    raise << "tried to calculate length of non-array " << x.to_string() << '\n' << end();
+    raise << "tried to calculate length of non-array " << x.original_string << '\n' << end();
     break;
   }
   products.resize(1);
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index c212be79..804d649f 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -11,16 +11,12 @@ type_ordinal tmp = Type_ordinal["number-or-point"] = Next_type_ordinal++;
 Type[tmp].size = 2;
 Type[tmp].kind = exclusive_container;
 Type[tmp].name = "number-or-point";
-//? cout << tmp << ": " << SIZE(Type[tmp].elements) << '\n'; //? 1
 vector<type_ordinal> t1;
 t1.push_back(number);
 Type[tmp].elements.push_back(t1);
-//? cout << SIZE(Type[tmp].elements) << '\n'; //? 1
 vector<type_ordinal> t2;
 t2.push_back(point);
 Type[tmp].elements.push_back(t2);
-//? cout << SIZE(Type[tmp].elements) << '\n'; //? 1
-//? cout << "point: " << point << '\n'; //? 1
 Type[tmp].element_names.push_back("i");
 Type[tmp].element_names.push_back("p");
 }
@@ -44,13 +40,9 @@ 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.at(0) << ' ' << t.size << '\n'; //? 1
-//?   cout << "point: " << Type_ordinal["point"] << " " << Type[Type_ordinal["point"]].name << " " << Type[Type_ordinal["point"]].size << '\n'; //? 1
-//?   cout << t.name << ' ' << t.size << ' ' << SIZE(t.elements) << '\n'; //? 1
   long long int result = 0;
   for (long long int i = 0; i < t.size; ++i) {
     long long int 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.
@@ -91,15 +83,19 @@ MAYBE_CONVERT,
 Recipe_ordinal["maybe-convert"] = MAYBE_CONVERT;
 :(before "End Primitive Recipe Implementations")
 case MAYBE_CONVERT: {
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'maybe-convert' expects exactly 2 ingredients in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   reagent base = canonize(current_instruction().ingredients.at(0));
   long long int base_address = base.value;
   type_ordinal base_type = base.types.at(0);
   if (Type[base_type].kind != exclusive_container) {
-    raise << current_recipe_name () << ": 'maybe-convert' on a non-exclusive-container " << base.original_string << '\n' << end();
+    raise << current_recipe_name () << ": first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end();
     break;
   }
   if (!is_literal(current_instruction().ingredients.at(1))) {
-    raise << current_recipe_name() << ": expected ingredient 1 of 'get' to have type 'variant', got '" << current_instruction().ingredients.at(1).original_string << "'\n" << end();
+    raise << current_recipe_name() << ": second ingredient of 'maybe-convert' should have type 'variant', but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
     break;
   }
   long long int tag = current_instruction().ingredients.at(1).value;
diff --git a/034call.cc b/034call.cc
index cafabd53..16b6ca41 100644
--- a/034call.cc
+++ b/034call.cc
@@ -85,7 +85,7 @@ default: {
     break;
   }
   Current_routine->calls.push_front(call(current_instruction().operation));
-complete_call:
+  call_housekeeping:
   ++Callstack_depth;
   assert(Callstack_depth < 9000);  // 9998-101 plus cushion
   continue;  // not done with caller; don't increment current_step_index()
diff --git a/035call_ingredient.cc b/035call_ingredient.cc
index fa8e012e..7ffa1207 100644
--- a/035call_ingredient.cc
+++ b/035call_ingredient.cc
@@ -26,7 +26,7 @@ long long int next_ingredient_to_process;
 :(before "End call Constructor")
 next_ingredient_to_process = 0;
 
-:(after "complete_call:")
+:(after "call_housekeeping:")
 for (long long int i = 0; i < SIZE(ingredients); ++i) {
   Current_routine->calls.front().ingredient_atoms.push_back(ingredients.at(i));
 }
@@ -37,6 +37,10 @@ NEXT_INGREDIENT,
 Recipe_ordinal["next-ingredient"] = NEXT_INGREDIENT;
 :(before "End Primitive Recipe Implementations")
 case NEXT_INGREDIENT: {
+  if (!ingredients.empty()) {
+    raise << current_recipe_name() << ": 'next-ingredient' didn't expect any ingredients in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   assert(!Current_routine->calls.empty());
   if (Current_routine->calls.front().next_ingredient_to_process < SIZE(Current_routine->calls.front().ingredient_atoms)) {
     products.push_back(
@@ -95,7 +99,14 @@ INGREDIENT,
 Recipe_ordinal["ingredient"] = INGREDIENT;
 :(before "End Primitive Recipe Implementations")
 case INGREDIENT: {
-  assert(is_literal(current_instruction().ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'ingredient' expects exactly one ingredient, but got '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
+  if (!is_literal(current_instruction().ingredients.at(0))) {
+    raise << current_recipe_name() << ": 'ingredient' expects a literal ingredient, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   assert(scalar(ingredients.at(0)));
   if (static_cast<long long int>(ingredients.at(0).at(0)) < SIZE(Current_routine->calls.front().ingredient_atoms)) {
     Current_routine->calls.front().next_ingredient_to_process = ingredients.at(0).at(0);
diff --git a/036call_reply.cc b/036call_reply.cc
index 75587351..a5f265a2 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -22,10 +22,6 @@ case REPLY: {
   const instruction& reply_inst = current_instruction();  // save pointer into recipe before pop
   const string& callee = current_recipe_name();
   --Callstack_depth;
-//?   if (tb_is_active()) { //? 1
-//?     tb_clear(); //? 1
-//?     cerr << Recipe[Current_routine->calls.front().running_recipe].name << ' ' << current_step_index() << '\n'; //? 1
-//?   } //? 1
   Current_routine->calls.pop_front();
   // just in case 'main' returns a value, drop it for now
   if (Current_routine->calls.empty()) goto stop_running_current_routine;
@@ -37,21 +33,22 @@ case REPLY: {
   if (SIZE(caller_instruction.products) > SIZE(ingredients))
     raise << "too few values replied from " << callee << '\n' << end();
   for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) {
-//?     cerr << Recipe[Current_routine->calls.front().running_recipe].name << '\n'; //? 1
     trace(Primitive_recipe_depth, "run") << "result " << i << " is " << to_string(ingredients.at(i)) << end();
     if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) {
       vector<string> tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
-      assert(SIZE(tmp) == 1);
+      if (SIZE(tmp) != 1) {
+        raise << current_recipe_name() << ": 'same-as-ingredient' metadata should take exactly one value in " << reply_inst.to_string() << '\n' << end();
+        goto finish_reply;
+      }
       long long int ingredient_index = to_integer(tmp.at(0));
       if (ingredient_index >= SIZE(caller_instruction.ingredients))
         raise << current_recipe_name() << ": 'same-as-ingredient' metadata overflows ingredients in: " << caller_instruction.to_string() << '\n' << end();
-//?       cerr << caller_instruction.products.size() << ' ' << i << ' ' << caller_instruction.ingredients.size() << ' ' << ingredient_index << '\n'; //? 1
-//?       cerr << caller_instruction.to_string() << '\n'; //? 1
       if (!is_dummy(caller_instruction.products.at(i)) && caller_instruction.products.at(i).value != caller_instruction.ingredients.at(ingredient_index).value)
         raise << current_recipe_name() << ": 'same-as-ingredient' result " << caller_instruction.products.at(i).value << " from call to " << callee << " must be location " << caller_instruction.ingredients.at(ingredient_index).value << '\n' << end();
     }
   }
   // End Reply
+  finish_reply:
   break;  // continue to process rest of *caller* instruction
 }
 
@@ -144,18 +141,22 @@ recipe test1 [
 //   reply b, c, ...
 //   ```
 if (curr.name == "reply-if") {
-  assert(curr.products.empty());
-  curr.operation = Recipe_ordinal["jump-unless"];
-  curr.name = "jump-unless";
-  vector<reagent> results;
-  copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
-  curr.ingredients.resize(1);
-  curr.ingredients.push_back(reagent("1:offset"));
-  result.steps.push_back(curr);
-  curr.clear();
-  curr.operation = Recipe_ordinal["reply"];
-  curr.name = "reply";
-  curr.ingredients.swap(results);
+  if (curr.products.empty()) {
+    curr.operation = Recipe_ordinal["jump-unless"];
+    curr.name = "jump-unless";
+    vector<reagent> results;
+    copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
+    curr.ingredients.resize(1);
+    curr.ingredients.push_back(reagent("1:offset"));
+    result.steps.push_back(curr);
+    curr.clear();
+    curr.operation = Recipe_ordinal["reply"];
+    curr.name = "reply";
+    curr.ingredients.swap(results);
+  }
+  else {
+    raise << "'reply-if' never yields any products\n" << end();
+  }
 }
 // rewrite `reply-unless a, b, c, ...` to
 //   ```
@@ -163,16 +164,20 @@ if (curr.name == "reply-if") {
 //   reply b, c, ...
 //   ```
 if (curr.name == "reply-unless") {
-  assert(curr.products.empty());
-  curr.operation = Recipe_ordinal["jump-if"];
-  curr.name = "jump-if";
-  vector<reagent> results;
-  copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
-  curr.ingredients.resize(1);
-  curr.ingredients.push_back(reagent("1:offset"));
-  result.steps.push_back(curr);
-  curr.clear();
-  curr.operation = Recipe_ordinal["reply"];
-  curr.name = "reply";
-  curr.ingredients.swap(results);
+  if (curr.products.empty()) {
+    curr.operation = Recipe_ordinal["jump-if"];
+    curr.name = "jump-if";
+    vector<reagent> results;
+    copy(++curr.ingredients.begin(), curr.ingredients.end(), inserter(results, results.end()));
+    curr.ingredients.resize(1);
+    curr.ingredients.push_back(reagent("1:offset"));
+    result.steps.push_back(curr);
+    curr.clear();
+    curr.operation = Recipe_ordinal["reply"];
+    curr.name = "reply";
+    curr.ingredients.swap(results);
+  }
+  else {
+    raise << "'reply-unless' never yields any products\n" << end();
+  }
 }
diff --git a/037recipe.cc b/037recipe.cc
index 95e61073..41c688f5 100644
--- a/037recipe.cc
+++ b/037recipe.cc
@@ -43,11 +43,19 @@ CALL,
 Recipe_ordinal["call"] = CALL;
 :(before "End Primitive Recipe Implementations")
 case CALL: {
-  assert(scalar(ingredients.at(0)));
+  if (ingredients.empty()) {
+    raise << current_recipe_name() << ": 'call' requires at least one ingredient (the recipe to call)\n" << end();
+    break;
+  }
+  // Begin Call
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'call' should be a recipe, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   // todo: when we start doing type checking this will be a prime point of
   // attention, so we don't accidentally allow external data to a program to
   // run as code.
   Current_routine->calls.push_front(call(ingredients.at(0).at(0)));
   ingredients.erase(ingredients.begin());  // drop the callee
-  goto complete_call;
+  goto call_housekeeping;
 }
diff --git a/038scheduler.cc b/038scheduler.cc
index 5334009c..a218799c 100644
--- a/038scheduler.cc
+++ b/038scheduler.cc
@@ -304,7 +304,14 @@ ROUTINE_STATE,
 Recipe_ordinal["routine-state"] = ROUTINE_STATE;
 :(before "End Primitive Recipe Implementations")
 case ROUTINE_STATE: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'routine-state' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   long long int id = ingredients.at(0).at(0);
   long long int result = -1;
   for (long long int i = 0; i < SIZE(Routines); ++i) {
@@ -326,7 +333,14 @@ RESTART,
 Recipe_ordinal["restart"] = RESTART;
 :(before "End Primitive Recipe Implementations")
 case RESTART: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'restart' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'restart' should be a routine id generated by 'start-running', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   long long int id = ingredients.at(0).at(0);
   for (long long int i = 0; i < SIZE(Routines); ++i) {
     if (Routines.at(i)->id == id) {
@@ -343,7 +357,14 @@ STOP,
 Recipe_ordinal["stop"] = STOP;
 :(before "End Primitive Recipe Implementations")
 case STOP: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'stop' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'stop' should be a routine id generated by 'start-running', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   long long int id = ingredients.at(0).at(0);
   for (long long int i = 0; i < SIZE(Routines); ++i) {
     if (Routines.at(i)->id == id) {
diff --git a/039wait.cc b/039wait.cc
index 8a40dc8a..5b589d6a 100644
--- a/039wait.cc
+++ b/039wait.cc
@@ -41,7 +41,6 @@ case WAIT_FOR_LOCATION: {
   Current_routine->waiting_on_location = loc.value;
   Current_routine->old_value_of_waiting_location = Memory[loc.value];
   trace(Primitive_recipe_depth, "run") << "waiting for location " << loc.value << " to change from " << Memory[loc.value] << end();
-//?   trace("schedule") << Current_routine->id << ": waiting for location " << loc.value << " to change from " << Memory[loc.value] << end(); //? 2
   break;
 }
 
@@ -49,12 +48,7 @@ case WAIT_FOR_LOCATION: {
 
 :(before "End Scheduler State Transitions")
 for (long long int i = 0; i < SIZE(Routines); ++i) {
-//?   trace("schedule") << "wake up loop 1: routine " << Routines.at(i)->id << " has state " << Routines.at(i)->state << end(); //? 1
   if (Routines.at(i)->state != WAITING) continue;
-//?   trace("schedule") << "waiting on location: " << Routines.at(i)->waiting_on_location << end(); //? 1
-//?   if (Routines.at(i)->waiting_on_location) //? 2
-//?     trace("schedule") << "checking routine " << Routines.at(i)->id << " waiting on location " //? 2
-//?       << Routines.at(i)->waiting_on_location << ": " << Memory[Routines.at(i)->waiting_on_location] << " vs " << Routines.at(i)->old_value_of_waiting_location; //? 2
   if (Routines.at(i)->waiting_on_location &&
       Memory[Routines.at(i)->waiting_on_location] != Routines.at(i)->old_value_of_waiting_location) {
     trace("schedule") << "waking up routine\n" << end();
@@ -96,8 +90,19 @@ WAIT_FOR_ROUTINE,
 Recipe_ordinal["wait-for-routine"] = WAIT_FOR_ROUTINE;
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_ROUTINE: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'wait-for-routine' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (ingredients.at(0).at(0) == Current_routine->id) {
+    raise << current_recipe_name() << ": routine can't wait for itself! " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
   Current_routine->state = WAITING;
-  assert(scalar(ingredients.at(0)));
   Current_routine->waiting_on_routine = ingredients.at(0).at(0);
   trace(Primitive_recipe_depth, "run") << "waiting for routine " << ingredients.at(0).at(0) << end();
   break;
@@ -111,7 +116,7 @@ for (long long int i = 0; i < SIZE(Routines); ++i) {
   if (Routines.at(i)->state != WAITING) continue;
   if (!Routines.at(i)->waiting_on_routine) continue;
   long long int id = Routines.at(i)->waiting_on_routine;
-  assert(id != Routines.at(i)->id);
+  assert(id != Routines.at(i)->id);  // routine can't wait on itself
   for (long long int j = 0; j < SIZE(Routines); ++j) {
     if (Routines.at(j)->id == id && Routines.at(j)->state != RUNNING) {
       trace("schedule") << "waking up routine " << Routines.at(i)->id << end();
@@ -130,7 +135,6 @@ case SWITCH: {
   long long int id = some_other_running_routine();
   if (id) {
     assert(id != Current_routine->id);
-//?     cerr << "waiting on " << id << " from " << Current_routine->id << '\n'; //? 1
     Current_routine->state = WAITING;
     Current_routine->waiting_on_routine = id;
   }
diff --git a/041jump_label.cc b/041jump_label.cc
index 2527e813..a24970d5 100644
--- a/041jump_label.cc
+++ b/041jump_label.cc
@@ -26,7 +26,6 @@ void transform_labels(const recipe_ordinal r) {
   for (long long int i = 0; i < SIZE(Recipe[r].steps); ++i) {
     instruction& inst = Recipe[r].steps.at(i);
     if (inst.operation == Recipe_ordinal["jump"]) {
-//?       cerr << inst.to_string() << '\n'; //? 1
       replace_offset(inst.ingredients.at(0), offset, i, r);
     }
     if (inst.operation == Recipe_ordinal["jump-if"] || inst.operation == Recipe_ordinal["jump-unless"]) {
@@ -46,15 +45,14 @@ void transform_labels(const recipe_ordinal r) {
 
 :(code)
 void replace_offset(reagent& x, /*const*/ map<string, long long int>& offset, const long long int current_offset, const recipe_ordinal r) {
-//?   cerr << "AAA " << x.to_string() << '\n'; //? 1
-  assert(is_literal(x));
-//?   cerr << "BBB " << x.to_string() << '\n'; //? 1
+  if (!is_literal(x)) {
+    raise << Recipe[r].name << ": jump target must be offset or label but is " << x.original_string << '\n' << end();
+    return;
+  }
   assert(!x.initialized);
-//?   cerr << "CCC " << x.to_string() << '\n'; //? 1
   if (is_integer(x.name)) return;  // non-labels will be handled like other number operands
-//?   cerr << "DDD " << x.to_string() << '\n'; //? 1
   if (offset.find(x.name) == offset.end())
-    raise << "can't find label " << x.name << " in routine " << Recipe[r].name << '\n' << end();
+    raise << Recipe[r].name << ": can't find label " << x.name << '\n' << end();
   x.set_value(offset[x.name]-current_offset);
 }
 
diff --git a/042name.cc b/042name.cc
index 283bcbd7..9e2d75b2 100644
--- a/042name.cc
+++ b/042name.cc
@@ -219,9 +219,10 @@ recipe main [
 // replace element names of containers with offsets
 if (inst.operation == Recipe_ordinal["get"]
     || inst.operation == Recipe_ordinal["get-address"]) {
-  // at least 2 args, and second arg is offset
-  assert(SIZE(inst.ingredients) >= 2);
-//?   cout << inst.ingredients.at(1).to_string() << '\n'; //? 1
+  if (SIZE(inst.ingredients) != 2) {
+    raise << Recipe[r].name << ": exactly 2 ingredients expected in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   if (!is_literal(inst.ingredients.at(1)))
     raise << Recipe[r].name << ": expected ingredient 1 of " << (inst.operation == Recipe_ordinal["get"] ? "'get'" : "'get-address'") << " to have type 'offset'; got " << inst.ingredients.at(1).original_string << '\n' << end();
   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
@@ -258,8 +259,10 @@ recipe main [
 :(after "Per-recipe Transforms")
 // convert variant names of exclusive containers
 if (inst.operation == Recipe_ordinal["maybe-convert"]) {
-  // at least 2 args, and second arg is offset
-  assert(SIZE(inst.ingredients) >= 2);
+  if (SIZE(inst.ingredients) != 2) {
+    raise << Recipe[r].name << ": exactly 2 ingredients expected in '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   assert(is_literal(inst.ingredients.at(1)));
   if (inst.ingredients.at(1).name.find_first_not_of("0123456789") != string::npos) {
     // since first non-address in base type must be an exclusive container, we don't have to canonize
diff --git a/043new.cc b/043new.cc
index 08c68eec..5fb62ed3 100644
--- a/043new.cc
+++ b/043new.cc
@@ -34,14 +34,14 @@ Type_ordinal["type"] = 0;
 if (inst.operation == Recipe_ordinal["new"]) {
   // End NEW Transform Special-cases
   // first arg must be of type 'type'
-  assert(SIZE(inst.ingredients) >= 1);
-  if (!is_literal(inst.ingredients.at(0)))
-    raise << "expected literal, got " << inst.ingredients.at(0).original_string << '\n' << end();
-  if (inst.ingredients.at(0).properties.at(0).second.at(0) != "type")
-    raise << "tried to allocate non-type " << inst.ingredients.at(0).to_string() << " in recipe " << Recipe[r].name << '\n' << end();
+  if (inst.ingredients.empty())
+    raise << Recipe[r].name << ": 'new' expects one or two ingredients\n" << end();
+  if (inst.ingredients.at(0).properties.empty()
+      || inst.ingredients.at(0).properties.at(0).second.empty()
+      || inst.ingredients.at(0).properties.at(0).second.at(0) != "type")
+    raise << Recipe[r].name << ": first ingredient of 'new' should be a type, but got " << inst.ingredients.at(0).original_string << '\n' << end();
   if (Type_ordinal.find(inst.ingredients.at(0).name) == Type_ordinal.end())
-    raise << "unknown type " << inst.ingredients.at(0).name << " in recipe " << Recipe[r].name << '\n' << end();
-//?   cerr << "type " << inst.ingredients.at(0).name << " => " << Type_ordinal[inst.ingredients.at(0).name] << '\n'; //? 1
+    raise << Recipe[r].name << ": unknown type " << inst.ingredients.at(0).name << '\n' << end();
   inst.ingredients.at(0).set_value(Type_ordinal[inst.ingredients.at(0).name]);
   trace(Primitive_recipe_depth, "new") << inst.ingredients.at(0).name << " -> " << inst.ingredients.at(0).name << end();
   end_new_transform:;
@@ -56,14 +56,19 @@ NEW,
 Recipe_ordinal["new"] = NEW;
 :(before "End Primitive Recipe Implementations")
 case NEW: {
+  if (ingredients.empty() || SIZE(ingredients) > 2) {
+    raise << current_recipe_name() << ": 'new' requires one or two ingredients, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'new' should be a type, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+  }
   // compute the space we need
   long long int size = 0;
   long long int array_length = 0;
   {
     vector<type_ordinal> type;
-    assert(is_literal(current_instruction().ingredients.at(0)));
     type.push_back(current_instruction().ingredients.at(0).value);
-//?     trace(Primitive_recipe_depth, "mem") << "type " << current_instruction().ingredients.at(0).to_string() << ' ' << type.size() << ' ' << type.back() << " has size " << size_of(type) << end(); //? 1
     if (SIZE(current_instruction().ingredients) > 1) {
       // array
       array_length = ingredients.at(1).at(0);
@@ -82,7 +87,6 @@ case NEW: {
   ensure_space(size);
   const long long int result = Current_routine->alloc;
   trace(Primitive_recipe_depth, "mem") << "new alloc: " << result << end();
-//?   trace(Primitive_recipe_depth, "mem") << "size: " << size << " locations" << end(); //? 1
   // save result
   products.resize(1);
   products.at(0).push_back(result);
@@ -115,7 +119,6 @@ case NEW: {
 :(code)
 void ensure_space(long long int 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;
@@ -208,14 +211,18 @@ ABANDON,
 Recipe_ordinal["abandon"] = ABANDON;
 :(before "End Primitive Recipe Implementations")
 case ABANDON: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'abandon' requires one ingredient, but got '" << current_instruction().to_string() << "'\n" << end();
+    break;
+  }
   if (!scalar(ingredients.at(0))) {
-    raise << "abandon's ingredient should be scalar\n" << end();
+    raise << current_recipe_name() << ": first ingredient of 'abandon' should be an address, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
     break;
   }
   long long int address = ingredients.at(0).at(0);
   reagent types = canonize(current_instruction().ingredients.at(0));
   if (types.types.at(0) != Type_ordinal["address"]) {
-    raise << "abandon's ingredient should be an address\n" << end();
+    raise << current_recipe_name() << ": first ingredient of 'abandon' should be an address, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
     break;
   }
   reagent target_type = deref(types);
@@ -322,7 +329,6 @@ long long int new_mu_string(const string& contents) {
 //?   Num_alloc++; //? 1
   ensure_space(string_length+1);  // don't forget the extra location for array size
   // initialize string
-//?   cout << "new string literal: " << current_instruction().ingredients.at(0).name << '\n'; //? 1
   long long int result = Current_routine->alloc;
   Memory[Current_routine->alloc++] = string_length;
   long long int curr = 0;
diff --git a/044space.cc b/044space.cc
index 17ca4d2a..31fca4ec 100644
--- a/044space.cc
+++ b/044space.cc
@@ -138,8 +138,7 @@ if (curr.name == "new-default-space") {
   }
 :(after "void write_memory(reagent x, vector<double> data)")
   if (x.name == "number-of-locals") {
-    assert(scalar(data));
-    raise << "can't write to special variable number-of-locals\n" << end();
+    raise << current_recipe_name() << ": can't write to special name 'number-of-locals'\n" << end();
     return;
   }
 
@@ -176,11 +175,9 @@ if (curr.name == "local-scope") {
 void try_reclaim_locals() {
   // only reclaim routines starting with 'local-scope'
   const recipe_ordinal r = Recipe_ordinal[current_recipe_name()];
+  if (Recipe[r].steps.empty()) return;
   const instruction& inst = Recipe[r].steps.at(0);
-  if (inst.name != "local-scope")
-    return;
-//?   cerr << inst.to_string() << '\n'; //? 1
-//?   cerr << current_recipe_name() << ": abandon " << Current_routine->calls.front().default_space << '\n'; //? 1
+  if (inst.name != "local-scope") return;
   abandon(Current_routine->calls.front().default_space,
           /*array length*/1+/*number-of-locals*/Name[r][""]);
 }
@@ -215,7 +212,8 @@ long long int address(long long int offset, long long int base) {
 
 :(after "void write_memory(reagent x, vector<double> data)")
   if (x.name == "default-space") {
-    assert(scalar(data));
+    if (!scalar(data))
+      raise << current_recipe_name() << ": 'default-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end();
     Current_routine->calls.front().default_space = data.at(0);
     return;
   }
diff --git a/045space_surround.cc b/045space_surround.cc
index b90d464f..1ab41c91 100644
--- a/045space_surround.cc
+++ b/045space_surround.cc
@@ -41,7 +41,8 @@ long long int space_base(const reagent& x, long long int space_index, long long
 long long int space_index(const reagent& x) {
   for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) {
     if (x.properties.at(i).first == "space") {
-      assert(SIZE(x.properties.at(i).second) == 1);
+      if (SIZE(x.properties.at(i).second) != 1)
+        raise << current_recipe_name() << ": /space metadata should take exactly one value in " << x.original_string << '\n' << end();
       return to_integer(x.properties.at(i).second.at(0));
     }
   }
diff --git a/046closure_name.cc b/046closure_name.cc
index bc7460f5..dc2c4b61 100644
--- a/046closure_name.cc
+++ b/046closure_name.cc
@@ -7,8 +7,6 @@
 recipe main [
   default-space:address:array:location <- new location:type, 30:literal
   1:address:array:location/names:new-counter <- new-counter
-#?   $print [AAAAAAAAAAAAAAAA]
-#?   $print 1:address:array:location
   2:number/raw <- increment-counter 1:address:array:location/names:new-counter
   3:number/raw <- increment-counter 1:address:array:location/names:new-counter
 ]
@@ -60,7 +58,7 @@ void collect_surrounding_spaces(const recipe_ordinal r) {
         raise << "slot 0 requires a /names property in recipe " << Recipe[r].name << end();
         continue;
       }
-      if (SIZE(s) > 1) raise << "slot 0 should have a single value in /names, got " << inst.products.at(j).to_string() << '\n' << end();
+      if (SIZE(s) > 1) raise << "slot 0 should have a single value in /names, but got " << inst.products.at(j).to_string() << '\n' << end();
       string surrounding_recipe_name = s.at(0);
       if (Surrounding_space.find(r) != Surrounding_space.end()
           && Surrounding_space[r] != Recipe_ordinal[surrounding_recipe_name]) {
@@ -128,7 +126,10 @@ recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n
 bool already_transformed(const reagent& r, const map<string, long long int>& names) {
   if (has_property(r, "space")) {
     vector<string> p = property(r, "space");
-    assert(SIZE(p) == 1);
+    if (SIZE(p) != 1) {
+      raise << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end();
+      return false;
+    }
     if (p.at(0) != "0") return true;
   }
   return names.find(r.name) != names.end();
diff --git a/047global.cc b/047global.cc
index 6ce4b78f..39be9bc2 100644
--- a/047global.cc
+++ b/047global.cc
@@ -31,7 +31,8 @@ long long int global_space;
 global_space = 0;
 :(after "void write_memory(reagent x, vector<double> data)")
   if (x.name == "global-space") {
-    assert(scalar(data));
+    if (!scalar(data))
+      raise << current_recipe_name() << ": 'global-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end();
     if (Current_routine->global_space)
       raise << "routine already has a global-space; you can't over-write your globals" << end();
     Current_routine->global_space = data.at(0);
@@ -66,7 +67,6 @@ $warn: 0
 
 :(code)
 bool is_global(const reagent& x) {
-//?   cerr << x.to_string() << '\n'; //? 1
   for (long long int i = /*skip name:type*/1; i < SIZE(x.properties); ++i) {
     if (x.properties.at(i).first == "space")
       return !x.properties.at(i).second.empty() && x.properties.at(i).second.at(0) == "global";
diff --git a/049continuation.cc b/049continuation.cc
index e625e7b7..c79db298 100644
--- a/049continuation.cc
+++ b/049continuation.cc
@@ -40,7 +40,10 @@ CONTINUE_FROM,
 Recipe_ordinal["continue-from"] = CONTINUE_FROM;
 :(before "End Primitive Recipe Implementations")
 case CONTINUE_FROM: {
-  assert(scalar(ingredients.at(0)));
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'continue-from' should be a continuation id generated by 'current-continuation', but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   long long int c = ingredients.at(0).at(0);
   Current_routine->calls = Continuation[c];  // deep copy; calls have no pointers
   continue;  // skip rest of this instruction
@@ -171,7 +174,7 @@ case CREATE_DELIMITED_CONTINUATION: {
   Current_routine->calls.front().is_reset = true;
   Current_routine->calls.push_front(call(Recipe_ordinal[current_instruction().ingredients.at(0).name]));
   ingredients.erase(ingredients.begin());  // drop the callee
-  goto complete_call;
+  goto call_housekeeping;
 }
 
 //: save the slice of current call stack until the 'create-delimited-continuation'
@@ -197,7 +200,10 @@ case REPLY_DELIMITED_CONTINUATION: {
   // copy the current call stack until the most recent 'reset' call
   call_stack::iterator find_reset(call_stack& c);  // manual prototype containing '::'
   call_stack::iterator reset = find_reset(Current_routine->calls);
-  assert(reset != Current_routine->calls.end());
+  if (reset == Current_routine->calls.end()) {
+    raise << current_recipe_name() << ": couldn't find a 'reset' call to jump out to\n" << end();
+    break;
+  }
   Delimited_continuation[Next_delimited_continuation_id] = call_stack(Current_routine->calls.begin(), reset);
   while (Current_routine->calls.begin() != reset) {
     --Callstack_depth;
@@ -218,15 +224,19 @@ call_stack::iterator find_reset(call_stack& c) {
 }
 
 //: overload 'call' for continuations
-:(after "case CALL:")
-  if (current_instruction().ingredients.at(0).properties.at(0).second.at(0) == "continuation") {
+:(after "Begin Call")
+  if (!current_instruction().ingredients.at(0).properties.empty()
+      && !current_instruction().ingredients.at(0).properties.at(0).second.empty()
+      && current_instruction().ingredients.at(0).properties.at(0).second.at(0) == "continuation") {
     // copy multiple calls on to current call stack
     assert(scalar(ingredients.at(0)));
-    assert(Delimited_continuation.find(ingredients.at(0).at(0)) != Delimited_continuation.end());
+    if (Delimited_continuation.find(ingredients.at(0).at(0)) == Delimited_continuation.end()) {
+      raise << current_recipe_name() << ": no such delimited continuation " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    }
     const call_stack& new_calls = Delimited_continuation[ingredients.at(0).at(0)];
     for (call_stack::const_reverse_iterator p = new_calls.rbegin(); p != new_calls.rend(); ++p)
       Current_routine->calls.push_front(*p);
     ++current_step_index();  // skip past the reply-delimited-continuation
     ingredients.erase(ingredients.begin());  // drop the callee
-    goto complete_call;
+    goto call_housekeeping;
   }
diff --git a/064random.cc b/064random.cc
index 6fadeb40..bd171132 100644
--- a/064random.cc
+++ b/064random.cc
@@ -27,7 +27,14 @@ ROUND,
 Recipe_ordinal["round"] = ROUND;
 :(before "End Primitive Recipe Implementations")
 case ROUND: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'round' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'round' should be a number, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   products.resize(1);
   products.at(0).push_back(rint(ingredients.at(0).at(0)));
   break;
diff --git a/070display.cc b/070display.cc
index 335a3e55..976be637 100644
--- a/070display.cc
+++ b/070display.cc
@@ -14,7 +14,6 @@ bool Autodisplay = true;
 OPEN_CONSOLE,
 :(before "End Primitive Recipe Numbers")
 Recipe_ordinal["open-console"] = OPEN_CONSOLE;
-//? cerr << "open-console: " << OPEN_CONSOLE << '\n'; //? 1
 :(before "End Primitive Recipe Implementations")
 case OPEN_CONSOLE: {
   tb_init();
@@ -71,22 +70,29 @@ case PRINT_CHARACTER_TO_DISPLAY: {
   int h=tb_height(), w=tb_width();
   long long int height = (h >= 0) ? h : 0;
   long long int width = (w >= 0) ? w : 0;
-  assert(scalar(ingredients.at(0)));
+  if (ingredients.empty()) {
+    raise << current_recipe_name() << ": 'print-character-to-display' requires at least one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'print-character-to-display' should be a character, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   long long int c = ingredients.at(0).at(0);
-//?   tb_shutdown(); //? 1
-//?   cerr << "AAA " << c << ' ' << (int)'\n' << ' ' << (int)'\r' << '\n'; //? 1
-//?   exit(1); //? 1
   int color = TB_BLACK;
   if (SIZE(ingredients) > 1) {
-    assert(scalar(ingredients.at(1)));
+    if (!scalar(ingredients.at(1))) {
+      raise << current_recipe_name() << ": second ingredient of 'print-character-to-display' should be a foreground color number, but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
+      break;
+    }
     color = ingredients.at(1).at(0);
-//?     tb_shutdown(); //? 1
-//?     cerr << "AAA " << color << '\n'; //? 1
-//?     exit(1); //? 1
   }
   int bg_color = TB_BLACK;
   if (SIZE(ingredients) > 2) {
-    assert(scalar(ingredients.at(2)));
+    if (!scalar(ingredients.at(2))) {
+      raise << current_recipe_name() << ": third ingredient of 'print-character-to-display' should be a background color number, but got " << current_instruction().ingredients.at(2).original_string << '\n' << end();
+      break;
+    }
     bg_color = ingredients.at(2).at(0);
     if (bg_color == 0) bg_color = TB_BLACK;
   }
@@ -135,9 +141,19 @@ MOVE_CURSOR_ON_DISPLAY,
 Recipe_ordinal["move-cursor-on-display"] = MOVE_CURSOR_ON_DISPLAY;
 :(before "End Primitive Recipe Implementations")
 case MOVE_CURSOR_ON_DISPLAY: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 2) {
+    raise << current_recipe_name() << ": 'move-cursor-on-display' requires two ingredients, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'move-cursor-on-display' should be a row number, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   Display_row = ingredients.at(0).at(0);
-  assert(scalar(ingredients.at(1)));
+  if (!scalar(ingredients.at(1))) {
+    raise << current_recipe_name() << ": second ingredient of 'move-cursor-on-display' should be a column number, but got " << current_instruction().ingredients.at(1).original_string << '\n' << end();
+    break;
+  }
   Display_column = ingredients.at(1).at(0);
   tb_set_cursor(Display_column, Display_row);
   if (Autodisplay) tb_present();
diff --git a/081run_interactive.cc b/081run_interactive.cc
index bbb49a97..75e58012 100644
--- a/081run_interactive.cc
+++ b/081run_interactive.cc
@@ -26,7 +26,14 @@ Recipe_ordinal["run-interactive"] = RUN_INTERACTIVE;
 //? cerr << "run-interactive: " << RUN_INTERACTIVE << '\n'; //? 1
 :(before "End Primitive Recipe Implementations")
 case RUN_INTERACTIVE: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'run-interactive' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'run-interactive' should be a literal string, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   products.resize(3);
   bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
   if (!new_code_pushed_to_stack) {
@@ -170,7 +177,6 @@ if (current_instruction().operation == RUN_INTERACTIVE && !current_instruction()
   }
   if (SIZE(current_instruction().products) >= 3) {
     vector<double> screen;
-//?     cerr << "returning screen " << Memory[SCREEN] << " to " << current_instruction().products.at(2).to_string() << " value " << current_instruction().products.at(2).value << '\n'; //? 1
     screen.push_back(Memory[SCREEN]);
     write_memory(current_instruction().products.at(2), screen);
   }
@@ -263,7 +269,14 @@ RELOAD,
 Recipe_ordinal["reload"] = RELOAD;
 :(before "End Primitive Recipe Implementations")
 case RELOAD: {
-  assert(scalar(ingredients.at(0)));
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'reload' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
+  if (!scalar(ingredients.at(0))) {
+    raise << current_recipe_name() << ": first ingredient of 'reload' should be a literal string, but got " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
   if (!Trace_stream) {
     Trace_file = "";  // if there wasn't already a stream we don't want to save it
     Trace_stream = new trace_stream;
diff --git a/082persist.cc b/082persist.cc
index 9d1fa8b2..00d81c53 100644
--- a/082persist.cc
+++ b/082persist.cc
@@ -8,8 +8,12 @@ RESTORE,
 Recipe_ordinal["restore"] = RESTORE;
 :(before "End Primitive Recipe Implementations")
 case RESTORE: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'restore' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
   if (!scalar(ingredients.at(0)))
-    raise << "restore: illegal operand " << current_instruction().ingredients.at(0).to_string() << '\n' << end();
+    raise << current_recipe_name() << ": first ingredient of 'restore' should be a literal string, but got " << current_instruction().ingredients.at(0).to_string() << '\n' << end();
   products.resize(1);
   string filename = current_instruction().ingredients.at(0).name;
   if (!is_literal(current_instruction().ingredients.at(0)))
@@ -47,14 +51,18 @@ SAVE,
 Recipe_ordinal["save"] = SAVE;
 :(before "End Primitive Recipe Implementations")
 case SAVE: {
+  if (SIZE(ingredients) != 1) {
+    raise << current_recipe_name() << ": 'save' requires exactly one ingredient, but got " << current_instruction().to_string() << '\n' << end();
+    break;
+  }
   if (!scalar(ingredients.at(0)))
-    raise << "save: illegal operand 0 " << current_instruction().ingredients.at(0).to_string() << '\n' << end();
+    raise << current_recipe_name() << ": first ingredient of 'save' should be a literal string, but got " << current_instruction().ingredients.at(0).to_string() << '\n' << end();
   string filename = current_instruction().ingredients.at(0).name;
   if (!is_literal(current_instruction().ingredients.at(0)))
     filename = to_string(ingredients.at(0).at(0));
   ofstream fout(("lesson/"+filename).c_str());
   if (!scalar(ingredients.at(1)))
-    raise << "save: illegal operand 1 " << current_instruction().ingredients.at(1).to_string() << '\n' << end();
+    raise << current_recipe_name() << ": second ingredient of 'save' should be an address:array:character, but got " << current_instruction().ingredients.at(1).to_string() << '\n' << end();
   string contents = read_mu_string(ingredients.at(1).at(0));
   fout << contents;
   fout.close();