about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--003trace.cc19
-rw-r--r--003trace.test.cc8
-rw-r--r--011load.cc22
-rw-r--r--013literal_string.cc4
-rw-r--r--020run.cc12
-rw-r--r--021check_instruction.cc20
-rw-r--r--022arithmetic.cc50
-rw-r--r--023boolean.cc8
-rw-r--r--024jump.cc16
-rw-r--r--025compare.cc18
-rw-r--r--029tools.cc42
-rw-r--r--030container.cc86
-rw-r--r--031address.cc18
-rw-r--r--032array.cc62
-rw-r--r--033exclusive_container.cc10
-rw-r--r--034call.cc14
-rw-r--r--035call_ingredient.cc16
-rw-r--r--036call_reply.cc26
-rw-r--r--037recipe.cc4
-rw-r--r--038scheduler.cc30
-rw-r--r--039wait.cc8
-rw-r--r--040brace.cc20
-rw-r--r--041jump_target.cc18
-rw-r--r--042name.cc55
-rw-r--r--043new.cc16
-rw-r--r--044space.cc14
-rw-r--r--045space_surround.cc2
-rw-r--r--046closure_name.cc24
-rw-r--r--047global.cc19
-rw-r--r--048check_type_by_name.cc22
-rw-r--r--050scenario.cc96
-rw-r--r--052tangle.cc14
-rw-r--r--053continuation.cc6
-rw-r--r--064random.cc6
-rw-r--r--070display.cc50
-rw-r--r--072scenario_screen.cc28
-rw-r--r--075scenario_console.cc4
-rw-r--r--081run_interactive.cc66
-rw-r--r--082persist.cc14
39 files changed, 507 insertions, 460 deletions
diff --git a/003trace.cc b/003trace.cc
index e37b9f12..455ced85 100644
--- a/003trace.cc
+++ b/003trace.cc
@@ -81,8 +81,10 @@
 // End Tracing  // hack to ensure most code in this layer comes before anything else
 
 :(before "End Tracing")
+bool Hide_errors = false;
 bool Hide_warnings = false;
 :(before "End Setup")
+Hide_errors = false;
 Hide_warnings = false;
 
 :(before "End Types")
@@ -132,7 +134,9 @@ struct trace_stream {
     string curr_contents = curr_stream->str();
     if (curr_contents.empty()) return;
     past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents));  // preserve indent in contents
-    if (!Hide_warnings && curr_label == "warn")
+    if (!Hide_errors && curr_label == "error")
+      cerr << curr_label << ": " << curr_contents << '\n';
+    else if (!Hide_warnings && curr_label == "warn")
       cerr << curr_label << ": " << curr_contents << '\n';
     delete curr_stream;
     curr_stream = NULL;
@@ -160,9 +164,10 @@ trace_stream* Trace_stream = NULL;
 
 // Top-level helper. IMPORTANT: can't nest.
 #define trace(...)  !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__)
-// Warnings should go straight to cerr by default since calls to trace() have
+// Errors and warnings should go straight to cerr by default since calls to trace() have
 // some unfriendly constraints (they delay printing, they can't nest)
 #define raise  ((!Trace_stream || !Hide_warnings) ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream("warn"))
+#define raise_error  ((!Trace_stream || !Hide_errors) ? (tb_shutdown(),cerr) /*do print*/ : Trace_stream->stream("error"))
 
 :(before "End Types")
 struct end {};
@@ -263,12 +268,12 @@ int trace_count(string label, string line) {
   return result;
 }
 
-#define CHECK_TRACE_WARNS()  CHECK(trace_count("warn") > 0)
-#define CHECK_TRACE_DOESNT_WARN() \
-  if (trace_count("warn") > 0) { \
+#define CHECK_TRACE_CONTAINS_ERROR()  CHECK(trace_count("error") > 0)
+#define CHECK_TRACE_DOESNT_CONTAIN_ERROR() \
+  if (trace_count("error") > 0) { \
     ++Num_failures; \
-    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected warnings\n"; \
-    DUMP("warn"); \
+    cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \
+    DUMP("error"); \
     Passed = false; \
     return; \
   }
diff --git a/003trace.test.cc b/003trace.test.cc
index a16457a1..f34cbf88 100644
--- a/003trace.test.cc
+++ b/003trace.test.cc
@@ -59,11 +59,11 @@ void test_trace_count_ignores_trailing_whitespace() {
 // pending: DUMP tests
 // pending: readable_contents() adds newline if necessary.
 // pending: raise also prints to stderr.
-// pending: raise doesn't print to stderr if Hide_warnings is set.
-// pending: raise doesn't have to be saved if Hide_warnings is set, just printed.
+// pending: raise doesn't print to stderr if Hide_errors is set.
+// pending: raise doesn't have to be saved if Hide_errors is set, just printed.
 // pending: raise prints to stderr if Trace_stream is NULL.
-// pending: raise prints to stderr if Trace_stream is NULL even if Hide_warnings is set.
-// pending: raise << ... die() doesn't die if Hide_warnings is set.
+// pending: raise prints to stderr if Trace_stream is NULL even if Hide_errors is set.
+// pending: raise << ... die() doesn't die if Hide_errors is set.
 
 
 
diff --git a/011load.cc b/011load.cc
index a084ba6e..4b1b1929 100644
--- a/011load.cc
+++ b/011load.cc
@@ -34,7 +34,7 @@ vector<recipe_ordinal> load(istream& in) {
     }
     // End Command Handlers
     else {
-      raise << "unknown top-level command: " << command << '\n' << end();
+      raise_error << "unknown top-level command: " << command << '\n' << end();
     }
   }
   return result;
@@ -43,7 +43,7 @@ vector<recipe_ordinal> load(istream& in) {
 long long int slurp_recipe(istream& in) {
   string recipe_name = next_word(in);
   if (recipe_name.empty())
-    raise << "empty recipe name\n" << end();
+    raise_error << "empty recipe name\n" << end();
   if (Recipe_ordinal.find(recipe_name) == Recipe_ordinal.end()) {
     Recipe_ordinal[recipe_name] = Next_recipe_ordinal++;
   }
@@ -63,7 +63,7 @@ recipe slurp_body(istream& in) {
   recipe result;
   skip_whitespace(in);
   if (in.get() != '[')
-    raise << "recipe body must begin with '['\n" << end();
+    raise_error << "recipe body must begin with '['\n" << end();
   skip_whitespace_and_comments(in);
   instruction curr;
   while (next_instruction(in, &curr)) {
@@ -77,17 +77,17 @@ bool next_instruction(istream& in, instruction* curr) {
   in >> std::noskipws;
   curr->clear();
   if (in.eof()) {
-    raise << "0: unbalanced '[' for recipe\n" << end();
+    raise_error << "0: unbalanced '[' for recipe\n" << end();
     return false;
   }
   skip_whitespace(in);
   if (in.eof()) {
-    raise << "1: unbalanced '[' for recipe\n" << end();
+    raise_error << "1: unbalanced '[' for recipe\n" << end();
     return false;
   }
   skip_whitespace_and_comments(in);
   if (in.eof()) {
-    raise << "2: unbalanced '[' for recipe\n" << end();
+    raise_error << "2: unbalanced '[' for recipe\n" << end();
     return false;
   }
 
@@ -95,7 +95,7 @@ bool next_instruction(istream& in, instruction* curr) {
   while (in.peek() != '\n' && !in.eof()) {
     skip_whitespace(in);
     if (in.eof()) {
-      raise << "3: unbalanced '[' for recipe\n" << end();
+      raise_error << "3: unbalanced '[' for recipe\n" << end();
       return false;
     }
     string word = next_word(in);
@@ -112,7 +112,7 @@ bool next_instruction(istream& in, instruction* curr) {
     curr->label = words.at(0);
     trace("parse") << "label: " << curr->label << end();
     if (in.eof()) {
-      raise << "7: unbalanced '[' for recipe\n" << end();
+      raise_error << "7: unbalanced '[' for recipe\n" << end();
       return false;
     }
     return true;
@@ -128,7 +128,7 @@ bool next_instruction(istream& in, instruction* curr) {
   }
 
   if (p == words.end()) {
-    raise << "instruction prematurely ended with '<-'\n" << end();
+    raise_error << "instruction prematurely ended with '<-'\n" << end();
     return false;
   }
   curr->name = *p;
@@ -136,7 +136,7 @@ bool next_instruction(istream& in, instruction* curr) {
     Recipe_ordinal[*p] = Next_recipe_ordinal++;
   }
   if (Recipe_ordinal[*p] == 0) {
-    raise << "Recipe " << *p << " has number 0, which is reserved for IDLE.\n" << end();
+    raise_error << "Recipe " << *p << " has number 0, which is reserved for IDLE.\n" << end();
     return false;
   }
   curr->operation = Recipe_ordinal[*p];  ++p;
@@ -154,7 +154,7 @@ bool next_instruction(istream& in, instruction* curr) {
     trace("parse") << "  product: " << p->to_string() << end();
   }
   if (in.eof()) {
-    raise << "9: unbalanced '[' for recipe\n" << end();
+    raise_error << "9: unbalanced '[' for recipe\n" << end();
     return false;
   }
   return true;
diff --git a/013literal_string.cc b/013literal_string.cc
index 7424a31d..98cc85e7 100644
--- a/013literal_string.cc
+++ b/013literal_string.cc
@@ -73,7 +73,7 @@ void slurp_quoted_comment_oblivious(istream& in, ostringstream& out) {
     if (brace_depth == 0) break;
   }
   if (in.eof() && brace_depth > 0) {
-    raise << "unbalanced '['\n" << end();
+    raise_error << "unbalanced '['\n" << end();
     out.clear();
   }
 }
@@ -100,7 +100,7 @@ void slurp_quoted_comment_aware(istream& in, ostringstream& out) {
     out << c;
     if (c == ']') return;
   }
-  raise << "unbalanced '['\n" << end();
+  raise_error << "unbalanced '['\n" << end();
   out.clear();
 }
 
diff --git a/020run.cc b/020run.cc
index 329bcdb0..c08049a0 100644
--- a/020run.cc
+++ b/020run.cc
@@ -64,7 +64,7 @@ void run_current_routine()
     if (current_instruction().is_label) { ++current_step_index(); continue; }
     trace(Initial_callstack_depth+Callstack_depth, "run") << current_instruction().to_string() << end();
     if (Memory[0] != 0) {
-      raise << "something wrote to location 0; this should never happen\n" << end();
+      raise_error << "something wrote to location 0; this should never happen\n" << end();
       Memory[0] = 0;
     }
     // Read all ingredients from memory.
@@ -92,7 +92,7 @@ void run_current_routine()
       }
     }
     if (SIZE(products) < SIZE(current_instruction().products)) {
-      raise << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products! " << current_instruction().to_string() << '\n' << end();
+      raise_error << SIZE(products) << " vs " << SIZE(current_instruction().products) << ": failed to write to all products! " << current_instruction().to_string() << '\n' << end();
     }
     else {
       for (long long int i = 0; i < SIZE(current_instruction().products); ++i) {
@@ -210,7 +210,7 @@ void load_permanently(string filename) {
   ifstream fin(filename.c_str());
   fin.peek();
   if (!fin) {
-    raise << "no such file " << filename << '\n' << end();
+    raise_error << "no such file " << filename << '\n' << end();
     return;
   }
   fin >> std::noskipws;
@@ -267,7 +267,7 @@ void write_memory(reagent x, vector<double> data) {
   if (is_literal(x)) return;
   long long int base = x.value;
   if (size_mismatch(x, data)) {
-    raise << maybe(current_recipe_name()) << "size mismatch in storing to " << x.original_string << " (" << size_of(x.types) << " vs " << SIZE(data) << ") at '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "size mismatch in storing to " << x.original_string << " (" << size_of(x.types) << " vs " << SIZE(data) << ") at '" << current_instruction().to_string() << "'\n" << end();
     return;
   }
   for (long long int offset = 0; offset < SIZE(data); ++offset) {
@@ -304,7 +304,7 @@ bool is_literal(const reagent& r) {
   return SIZE(r.types) == 1 && r.types.at(0) == 0;
 }
 
-// hook to suppress inserting recipe name into warnings
+// hook to suppress inserting recipe name into errors and warnings (for later layers)
 string maybe(string s) {
   return s + ": ";
 }
@@ -334,7 +334,7 @@ recipe main [
 +run: _ <- copy 0
 
 :(scenario write_to_0_disallowed)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   0:number <- copy 34
 ]
diff --git a/021check_instruction.cc b/021check_instruction.cc
index bf238020..fcd861da 100644
--- a/021check_instruction.cc
+++ b/021check_instruction.cc
@@ -21,12 +21,12 @@ void check_instruction(const recipe_ordinal r) {
       // Primitive Recipe Checks
       case COPY: {
         if (SIZE(inst.products) != SIZE(inst.ingredients)) {
-          raise << "ingredients and products should match in '" << inst.to_string() << "'\n" << end();
+          raise_error << "ingredients and products should match in '" << inst.to_string() << "'\n" << end();
           break;
         }
         for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
           if (!types_match(inst.products.at(i), inst.ingredients.at(i))) {
-            raise << maybe(Recipe[r].name) << "can't copy " << inst.ingredients.at(i).original_string << " to " << inst.products.at(i).original_string << "; types don't match\n" << end();
+            raise_error << maybe(Recipe[r].name) << "can't copy " << inst.ingredients.at(i).original_string << " to " << inst.products.at(i).original_string << "; types don't match\n" << end();
             goto finish_checking_instruction;
           }
         }
@@ -43,32 +43,32 @@ void check_instruction(const recipe_ordinal r) {
 }
 
 :(scenario copy_checks_reagent_count)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 34, 35
 ]
-+warn: ingredients and products should match in '1:number <- copy 34, 35'
++error: ingredients and products should match in '1:number <- copy 34, 35'
 
 :(scenario write_scalar_to_array_disallowed)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:number <- copy 34
 ]
-+warn: main: can't copy 34 to 1:array:number; types don't match
++error: main: can't copy 34 to 1:array:number; types don't match
 
 :(scenario write_scalar_to_array_disallowed_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number, 2:array:number <- copy 34, 35
 ]
-+warn: main: can't copy 35 to 2:array:number; types don't match
++error: main: can't copy 35 to 2:array:number; types don't match
 
 :(scenario write_scalar_to_address_disallowed)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:address:number <- copy 34
 ]
-+warn: main: can't copy 34 to 1:address:number; types don't match
++error: main: can't copy 34 to 1:address:number; types don't match
 
 :(code)
 bool types_match(reagent lhs, reagent rhs) {
diff --git a/022arithmetic.cc b/022arithmetic.cc
index fbf3029a..16fab86c 100644
--- a/022arithmetic.cc
+++ b/022arithmetic.cc
@@ -9,16 +9,16 @@ case ADD: {
   // primary goal of these checks is to forbid address arithmetic
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'add' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'add' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
   if (SIZE(inst.products) > 1) {
-    raise << maybe(Recipe[r].name) << "'add' yields exactly one product in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'add' yields exactly one product in '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-    raise << maybe(Recipe[r].name) << "'add' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'add' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -55,18 +55,18 @@ recipe main [
 +mem: storing 12 in location 1
 
 :(scenario add_checks_type)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- add 2:boolean, 1
 ]
-+warn: main: 'add' requires number ingredients, but got 2:boolean
++error: main: 'add' requires number ingredients, but got 2:boolean
 
 :(scenario add_checks_return_type)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:address:number <- add 2, 2
 ]
-+warn: main: 'add' should yield a number, but got 1:address:number
++error: main: 'add' should yield a number, but got 1:address:number
 
 :(before "End Primitive Recipe Declarations")
 SUBTRACT,
@@ -75,22 +75,22 @@ Recipe_ordinal["subtract"] = SUBTRACT;
 :(before "End Primitive Recipe Checks")
 case SUBTRACT: {
   if (inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'subtract' has no ingredients\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'subtract' has no ingredients\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (is_raw(inst.ingredients.at(i))) continue;  // permit address offset computations in tests
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'subtract' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'subtract' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
   if (SIZE(inst.products) > 1) {
-    raise << maybe(Recipe[r].name) << "'subtract' yields exactly one product in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'subtract' yields exactly one product in '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-    raise << maybe(Recipe[r].name) << "'subtract' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'subtract' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -133,16 +133,16 @@ Recipe_ordinal["multiply"] = MULTIPLY;
 case MULTIPLY: {
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'add' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'multiply' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
   if (SIZE(inst.products) > 1) {
-    raise << maybe(Recipe[r].name) << "'multiply' yields exactly one product in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'multiply' yields exactly one product in '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-    raise << maybe(Recipe[r].name) << "'multiply' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'multiply' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -185,21 +185,21 @@ Recipe_ordinal["divide"] = DIVIDE;
 :(before "End Primitive Recipe Checks")
 case DIVIDE: {
   if (inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'divide' has no ingredients\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'divide' has no ingredients\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'divide' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'divide' requires number ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
   if (SIZE(inst.products) > 1) {
-    raise << maybe(Recipe[r].name) << "'divide' yields exactly one product in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'divide' yields exactly one product in '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!inst.products.empty() && !is_dummy(inst.products.at(0)) && !is_mu_number(inst.products.at(0))) {
-    raise << maybe(Recipe[r].name) << "'divide' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'divide' should yield a number, but got " << inst.products.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -243,20 +243,20 @@ Recipe_ordinal["divide-with-remainder"] = DIVIDE_WITH_REMAINDER;
 :(before "End Primitive Recipe Checks")
 case DIVIDE_WITH_REMAINDER: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(current_recipe_name()) << "'divide-with-remainder' requires exactly two ingredients, but got '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "'divide-with-remainder' requires exactly two ingredients, but got '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   if (!is_mu_number(inst.ingredients.at(0)) || !is_mu_number(inst.ingredients.at(1))) {
-    raise << maybe(current_recipe_name()) << "'divide-with-remainder' requires number ingredients, but got '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "'divide-with-remainder' requires number ingredients, but got '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   if (SIZE(inst.products) > 2) {
-    raise << maybe(Recipe[r].name) << "'divide-with-remainder' yields two products in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'divide-with-remainder' yields two products in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.products); ++i) {
     if (!is_dummy(inst.products.at(i)) && !is_mu_number(inst.products.at(i))) {
-      raise << maybe(Recipe[r].name) << "'divide-with-remainder' should yield a number, but got " << inst.products.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'divide-with-remainder' should yield a number, but got " << inst.products.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -268,7 +268,7 @@ case DIVIDE_WITH_REMAINDER: {
   long long int a = static_cast<long long int>(ingredients.at(0).at(0));
   long long int b = static_cast<long long int>(ingredients.at(1).at(0));
   if (b == 0) {
-    raise << maybe(current_recipe_name()) << "divide by zero in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "divide by zero in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   long long int quotient = a / b;
@@ -308,12 +308,12 @@ recipe main [
 +mem: storing inf in location 1
 
 :(scenario divide_by_zero_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- divide-with-remainder 4, 0
 ]
 # integer division can't return floating-point infinity
-+warn: main: divide by zero in '1:number <- divide-with-remainder 4, 0'
++error: main: divide by zero in '1:number <- divide-with-remainder 4, 0'
 
 :(code)
 inline bool scalar(const vector<long long int>& x) {
diff --git a/023boolean.cc b/023boolean.cc
index 24752e72..49683455 100644
--- a/023boolean.cc
+++ b/023boolean.cc
@@ -8,7 +8,7 @@ Recipe_ordinal["and"] = AND;
 case AND: {
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_scalar(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -58,7 +58,7 @@ Recipe_ordinal["or"] = OR;
 case OR: {
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_scalar(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'and' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -107,12 +107,12 @@ Recipe_ordinal["not"] = NOT;
 :(before "End Primitive Recipe Checks")
 case NOT: {
   if (SIZE(inst.products) > SIZE(inst.ingredients)) {
-    raise << maybe(Recipe[r].name) << "'not' cannot have fewer ingredients than products in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'not' cannot have fewer ingredients than products in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_scalar(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'not' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'not' requires boolean ingredients, but got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
diff --git a/024jump.cc b/024jump.cc
index b249535e..9e5c0838 100644
--- a/024jump.cc
+++ b/024jump.cc
@@ -16,11 +16,11 @@ Recipe_ordinal["jump"] = JUMP;
 :(before "End Primitive Recipe Checks")
 case JUMP: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'jump' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'jump' should be a label or offset, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'jump' should be a label or offset, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -55,15 +55,15 @@ Recipe_ordinal["jump-if"] = JUMP_IF;
 :(before "End Primitive Recipe Checks")
 case JUMP_IF: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'jump-if' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-if' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "'jump-if' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-if' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "'jump-if' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-if' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -107,15 +107,15 @@ Recipe_ordinal["jump-unless"] = JUMP_UNLESS;
 :(before "End Primitive Recipe Checks")
 case JUMP_UNLESS: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'jump-unless' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-unless' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "'jump-unless' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-unless' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "'jump-unless' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'jump-unless' requires a label or offset for its second ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/025compare.cc b/025compare.cc
index 74f1e4f1..f7acb64c 100644
--- a/025compare.cc
+++ b/025compare.cc
@@ -7,7 +7,7 @@ Recipe_ordinal["equal"] = EQUAL;
 :(before "End Primitive Recipe Checks")
 case EQUAL: {
   if (SIZE(inst.ingredients) <= 1) {
-    raise << maybe(Recipe[r].name) << "'equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
     break;
   }
   break;
@@ -66,12 +66,12 @@ Recipe_ordinal["greater-than"] = GREATER_THAN;
 :(before "End Primitive Recipe Checks")
 case GREATER_THAN: {
   if (SIZE(inst.ingredients) <= 1) {
-    raise << maybe(Recipe[r].name) << "'greater-than' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'greater-than' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'greater-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'greater-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -125,12 +125,12 @@ Recipe_ordinal["lesser-than"] = LESSER_THAN;
 :(before "End Primitive Recipe Checks")
 case LESSER_THAN: {
   if (SIZE(inst.ingredients) <= 1) {
-    raise << maybe(Recipe[r].name) << "'lesser-than' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'lesser-than' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'lesser-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'lesser-than' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -184,12 +184,12 @@ Recipe_ordinal["greater-or-equal"] = GREATER_OR_EQUAL;
 :(before "End Primitive Recipe Checks")
 case GREATER_OR_EQUAL: {
   if (SIZE(inst.ingredients) <= 1) {
-    raise << maybe(Recipe[r].name) << "'greater-or-equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'greater-or-equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'greater-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'greater-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
@@ -251,12 +251,12 @@ Recipe_ordinal["lesser-or-equal"] = LESSER_OR_EQUAL;
 :(before "End Primitive Recipe Checks")
 case LESSER_OR_EQUAL: {
   if (SIZE(inst.ingredients) <= 1) {
-    raise << maybe(Recipe[r].name) << "'lesser-or-equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'lesser-or-equal' needs at least two ingredients to compare in '" << inst.to_string() << "'\n" << end();
     break;
   }
   for (long long int i = 0; i < SIZE(inst.ingredients); ++i) {
     if (!is_mu_number(inst.ingredients.at(i))) {
-      raise << maybe(Recipe[r].name) << "'lesser-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "'lesser-or-equal' can only compare numbers; got " << inst.ingredients.at(i).original_string << '\n' << end();
       goto finish_checking_instruction;
     }
   }
diff --git a/029tools.cc b/029tools.cc
index 17dea6ef..d17c9b81 100644
--- a/029tools.cc
+++ b/029tools.cc
@@ -13,15 +13,15 @@ Recipe_ordinal["trace"] = TRACE;
 :(before "End Primitive Recipe Checks")
 case TRACE: {
   if (SIZE(inst.ingredients) < 3) {
-    raise << maybe(Recipe[r].name) << "'trace' takes three or more ingredients rather than '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'trace' takes three or more ingredients rather than '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!is_mu_number(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'trace' should be a number (depth), but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'trace' should be a number (depth), but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_literal_string(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'trace' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'trace' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
@@ -90,30 +90,30 @@ string print_mu(const reagent& r, const vector<double>& data) {
 }
 
 :(before "End Primitive Recipe Declarations")
-HIDE_WARNINGS,
+HIDE_ERRORS,
 :(before "End Primitive Recipe Numbers")
-Recipe_ordinal["hide-warnings"] = HIDE_WARNINGS;
+Recipe_ordinal["hide-errors"] = HIDE_ERRORS;
 :(before "End Primitive Recipe Checks")
-case HIDE_WARNINGS: {
+case HIDE_ERRORS: {
   break;
 }
 :(before "End Primitive Recipe Implementations")
-case HIDE_WARNINGS: {
-  Hide_warnings = true;
+case HIDE_ERRORS: {
+  Hide_errors = true;
   break;
 }
 
 :(before "End Primitive Recipe Declarations")
-SHOW_WARNINGS,
+SHOW_ERRORS,
 :(before "End Primitive Recipe Numbers")
-Recipe_ordinal["show-warnings"] = SHOW_WARNINGS;
+Recipe_ordinal["show-errors"] = SHOW_ERRORS;
 :(before "End Primitive Recipe Checks")
-case SHOW_WARNINGS: {
+case SHOW_ERRORS: {
   break;
 }
 :(before "End Primitive Recipe Implementations")
-case SHOW_WARNINGS: {
-  Hide_warnings = false;
+case SHOW_ERRORS: {
+  Hide_errors = false;
   break;
 }
 
@@ -170,11 +170,11 @@ case _CLEAR_TRACE: {
 //: assert: perform sanity checks at runtime
 
 :(scenario assert)
-% Hide_warnings = true;  // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line.
+% Hide_errors = true;  // '%' lines insert arbitrary C code into tests before calling 'run' with the lines below. Must be immediately after :(scenario) line.
 recipe main [
   assert 0, [this is an assert in mu]
 ]
-+warn: this is an assert in mu
++error: this is an assert in mu
 
 :(before "End Primitive Recipe Declarations")
 ASSERT,
@@ -183,15 +183,15 @@ Recipe_ordinal["assert"] = ASSERT;
 :(before "End Primitive Recipe Checks")
 case ASSERT: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'assert' takes exactly two ingredients rather than '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'assert' takes exactly two ingredients rather than '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "'assert' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'assert' requires a boolean for its first ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_literal_string(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "'assert' requires a literal string for its second ingredient, but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'assert' requires a literal string for its second ingredient, but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
@@ -199,7 +199,7 @@ case ASSERT: {
 :(before "End Primitive Recipe Implementations")
 case ASSERT: {
   if (!ingredients.at(0).at(0)) {
-    raise << current_instruction().ingredients.at(1).name << '\n' << end();
+    raise_error << current_instruction().ingredients.at(1).name << '\n' << end();
   }
   break;
 }
@@ -255,8 +255,8 @@ _SYSTEM,
 Recipe_ordinal["$system"] = _SYSTEM;
 :(before "End Primitive Recipe Checks")
 case _SYSTEM: {
-  if (inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'$system' requires exactly one ingredient, but got none\n" << end();
+  if (SIZE(inst.ingredients) != 1) {
+    raise_error << maybe(Recipe[r].name) << "'$system' requires exactly one ingredient, but got none\n" << end();
     break;
   }
   break;
diff --git a/030container.cc b/030container.cc
index 0cecdec7..a0b214a0 100644
--- a/030container.cc
+++ b/030container.cc
@@ -16,7 +16,7 @@ Type[point].elements.push_back(i);
 
 //: Tests in this layer often explicitly setup memory before reading it as a
 //: container. Don't do this in general. I'm tagging exceptions with /raw to
-//: avoid warnings.
+//: avoid errors.
 :(scenario copy_multiple_locations)
 recipe main [
   1:number <- copy 34
@@ -28,11 +28,11 @@ recipe main [
 
 //: trying to copy to a differently-typed destination will fail
 :(scenario copy_checks_size)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   2:point <- copy 1:number
 ]
-+warn: main: can't copy 1:number to 2:point; types don't match
++error: main: can't copy 1:number to 2:point; types don't match
 
 :(before "End Mu Types Initialization")
 // A more complex container, containing another container as one of its
@@ -92,7 +92,7 @@ if (t.kind == container) {
   for (long long int i = 0; i < SIZE(t.elements); ++i) {
     // todo: strengthen assertion to disallow mutual type recursion
     if (types.at(0) == t.elements.at(i).at(0)) {
-      raise << "container " << t.name << " can't include itself as a member\n" << end();
+      raise_error << "container " << t.name << " can't include itself as a member\n" << end();
       return 0;
     }
     // End size_of(type) Container Cases
@@ -126,26 +126,26 @@ Recipe_ordinal["get"] = GET;
 :(before "End Primitive Recipe Checks")
 case GET: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'get' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'get' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent base = inst.ingredients.at(0);
   // Update GET base in Check
   if (base.types.empty() || Type[base.types.at(0)].kind != container) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'get' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'get' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   type_ordinal base_type = base.types.at(0);
   reagent offset = inst.ingredients.at(1);
   if (!is_literal(offset) || !is_mu_scalar(offset)) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   long long int offset_value = 0;
   if (is_integer(offset.name)) {  // later layers permit non-integer offsets
     offset_value = to_integer(offset.name);
     if (offset_value < 0 || offset_value >= SIZE(Type[base_type].elements)) {
-      raise << maybe(Recipe[r].name) << "invalid offset " << offset_value << " for " << Type[base_type].name << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "invalid offset " << offset_value << " for " << Type[base_type].name << '\n' << end();
       break;
     }
   }
@@ -157,7 +157,7 @@ case GET: {
   reagent element;
   element.types = Type[base_type].elements.at(offset_value);
   if (!types_match(product, element)) {
-    raise << maybe(Recipe[r].name) << "'get' " << offset.original_string << " (" << offset_value << ") on " << Type[base_type].name << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'get' " << offset.original_string << " (" << offset_value << ") on " << Type[base_type].name << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
     break;
   }
   break;
@@ -168,7 +168,7 @@ case GET: {
   // Update GET base in Run
   long long int base_address = base.value;
   if (base_address == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   type_ordinal base_type = base.types.at(0);
@@ -219,34 +219,34 @@ recipe main [
 +mem: storing 13 in location 15
 
 :(scenario get_out_of_bounds)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   get 12:point-number/raw, 2:offset  # point-number occupies 3 locations but has only 2 fields; out of bounds
 ]
-+warn: main: invalid offset 2 for point-number
++error: main: invalid offset 2 for point-number
 
 :(scenario get_out_of_bounds_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   get 12:point-number/raw, -1:offset
 ]
-+warn: main: invalid offset -1 for point-number
++error: main: invalid offset -1 for point-number
 
 :(scenario get_product_type_mismatch)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   15:address:number <- get 12:point-number/raw, 1:offset
 ]
-+warn: main: 'get' 1:offset (1) on point-number can't be saved in 15:address:number; type should be number
++error: main: 'get' 1:offset (1) on point-number can't be saved in 15:address:number; type should be number
 
 :(before "End Primitive Recipe Declarations")
 GET_ADDRESS,
@@ -255,26 +255,26 @@ Recipe_ordinal["get-address"] = GET_ADDRESS;
 :(before "End Primitive Recipe Checks")
 case GET_ADDRESS: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'get-address' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'get-address' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent base = inst.ingredients.at(0);
   // Update GET_ADDRESS base in Check
   if (base.types.empty() || Type[base.types.at(0)].kind != container) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'get-address' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'get-address' should be a container, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   type_ordinal base_type = base.types.at(0);
   reagent offset = inst.ingredients.at(1);
   if (!is_literal(offset) || !is_mu_scalar(offset)) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'get' should have type 'offset', but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   long long int offset_value = 0;
   if (is_integer(offset.name)) {  // later layers permit non-integer offsets
     offset_value = to_integer(offset.name);
     if (offset_value < 0 || offset_value >= SIZE(Type[base_type].elements)) {
-      raise << maybe(Recipe[r].name) << "invalid offset " << offset_value << " for " << Type[base_type].name << '\n' << end();
+      raise_error << maybe(Recipe[r].name) << "invalid offset " << offset_value << " for " << Type[base_type].name << '\n' << end();
       break;
     }
   }
@@ -287,7 +287,7 @@ case GET_ADDRESS: {
   element.types = Type[base_type].elements.at(offset_value);
   element.types.insert(element.types.begin(), Type_ordinal["address"]);
   if (!types_match(product, element)) {
-    raise << maybe(Recipe[r].name) << "'get-address' " << offset.original_string << " (" << offset_value << ") on " << Type[base_type].name << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'get-address' " << offset.original_string << " (" << offset_value << ") on " << Type[base_type].name << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
     break;
   }
   break;
@@ -298,7 +298,7 @@ case GET_ADDRESS: {
   // Update GET_ADDRESS base in Run
   long long int base_address = base.value;
   if (base_address == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   type_ordinal base_type = base.types.at(0);
@@ -316,34 +316,34 @@ case GET_ADDRESS: {
 }
 
 :(scenario get_address_out_of_bounds)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   get-address 12:point-number/raw, 2:offset  # point-number occupies 3 locations but has only 2 fields; out of bounds
 ]
-+warn: main: invalid offset 2 for point-number
++error: main: invalid offset 2 for point-number
 
 :(scenario get_address_out_of_bounds_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   get-address 12:point-number/raw, -1:offset
 ]
-+warn: main: invalid offset -1 for point-number
++error: main: invalid offset -1 for point-number
 
 :(scenario get_address_product_type_mismatch)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   12:number <- copy 34
   13:number <- copy 35
   14:number <- copy 36
   15:number <- get-address 12:point-number/raw, 1:offset
 ]
-+warn: main: 'get-address' 1:offset (1) on point-number can't be saved in 15:number; type should be address:number
++error: main: 'get-address' 1:offset (1) on point-number can't be saved in 15:number; type should be address:number
 
 //:: Allow containers to be defined in mu code.
 
@@ -427,7 +427,7 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
 void skip_bracket(istream& in, string message) {
   skip_whitespace_and_comments(in);
   if (in.get() != '[')
-    raise << message << '\n' << end();
+    raise_error << message << '\n' << end();
 }
 
 :(scenarios run)
@@ -482,19 +482,19 @@ assert(Next_type_ordinal < 1000);
 :(before "End Setup")
 Next_type_ordinal = 1000;
 
-//:: Allow container definitions anywhere in the codebase, but warn if you
-//:: can't find a definition.
+//:: Allow container definitions anywhere in the codebase, but complain if you
+//:: can't find a definition at the end.
 
-:(scenario run_warns_on_unknown_types)
-% Hide_warnings = true;
+:(scenario run_complains_on_unknown_types)
+% Hide_errors = true;
 recipe main [
   # integer is not a type
   1:integer <- copy 0
 ]
-+warn: unknown type: integer
++error: unknown type: integer
 
 :(scenario run_allows_type_definition_after_use)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:bar <- copy 0/raw
 ]
@@ -502,8 +502,8 @@ recipe main [
 container bar [
   x:number
 ]
--warn: unknown type: bar
-$warn: 0
+-error: unknown type: bar
+$error: 0
 
 :(after "int main")
   Transform.push_back(check_invalid_types);
@@ -525,17 +525,17 @@ void check_invalid_types(const reagent& r) {
   for (long long int i = 0; i < SIZE(r.types); ++i) {
     if (r.types.at(i) == 0) continue;
     if (Type.find(r.types.at(i)) == Type.end())
-      raise << "unknown type: " << r.properties.at(0).second.at(i) << '\n' << end();
+      raise_error << "unknown type: " << r.properties.at(0).second.at(i) << '\n' << end();
   }
 }
 
 :(scenario container_unknown_field)
-% Hide_warnings = true;
+% Hide_errors = true;
 container foo [
   x:number
   y:bar
 ]
-+warn: unknown type for field y in foo
++error: unknown type for field y in foo
 
 :(scenario read_container_with_bracket_in_comment)
 container foo [
@@ -561,7 +561,7 @@ void check_container_field_types() {
         if (info.elements.at(i).at(j) == 0) continue;
         if (Type.find(info.elements.at(i).at(j)) != Type.end()) continue;
         // End Container Type Checks
-        raise << "unknown type for field " << info.element_names.at(i) << " in " << info.name << '\n' << end();
+        raise_error << "unknown type for field " << info.element_names.at(i) << " in " << info.name << '\n' << end();
       }
     }
   }
@@ -630,11 +630,11 @@ map<string, type_ordinal> ingredient_names;
 if (element.find(':') == string::npos) {
   // no type; we're defining a generic variable
   if (next_word(in) != "<-") {
-    raise << "Element " << element << " of container " << name << " doesn't provide a type.\n" << end();
+    raise_error << "Element " << element << " of container " << name << " doesn't provide a type.\n" << end();
     break;
   }
   if (next_word(in) != "next-type") {
-    raise << "Type " << element << " of container " << name << " must be defined using 'next-type'\n" << end();
+    raise_error << "Type " << element << " of container " << name << " must be defined using 'next-type'\n" << end();
     break;
   }
   type_ordinal next_type_ordinal = SIZE(info.ingredient_names);
diff --git a/031address.cc b/031address.cc
index 66172fbe..fd7c61e0 100644
--- a/031address.cc
+++ b/031address.cc
@@ -25,19 +25,19 @@ recipe main [
 :(before "long long int base = x.value" following "void write_memory(reagent x, vector<double> data)")
 x = canonize(x);
 if (x.value == 0) {
-  raise << "can't write to location 0 in '" << current_instruction().to_string() << "'\n" << end();
+  raise_error << "can't write to location 0 in '" << current_instruction().to_string() << "'\n" << end();
   return;
 }
 
 //: writes to address 0 always loudly fail
-:(scenario store_to_0_warns)
-% Hide_warnings = true;
+:(scenario store_to_0_fails)
+% Hide_errors = true;
 recipe main [
   1:address:number <- copy 0
   1:address:number/lookup <- copy 34
 ]
 -mem: storing 34 in location 0
-+warn: can't write to location 0 in '1:address:number/lookup <- copy 34'
++error: can't write to location 0 in '1:address:number/lookup <- copy 34'
 
 :(code)
 reagent canonize(reagent x) {
@@ -52,12 +52,12 @@ reagent lookup_memory(reagent x) {
   static const type_ordinal ADDRESS = Type_ordinal["address"];
   reagent result;
   if (x.types.empty() || x.types.at(0) != ADDRESS) {
-    raise << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to /lookup " << x.original_string << " but it isn't an address\n" << end();
     return result;
   }
   // compute value
   if (x.value == 0) {
-    raise << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to /lookup 0\n" << end();
     return result;
   }
   result.set_value(Memory[x.value]);
@@ -97,11 +97,11 @@ reagent lookup_memory(reagent x) {
 bool canonize_type(reagent& r) {
   while (has_property(r, "lookup")) {
     if (r.types.empty()) {
-      raise << "can't lookup non-address: " << r.original_string << '\n' << end();
+      raise_error << "can't lookup non-address: " << r.original_string << '\n' << end();
       return false;
     }
     if (r.types.at(0) != Type_ordinal["address"]) {
-      raise << "can't lookup non-address: " << r.original_string << '\n' << end();
+      raise_error << "can't lookup non-address: " << r.original_string << '\n' << end();
       return false;
     }
     r.types.erase(r.types.begin());
@@ -180,7 +180,7 @@ recipe main [
     properties.push_back(pair<string, vector<string> >("lookup", vector<string>()));
   }
   if (name.empty())
-    raise << "illegal name " << original_string << '\n' << end();
+    raise_error << "illegal name " << original_string << '\n' << end();
 }
 
 //:: helpers for debugging
diff --git a/032array.cc b/032array.cc
index f8df003f..1a1b2d17 100644
--- a/032array.cc
+++ b/032array.cc
@@ -20,26 +20,26 @@ Recipe_ordinal["create-array"] = CREATE_ARRAY;
 :(before "End Primitive Recipe Checks")
 case CREATE_ARRAY: {
   if (inst.products.empty()) {
-    raise << maybe(Recipe[r].name) << "'create-array' needs one product and no ingredients but got '" << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'create-array' needs one product and no ingredients but got '" << inst.to_string() << '\n' << end();
     break;
   }
   reagent product = inst.products.at(0);
   canonize_type(product);
   if (!is_mu_array(product)) {
-    raise << maybe(Recipe[r].name) << "'create-array' cannot create non-array " << product.original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'create-array' cannot create non-array " << product.original_string << '\n' << end();
     break;
   }
   if (SIZE(product.types) == 1) {
-    raise << maybe(Recipe[r].name) << "create array of what? " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "create array of what? " << inst.to_string() << '\n' << end();
     break;
   }
   // 'create-array' will need to check properties rather than types
   if (SIZE(product.properties.at(0).second) <= 2) {
-    raise << maybe(Recipe[r].name) << "create array of what size? " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "create array of what size? " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_integer(product.properties.at(0).second.at(2))) {
-    raise << maybe(Recipe[r].name) << "'create-array' product should specify size of array after its element type, but got " << product.properties.at(0).second.at(2) << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'create-array' product should specify size of array after its element type, but got " << product.properties.at(0).second.at(2) << '\n' << end();
     break;
   }
   break;
@@ -108,7 +108,7 @@ if (x.types.at(0) == Type_ordinal["array"]) return false;
 :(before "End size_of(reagent) Cases")
 if (r.types.at(0) == Type_ordinal["array"]) {
   if (SIZE(r.types) == 1) {
-    raise << maybe(current_recipe_name()) << "'" << r.original_string << "' is an array of what?\n" << end();
+    raise_error << maybe(current_recipe_name()) << "'" << r.original_string << "' is an array of what?\n" << end();
     return 1;
   }
   // skip the 'array' type to get at the element type
@@ -145,13 +145,13 @@ Recipe_ordinal["index"] = INDEX;
 :(before "End Primitive Recipe Checks")
 case INDEX: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'index' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'index' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent base = inst.ingredients.at(0);
   canonize_type(base);
   if (!is_mu_array(base)) {
-    raise << maybe(Recipe[r].name) << "'index' on a non-array " << base.original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'index' on a non-array " << base.original_string << '\n' << end();
     break;
   }
   if (inst.products.empty()) break;
@@ -160,7 +160,7 @@ case INDEX: {
   reagent element;
   element.types = array_element(base.types);
   if (!types_match(product, element)) {
-    raise << maybe(Recipe[r].name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
     break;
   }
   break;
@@ -170,14 +170,14 @@ case INDEX: {
   reagent base = canonize(current_instruction().ingredients.at(0));
   long long int base_address = base.value;
   if (base_address == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   reagent offset = canonize(current_instruction().ingredients.at(1));
   vector<double> offset_val(read_memory(offset));
   vector<type_ordinal> element_type = array_element(base.types);
   if (offset_val.at(0) < 0 || offset_val.at(0) >= Memory[base_address]) {
-    raise << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
+    raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
     break;
   }
   long long int src = base_address + 1 + offset_val.at(0)*size_of(element_type);
@@ -207,7 +207,7 @@ recipe main [
 +mem: storing 15 in location 6
 
 :(scenario index_out_of_bounds)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:number:3 <- create-array
   2:number <- copy 14
@@ -219,10 +219,10 @@ recipe main [
   8:address:array:point <- copy 1
   index *8:address:array:point, 4  # less than size of array in locations, but larger than its length in elements
 ]
-+warn: main: invalid index 4
++error: main: invalid index 4
 
 :(scenario index_out_of_bounds_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:point:3 <- create-array
   2:number <- copy 14
@@ -234,10 +234,10 @@ recipe main [
   8:address:array:point <- copy 1/raw
   index *8:address:array:point, -1
 ]
-+warn: main: invalid index -1
++error: main: invalid index -1
 
 :(scenario index_product_type_mismatch)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:point:3 <- create-array
   2:number <- copy 14
@@ -249,7 +249,7 @@ recipe main [
   8:address:array:point <- copy 1/raw
   9:number <- index *8:address:array:point, 0
 ]
-+warn: main: 'index' on *8:address:array:point can't be saved in 9:number; type should be point
++error: main: 'index' on *8:address:array:point can't be saved in 9:number; type should be point
 
 //:: To write to elements of containers, you need their address.
 
@@ -270,13 +270,13 @@ Recipe_ordinal["index-address"] = INDEX_ADDRESS;
 :(before "End Primitive Recipe Checks")
 case INDEX_ADDRESS: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'index-address' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'index-address' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent base = inst.ingredients.at(0);
   canonize_type(base);
   if (!is_mu_array(base)) {
-    raise << current_recipe_name () << "'index-address' on a non-array " << base.original_string << '\n' << end();
+    raise_error << current_recipe_name () << "'index-address' on a non-array " << base.original_string << '\n' << end();
     break;
   }
   if (inst.products.empty()) break;
@@ -286,7 +286,7 @@ case INDEX_ADDRESS: {
   element.types = array_element(base.types);
   element.types.insert(element.types.begin(), Type_ordinal["address"]);
   if (!types_match(product, element)) {
-    raise << maybe(Recipe[r].name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'index' on " << base.original_string << " can't be saved in " << product.original_string << "; type should be " << dump_types(element) << '\n' << end();
     break;
   }
   break;
@@ -296,14 +296,14 @@ case INDEX_ADDRESS: {
   reagent base = canonize(current_instruction().ingredients.at(0));
   long long int base_address = base.value;
   if (base_address == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   reagent offset = canonize(current_instruction().ingredients.at(1));
   vector<double> offset_val(read_memory(offset));
   vector<type_ordinal> element_type = array_element(base.types);
   if (offset_val.at(0) < 0 || offset_val.at(0) >= Memory[base_address]) {
-    raise << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
+    raise_error << maybe(current_recipe_name()) << "invalid index " << no_scientific(offset_val.at(0)) << '\n' << end();
     break;
   }
   long long int result = base_address + 1 + offset_val.at(0)*size_of(element_type);
@@ -313,7 +313,7 @@ case INDEX_ADDRESS: {
 }
 
 :(scenario index_address_out_of_bounds)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:point:3 <- create-array
   2:number <- copy 14
@@ -325,10 +325,10 @@ recipe main [
   8:address:array:point <- copy 1  # unsafe
   index-address *8:address:array:point, 4  # less than size of array in locations, but larger than its length in elements
 ]
-+warn: main: invalid index 4
++error: main: invalid index 4
 
 :(scenario index_address_out_of_bounds_2)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:point:3 <- create-array
   2:number <- copy 14
@@ -340,10 +340,10 @@ recipe main [
   8:address:array:point <- copy 1/raw
   index-address *8:address:array:point, -1
 ]
-+warn: main: invalid index -1
++error: main: invalid index -1
 
 :(scenario index_address_product_type_mismatch)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:array:point:3 <- create-array
   2:number <- copy 14
@@ -355,7 +355,7 @@ recipe main [
   8:address:array:point <- copy 1/raw
   9:address:number <- index-address *8:address:array:point, 0
 ]
-+warn: main: 'index' on *8:address:array:point can't be saved in 9:address:number; type should be address:point
++error: main: 'index' on *8:address:array:point can't be saved in 9:address:number; type should be address:point
 
 //:: compute the length of an array
 
@@ -376,13 +376,13 @@ Recipe_ordinal["length"] = LENGTH;
 :(before "End Primitive Recipe Checks")
 case LENGTH: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'length' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'length' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent x = inst.ingredients.at(0);
   canonize_type(x);
   if (!is_mu_array(x)) {
-    raise << "tried to calculate length of non-array " << x.original_string << '\n' << end();
+    raise_error << "tried to calculate length of non-array " << x.original_string << '\n' << end();
     break;
   }
   break;
@@ -391,7 +391,7 @@ case LENGTH: {
 case LENGTH: {
   reagent x = canonize(current_instruction().ingredients.at(0));
   if (x.value == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   products.resize(1);
diff --git a/033exclusive_container.cc b/033exclusive_container.cc
index d4973c7c..388a4f4f 100644
--- a/033exclusive_container.cc
+++ b/033exclusive_container.cc
@@ -23,7 +23,7 @@ Type[tmp].element_names.push_back("p");
 
 //: Tests in this layer often explicitly setup memory before reading it as an
 //: array. Don't do this in general. I'm tagging exceptions with /raw to
-//: avoid warnings.
+//: avoid errors.
 :(scenario copy_exclusive_container)
 # Copying exclusive containers copies all their contents and an extra location for the tag.
 recipe main [
@@ -84,17 +84,17 @@ Recipe_ordinal["maybe-convert"] = MAYBE_CONVERT;
 :(before "End Primitive Recipe Checks")
 case MAYBE_CONVERT: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'maybe-convert' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'maybe-convert' expects exactly 2 ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent base = inst.ingredients.at(0);
   canonize_type(base);
   if (base.types.empty() || Type[base.types.at(0)].kind != exclusive_container) {
-    raise << current_recipe_name () << "first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end();
+    raise_error << current_recipe_name () << "first ingredient of 'maybe-convert' should be an exclusive-container, but got " << base.original_string << '\n' << end();
     break;
   }
   if (!is_literal(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'maybe-convert' should have type 'variant', but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'maybe-convert' should have type 'variant', but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
@@ -104,7 +104,7 @@ case MAYBE_CONVERT: {
   reagent base = canonize(current_instruction().ingredients.at(0));
   long long int base_address = base.value;
   if (base_address == 0) {
-    raise << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "tried to access location 0 in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   long long int tag = current_instruction().ingredients.at(1).value;
diff --git a/034call.cc b/034call.cc
index 27a26e90..ebfb9534 100644
--- a/034call.cc
+++ b/034call.cc
@@ -80,7 +80,7 @@ inline const instruction& current_instruction() {
 :(after "Defined Recipe Checks")
 // not a primitive; check that it's present in the book of recipes
 if (Recipe.find(inst.operation) == Recipe.end()) {
-  raise << maybe(Recipe[r].name) << "undefined operation in '" << inst.to_string() << "'\n" << end();
+  raise_error << maybe(Recipe[r].name) << "undefined operation in '" << inst.to_string() << "'\n" << end();
   break;
 }
 :(replace{} "default:" following "End Primitive Recipe Implementations")
@@ -98,19 +98,19 @@ default: {
   continue;  // not done with caller; don't increment current_step_index()
 }
 
-:(scenario calling_undefined_recipe_warns)
-% Hide_warnings = true;
+:(scenario calling_undefined_recipe_fails)
+% Hide_errors = true;
 recipe main [
   foo
 ]
-+warn: main: undefined operation in 'foo '
++error: main: undefined operation in 'foo '
 
 :(scenario calling_undefined_recipe_handles_missing_result)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   x:number <- foo
 ]
-+warn: main: undefined operation in 'x:number <- foo '
++error: main: undefined operation in 'x:number <- foo '
 
 //:: finally, we need to fix the termination conditions for the run loop
 
@@ -133,7 +133,7 @@ while (current_step_index() >= SIZE(Current_routine->steps())) {
   Current_routine->calls.pop_front();
   if (Current_routine->calls.empty()) return;
   // Complete Call Fallthrough
-  // todo: no products returned warning
+  // todo: fail if no products returned
   ++current_step_index();
 }
 
diff --git a/035call_ingredient.cc b/035call_ingredient.cc
index fd8e7508..a56510d6 100644
--- a/035call_ingredient.cc
+++ b/035call_ingredient.cc
@@ -38,7 +38,7 @@ Recipe_ordinal["next-ingredient"] = NEXT_INGREDIENT;
 :(before "End Primitive Recipe Checks")
 case NEXT_INGREDIENT: {
   if (!inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'next-ingredient' didn't expect any ingredients in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'next-ingredient' didn't expect any ingredients in '" << inst.to_string() << "'\n" << end();
     break;
   }
   break;
@@ -55,7 +55,7 @@ case NEXT_INGREDIENT: {
   }
   else {
     if (SIZE(current_instruction().products) < 2)
-      raise << maybe(current_recipe_name()) << "no ingredient to save in " << current_instruction().products.at(0).original_string << '\n' << end();
+      raise_error << maybe(current_recipe_name()) << "no ingredient to save in " << current_instruction().products.at(0).original_string << '\n' << end();
     if (current_instruction().products.empty()) break;
     products.resize(2);
     // pad the first product with sufficient zeros to match its type
@@ -68,15 +68,15 @@ case NEXT_INGREDIENT: {
   break;
 }
 
-:(scenario next_ingredient_warn_on_missing)
-% Hide_warnings = true;
+:(scenario next_ingredient_fail_on_missing)
+% Hide_errors = true;
 recipe main [
   f
 ]
 recipe f [
   11:number <- next-ingredient
 ]
-+warn: f: no ingredient to save in 11:number
++error: f: no ingredient to save in 11:number
 
 :(scenario rewind_ingredients)
 recipe main [
@@ -125,11 +125,11 @@ Recipe_ordinal["ingredient"] = INGREDIENT;
 :(before "End Primitive Recipe Checks")
 case INGREDIENT: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'ingredient' expects exactly one ingredient, but got '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'ingredient' expects exactly one ingredient, but got '" << inst.to_string() << "'\n" << end();
     break;
   }
-  if (!is_literal(inst.ingredients.at(0)) && !is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "'ingredient' expects a literal ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+  if (!is_literal(inst.ingredients.at(0)) && !is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "'ingredient' expects a literal ingredient, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/036call_reply.cc b/036call_reply.cc
index 14fb0c5c..ccf18a2f 100644
--- a/036call_reply.cc
+++ b/036call_reply.cc
@@ -32,13 +32,13 @@ case REPLY: {
   const instruction& caller_instruction = current_instruction();
   // check types with the caller
   if (SIZE(caller_instruction.products) > SIZE(ingredients)) {
-    raise << "too few values replied from " << callee << '\n' << end();
+    raise_error << "too few values replied from " << callee << '\n' << end();
     break;
   }
   for (long long int i = 0; i < SIZE(caller_instruction.products); ++i) {
     if (has_property(caller_instruction.products.at(i), "skiptypecheck")) continue;  // todo: drop this once we have generic containers
     if (!types_match(caller_instruction.products.at(i), reply_inst.ingredients.at(i))) {
-      raise << maybe(callee) << "reply ingredient " << reply_inst.ingredients.at(i).original_string << " can't be saved in " << caller_instruction.products.at(i).original_string << '\n' << end();
+      raise_error << maybe(callee) << "reply ingredient " << reply_inst.ingredients.at(i).original_string << " can't be saved in " << caller_instruction.products.at(i).original_string << '\n' << end();
       goto finish_reply;
     }
   }
@@ -51,14 +51,14 @@ case REPLY: {
     if (has_property(reply_inst.ingredients.at(i), "same-as-ingredient")) {
       vector<string> tmp = property(reply_inst.ingredients.at(i), "same-as-ingredient");
       if (SIZE(tmp) != 1) {
-        raise << maybe(current_recipe_name()) << "'same-as-ingredient' metadata should take exactly one value in " << reply_inst.to_string() << '\n' << end();
+        raise_error << maybe(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 << maybe(current_recipe_name()) << "'same-as-ingredient' metadata overflows ingredients in: " << caller_instruction.to_string() << '\n' << end();
+        raise_error << maybe(current_recipe_name()) << "'same-as-ingredient' metadata overflows ingredients in: " << caller_instruction.to_string() << '\n' << end();
       if (!is_dummy(caller_instruction.products.at(i)) && caller_instruction.products.at(i).value != caller_instruction.ingredients.at(ingredient_index).value)
-        raise << maybe(current_recipe_name()) << "'same-as-ingredient' product from call to " << callee << " must be " << caller_instruction.ingredients.at(ingredient_index).original_string << " rather than " << caller_instruction.products.at(i).original_string << '\n' << end();
+        raise_error << maybe(current_recipe_name()) << "'same-as-ingredient' product from call to " << callee << " must be " << caller_instruction.ingredients.at(ingredient_index).original_string << " rather than " << caller_instruction.products.at(i).original_string << '\n' << end();
     }
   }
   // End Reply
@@ -81,7 +81,7 @@ recipe f [
 +mem: storing 35 in location 4
 
 :(scenario reply_type_mismatch)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   3:number <- f 2
 ]
@@ -91,7 +91,7 @@ recipe f [
   14:point <- copy 12:point/raw
   reply 14:point
 ]
-+warn: f: reply ingredient 14:point can't be saved in 3:number
++error: f: reply ingredient 14:point can't be saved in 3:number
 
 //: In mu we'd like to assume that any instruction doesn't modify its
 //: ingredients unless they're also products. The /same-as-ingredient inside
@@ -99,7 +99,7 @@ recipe f [
 //: 'ingredient-products' (sometimes called in-out parameters in other languages).
 
 :(scenario reply_same_as_ingredient)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 0
   2:number <- test1 1:number  # call with different ingredient and product
@@ -108,10 +108,10 @@ recipe test1 [
   10:number <- next-ingredient
   reply 10:number/same-as-ingredient:0
 ]
-+warn: main: 'same-as-ingredient' product from call to test1 must be 1:number rather than 2:number
++error: main: 'same-as-ingredient' product from call to test1 must be 1:number rather than 2:number
 
 :(scenario reply_same_as_ingredient_dummy)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 0
   _ <- test1 1:number  # call with different ingredient and product
@@ -120,7 +120,7 @@ recipe test1 [
   10:address:number <- next-ingredient
   reply 10:address:number/same-as-ingredient:0
 ]
-$warn: 0
+$error: 0
 
 :(code)
 string to_string(const vector<double>& in) {
@@ -182,7 +182,7 @@ if (curr.name == "reply-if") {
     curr.ingredients.swap(results);
   }
   else {
-    raise << "'reply-if' never yields any products\n" << end();
+    raise_error << "'reply-if' never yields any products\n" << end();
   }
 }
 // rewrite `reply-unless a, b, c, ...` to
@@ -205,6 +205,6 @@ if (curr.name == "reply-unless") {
     curr.ingredients.swap(results);
   }
   else {
-    raise << "'reply-unless' never yields any products\n" << end();
+    raise_error << "'reply-unless' never yields any products\n" << end();
   }
 }
diff --git a/037recipe.cc b/037recipe.cc
index c3f258da..f7dd62d7 100644
--- a/037recipe.cc
+++ b/037recipe.cc
@@ -43,11 +43,11 @@ Recipe_ordinal["call"] = CALL;
 :(before "End Primitive Recipe Checks")
 case CALL: {
   if (inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'call' requires at least one ingredient (the recipe to call)\n" << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'call' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'call' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/038scheduler.cc b/038scheduler.cc
index 8f2706ff..ac16db22 100644
--- a/038scheduler.cc
+++ b/038scheduler.cc
@@ -147,11 +147,11 @@ Recipe_ordinal["start-running"] = START_RUNNING;
 :(before "End Primitive Recipe Checks")
 case START_RUNNING: {
   if (inst.ingredients.empty()) {
-    raise << maybe(Recipe[r].name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'start-running' requires at least one ingredient: the recipe to start running\n" << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'start-running' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'start-running' should be a recipe, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -258,7 +258,7 @@ recipe f1 [
 //:: Errors in a routine cause it to terminate.
 
 :(scenario scheduler_terminates_routines_after_errors)
-% Hide_warnings = true;
+% Hide_errors = true;
 % Scheduling_interval = 2;
 recipe f1 [
   start-running f2:recipe
@@ -271,11 +271,11 @@ recipe f2 [
   4:number <- divide-with-remainder 4, 0
 ]
 # f2 should stop after first divide by 0
-+warn: f2: divide by zero in '3:number <- divide-with-remainder 4, 0'
--warn: f2: divide by zero in '4:number <- divide-with-remainder 4, 0'
++error: f2: divide by zero in '3:number <- divide-with-remainder 4, 0'
+-error: f2: divide by zero in '4:number <- divide-with-remainder 4, 0'
 
 :(after "operator<<(ostream& os, unused end)")
-  if (Trace_stream && Trace_stream->curr_label == "warn" && Current_routine) {
+  if (Trace_stream && Trace_stream->curr_label == "error" && Current_routine) {
     Current_routine->state = COMPLETED;
   }
 
@@ -333,11 +333,11 @@ Recipe_ordinal["routine-state"] = ROUTINE_STATE;
 :(before "End Primitive Recipe Checks")
 case ROUTINE_STATE: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'routine-state' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'routine-state' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'routine-state' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -366,11 +366,11 @@ Recipe_ordinal["restart"] = RESTART;
 :(before "End Primitive Recipe Checks")
 case RESTART: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'restart' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'restart' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'restart' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'restart' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -394,11 +394,11 @@ Recipe_ordinal["stop"] = STOP;
 :(before "End Primitive Recipe Checks")
 case STOP: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'stop' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'stop' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'stop' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -476,15 +476,15 @@ Recipe_ordinal["limit-time"] = LIMIT_TIME;
 :(before "End Primitive Recipe Checks")
 case LIMIT_TIME: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'limit-time' requires exactly two ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'limit-time' requires exactly two ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'limit-time' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'limit-time' should be a number (of instructions to run for), but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/039wait.cc b/039wait.cc
index b21f92ca..314ab1cd 100644
--- a/039wait.cc
+++ b/039wait.cc
@@ -95,11 +95,11 @@ Recipe_ordinal["wait-for-routine"] = WAIT_FOR_ROUTINE;
 :(before "End Primitive Recipe Checks")
 case WAIT_FOR_ROUTINE: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'wait-for-routine' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'wait-for-routine' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'wait-for-routine' should be a routine id generated by 'start-running', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -107,7 +107,7 @@ case WAIT_FOR_ROUTINE: {
 :(before "End Primitive Recipe Implementations")
 case WAIT_FOR_ROUTINE: {
   if (ingredients.at(0).at(0) == Current_routine->id) {
-    raise << maybe(current_recipe_name()) << "routine can't wait for itself! " << current_instruction().to_string() << '\n' << end();
+    raise_error << maybe(current_recipe_name()) << "routine can't wait for itself! " << current_instruction().to_string() << '\n' << end();
     break;
   }
   Current_routine->state = WAITING;
diff --git a/040brace.cc b/040brace.cc
index d5e89a6c..ba8e9fd6 100644
--- a/040brace.cc
+++ b/040brace.cc
@@ -76,7 +76,7 @@ void transform_braces(const recipe_ordinal r) {
     // check for errors
     if (inst.name.find("-if") != string::npos || inst.name.find("-unless") != string::npos) {
       if (inst.ingredients.empty()) {
-        raise << inst.name << " expects 1 or 2 ingredients, but got none\n" << end();
+        raise_error << inst.name << " expects 1 or 2 ingredients, but got none\n" << end();
         continue;
       }
     }
@@ -107,7 +107,7 @@ void transform_braces(const recipe_ordinal r) {
     target.types.push_back(Type_ordinal["offset"]);
     target.set_value(0);
     if (open_braces.empty())
-      raise << inst.name << " needs a '{' before\n" << end();
+      raise_error << inst.name << " needs a '{' before\n" << end();
     else if (inst.name.find("loop") != string::npos)
       target.set_value(open_braces.top()-index);
     else  // break instruction
@@ -132,7 +132,7 @@ long long int matching_brace(long long int index, const list<pair<int, long long
     stacksize += (p->first ? 1 : -1);
     if (stacksize == 0) return p->second;
   }
-  raise << maybe(Recipe[r].name) << "unbalanced '{'\n" << end();
+  raise_error << maybe(Recipe[r].name) << "unbalanced '{'\n" << end();
   return SIZE(Recipe[r].steps);  // exit current routine
 }
 
@@ -279,7 +279,7 @@ recipe main [
 +after-brace: jump 2:offset
 
 :(scenario break_label)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 0
   {
@@ -361,18 +361,18 @@ recipe test-factorial [
 ]
 +mem: location 2 is 120
 
-:(scenario break_outside_braces_warns)
-% Hide_warnings = true;
+:(scenario break_outside_braces_fails)
+% Hide_errors = true;
 recipe main [
   break
 ]
-+warn: break needs a '{' before
++error: break needs a '{' before
 
-:(scenario break_conditional_without_ingredient_warns)
-% Hide_warnings = true;
+:(scenario break_conditional_without_ingredient_fails)
+% Hide_errors = true;
 recipe main [
   {
     break-if
   }
 ]
-+warn: break-if expects 1 or 2 ingredients, but got none
++error: break-if expects 1 or 2 ingredients, but got none
diff --git a/041jump_target.cc b/041jump_target.cc
index fad1e3c7..7207c799 100644
--- a/041jump_target.cc
+++ b/041jump_target.cc
@@ -31,7 +31,7 @@ void transform_labels(const recipe_ordinal r) {
         offset[inst.label] = i;
       }
       else {
-        raise << maybe(Recipe[r].name) << "duplicate label '" << inst.label << "'" << end();
+        raise_error << maybe(Recipe[r].name) << "duplicate label '" << inst.label << "'" << end();
         // have all jumps skip some random but noticeable and deterministic amount of code
         offset[inst.label] = 9999;
       }
@@ -60,19 +60,19 @@ 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) {
   if (!is_literal(x)) {
-    raise << maybe(Recipe[r].name) << "jump target must be offset or label but is " << x.original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "jump target must be offset or label but is " << x.original_string << '\n' << end();
     x.set_value(0);  // no jump by default
     return;
   }
   assert(!x.initialized);
   if (is_integer(x.name)) return;  // non-labels will be handled like other number operands
   if (!is_jump_target(x.name)) {
-    raise << maybe(Recipe[r].name) << "can't jump to label " << x.name << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "can't jump to label " << x.name << '\n' << end();
     x.set_value(0);  // no jump by default
     return;
   }
   if (offset.find(x.name) == offset.end()) {
-    raise << maybe(Recipe[r].name) << "can't find label " << x.name << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "can't find label " << x.name << '\n' << end();
     x.set_value(0);  // no jump by default
     return;
   }
@@ -133,18 +133,18 @@ recipe main [
 +mem: storing 0 in location 5
 -mem: storing 0 in location 4
 
-:(scenario recipe_warns_on_duplicate_jump_target)
-% Hide_warnings = true;
+:(scenario recipe_fails_on_duplicate_jump_target)
+% Hide_errors = true;
 recipe main [
   +label
   1:number <- copy 0
   +label
   2:number <- copy 0
 ]
-+warn: main: duplicate label '+label'
++error: main: duplicate label '+label'
 
 :(scenario jump_ignores_nontarget_label)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   # first a few lines of padding to exercise the offset computation
   1:number <- copy 0
@@ -155,4 +155,4 @@ recipe main [
   $target
   5:number <- copy 0
 ]
-+warn: main: can't jump to label $target
++error: main: can't jump to label $target
diff --git a/042name.cc b/042name.cc
index fbc83abd..99fda76c 100644
--- a/042name.cc
+++ b/042name.cc
@@ -10,12 +10,13 @@ recipe main [
 +mem: storing 0 in location 1
 
 :(scenarios transform)
-:(scenario transform_names_warns)
-% Hide_warnings = true;
+:(scenario transform_names_fails_on_use_before_define)
+% Hide_errors = true;
 recipe main [
   x:number <- copy y:number
 ]
-+warn: main: use before set: y
++error: main: use before set: y
+# todo: detect conditional defines
 
 :(after "int main")
   Transform.push_back(transform_names);
@@ -44,7 +45,7 @@ void transform_names(const recipe_ordinal r) {
       if (is_named_location(inst.ingredients.at(in))) names_used = true;
       if (disqualified(inst.ingredients.at(in), inst, Recipe[r].name)) continue;
       if (!already_transformed(inst.ingredients.at(in), names)) {
-        raise << maybe(Recipe[r].name) << "use before set: " << inst.ingredients.at(in).name << '\n' << end();
+        raise_error << maybe(Recipe[r].name) << "use before set: " << inst.ingredients.at(in).name << '\n' << end();
       }
       inst.ingredients.at(in).set_value(lookup_name(inst.ingredients.at(in), r));
     }
@@ -61,12 +62,12 @@ void transform_names(const recipe_ordinal r) {
     }
   }
   if (names_used && numeric_locations_used)
-    raise << maybe(Recipe[r].name) << "mixing variable names and numeric addresses\n" << end();
+    raise_error << maybe(Recipe[r].name) << "mixing variable names and numeric addresses\n" << end();
 }
 
 bool disqualified(/*mutable*/ reagent& x, const instruction& inst, const string& recipe_name) {
   if (x.types.empty()) {
-    raise << maybe(recipe_name) << "missing type for " << x.original_string << " in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(recipe_name) << "missing type for " << x.original_string << " in '" << inst.to_string() << "'\n" << end();
     return true;
   }
   if (is_raw(x)) return true;
@@ -89,7 +90,7 @@ type_ordinal skip_addresses(const vector<type_ordinal>& types, const string& rec
   for (long long int i = 0; i < SIZE(types); ++i) {
     if (types.at(i) != Type_ordinal["address"]) return types.at(i);
   }
-  raise << maybe(recipe_name) << "expected a container" << '\n' << end();
+  raise_error << maybe(recipe_name) << "expected a container" << '\n' << end();
   return -1;
 }
 
@@ -98,7 +99,7 @@ int find_element_name(const type_ordinal t, const string& name, const string& re
   for (long long int i = 0; i < SIZE(container.element_names); ++i) {
     if (container.element_names.at(i) == name) return i;
   }
-  raise << maybe(recipe_name) << "unknown element " << name << " in container " << Type[t].name << '\n' << end();
+  raise_error << maybe(recipe_name) << "unknown element " << name << " in container " << Type[t].name << '\n' << end();
   return -1;
 }
 
@@ -134,44 +135,44 @@ recipe main [
 //: an escape hatch to suppress name conversion that we'll use later
 :(scenarios run)
 :(scenario transform_names_passes_raw)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   x:number/raw <- copy 0
 ]
 -name: assign x 1
-+warn: can't write to location 0 in 'x:number/raw <- copy 0'
++error: can't write to location 0 in 'x:number/raw <- copy 0'
 
 :(scenarios transform)
-:(scenario transform_names_warns_when_mixing_names_and_numeric_locations)
-% Hide_warnings = true;
+:(scenario transform_names_fails_when_mixing_names_and_numeric_locations)
+% Hide_errors = true;
 recipe main [
   x:number <- copy 1:number
 ]
-+warn: main: mixing variable names and numeric addresses
++error: main: mixing variable names and numeric addresses
 
-:(scenario transform_names_warns_when_mixing_names_and_numeric_locations_2)
-% Hide_warnings = true;
+:(scenario transform_names_fails_when_mixing_names_and_numeric_locations_2)
+% Hide_errors = true;
 recipe main [
   x:number <- copy 1
   1:number <- copy x:number
 ]
-+warn: main: mixing variable names and numeric addresses
++error: main: mixing variable names and numeric addresses
 
-:(scenario transform_names_does_not_warn_when_mixing_names_and_raw_locations)
-% Hide_warnings = true;
+:(scenario transform_names_does_not_fail_when_mixing_names_and_raw_locations)
+% Hide_errors = true;
 recipe main [
   x:number <- copy 1:number/raw
 ]
--warn: main: mixing variable names and numeric addresses
-$warn: 0
+-error: main: mixing variable names and numeric addresses
+$error: 0
 
-:(scenario transform_names_does_not_warn_when_mixing_names_and_literals)
-% Hide_warnings = true;
+:(scenario transform_names_does_not_fail_when_mixing_names_and_literals)
+% Hide_errors = true;
 recipe main [
   x:number <- copy 1
 ]
--warn: main: mixing variable names and numeric addresses
-$warn: 0
+-error: main: mixing variable names and numeric addresses
+$error: 0
 
 //:: Support element names for containers in 'get' and 'get-address'.
 
@@ -193,11 +194,11 @@ recipe main [
 if (inst.operation == Recipe_ordinal["get"]
     || inst.operation == Recipe_ordinal["get-address"]) {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "exactly 2 ingredients expected in '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "exactly 2 ingredients expected in '" << inst.to_string() << "'\n" << end();
     break;
   }
   if (!is_literal(inst.ingredients.at(1)))
-    raise << maybe(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();
+    raise_error << maybe(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) {
     // since first non-address in base type must be a container, we don't have to canonize
     type_ordinal base_type = skip_addresses(inst.ingredients.at(0).types, Recipe[r].name);
@@ -233,7 +234,7 @@ recipe main [
 // convert variant names of exclusive containers
 if (inst.operation == Recipe_ordinal["maybe-convert"]) {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "exactly 2 ingredients expected in '" << current_instruction().to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "exactly 2 ingredients expected in '" << current_instruction().to_string() << "'\n" << end();
     break;
   }
   assert(is_literal(inst.ingredients.at(1)));
diff --git a/043new.cc b/043new.cc
index 6bd733d1..f821fad3 100644
--- a/043new.cc
+++ b/043new.cc
@@ -34,13 +34,13 @@ if (inst.operation == Recipe_ordinal["new"]) {
   // End NEW Transform Special-cases
   // first arg must be of type 'type'
   if (inst.ingredients.empty())
-    raise << maybe(Recipe[r].name) << "'new' expects one or two ingredients\n" << end();
+    raise_error << maybe(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 << maybe(Recipe[r].name) << "first ingredient of 'new' should be a type, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(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 << maybe(Recipe[r].name) << "unknown type " << inst.ingredients.at(0).name << '\n' << end();
+    raise_error << maybe(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,12 +56,12 @@ Recipe_ordinal["new"] = NEW;
 :(before "End Primitive Recipe Checks")
 case NEW: {
   if (inst.ingredients.empty() || SIZE(inst.ingredients) > 2) {
-    raise << maybe(Recipe[r].name) << "'new' requires one or two ingredients, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'new' requires one or two ingredients, but got " << inst.to_string() << '\n' << end();
     break;
   }
   reagent type = inst.ingredients.at(0);
   if (!is_mu_scalar(type) && !is_literal(type)) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'new' should be a type, but got " << type.original_string << '\n' << end();
     break;
   }
   break;
@@ -221,13 +221,13 @@ Recipe_ordinal["abandon"] = ABANDON;
 :(before "End Primitive Recipe Checks")
 case ABANDON: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'abandon' requires one ingredient, but got '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'abandon' requires one ingredient, but got '" << inst.to_string() << "'\n" << end();
     break;
   }
   reagent types = inst.ingredients.at(0);
   canonize_type(types);
   if (types.types.empty() || types.types.at(0) != Type_ordinal["address"]) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'abandon' should be an address, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'abandon' should be an address, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -260,7 +260,7 @@ if (Free_list[size]) {
   Free_list[size] = Memory[result];
   for (long long int curr = result+1; curr < result+size; ++curr) {
     if (Memory[curr] != 0) {
-      raise << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
+      raise_error << maybe(current_recipe_name()) << "memory in free list was not zeroed out: " << curr << '/' << result << "; somebody wrote to us after free!!!\n" << end();
       break;  // always fatal
     }
   }
diff --git a/044space.cc b/044space.cc
index 59b292a5..55e3cc23 100644
--- a/044space.cc
+++ b/044space.cc
@@ -52,7 +52,7 @@ reagent absolutize(reagent x) {
   if (is_raw(x) || is_dummy(x)) return x;
   if (x.name == "default-space") return x;
   if (!x.initialized) {
-    raise << current_instruction().to_string() << ": reagent not initialized: " << x.original_string << '\n' << end();
+    raise_error << current_instruction().to_string() << ": reagent not initialized: " << x.original_string << '\n' << end();
     return x;
   }
   reagent r = x;
@@ -133,12 +133,12 @@ if (curr.name == "new-default-space") {
     vector<double> result;
     result.push_back(Name[Recipe_ordinal[current_recipe_name()]][""]);
     if (result.back() == 0)
-      raise << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names\n" << end();
+      raise_error << "no space allocated for default-space in recipe " << current_recipe_name() << "; are you using names\n" << end();
     return result;
   }
 :(after "void write_memory(reagent x, vector<double> data)")
   if (x.name == "number-of-locals") {
-    raise << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
+    raise_error << maybe(current_recipe_name()) << "can't write to special name 'number-of-locals'\n" << end();
     return;
   }
 
@@ -185,11 +185,11 @@ void try_reclaim_locals() {
 void rewrite_default_space_instruction(instruction& curr) {
   curr.operation = Recipe_ordinal["new"];
   if (!curr.ingredients.empty())
-    raise << "new-default-space can't take any ingredients\n" << end();
+    raise_error << "new-default-space can't take any ingredients\n" << end();
   curr.ingredients.push_back(reagent("location:type"));
   curr.ingredients.push_back(reagent("number-of-locals:literal"));
   if (!curr.products.empty())
-    raise << "new-default-space can't take any results\n" << end();
+    raise_error << "new-default-space can't take any results\n" << end();
   curr.products.push_back(reagent("default-space:address:array:location"));
 }
 
@@ -204,7 +204,7 @@ long long int address(long long int offset, long long int base) {
   if (base == 0) return offset;  // raw
   if (offset >= static_cast<long long int>(Memory[base])) {
     // todo: test
-    raise << "location " << offset << " is out of bounds " << no_scientific(Memory[base]) << " at " << base << '\n' << end();
+    raise_error << "location " << offset << " is out of bounds " << no_scientific(Memory[base]) << " at " << base << '\n' << end();
   }
   return base+1 + offset;
 }
@@ -212,7 +212,7 @@ 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") {
     if (!scalar(data))
-      raise << maybe(current_recipe_name()) << "'default-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end();
+      raise_error << maybe(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 94a717a0..a29ad67c 100644
--- a/045space_surround.cc
+++ b/045space_surround.cc
@@ -42,7 +42,7 @@ 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") {
       if (SIZE(x.properties.at(i).second) != 1)
-        raise << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end();
+        raise_error << maybe(current_recipe_name()) << "/space metadata should take exactly one value in " << x.original_string << '\n' << end();
       return to_integer(x.properties.at(i).second.at(0));
     }
   }
diff --git a/046closure_name.cc b/046closure_name.cc
index eef4753f..82ba5db3 100644
--- a/046closure_name.cc
+++ b/046closure_name.cc
@@ -50,19 +50,19 @@ void collect_surrounding_spaces(const recipe_ordinal r) {
           || inst.products.at(j).types.at(0) != Type_ordinal["address"]
           || inst.products.at(j).types.at(1) != Type_ordinal["array"]
           || inst.products.at(j).types.at(2) != Type_ordinal["location"]) {
-        raise << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n' << end();
+        raise_error << "slot 0 should always have type address:array:location, but is " << inst.products.at(j).to_string() << '\n' << end();
         continue;
       }
       vector<string> s = property(inst.products.at(j), "names");
       if (s.empty()) {
-        raise << "slot 0 requires a /names property in recipe " << Recipe[r].name << end();
+        raise_error << "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, but got " << inst.products.at(j).to_string() << '\n' << end();
+      if (SIZE(s) > 1) raise_error << "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]) {
-        raise << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n' << end();
+        raise_error << "recipe " << Recipe[r].name << " can have only one 'surrounding' recipe but has " << Recipe[Surrounding_space[r]].name << " and " << surrounding_recipe_name << '\n' << end();
         continue;
       }
       trace("name") << "recipe " << Recipe[r].name << " is surrounded by " << surrounding_recipe_name << end();
@@ -77,11 +77,11 @@ void collect_surrounding_spaces(const recipe_ordinal r) {
 :(replace{} "long long int lookup_name(const reagent& r, const recipe_ordinal default_recipe)")
 long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe) {
   if (!has_property(x, "space")) {
-    if (Name[default_recipe].empty()) raise << "name not found: " << x.name << '\n' << end();
+    if (Name[default_recipe].empty()) raise_error << "name not found: " << x.name << '\n' << end();
     return Name[default_recipe][x.name];
   }
   vector<string> p = property(x, "space");
-  if (SIZE(p) != 1) raise << "/space property should have exactly one (non-negative integer) value\n" << end();
+  if (SIZE(p) != 1) raise_error << "/space property should have exactly one (non-negative integer) value\n" << end();
   long long int n = to_integer(p.at(0));
   assert(n >= 0);
   recipe_ordinal surrounding_recipe = lookup_surrounding_recipe(default_recipe, n);
@@ -95,11 +95,11 @@ long long int lookup_name(const reagent& x, const recipe_ordinal default_recipe)
 long long int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_ordinal>& done, vector<recipe_ordinal>& path) {
   if (!Name[r].empty()) return Name[r][x.name];
   if (done.find(r) != done.end()) {
-    raise << "can't compute address of " << x.to_string() << " because " << end();
+    raise_error << "can't compute address of " << x.to_string() << " because " << end();
     for (long long int i = 1; i < SIZE(path); ++i) {
-      raise << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end();
+      raise_error << path.at(i-1) << " requires computing names of " << path.at(i) << '\n' << end();
     }
-    raise << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end();
+    raise_error << path.at(SIZE(path)-1) << " requires computing names of " << r << "..ad infinitum\n" << end();
     return 0;
   }
   done.insert(r);
@@ -112,20 +112,20 @@ long long int lookup_name(const reagent& x, const recipe_ordinal r, set<recipe_o
 recipe_ordinal lookup_surrounding_recipe(const recipe_ordinal r, long long int n) {
   if (n == 0) return r;
   if (Surrounding_space.find(r) == Surrounding_space.end()) {
-    raise << "don't know surrounding recipe of " << Recipe[r].name << '\n' << end();
+    raise_error << "don't know surrounding recipe of " << Recipe[r].name << '\n' << end();
     return 0;
   }
   assert(Surrounding_space[r]);
   return lookup_surrounding_recipe(Surrounding_space[r], n-1);
 }
 
-//: weaken use-before-set warnings just a tad
+//: weaken use-before-set detection just a tad
 :(replace{} "bool already_transformed(const reagent& r, const map<string, long long int>& names)")
 bool already_transformed(const reagent& r, const map<string, long long int>& names) {
   if (has_property(r, "space")) {
     vector<string> p = property(r, "space");
     if (SIZE(p) != 1) {
-      raise << "/space property should have exactly one (non-negative integer) value in " << r.original_string << '\n' << end();
+      raise_error << "/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;
diff --git a/047global.cc b/047global.cc
index 00e8ff15..d0a3d47e 100644
--- a/047global.cc
+++ b/047global.cc
@@ -31,10 +31,15 @@ long long int global_space;
 global_space = 0;
 :(after "void write_memory(reagent x, vector<double> data)")
   if (x.name == "global-space") {
-    if (!scalar(data))
-      raise << maybe(current_recipe_name()) << "'global-space' should be of type address:array:location, but tried to write " << to_string(data) << '\n' << end();
+    if (!scalar(data)
+        || SIZE(x.types) != 3
+        || x.types.at(0) != Type_ordinal["address"]
+        || x.types.at(1) != Type_ordinal["array"]
+        || x.types.at(2) != Type_ordinal["location"]) {
+      raise_error << maybe(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();
+      raise_error << "routine already has a global-space; you can't over-write your globals" << end();
     Current_routine->global_space = data.at(0);
     return;
   }
@@ -43,7 +48,7 @@ global_space = 0;
 :(after "long long int space_base(const reagent& x)")
   if (is_global(x)) {
     if (!Current_routine->global_space)
-      raise << "routine has no global space\n" << end();
+      raise_error << "routine has no global space\n" << end();
     return Current_routine->global_space;
   }
 
@@ -51,14 +56,14 @@ global_space = 0;
 //: don't want to make them too comfortable to use.
 
 :(scenario global_space_with_names)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   global-space:address:array:location <- new location:type, 10
   x:number <- copy 23
   1:number/space:global <- copy 24
 ]
-# don't warn that we're mixing numeric addresses and names
-$warn: 0
+# don't complain about mixing numeric addresses and names
+$error: 0
 
 :(after "bool is_numeric_location(const reagent& x)")
   if (is_global(x)) return false;
diff --git a/048check_type_by_name.cc b/048check_type_by_name.cc
index cd113631..e96c54f2 100644
--- a/048check_type_by_name.cc
+++ b/048check_type_by_name.cc
@@ -6,13 +6,13 @@
 //: every single time. You can't use the same name with multiple types in a
 //: single recipe.
 
-:(scenario transform_warns_on_reusing_name_with_different_type)
-% Hide_warnings = true;
+:(scenario transform_fails_on_reusing_name_with_different_type)
+% Hide_errors = true;
 recipe main [
   x:number <- copy 1
   x:boolean <- copy 1
 ]
-+warn: main: x used with multiple types
++error: main: x used with multiple types
 
 :(after "int main")
   Transform.push_back(check_types_by_name);
@@ -38,11 +38,11 @@ void check_metadata(map<string, vector<type_ordinal> >& metadata, const reagent&
   if (is_raw(x)) return;
   // if you use raw locations you're probably doing something unsafe
   if (is_integer(x.name)) return;
-  if (x.types.empty()) return;  // will throw a more precise warning elsewhere
+  if (x.types.empty()) return;  // will throw a more precise error elsewhere
   if (metadata.find(x.name) == metadata.end())
     metadata[x.name] = x.types;
   if (metadata[x.name] != x.types)
-    raise << maybe(Recipe[r].name) << x.name << " used with multiple types\n" << end();
+    raise_error << maybe(Recipe[r].name) << x.name << " used with multiple types\n" << end();
 }
 
 :(scenario transform_fills_in_missing_types)
@@ -74,18 +74,18 @@ recipe main [
 ]
 +mem: storing 2 in location 1
 
-:(scenario transform_warns_on_missing_types_in_first_mention)
-% Hide_warnings = true;
+:(scenario transform_fails_on_missing_types_in_first_mention)
+% Hide_errors = true;
 recipe main [
   x <- copy 1
   x:number <- copy 2
 ]
-+warn: main: missing type for x in 'x <- copy 1'
++error: main: missing type for x in 'x <- copy 1'
 
-:(scenario typo_in_address_type_warns)
-% Hide_warnings = true;
+:(scenario typo_in_address_type_fails)
+% Hide_errors = true;
 recipe main [
   y:address:charcter <- new character:type
   *y <- copy 67
 ]
-+warn: unknown type: charcter
++error: unknown type: charcter
diff --git a/050scenario.cc b/050scenario.cc
index 5f92fec8..6dbd9f56 100644
--- a/050scenario.cc
+++ b/050scenario.cc
@@ -75,7 +75,7 @@ scenario parse_scenario(istream& in) {
   scenario result;
   result.name = next_word(in);
   if (Scenario_names.find(result.name) != Scenario_names.end())
-    raise << "duplicate scenario name: " << result.name << '\n' << end();
+    raise_error << "duplicate scenario name: " << result.name << '\n' << end();
   Scenario_names.insert(result.name);
   skip_whitespace_and_comments(in);
   assert(in.peek() == '[');
@@ -224,7 +224,7 @@ recipe main [
 +mem: storing 13 in location 1
 +mem: storing 13 in location 2
 
-//: 'memory-should-contain' raises warnings if specific locations aren't as expected
+//: 'memory-should-contain' raises errors if specific locations aren't as expected
 //: Also includes some special support for checking strings.
 
 :(before "End Globals")
@@ -234,14 +234,14 @@ Scenario_testing_scenario = false;
 
 :(scenario memory_check)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   memory-should-contain [
     1 <- 13
   ]
 ]
 +run: checking location 1
-+warn: expected location 1 to contain 13 but saw 0
++error: expected location 1 to contain 13 but saw 0
 
 :(before "End Primitive Recipe Declarations")
 MEMORY_SHOULD_CONTAIN,
@@ -277,16 +277,16 @@ void check_memory(const string& s) {
     skip_whitespace_and_comments(in);
     double value = 0;  in >> value;
     if (locations_checked.find(address) != locations_checked.end())
-      raise << "duplicate expectation for location " << address << '\n' << end();
+      raise_error << "duplicate expectation for location " << address << '\n' << end();
     trace(Primitive_recipe_depth, "run") << "checking location " << address << end();
     if (Memory[address] != value) {
       if (Current_scenario && !Scenario_testing_scenario) {
         // genuine test in a mu file
-        raise << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(Memory[address]) << '\n' << end();
+        raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(Memory[address]) << '\n' << end();
       }
       else {
         // just testing scenario support
-        raise << "expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(Memory[address]) << '\n' << end();
+        raise_error << "expected location " << address << " to contain " << no_scientific(value) << " but saw " << no_scientific(Memory[address]) << '\n' << end();
       }
       if (!Scenario_testing_scenario) {
         Passed = false;
@@ -314,16 +314,16 @@ void check_type(const string& lhs, istream& in) {
     check_string(address, literal);
     return;
   }
-  raise << "don't know how to check memory for " << lhs << '\n' << end();
+  raise_error << "don't know how to check memory for " << lhs << '\n' << end();
 }
 
 void check_string(long long int address, const string& literal) {
   trace(Primitive_recipe_depth, "run") << "checking string length at " << address << end();
   if (Memory[address] != SIZE(literal)) {
     if (Current_scenario && !Scenario_testing_scenario)
-      raise << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(Memory[address]) << '\n' << end();
+      raise_error << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(Memory[address]) << '\n' << end();
     else
-      raise << "expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(Memory[address]) << '\n' << end();
+      raise_error << "expected location " << address << " to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(Memory[address]) << '\n' << end();
     if (!Scenario_testing_scenario) {
       Passed = false;
       ++Num_failures;
@@ -336,11 +336,11 @@ void check_string(long long int address, const string& literal) {
     if (Memory[address+i] != literal.at(i)) {
       if (Current_scenario && !Scenario_testing_scenario) {
         // genuine test in a mu file
-        raise << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(Memory[address+i]) << '\n' << end();
+        raise_error << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(Memory[address+i]) << '\n' << end();
       }
       else {
         // just testing scenario support
-        raise << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(Memory[address+i]) << '\n' << end();
+        raise_error << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(Memory[address+i]) << '\n' << end();
       }
       if (!Scenario_testing_scenario) {
         Passed = false;
@@ -353,18 +353,18 @@ void check_string(long long int address, const string& literal) {
 
 :(scenario memory_check_multiple)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   memory-should-contain [
     1 <- 0
     1 <- 0
   ]
 ]
-+warn: duplicate expectation for location 1
++error: duplicate expectation for location 1
 
 :(scenario memory_check_string_length)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 3
   2:number <- copy 97  # 'a'
@@ -374,7 +374,7 @@ recipe main [
     1:string <- [ab]
   ]
 ]
-+warn: expected location 1 to contain length 2 of string [ab] but saw 3
++error: expected location 1 to contain length 2 of string [ab] but saw 3
 
 :(scenario memory_check_string)
 recipe main [
@@ -397,16 +397,16 @@ recipe main [
 // that the lines are present *and* in the specified sequence. (There can be
 // other lines in between.)
 
-:(scenario trace_check_warns_on_failure)
+:(scenario trace_check_fails)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   trace-should-contain [
     a: b
     a: d
   ]
 ]
-+warn: missing [b] in trace with label a
++error: missing [b] in trace with label a
 
 :(before "End Primitive Recipe Declarations")
 TRACE_SHOULD_CONTAIN,
@@ -424,7 +424,7 @@ case TRACE_SHOULD_CONTAIN: {
 }
 
 :(code)
-// simplified version of check_trace_contents() that emits warnings rather
+// simplified version of check_trace_contents() that emits errors rather
 // than just printing to stderr
 bool check_trace(const string& expected) {
   Trace_stream->newline();
@@ -441,8 +441,8 @@ bool check_trace(const string& expected) {
     }
   }
 
-  raise << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
-        << "in trace with label " << expected_lines.at(curr_expected_line).label << '\n' << end();
+  raise_error << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
+              << "in trace with label " << expected_lines.at(curr_expected_line).label << '\n' << end();
   Passed = false;
   return false;
 }
@@ -459,9 +459,9 @@ vector<trace_line> parse_trace(const string& expected) {
   return result;
 }
 
-:(scenario trace_check_warns_on_failure_in_later_line)
+:(scenario trace_check_fails_in_nonfirst_line)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   run [
     trace 1, [a], [b]
@@ -471,11 +471,11 @@ recipe main [
     a: d
   ]
 ]
-+warn: missing [d] in trace with label a
++error: missing [d] in trace with label a
 
 :(scenario trace_check_passes_silently)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   run [
     trace 1, [a], [b]
@@ -484,16 +484,16 @@ recipe main [
     a: b
   ]
 ]
--warn: missing [b] in trace with label a
-$warn: 0
+-error: missing [b] in trace with label a
+$error: 0
 
 //: 'trace-should-not-contain' is like the '-' lines in our scenarios so far
 //: Each trace line is separately checked for absense. Order is *not*
 //: important, so you can't say things like "B should not exist after A."
 
-:(scenario trace_negative_check_warns_on_failure)
+:(scenario trace_negative_check_fails)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   run [
     trace 1, [a], [b]
@@ -502,7 +502,7 @@ recipe main [
     a: b
   ]
 ]
-+warn: unexpected [b] in trace with label a
++error: unexpected [b] in trace with label a
 
 :(before "End Primitive Recipe Declarations")
 TRACE_SHOULD_NOT_CONTAIN,
@@ -520,14 +520,14 @@ case TRACE_SHOULD_NOT_CONTAIN: {
 }
 
 :(code)
-// simplified version of check_trace_contents() that emits warnings rather
+// simplified version of check_trace_contents() that emits errors rather
 // than just printing to stderr
 bool check_trace_missing(const string& in) {
   Trace_stream->newline();
   vector<trace_line> lines = parse_trace(in);
   for (long long int i = 0; i < SIZE(lines); ++i) {
     if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) {
-      raise << "unexpected [" << lines.at(i).contents << "] in trace with label " << lines.at(i).label << '\n' << end();
+      raise_error << "unexpected [" << lines.at(i).contents << "] in trace with label " << lines.at(i).label << '\n' << end();
       Passed = false;
       return false;
     }
@@ -537,18 +537,18 @@ bool check_trace_missing(const string& in) {
 
 :(scenario trace_negative_check_passes_silently)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   trace-should-not-contain [
     a: b
   ]
 ]
--warn: unexpected [b] in trace with label a
-$warn: 0
+-error: unexpected [b] in trace with label a
+$error: 0
 
-:(scenario trace_negative_check_warns_on_any_unexpected_line)
+:(scenario trace_negative_check_fails_on_any_unexpected_line)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   run [
     trace 1, [a], [d]
@@ -558,7 +558,7 @@ recipe main [
     a: d
   ]
 ]
-+warn: unexpected [d] in trace with label a
++error: unexpected [d] in trace with label a
 
 :(scenario trace_count_check)
 recipe main [
@@ -575,15 +575,15 @@ Recipe_ordinal["check-trace-count-for-label"] = CHECK_TRACE_COUNT_FOR_LABEL;
 :(before "End Primitive Recipe Checks")
 case CHECK_TRACE_COUNT_FOR_LABEL: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'check-trace-for-label' requires exactly two ingredients, but got '" << inst.to_string() << "'\n" << end();
+    raise_error << maybe(Recipe[r].name) << "'check-trace-for-label' requires exactly two ingredients, but got '" << inst.to_string() << "'\n" << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'check-trace-for-label' should be a number (count), but got " << inst.ingredients.at(0).original_string << '\n' << end();
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'check-trace-for-label' should be a number (count), but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   if (!is_literal_string(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'check-trace-for-label' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'check-trace-for-label' should be a literal string (label), but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
@@ -597,13 +597,13 @@ case CHECK_TRACE_COUNT_FOR_LABEL: {
   if (count != expected_count) {
     if (Current_scenario && !Scenario_testing_scenario) {
       // genuine test in a mu file
-      raise << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace: ";
+      raise_error << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace: ";
       DUMP(label);
-      raise;
+      raise_error;
     }
     else {
       // just testing scenario support
-      raise << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace\n" << end();
+      raise_error << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label " << label << " in trace\n" << end();
     }
     if (!Scenario_testing_scenario) {
       Passed = false;
@@ -615,14 +615,14 @@ case CHECK_TRACE_COUNT_FOR_LABEL: {
 
 :(scenario trace_count_check_2)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   run [
     trace 1, [a], [foo]
   ]
   check-trace-count-for-label 2, [a]
 ]
-+warn: main: expected 2 lines in trace with label a in trace
++error: main: expected 2 lines in trace with label a in trace
 
 //: Minor detail: ignore 'system' calls in scenarios, since anything we do
 //: with them is by definition impossible to test through mu.
diff --git a/052tangle.cc b/052tangle.cc
index 84b96d7f..08c5f790 100644
--- a/052tangle.cc
+++ b/052tangle.cc
@@ -39,7 +39,7 @@ else if (command == "before") {
   if (is_waypoint(label))
     Before_fragments[label].steps.insert(Before_fragments[label].steps.end(), tmp.steps.begin(), tmp.steps.end());
   else
-    raise << "can't tangle before label " << label << '\n' << end();
+    raise_error << "can't tangle before label " << label << '\n' << end();
 }
 else if (command == "after") {
   string label = next_word(in);
@@ -47,7 +47,7 @@ else if (command == "after") {
   if (is_waypoint(label))
     After_fragments[label].steps.insert(After_fragments[label].steps.begin(), tmp.steps.begin(), tmp.steps.end());
   else
-    raise << "can't tangle after label " << label << '\n' << end();
+    raise_error << "can't tangle after label " << label << '\n' << end();
 }
 
 //: after all recipes are loaded, insert fragments at appropriate labels.
@@ -130,7 +130,7 @@ bool is_waypoint(string label) {
   return *label.begin() == '<' && *label.rbegin() == '>';
 }
 
-//: warn about unapplied fragments
+//: complain about unapplied fragments
 :(before "End Globals")
 bool Transform_check_insert_fragments_Ran = false;
 :(before "End One-time Setup")
@@ -141,11 +141,11 @@ void check_insert_fragments(unused recipe_ordinal) {
   Transform_check_insert_fragments_Ran = true;
   for (map<string, recipe>::iterator p = Before_fragments.begin(); p != Before_fragments.end(); ++p) {
     if (Fragments_used.find(p->first) == Fragments_used.end())
-      raise << "could not locate insert before " << p->first << '\n' << end();
+      raise_error << "could not locate insert before " << p->first << '\n' << end();
   }
   for (map<string, recipe>::iterator p = After_fragments.begin(); p != After_fragments.end(); ++p) {
     if (Fragments_used.find(p->first) == Fragments_used.end())
-      raise << "could not locate insert after " << p->first << '\n' << end();
+      raise_error << "could not locate insert after " << p->first << '\n' << end();
   }
 }
 
@@ -170,7 +170,7 @@ after <label1> [
 $mem: 4
 
 :(scenario tangle_ignores_jump_target)
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   1:number <- copy 0
   +label1
@@ -179,7 +179,7 @@ recipe main [
 before +label1 [
   2:number <- copy 0
 ]
-+warn: can't tangle before label +label1
++error: can't tangle before label +label1
 +mem: storing 0 in location 1
 +mem: storing 0 in location 4
 # label1
diff --git a/053continuation.cc b/053continuation.cc
index 18174cc1..c9a6a5cb 100644
--- a/053continuation.cc
+++ b/053continuation.cc
@@ -45,7 +45,7 @@ Recipe_ordinal["continue-from"] = CONTINUE_FROM;
 :(before "End Primitive Recipe Checks")
 case CONTINUE_FROM: {
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'continue-from' should be a continuation id generated by 'current-continuation', but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'continue-from' should be a continuation id generated by 'current-continuation', but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -214,7 +214,7 @@ case REPLY_DELIMITED_CONTINUATION: {
   call_stack::iterator find_reset(call_stack& c);  // manual prototype containing '::'
   call_stack::iterator reset = find_reset(Current_routine->calls);
   if (reset == Current_routine->calls.end()) {
-    raise << maybe(current_recipe_name()) << "couldn't find a 'reset' call to jump out to\n" << end();
+    raise_error << maybe(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);
@@ -244,7 +244,7 @@ call_stack::iterator find_reset(call_stack& c) {
     // copy multiple calls on to current call stack
     assert(scalar(ingredients.at(0)));
     if (Delimited_continuation.find(ingredients.at(0).at(0)) == Delimited_continuation.end()) {
-      raise << maybe(current_recipe_name()) << "no such delimited continuation " << current_instruction().ingredients.at(0).original_string << '\n' << end();
+      raise_error << maybe(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)
diff --git a/064random.cc b/064random.cc
index de304714..12e1b848 100644
--- a/064random.cc
+++ b/064random.cc
@@ -36,11 +36,11 @@ Recipe_ordinal["round"] = ROUND;
 :(before "End Primitive Recipe Checks")
 case ROUND: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'round' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'round' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'round' should be a number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/070display.cc b/070display.cc
index 8859a909..49668a99 100644
--- a/070display.cc
+++ b/070display.cc
@@ -22,9 +22,9 @@ case OPEN_CONSOLE: {
   long long int height = tb_height();
   if (width > 222 || height > 222) tb_shutdown();
   if (width > 222)
-    raise << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end();
+    raise_error << "sorry, mu doesn't support windows wider than 222 characters. Please resize your window.\n" << end();
   if (height > 222)
-    raise << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end();
+    raise_error << "sorry, mu doesn't support windows taller than 222 characters. Please resize your window.\n" << end();
   break;
 }
 
@@ -99,6 +99,26 @@ PRINT_CHARACTER_TO_DISPLAY,
 Recipe_ordinal["print-character-to-display"] = PRINT_CHARACTER_TO_DISPLAY;
 :(before "End Primitive Recipe Checks")
 case PRINT_CHARACTER_TO_DISPLAY: {
+  if (inst.ingredients.empty()) {
+    raise_error << maybe(Recipe[r].name) << "'print-character-to-display' requires at least one ingredient, but got " << inst.to_string() << '\n' << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'print-character-to-display' should be a character, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    break;
+  }
+  if (SIZE(inst.ingredients) > 1) {
+    if (!is_mu_number(inst.ingredients.at(1))) {
+      raise_error << maybe(Recipe[r].name) << "second ingredient of 'print-character-to-display' should be a foreground color number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
+      break;
+    }
+  }
+  if (SIZE(inst.ingredients) > 2) {
+    if (!is_mu_number(inst.ingredients.at(2))) {
+      raise_error << maybe(Recipe[r].name) << "third ingredient of 'print-character-to-display' should be a background color number, but got " << inst.ingredients.at(2).original_string << '\n' << end();
+      break;
+    }
+  }
   break;
 }
 :(before "End Primitive Recipe Implementations")
@@ -106,29 +126,13 @@ 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;
-  if (ingredients.empty()) {
-    raise << maybe(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 << maybe(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);
   int color = TB_BLACK;
   if (SIZE(ingredients) > 1) {
-    if (!scalar(ingredients.at(1))) {
-      raise << maybe(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);
   }
   int bg_color = TB_BLACK;
   if (SIZE(ingredients) > 2) {
-    if (!scalar(ingredients.at(2))) {
-      raise << maybe(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;
   }
@@ -182,15 +186,15 @@ Recipe_ordinal["move-cursor-on-display"] = MOVE_CURSOR_ON_DISPLAY;
 :(before "End Primitive Recipe Checks")
 case MOVE_CURSOR_ON_DISPLAY: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'move-cursor-on-display' requires two ingredients, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'move-cursor-on-display' requires two ingredients, but got " << inst.to_string() << '\n' << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'move-cursor-on-display' should be a row number, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
+  if (!is_mu_number(inst.ingredients.at(1))) {
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'move-cursor-on-display' should be a column number, but got " << inst.ingredients.at(1).original_string << '\n' << end();
     break;
   }
   break;
diff --git a/072scenario_screen.cc b/072scenario_screen.cc
index 76900238..4ecbbd4d 100644
--- a/072scenario_screen.cc
+++ b/072scenario_screen.cc
@@ -68,7 +68,7 @@ scenario screen-in-scenario-color [
 
 :(scenario screen_in_scenario_error)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 scenario screen-in-scenario-error [
   assume-screen 5/width, 3/height
   run [
@@ -81,11 +81,11 @@ scenario screen-in-scenario-error [
     .     .
   ]
 ]
-+warn: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
++error: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
 
 :(scenario screen_in_scenario_color_error)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 # screen-should-contain can check unicode characters in the fake screen
 scenario screen-in-scenario-color [
   assume-screen 5/width, 3/height
@@ -99,21 +99,21 @@ scenario screen-in-scenario-color [
     .     .
   ]
 ]
-+warn: expected screen location (0, 0) to be in color 2 instead of 1
++error: expected screen location (0, 0) to be in color 2 instead of 1
 
 //: allow naming just for 'screen'
 :(before "End is_special_name Cases")
 if (s == "screen") return true;
 
 :(scenarios run)
-:(scenario convert_names_does_not_warn_when_mixing_special_names_and_numeric_locations)
+:(scenario convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations)
 % Scenario_testing_scenario = true;
-% Hide_warnings = true;
+% Hide_errors = true;
 recipe main [
   screen:number <- copy 1:number
 ]
--warn: mixing variable names and numeric addresses in main
-$warn: 0
+-error: mixing variable names and numeric addresses in main
+$error: 0
 :(scenarios run_mu_scenario)
 
 :(before "End Globals")
@@ -227,11 +227,11 @@ void check_screen(const string& expected_contents, const int color) {
         // contents match but color is off
         if (Current_scenario && !Scenario_testing_scenario) {
           // genuine test in a mu file
-          raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(Memory[addr]) << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << "\n" << end();
+          raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(Memory[addr]) << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << "\n" << end();
         }
         else {
           // just testing check_screen
-          raise << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << '\n' << end();
+          raise_error << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(Memory[addr+cell_color_offset]) << '\n' << end();
         }
         if (!Scenario_testing_scenario) {
           Passed = false;
@@ -241,7 +241,7 @@ void check_screen(const string& expected_contents, const int color) {
       }
 
       // really a mismatch
-      // can't print multi-byte unicode characters in warnings just yet. not very useful for debugging anyway.
+      // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
       char expected_pretty[10] = {0};
       if (curr < 256 && !iscntrl(curr)) {
         // " ('<curr>')"
@@ -257,12 +257,12 @@ void check_screen(const string& expected_contents, const int color) {
       if (color != -1) color_phrase << " in color " << color;
       if (Current_scenario && !Scenario_testing_scenario) {
         // genuine test in a mu file
-        raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(Memory[addr]) << actual_pretty << '\n' << end();
+        raise_error << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(Memory[addr]) << actual_pretty << '\n' << end();
         dump_screen();
       }
       else {
         // just testing check_screen
-        raise << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(Memory[addr]) << actual_pretty << '\n' << end();
+        raise_error << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(Memory[addr]) << actual_pretty << '\n' << end();
       }
       if (!Scenario_testing_scenario) {
         Passed = false;
@@ -281,7 +281,7 @@ raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(
 bool raw_string_stream::at_end() const {
   if (index >= max) return true;
   if (tb_utf8_char_length(buf[index]) > max-index) {
-    raise << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
+    raise_error << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
     return true;
   }
   return false;
diff --git a/075scenario_console.cc b/075scenario_console.cc
index 44bed831..ab6aba94 100644
--- a/075scenario_console.cc
+++ b/075scenario_console.cc
@@ -74,7 +74,7 @@ case ASSUME_CONSOLE: {
       else if (Key.find(key) != Key.end())
         Memory[Current_routine->alloc+1] = Key[key];
       else
-        raise << "assume-console: can't press " << key << '\n' << end();
+        raise_error << "assume-console: can't press " << key << '\n' << end();
       if (Memory[Current_routine->alloc+1] < 256)
         // these keys are in ascii
         Memory[Current_routine->alloc] = /*tag for 'text' variant of 'event' exclusive-container*/0;
@@ -235,7 +235,7 @@ case REPLACE_IN_CONSOLE: {
 case REPLACE_IN_CONSOLE: {
   assert(scalar(ingredients.at(0)));
   if (!Memory[CONSOLE]) {
-    raise << "console not initialized\n" << end();
+    raise_error << "console not initialized\n" << end();
     break;
   }
   long long int console_data = Memory[Memory[CONSOLE]+1];
diff --git a/081run_interactive.cc b/081run_interactive.cc
index 224998ec..7eb0cc8c 100644
--- a/081run_interactive.cc
+++ b/081run_interactive.cc
@@ -17,9 +17,9 @@ recipe main [
 # result is null
 +mem: storing 0 in location 1
 
-//: run code in 'interactive mode', i.e. with warnings off and return:
+//: run code in 'interactive mode', i.e. with errors+warnings off and return:
 //:   stringified output in case we want to print it to screen
-//:   any warnings encountered
+//:   any errors+warnings encountered
 //:   simulated screen any prints went to
 //:   any 'app' layer traces generated
 :(before "End Primitive Recipe Declarations")
@@ -29,11 +29,11 @@ Recipe_ordinal["run-interactive"] = RUN_INTERACTIVE;
 :(before "End Primitive Recipe Checks")
 case RUN_INTERACTIVE: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'run-interactive' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'run-interactive' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'run-interactive' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'run-interactive' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
     break;
   }
   break;
@@ -44,7 +44,7 @@ case RUN_INTERACTIVE: {
   if (!new_code_pushed_to_stack) {
     products.resize(5);
     products.at(0).push_back(0);
-    products.at(1).push_back(trace_contents("warn"));
+    products.at(1).push_back(trace_error_warning_contents());
     products.at(2).push_back(0);
     products.at(3).push_back(trace_contents("app"));
     products.at(4).push_back(1);  // completed
@@ -94,7 +94,7 @@ bool run_interactive(long long int address) {
        "]\n");
   transform_all();
   Current_routine = save_current_routine;
-  if (trace_count("warn") > 0) return false;
+  if (trace_count("error") > 0) return false;
   // now call 'sandbox' which will run 'interactive' in a separate routine,
   // and wait for it
   Current_routine->calls.push_front(call(Recipe_ordinal["sandbox"]));
@@ -104,17 +104,20 @@ bool run_interactive(long long int address) {
 void run_code_begin() {
   // stuff to undo later, in run_code_end()
   Hide_warnings = true;
+  Hide_errors = true;
   Disable_redefine_warnings = true;
   Save_trace_stream = Trace_stream;
   Save_trace_file = Trace_file;
   Trace_file = "";
   Trace_stream = new trace_stream;
+  Trace_stream->collect_layers.insert("error");
   Trace_stream->collect_layers.insert("warn");
   Trace_stream->collect_layers.insert("app");
 }
 
 void run_code_end() {
   Hide_warnings = false;
+  Hide_errors = false;
   Disable_redefine_warnings = false;
   delete Trace_stream;
   Trace_stream = Save_trace_stream;
@@ -136,7 +139,7 @@ load(string(
   "sandbox-state:number <- routine-state r/routine_id\n" +
   "completed?:boolean <- equal sandbox-state, 1/completed\n" +
   "output:address:array:character <- $most-recent-products\n" +
-  "warnings:address:array:character <- save-trace [warn]\n" +
+  "warnings:address:array:character <- save-errors-warnings\n" +
   "stashes:address:array:character <- save-trace [app]\n" +
   "$cleanup-run-interactive\n" +
   "reply output, warnings, screen, stashes, completed?\n" +
@@ -144,7 +147,7 @@ load(string(
 transform_all();
 recently_added_recipes.clear();
 
-//: adjust warnings in the sandbox
+//: adjust errors/warnings in the sandbox
 :(after "string maybe(string s)")
   if (s == "interactive") return "";
 
@@ -210,6 +213,21 @@ case _MOST_RECENT_PRODUCTS: {
 }
 
 :(before "End Primitive Recipe Declarations")
+SAVE_ERRORS_WARNINGS,
+:(before "End Primitive Recipe Numbers")
+Recipe_ordinal["save-errors-warnings"] = SAVE_ERRORS_WARNINGS;
+:(before "End Primitive Recipe Checks")
+case SAVE_ERRORS_WARNINGS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE_ERRORS_WARNINGS: {
+  products.resize(1);
+  products.at(0).push_back(trace_error_warning_contents());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
 SAVE_TRACE,
 :(before "End Primitive Recipe Numbers")
 Recipe_ordinal["save-trace"] = SAVE_TRACE;
@@ -263,15 +281,15 @@ recipe main [
 +mem: storing 97 in location 11
 +mem: storing 98 in location 12
 
-:(scenario "run_interactive_returns_warnings")
+:(scenario "run_interactive_returns_errors")
 recipe main [
-  # run a command that generates a warning
+  # run a command that generates an error
   1:address:array:character <- new [x:number <- copy 34
 get x:number, foo:offset]
   2:address:array:character, 3:address:array:character <- run-interactive 1:address:array:character
   10:array:character <- copy 3:address:array:character/lookup
 ]
-# warning should be "unknown element foo in container number"
+# error should be "unknown element foo in container number"
 +mem: storing 117 in location 11
 +mem: storing 110 in location 12
 +mem: storing 107 in location 13
@@ -295,7 +313,7 @@ void track_most_recent_products(const instruction& instruction, const vector<vec
       if (is_mu_string(instruction.products.at(i))) {
         if (!scalar(products.at(i))) {
           tb_shutdown();
-          cerr << read_mu_string(trace_contents("warn")) << '\n';
+          cerr << read_mu_string(trace_error_warning_contents()) << '\n';
           cerr << SIZE(products.at(i)) << ": ";
           for (long long int j = 0; j < SIZE(products.at(i)); ++j)
             cerr << no_scientific(products.at(i).at(j)) << ' ';
@@ -336,6 +354,20 @@ long long int stringified_value_of_location(long long int address) {
   return new_mu_string(out.str());
 }
 
+long long int trace_error_warning_contents() {
+  if (!Trace_stream) return 0;
+  ostringstream out;
+  for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
+    if (p->label != "warn" && p->label != "error") continue;
+    out << p->contents;
+    if (*--p->contents.end() != '\n') out << '\n';
+  }
+  string result = out.str();
+  if (result.empty()) return 0;
+  truncate(result);
+  return new_mu_string(result);
+}
+
 long long int trace_contents(const string& layer) {
   if (!Trace_stream) return 0;
   if (trace_count(layer) <= 0) return 0;
@@ -361,7 +393,7 @@ void truncate(string& x) {
 }
 
 //: simpler version of run-interactive: doesn't do any running, just loads
-//: recipes and reports warnings.
+//: recipes and reports errors+warnings.
 
 :(before "End Primitive Recipe Declarations")
 RELOAD,
@@ -370,11 +402,11 @@ Recipe_ordinal["reload"] = RELOAD;
 :(before "End Primitive Recipe Checks")
 case RELOAD: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'reload' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'reload' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (!is_mu_scalar(inst.ingredients.at(0))) {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'reload' should be a literal string, but got " << inst.ingredients.at(0).original_string << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'reload' should be a literal string, but got " << inst.ingredients.at(0).original_string << '\n' << end();
     break;
   }
   break;
@@ -398,12 +430,12 @@ case RELOAD: {
   Trace_stream->newline();  // flush trace
   Current_routine = save_current_routine;
   products.resize(1);
-  products.at(0).push_back(trace_contents("warn"));
+  products.at(0).push_back(trace_error_warning_contents());
   run_code_end();  // wait until we're done with the trace contents
   break;
 }
 
-:(scenario reload_continues_past_warning)
+:(scenario reload_continues_past_error)
 recipe main [
   local-scope
   x:address:array:character <- new [recipe foo [
diff --git a/082persist.cc b/082persist.cc
index 0a0064d3..a43a4395 100644
--- a/082persist.cc
+++ b/082persist.cc
@@ -9,7 +9,7 @@ Recipe_ordinal["restore"] = RESTORE;
 :(before "End Primitive Recipe Checks")
 case RESTORE: {
   if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(Recipe[r].name) << "'restore' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'restore' requires exactly one ingredient, but got " << inst.to_string() << '\n' << end();
     break;
   }
   string filename;
@@ -20,7 +20,7 @@ case RESTORE: {
     ;
   }
   else {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'restore' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'restore' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
     break;
   }
   break;
@@ -73,7 +73,7 @@ Recipe_ordinal["save"] = SAVE;
 :(before "End Primitive Recipe Checks")
 case SAVE: {
   if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(Recipe[r].name) << "'save' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "'save' requires exactly two ingredients, but got " << inst.to_string() << '\n' << end();
     break;
   }
   if (is_literal_string(inst.ingredients.at(0))) {
@@ -83,11 +83,11 @@ case SAVE: {
     ;
   }
   else {
-    raise << maybe(Recipe[r].name) << "first ingredient of 'save' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
+    raise_error << maybe(Recipe[r].name) << "first ingredient of 'save' should be a string, but got " << inst.ingredients.at(0).to_string() << '\n' << end();
     break;
   }
-  if (!is_mu_scalar(inst.ingredients.at(1))) {
-    raise << maybe(Recipe[r].name) << "second ingredient of 'save' should be an address:array:character, but got " << inst.ingredients.at(1).to_string() << '\n' << end();
+  if (!is_mu_string(inst.ingredients.at(1))) {
+    raise_error << maybe(Recipe[r].name) << "second ingredient of 'save' should be an address:array:character, but got " << inst.ingredients.at(1).to_string() << '\n' << end();
     break;
   }
   break;
@@ -111,7 +111,7 @@ case SAVE: {
   // explicitly say '--all' for git 1.9
   int status = system("cd lesson; git add --all .; git diff HEAD --exit-code >/dev/null || git commit -a -m . >/dev/null");
   if (status != 0)
-    raise << "error in commit: contents " << contents << '\n' << end();
+    raise_error << "error in commit: contents " << contents << '\n' << end();
   break;
 }