From 204dae921abff0c70e017215bb3c91fa6ca11aff Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 26 Dec 2016 11:44:14 -0800 Subject: 3710 Turns out we don't need to explicitly add anchors for each line. Vim's TOhtml has magic for that out of the box. --- html/050scenario.cc.html | 1664 +++++++++++++++++++++++----------------------- 1 file changed, 832 insertions(+), 832 deletions(-) (limited to 'html/050scenario.cc.html') diff --git a/html/050scenario.cc.html b/html/050scenario.cc.html index 791e7bd4..eefb97bc 100644 --- a/html/050scenario.cc.html +++ b/html/050scenario.cc.html @@ -61,838 +61,838 @@ if ('onhashchange' in window) {
-  1 //: Mu scenarios. This will get long, but these are the tests we want to
-  2 //: support in this layer.
-  3 
-  4 //: We avoid raw numeric locations in Mu -- except in scenarios, where they're
-  5 //: handy to check the values of specific variables
-  6 :(scenarios run_mu_scenario)
-  7 :(scenario scenario_block)
-  8 scenario foo [
-  9   run [
- 10     1:num <- copy 13
- 11   ]
- 12   memory-should-contain [
- 13     1 <- 13
- 14   ]
- 15 ]
- 16 # checks are inside scenario
- 17 
- 18 :(scenario scenario_multiple_blocks)
- 19 scenario foo [
- 20   run [
- 21     1:num <- copy 13
- 22   ]
- 23   memory-should-contain [
- 24     1 <- 13
- 25   ]
- 26   run [
- 27     2:num <- copy 13
- 28   ]
- 29   memory-should-contain [
- 30     1 <- 13
- 31     2 <- 13
- 32   ]
- 33 ]
- 34 # checks are inside scenario
- 35 
- 36 :(scenario scenario_check_memory_and_trace)
- 37 scenario foo [
- 38   run [
- 39     1:num <- copy 13
- 40     trace 1, [a], [a b c]
- 41   ]
- 42   memory-should-contain [
- 43     1 <- 13
- 44   ]
- 45   trace-should-contain [
- 46     a: a b c
- 47   ]
- 48   trace-should-not-contain [
- 49     a: x y z
- 50   ]
- 51 ]
- 52 # checks are inside scenario
- 53 
- 54 //:: Core data structure
- 55 
- 56 :(before "End Types")
- 57 struct scenario {
- 58   string name;
- 59   string to_run;
- 60   void clear() {
- 61     name.clear();
- 62     to_run.clear();
- 63   }
- 64 };
- 65 
- 66 :(before "End Globals")
- 67 vector<scenario> Scenarios;
- 68 
- 69 //:: Parse the 'scenario' form.
- 70 //: Simply store the text of the scenario.
- 71 
- 72 :(before "End Command Handlers")
- 73 else if (command == "scenario") {
- 74   scenario result = parse_scenario(in);
- 75   if (!result.name.empty())
- 76     Scenarios.push_back(result);
- 77 }
- 78 else if (command == "pending-scenario") {
- 79   // for temporary use only
- 80   parse_scenario(in);  // discard
- 81 }
- 82 
- 83 :(code)
- 84 scenario parse_scenario(istream& in) {
- 85   scenario result;
- 86   result.name = next_word(in);
- 87   if (result.name.empty()) {
- 88     assert(!has_data(in));
- 89     raise << "incomplete scenario at end of file\n" << end();
- 90     return result;
- 91   }
- 92   skip_whitespace_and_comments(in);
- 93   if (in.peek() != '[') {
- 94     raise << "Expected '[' after scenario '" << result.name << "'\n" << end();
- 95     exit(0);
- 96   }
- 97   // scenarios are take special 'code' strings so we need to ignore brackets
- 98   // inside comments
- 99   result.to_run = slurp_quoted(in);
-100   // delete [] delimiters
-101   if (!starts_with(result.to_run, "[")) {
-102     raise << "scenario " << result.name << " should start with '['\n" << end();
-103     result.clear();
-104     return result;
-105   }
-106   result.to_run.erase(0, 1);
-107   if (result.to_run.at(SIZE(result.to_run)-1) != ']') {
-108     raise << "scenario " << result.name << " has an unbalanced '['\n" << end();
-109     result.clear();
-110     return result;
-111   }
-112   result.to_run.erase(SIZE(result.to_run)-1);
-113   return result;
-114 }
-115 
-116 :(scenario read_scenario_with_bracket_in_comment)
-117 scenario foo [
-118   # ']' in comment
-119   1:num <- copy 0
-120 ]
-121 +run: {1: "number"} <- copy {0: "literal"}
-122 
-123 :(scenario read_scenario_with_bracket_in_comment_in_nested_string)
-124 scenario foo [
-125   1:text <- new [# not a comment]
-126 ]
-127 +run: {1: ("address" "array" "character")} <- new {"# not a comment": "literal-string"}
-128 
-129 //:: Run scenarios when we run './mu test'.
-130 //: Treat the text of the scenario as a regular series of instructions.
-131 
-132 :(before "End Globals")
-133 int Num_core_mu_scenarios = 0;
-134 :(after "Check For .mu Files")
-135 Num_core_mu_scenarios = SIZE(Scenarios);
-136 :(before "End Tests")
-137 Hide_missing_default_space_errors = false;
-138 if (Num_core_mu_scenarios > 0) {
-139   time(&t);
-140   cerr << "Mu tests: " << ctime(&t);
-141   for (int i = 0;  i < Num_core_mu_scenarios;  ++i) {
-142 //?     cerr << '\n' << i << ": " << Scenarios.at(i).name;
-143     run_mu_scenario(Scenarios.at(i));
-144     if (Passed) cerr << ".";
-145     else ++Num_failures;
-146   }
-147   cerr << "\n";
-148 }
-149 run_app_scenarios:
-150 if (Num_core_mu_scenarios != SIZE(Scenarios)) {
-151   time(&t);
-152   cerr << "App tests: " << ctime(&t);
-153   for (int i = Num_core_mu_scenarios;  i < SIZE(Scenarios);  ++i) {
-154 //?     cerr << '\n' << i << ": " << Scenarios.at(i).name;
-155     run_mu_scenario(Scenarios.at(i));
-156     if (Passed) cerr << ".";
-157     else ++Num_failures;
-158   }
-159   cerr << "\n";
-160 }
-161 
-162 //: For faster debugging, support running tests for just the Mu app(s) we are
-163 //: loading.
-164 :(before "End Globals")
-165 bool Test_only_app = false;
-166 :(before "End Commandline Options(*arg)")
-167 else if (is_equal(*arg, "--test-only-app")) {
-168   Test_only_app = true;
-169 }
-170 :(after "End Test Run Initialization")
-171 if (Test_only_app && Num_core_mu_scenarios < SIZE(Scenarios)) {
-172   goto run_app_scenarios;
-173 }
-174 
-175 //: Convenience: run a single named scenario.
-176 :(after "Test Runs")
-177 for (int i = 0;  i < SIZE(Scenarios);  ++i) {
-178   if (Scenarios.at(i).name == argv[argc-1]) {
-179     if (Start_tracing) {
-180       Trace_stream = new trace_stream;
-181       Save_trace = true;
-182     }
-183     run_mu_scenario(Scenarios.at(i));
-184     if (Passed) cerr << ".\n";
-185     return 0;
-186   }
-187 }
-188 
-189 :(before "End Globals")
-190 // this isn't a constant, just a global of type const*
-191 const scenario* Current_scenario = NULL;
-192 :(code)
-193 void run_mu_scenario(const scenario& s) {
-194   Current_scenario = &s;
-195   bool not_already_inside_test = !Trace_stream;
-196 //?   cerr << s.name << '\n';
-197   if (not_already_inside_test) {
-198     Trace_stream = new trace_stream;
-199     setup();
-200   }
-201   vector<recipe_ordinal> tmp = load("recipe scenario_"+s.name+" [ "+s.to_run+" ]");
-202   mark_autogenerated(tmp.at(0));
-203   bind_special_scenario_names(tmp.at(0));
-204   transform_all();
-205   if (!trace_contains_errors())
-206     run(tmp.front());
-207   // End Mu Test Teardown
-208   if (!Hide_errors && trace_contains_errors() && !Scenario_testing_scenario)
-209     Passed = false;
-210   if (not_already_inside_test && Trace_stream) {
-211     teardown();
-212     if (Save_trace) {
-213       ofstream fout("last_trace");
-214       fout << Trace_stream->readable_contents("");
-215       fout.close();
-216     }
-217     delete Trace_stream;
-218     Trace_stream = NULL;
-219   }
-220   Current_scenario = NULL;
-221 }
-222 
-223 //: Permit numeric locations to be accessed in scenarios.
-224 :(before "End check_default_space Special-cases")
-225 // user code should never create recipes with underscores in their names
-226 if (caller.name.find("scenario_") == 0) return;  // skip Mu scenarios which will use raw memory locations
-227 if (caller.name.find("run_") == 0) return;  // skip calls to 'run', which should be in scenarios and will also use raw memory locations
-228 
-229 //: Some variables for fake resources always get special /raw addresses in scenarios.
-230 
-231 :(code)
-232 // Should contain everything passed by is_special_name but failed by is_disqualified.
-233 void bind_special_scenario_names(const recipe_ordinal r) {
-234   // Special Scenario Variable Names(r)
-235   // End Special Scenario Variable Names(r)
-236 }
-237 :(before "Done Placing Ingredient(ingredient, inst, caller)")
-238 maybe_make_raw(ingredient, caller);
-239 :(before "Done Placing Product(product, inst, caller)")
-240 maybe_make_raw(product, caller);
-241 :(code)
-242 void maybe_make_raw(reagent& r, const recipe& caller) {
-243   if (!is_special_name(r.name)) return;
-244   if (starts_with(caller.name, "scenario_"))
-245     r.properties.push_back(pair<string, string_tree*>("raw", NULL));
-246   // End maybe_make_raw
-247 }
-248 
-249 //: Test.
-250 :(before "End is_special_name Special-cases")
-251 if (s == "__maybe_make_raw_test__") return true;
-252 :(before "End Special Scenario Variable Names(r)")
-253 //: ugly: we only need this for this one test, but need to define it for all time
-254 Name[r]["__maybe_make_raw_test__"] = Reserved_for_tests-1;
-255 :(code)
-256 void test_maybe_make_raw() {
-257   // check that scenarios can use local-scope and special variables together
-258   vector<recipe_ordinal> tmp = load(
-259       "def scenario_foo [\n"
-260       "  local-scope\n"
-261       "  __maybe_make_raw_test__:num <- copy 34\n"
-262       "]\n");
-263   mark_autogenerated(tmp.at(0));
-264   bind_special_scenario_names(tmp.at(0));
-265   transform_all();
-266   run(tmp.at(0));
-267   CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
-268 }
-269 
-270 //: Watch out for redefinitions of scenario routines. We should never ever be
-271 //: doing that, regardless of anything else.
-272 :(scenarios run)
-273 :(scenario forbid_redefining_scenario_even_if_forced)
-274 % Hide_errors = true;
-275 % Disable_redefine_checks = true;
-276 def scenario-foo [
-277   1:num <- copy 34
-278 ]
-279 def scenario-foo [
-280   1:num <- copy 35
-281 ]
-282 +error: redefining recipe scenario-foo
-283 
-284 :(scenario scenario_containing_parse_error)
-285 % Hide_errors = true;
-286 scenario foo [
-287   memory-should-contain [
-288     1 <- 0
-289   # missing ']'
-290 ]
-291 # no crash
-292 
-293 :(scenario scenario_containing_transform_error)
-294 % Hide_errors = true;
-295 def main [
-296   local-scope
-297   add x, 1
-298 ]
-299 # no crash
-300 
-301 :(after "bool should_check_for_redefine(const string& recipe_name)")
-302   if (recipe_name.find("scenario-") == 0) return true;
-303 
-304 //:: The special instructions we want to support inside scenarios.
-305 //: These are easy to support in an interpreter, but will require more work
-306 //: when we eventually build a compiler.
-307 
-308 //: 'run' is a purely lexical convenience to separate the code actually being
-309 //: tested from any setup or teardown
-310 
-311 :(scenario run)
-312 def main [
-313   run [
-314     1:num <- copy 13
-315   ]
-316 ]
-317 +mem: storing 13 in location 1
-318 
-319 :(before "End Rewrite Instruction(curr, recipe result)")
-320 if (curr.name == "run") {
-321   // Just inline all instructions inside the run block in the containing
-322   // recipe. 'run' is basically a comment; pretend it doesn't exist.
-323   istringstream in2("[\n"+curr.ingredients.at(0).name+"\n]\n");
-324   slurp_body(in2, result);
-325   curr.clear();
-326 }
-327 
-328 :(scenario run_multiple)
-329 def main [
-330   run [
-331     1:num <- copy 13
-332   ]
-333   run [
-334     2:num <- copy 13
-335   ]
-336 ]
-337 +mem: storing 13 in location 1
-338 +mem: storing 13 in location 2
-339 
-340 //: 'memory-should-contain' raises errors if specific locations aren't as expected
-341 //: Also includes some special support for checking strings.
-342 
-343 :(before "End Globals")
-344 bool Scenario_testing_scenario = false;
-345 :(before "End Setup")
-346 Scenario_testing_scenario = false;
-347 
-348 :(scenario memory_check)
-349 % Scenario_testing_scenario = true;
-350 % Hide_errors = true;
-351 def main [
-352   memory-should-contain [
-353     1 <- 13
-354   ]
-355 ]
-356 +run: checking location 1
-357 +error: expected location '1' to contain 13 but saw 0
-358 
-359 :(before "End Primitive Recipe Declarations")
-360 MEMORY_SHOULD_CONTAIN,
-361 :(before "End Primitive Recipe Numbers")
-362 put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN);
-363 :(before "End Primitive Recipe Checks")
-364 case MEMORY_SHOULD_CONTAIN: {
-365   break;
-366 }
-367 :(before "End Primitive Recipe Implementations")
-368 case MEMORY_SHOULD_CONTAIN: {
-369   if (!Passed) break;
-370   check_memory(current_instruction().ingredients.at(0).name);
-371   break;
-372 }
-373 
-374 :(code)
-375 void check_memory(const string& s) {
-376   istringstream in(s);
-377   in >> std::noskipws;
-378   set<int> locations_checked;
-379   while (true) {
-380     skip_whitespace_and_comments(in);
-381     if (!has_data(in)) break;
-382     string lhs = next_word(in);
-383     if (lhs.empty()) {
-384       assert(!has_data(in));
-385       raise << "incomplete 'memory-should-contain' block at end of file (0)\n" << end();
-386       return;
-387     }
-388     if (!is_integer(lhs)) {
-389       check_type(lhs, in);
-390       continue;
-391     }
-392     int address = to_integer(lhs);
-393     skip_whitespace_and_comments(in);
-394     string _assign;  in >> _assign;  assert(_assign == "<-");
-395     skip_whitespace_and_comments(in);
-396     string rhs = next_word(in);
-397     if (rhs.empty()) {
-398       assert(!has_data(in));
-399       raise << "incomplete 'memory-should-contain' block at end of file (1)\n" << end();
-400       return;
-401     }
-402     if (!is_integer(rhs) && !is_noninteger(rhs)) {
-403       if (Current_scenario && !Scenario_testing_scenario)
-404         // genuine test in a .mu file
-405         raise << "\nF - " << Current_scenario->name << ": location '" << address << "' can't contain non-number " << rhs << "\n" << end();
-406       else
-407         // just testing scenario support
-408         raise << "location '" << address << "' can't contain non-number " << rhs << '\n' << end();
-409       if (!Scenario_testing_scenario) Passed = false;
-410       return;
-411     }
-412     double value = to_double(rhs);
-413     if (contains_key(locations_checked, address))
-414       raise << "duplicate expectation for location '" << address << "'\n" << end();
-415     trace(9999, "run") << "checking location " << address << end();
-416     if (get_or_insert(Memory, address) != value) {
-417       if (Current_scenario && !Scenario_testing_scenario) {
-418         // genuine test in a .mu file
-419         raise << "\nF - " << Current_scenario->name << ": expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-420       }
-421       else {
-422         // just testing scenario support
-423         raise << "expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-424       }
-425       if (!Scenario_testing_scenario) Passed = false;
-426       return;
-427     }
-428     locations_checked.insert(address);
-429   }
-430 }
-431 
-432 void check_type(const string& lhs, istream& in) {
-433   reagent x(lhs);
-434   if (is_mu_array(x.type) && is_mu_character(array_element(x.type))) {
-435     x.set_value(to_integer(x.name));
-436     skip_whitespace_and_comments(in);
-437     string _assign = next_word(in);
-438     if (_assign.empty()) {
-439       assert(!has_data(in));
-440       raise << "incomplete 'memory-should-contain' block at end of file (2)\n" << end();
-441       return;
-442     }
-443     assert(_assign == "<-");
-444     skip_whitespace_and_comments(in);
-445     string literal = next_word(in);
-446     if (literal.empty()) {
-447       assert(!has_data(in));
-448       raise << "incomplete 'memory-should-contain' block at end of file (3)\n" << end();
-449       return;
-450     }
-451     int address = x.value;
-452     // exclude quoting brackets
-453     assert(*literal.begin() == '[');  literal.erase(literal.begin());
-454     assert(*--literal.end() == ']');  literal.erase(--literal.end());
-455     check_string(address, literal);
-456     return;
-457   }
-458   // End Scenario Type Special-cases
-459   raise << "don't know how to check memory for '" << lhs << "'\n" << end();
-460 }
-461 
-462 void check_string(int address, const string& literal) {
-463   trace(9999, "run") << "checking string length at " << address << end();
-464   if (get_or_insert(Memory, address) != SIZE(literal)) {
-465     if (Current_scenario && !Scenario_testing_scenario)
-466       raise << "\nF - " << Current_scenario->name << ": expected location '" << address << "' to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << " (" << read_mu_text(address-/*fake refcount*/1) << ")\n" << end();
-467     else
-468       raise << "expected location '" << address << "' to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
-469     if (!Scenario_testing_scenario) Passed = false;
-470     return;
-471   }
-472   ++address;  // now skip length
-473   for (int i = 0;  i < SIZE(literal);  ++i) {
-474     trace(9999, "run") << "checking location " << address+i << end();
-475     if (get_or_insert(Memory, address+i) != literal.at(i)) {
-476       if (Current_scenario && !Scenario_testing_scenario) {
-477         // genuine test in a .mu file
-478         raise << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << " ('" << static_cast<char>(get_or_insert(Memory, address+i)) << "')\n" << end();
-479       }
-480       else {
-481         // just testing scenario support
-482         raise << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << '\n' << end();
-483       }
-484       if (!Scenario_testing_scenario) Passed = false;
-485       return;
-486     }
-487   }
-488 }
-489 
-490 :(scenario memory_check_multiple)
-491 % Scenario_testing_scenario = true;
-492 % Hide_errors = true;
-493 def main [
-494   memory-should-contain [
-495     1 <- 0
-496     1 <- 0
-497   ]
-498 ]
-499 +error: duplicate expectation for location '1'
-500 
-501 :(scenario memory_check_string_length)
-502 % Scenario_testing_scenario = true;
-503 % Hide_errors = true;
-504 def main [
-505   1:num <- copy 3
-506   2:num <- copy 97  # 'a'
-507   3:num <- copy 98  # 'b'
-508   4:num <- copy 99  # 'c'
-509   memory-should-contain [
-510     1:array:character <- [ab]
-511   ]
-512 ]
-513 +error: expected location '1' to contain length 2 of string [ab] but saw 3
-514 
-515 :(scenario memory_check_string)
-516 def main [
-517   1:num <- copy 3
-518   2:num <- copy 97  # 'a'
-519   3:num <- copy 98  # 'b'
-520   4:num <- copy 99  # 'c'
-521   memory-should-contain [
-522     1:array:character <- [abc]
-523   ]
-524 ]
-525 +run: checking string length at 1
-526 +run: checking location 2
-527 +run: checking location 3
-528 +run: checking location 4
-529 
-530 :(scenario memory_invalid_string_check)
-531 % Scenario_testing_scenario = true;
-532 % Hide_errors = true;
-533 def main [
-534   memory-should-contain [
-535     1 <- [abc]
-536   ]
-537 ]
-538 +error: location '1' can't contain non-number [abc]
-539 
-540 :(scenario memory_check_with_comment)
-541 % Scenario_testing_scenario = true;
-542 % Hide_errors = true;
-543 def main [
-544   memory-should-contain [
-545     1 <- 34  # comment
-546   ]
-547 ]
-548 -error: location 1 can't contain non-number 34  # comment
-549 # but there'll be an error signalled by memory-should-contain
-550 
-551 //: 'trace-should-contain' is like the '+' lines in our scenarios so far
-552 // Like runs of contiguous '+' lines, order is important. The trace checks
-553 // that the lines are present *and* in the specified sequence. (There can be
-554 // other lines in between.)
-555 
-556 :(scenario trace_check_fails)
-557 % Scenario_testing_scenario = true;
-558 % Hide_errors = true;
-559 def main [
-560   trace-should-contain [
-561     a: b
-562     a: d
-563   ]
-564 ]
-565 +error: missing [b] in trace with label 'a'
-566 
-567 :(before "End Primitive Recipe Declarations")
-568 TRACE_SHOULD_CONTAIN,
-569 :(before "End Primitive Recipe Numbers")
-570 put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN);
-571 :(before "End Primitive Recipe Checks")
-572 case TRACE_SHOULD_CONTAIN: {
-573   break;
-574 }
-575 :(before "End Primitive Recipe Implementations")
-576 case TRACE_SHOULD_CONTAIN: {
-577   if (!Passed) break;
-578   check_trace(current_instruction().ingredients.at(0).name);
-579   break;
-580 }
-581 
-582 :(code)
-583 // simplified version of check_trace_contents() that emits errors rather
-584 // than just printing to stderr
-585 void check_trace(const string& expected) {
-586   Trace_stream->newline();
-587   vector<trace_line> expected_lines = parse_trace(expected);
-588   if (expected_lines.empty()) return;
-589   int curr_expected_line = 0;
-590   for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
-591     if (expected_lines.at(curr_expected_line).label != p->label) continue;
-592     if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue;
-593     // match
-594     ++curr_expected_line;
-595     if (curr_expected_line == SIZE(expected_lines)) return;
-596   }
-597   if (Current_scenario && !Scenario_testing_scenario)
-598     raise << "\nF - " << Current_scenario->name << ": missing [" << expected_lines.at(curr_expected_line).contents << "] "
-599           << "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end();
-600   else
-601     raise << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
-602           << "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end();
-603   if (!Hide_errors)
-604     DUMP(expected_lines.at(curr_expected_line).label);
-605   if (!Scenario_testing_scenario) Passed = false;
-606 }
-607 
-608 vector<trace_line> parse_trace(const string& expected) {
-609   vector<string> buf = split(expected, "\n");
-610   vector<trace_line> result;
-611   for (int i = 0;  i < SIZE(buf);  ++i) {
-612     buf.at(i) = trim(buf.at(i));
-613     if (buf.at(i).empty()) continue;
-614     int delim = buf.at(i).find(": ");
-615     if (delim == -1) {
-616       raise << Current_scenario->name << ": lines in 'trace-should-contain' should be of the form <label>: <contents>. Both parts are required.\n" << end();
-617       result.clear();
-618       return result;
-619     }
-620     result.push_back(trace_line(trim(buf.at(i).substr(0, delim)),  trim(buf.at(i).substr(delim+2))));
-621   }
-622   return result;
-623 }
-624 
-625 :(scenario trace_check_fails_in_nonfirst_line)
-626 % Scenario_testing_scenario = true;
-627 % Hide_errors = true;
-628 def main [
-629   run [
-630     trace 1, [a], [b]
-631   ]
-632   trace-should-contain [
-633     a: b
-634     a: d
-635   ]
-636 ]
-637 +error: missing [d] in trace with label 'a'
-638 
-639 :(scenario trace_check_passes_silently)
-640 % Scenario_testing_scenario = true;
-641 def main [
-642   run [
-643     trace 1, [a], [b]
-644   ]
-645   trace-should-contain [
-646     a: b
-647   ]
-648 ]
-649 -error: missing [b] in trace with label 'a'
-650 $error: 0
-651 
-652 //: 'trace-should-not-contain' is like the '-' lines in our scenarios so far
-653 //: Each trace line is separately checked for absense. Order is *not*
-654 //: important, so you can't say things like "B should not exist after A."
-655 
-656 :(scenario trace_negative_check_fails)
-657 % Scenario_testing_scenario = true;
-658 % Hide_errors = true;
-659 def main [
-660   run [
-661     trace 1, [a], [b]
-662   ]
-663   trace-should-not-contain [
-664     a: b
-665   ]
-666 ]
-667 +error: unexpected [b] in trace with label 'a'
-668 
-669 :(before "End Primitive Recipe Declarations")
-670 TRACE_SHOULD_NOT_CONTAIN,
-671 :(before "End Primitive Recipe Numbers")
-672 put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN);
-673 :(before "End Primitive Recipe Checks")
-674 case TRACE_SHOULD_NOT_CONTAIN: {
-675   break;
-676 }
-677 :(before "End Primitive Recipe Implementations")
-678 case TRACE_SHOULD_NOT_CONTAIN: {
-679   if (!Passed) break;
-680   check_trace_missing(current_instruction().ingredients.at(0).name);
-681   break;
-682 }
-683 
-684 :(code)
-685 // simplified version of check_trace_contents() that emits errors rather
-686 // than just printing to stderr
-687 bool check_trace_missing(const string& in) {
-688   Trace_stream->newline();
-689   vector<trace_line> lines = parse_trace(in);
-690   for (int i = 0;  i < SIZE(lines);  ++i) {
-691     if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) {
-692       raise << "unexpected [" << lines.at(i).contents << "] in trace with label '" << lines.at(i).label << "'\n" << end();
-693       if (!Scenario_testing_scenario) Passed = false;
-694       return false;
-695     }
-696   }
-697   return true;
-698 }
-699 
-700 :(scenario trace_negative_check_passes_silently)
-701 % Scenario_testing_scenario = true;
-702 def main [
-703   trace-should-not-contain [
-704     a: b
-705   ]
-706 ]
-707 -error: unexpected [b] in trace with label 'a'
-708 $error: 0
-709 
-710 :(scenario trace_negative_check_fails_on_any_unexpected_line)
-711 % Scenario_testing_scenario = true;
-712 % Hide_errors = true;
-713 def main [
-714   run [
-715     trace 1, [a], [d]
-716   ]
-717   trace-should-not-contain [
-718     a: b
-719     a: d
-720   ]
-721 ]
-722 +error: unexpected [d] in trace with label 'a'
-723 
-724 :(scenario trace_count_check)
-725 def main [
-726   run [
-727     trace 1, [a], [foo]
-728   ]
-729   check-trace-count-for-label 1, [a]
-730 ]
-731 # checks are inside scenario
-732 
-733 :(before "End Primitive Recipe Declarations")
-734 CHECK_TRACE_COUNT_FOR_LABEL,
-735 :(before "End Primitive Recipe Numbers")
-736 put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL);
-737 :(before "End Primitive Recipe Checks")
-738 case CHECK_TRACE_COUNT_FOR_LABEL: {
-739   if (SIZE(inst.ingredients) != 2) {
-740     raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
-741     break;
-742   }
-743   if (!is_mu_number(inst.ingredients.at(0))) {
-744     raise << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-count-for-label' should be a number (count), but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
-745     break;
-746   }
-747   if (!is_literal_text(inst.ingredients.at(1))) {
-748     raise << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-count-for-label' should be a literal string (label), but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
-749     break;
-750   }
-751   break;
-752 }
-753 :(before "End Primitive Recipe Implementations")
-754 case CHECK_TRACE_COUNT_FOR_LABEL: {
-755   if (!Passed) break;
-756   int expected_count = ingredients.at(0).at(0);
-757   string label = current_instruction().ingredients.at(1).name;
-758   int count = trace_count(label);
-759   if (count != expected_count) {
-760     if (Current_scenario && !Scenario_testing_scenario) {
-761       // genuine test in a .mu file
-762       raise << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace: " << end();
-763       DUMP(label);
-764     }
-765     else {
-766       // just testing scenario support
-767       raise << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end();
-768     }
-769     if (!Scenario_testing_scenario) Passed = false;
-770   }
-771   break;
-772 }
-773 
-774 :(scenario trace_count_check_2)
-775 % Scenario_testing_scenario = true;
-776 % Hide_errors = true;
-777 def main [
-778   run [
-779     trace 1, [a], [foo]
-780   ]
-781   check-trace-count-for-label 2, [a]
-782 ]
-783 +error: main: expected 2 lines in trace with label 'a' in trace
-784 
-785 //: Minor detail: ignore 'system' calls in scenarios, since anything we do
-786 //: with them is by definition impossible to test through Mu.
-787 :(after "case _SYSTEM:")
-788   if (Current_scenario) break;
-789 
-790 //:: Warn if people use '_' manually in function names. They're reserved for internal use.
-791 
-792 :(scenario recipe_name_with_underscore)
-793 % Hide_errors = true;
-794 def foo_bar [
-795 ]
-796 +error: foo_bar: don't create recipes with '_' in the name
-797 
-798 :(before "End recipe Fields")
-799 bool is_autogenerated;
-800 :(before "End recipe Constructor")
-801 is_autogenerated = false;
-802 :(code)
-803 void mark_autogenerated(recipe_ordinal r) {
-804   get(Recipe, r).is_autogenerated = true;
-805 }
-806 
-807 :(after "void transform_all()")
-808   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p) {
-809     const recipe& r = p->second;
-810     if (r.name.find('_') == string::npos) continue;
-811     if (r.is_autogenerated) continue;  // created by previous call to transform_all()
-812     raise << r.name << ": don't create recipes with '_' in the name\n" << end();
-813   }
-814 
-815 //:: Helpers
-816 
-817 :(code)
-818 // just for the scenarios running scenarios in C++ layers
-819 void run_mu_scenario(const string& form) {
-820   istringstream in(form);
-821   in >> std::noskipws;
-822   skip_whitespace_and_comments(in);
-823   string _scenario = next_word(in);
-824   if (_scenario.empty()) {
-825     assert(!has_data(in));
-826     raise << "no scenario in string passed into run_mu_scenario()\n" << end();
-827     return;
-828   }
-829   assert(_scenario == "scenario");
-830   scenario s = parse_scenario(in);
-831   run_mu_scenario(s);
-832 }
+  1 //: Mu scenarios. This will get long, but these are the tests we want to
+  2 //: support in this layer.
+  3 
+  4 //: We avoid raw numeric locations in Mu -- except in scenarios, where they're
+  5 //: handy to check the values of specific variables
+  6 :(scenarios run_mu_scenario)
+  7 :(scenario scenario_block)
+  8 scenario foo [
+  9   run [
+ 10     1:num <- copy 13
+ 11   ]
+ 12   memory-should-contain [
+ 13     1 <- 13
+ 14   ]
+ 15 ]
+ 16 # checks are inside scenario
+ 17 
+ 18 :(scenario scenario_multiple_blocks)
+ 19 scenario foo [
+ 20   run [
+ 21     1:num <- copy 13
+ 22   ]
+ 23   memory-should-contain [
+ 24     1 <- 13
+ 25   ]
+ 26   run [
+ 27     2:num <- copy 13
+ 28   ]
+ 29   memory-should-contain [
+ 30     1 <- 13
+ 31     2 <- 13
+ 32   ]
+ 33 ]
+ 34 # checks are inside scenario
+ 35 
+ 36 :(scenario scenario_check_memory_and_trace)
+ 37 scenario foo [
+ 38   run [
+ 39     1:num <- copy 13
+ 40     trace 1, [a], [a b c]
+ 41   ]
+ 42   memory-should-contain [
+ 43     1 <- 13
+ 44   ]
+ 45   trace-should-contain [
+ 46     a: a b c
+ 47   ]
+ 48   trace-should-not-contain [
+ 49     a: x y z
+ 50   ]
+ 51 ]
+ 52 # checks are inside scenario
+ 53 
+ 54 //:: Core data structure
+ 55 
+ 56 :(before "End Types")
+ 57 struct scenario {
+ 58   string name;
+ 59   string to_run;
+ 60   void clear() {
+ 61     name.clear();
+ 62     to_run.clear();
+ 63   }
+ 64 };
+ 65 
+ 66 :(before "End Globals")
+ 67 vector<scenario> Scenarios;
+ 68 
+ 69 //:: Parse the 'scenario' form.
+ 70 //: Simply store the text of the scenario.
+ 71 
+ 72 :(before "End Command Handlers")
+ 73 else if (command == "scenario") {
+ 74   scenario result = parse_scenario(in);
+ 75   if (!result.name.empty())
+ 76     Scenarios.push_back(result);
+ 77 }
+ 78 else if (command == "pending-scenario") {
+ 79   // for temporary use only
+ 80   parse_scenario(in);  // discard
+ 81 }
+ 82 
+ 83 :(code)
+ 84 scenario parse_scenario(istream& in) {
+ 85   scenario result;
+ 86   result.name = next_word(in);
+ 87   if (result.name.empty()) {
+ 88     assert(!has_data(in));
+ 89     raise << "incomplete scenario at end of file\n" << end();
+ 90     return result;
+ 91   }
+ 92   skip_whitespace_and_comments(in);
+ 93   if (in.peek() != '[') {
+ 94     raise << "Expected '[' after scenario '" << result.name << "'\n" << end();
+ 95     exit(0);
+ 96   }
+ 97   // scenarios are take special 'code' strings so we need to ignore brackets
+ 98   // inside comments
+ 99   result.to_run = slurp_quoted(in);
+100   // delete [] delimiters
+101   if (!starts_with(result.to_run, "[")) {
+102     raise << "scenario " << result.name << " should start with '['\n" << end();
+103     result.clear();
+104     return result;
+105   }
+106   result.to_run.erase(0, 1);
+107   if (result.to_run.at(SIZE(result.to_run)-1) != ']') {
+108     raise << "scenario " << result.name << " has an unbalanced '['\n" << end();
+109     result.clear();
+110     return result;
+111   }
+112   result.to_run.erase(SIZE(result.to_run)-1);
+113   return result;
+114 }
+115 
+116 :(scenario read_scenario_with_bracket_in_comment)
+117 scenario foo [
+118   # ']' in comment
+119   1:num <- copy 0
+120 ]
+121 +run: {1: "number"} <- copy {0: "literal"}
+122 
+123 :(scenario read_scenario_with_bracket_in_comment_in_nested_string)
+124 scenario foo [
+125   1:text <- new [# not a comment]
+126 ]
+127 +run: {1: ("address" "array" "character")} <- new {"# not a comment": "literal-string"}
+128 
+129 //:: Run scenarios when we run './mu test'.
+130 //: Treat the text of the scenario as a regular series of instructions.
+131 
+132 :(before "End Globals")
+133 int Num_core_mu_scenarios = 0;
+134 :(after "Check For .mu Files")
+135 Num_core_mu_scenarios = SIZE(Scenarios);
+136 :(before "End Tests")
+137 Hide_missing_default_space_errors = false;
+138 if (Num_core_mu_scenarios > 0) {
+139   time(&t);
+140   cerr << "Mu tests: " << ctime(&t);
+141   for (int i = 0;  i < Num_core_mu_scenarios;  ++i) {
+142 //?     cerr << '\n' << i << ": " << Scenarios.at(i).name;
+143     run_mu_scenario(Scenarios.at(i));
+144     if (Passed) cerr << ".";
+145     else ++Num_failures;
+146   }
+147   cerr << "\n";
+148 }
+149 run_app_scenarios:
+150 if (Num_core_mu_scenarios != SIZE(Scenarios)) {
+151   time(&t);
+152   cerr << "App tests: " << ctime(&t);
+153   for (int i = Num_core_mu_scenarios;  i < SIZE(Scenarios);  ++i) {
+154 //?     cerr << '\n' << i << ": " << Scenarios.at(i).name;
+155     run_mu_scenario(Scenarios.at(i));
+156     if (Passed) cerr << ".";
+157     else ++Num_failures;
+158   }
+159   cerr << "\n";
+160 }
+161 
+162 //: For faster debugging, support running tests for just the Mu app(s) we are
+163 //: loading.
+164 :(before "End Globals")
+165 bool Test_only_app = false;
+166 :(before "End Commandline Options(*arg)")
+167 else if (is_equal(*arg, "--test-only-app")) {
+168   Test_only_app = true;
+169 }
+170 :(after "End Test Run Initialization")
+171 if (Test_only_app && Num_core_mu_scenarios < SIZE(Scenarios)) {
+172   goto run_app_scenarios;
+173 }
+174 
+175 //: Convenience: run a single named scenario.
+176 :(after "Test Runs")
+177 for (int i = 0;  i < SIZE(Scenarios);  ++i) {
+178   if (Scenarios.at(i).name == argv[argc-1]) {
+179     if (Start_tracing) {
+180       Trace_stream = new trace_stream;
+181       Save_trace = true;
+182     }
+183     run_mu_scenario(Scenarios.at(i));
+184     if (Passed) cerr << ".\n";
+185     return 0;
+186   }
+187 }
+188 
+189 :(before "End Globals")
+190 // this isn't a constant, just a global of type const*
+191 const scenario* Current_scenario = NULL;
+192 :(code)
+193 void run_mu_scenario(const scenario& s) {
+194   Current_scenario = &s;
+195   bool not_already_inside_test = !Trace_stream;
+196 //?   cerr << s.name << '\n';
+197   if (not_already_inside_test) {
+198     Trace_stream = new trace_stream;
+199     setup();
+200   }
+201   vector<recipe_ordinal> tmp = load("recipe scenario_"+s.name+" [ "+s.to_run+" ]");
+202   mark_autogenerated(tmp.at(0));
+203   bind_special_scenario_names(tmp.at(0));
+204   transform_all();
+205   if (!trace_contains_errors())
+206     run(tmp.front());
+207   // End Mu Test Teardown
+208   if (!Hide_errors && trace_contains_errors() && !Scenario_testing_scenario)
+209     Passed = false;
+210   if (not_already_inside_test && Trace_stream) {
+211     teardown();
+212     if (Save_trace) {
+213       ofstream fout("last_trace");
+214       fout << Trace_stream->readable_contents("");
+215       fout.close();
+216     }
+217     delete Trace_stream;
+218     Trace_stream = NULL;
+219   }
+220   Current_scenario = NULL;
+221 }
+222 
+223 //: Permit numeric locations to be accessed in scenarios.
+224 :(before "End check_default_space Special-cases")
+225 // user code should never create recipes with underscores in their names
+226 if (caller.name.find("scenario_") == 0) return;  // skip Mu scenarios which will use raw memory locations
+227 if (caller.name.find("run_") == 0) return;  // skip calls to 'run', which should be in scenarios and will also use raw memory locations
+228 
+229 //: Some variables for fake resources always get special /raw addresses in scenarios.
+230 
+231 :(code)
+232 // Should contain everything passed by is_special_name but failed by is_disqualified.
+233 void bind_special_scenario_names(const recipe_ordinal r) {
+234   // Special Scenario Variable Names(r)
+235   // End Special Scenario Variable Names(r)
+236 }
+237 :(before "Done Placing Ingredient(ingredient, inst, caller)")
+238 maybe_make_raw(ingredient, caller);
+239 :(before "Done Placing Product(product, inst, caller)")
+240 maybe_make_raw(product, caller);
+241 :(code)
+242 void maybe_make_raw(reagent& r, const recipe& caller) {
+243   if (!is_special_name(r.name)) return;
+244   if (starts_with(caller.name, "scenario_"))
+245     r.properties.push_back(pair<string, string_tree*>("raw", NULL));
+246   // End maybe_make_raw
+247 }
+248 
+249 //: Test.
+250 :(before "End is_special_name Special-cases")
+251 if (s == "__maybe_make_raw_test__") return true;
+252 :(before "End Special Scenario Variable Names(r)")
+253 //: ugly: we only need this for this one test, but need to define it for all time
+254 Name[r]["__maybe_make_raw_test__"] = Reserved_for_tests-1;
+255 :(code)
+256 void test_maybe_make_raw() {
+257   // check that scenarios can use local-scope and special variables together
+258   vector<recipe_ordinal> tmp = load(
+259       "def scenario_foo [\n"
+260       "  local-scope\n"
+261       "  __maybe_make_raw_test__:num <- copy 34\n"
+262       "]\n");
+263   mark_autogenerated(tmp.at(0));
+264   bind_special_scenario_names(tmp.at(0));
+265   transform_all();
+266   run(tmp.at(0));
+267   CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
+268 }
+269 
+270 //: Watch out for redefinitions of scenario routines. We should never ever be
+271 //: doing that, regardless of anything else.
+272 :(scenarios run)
+273 :(scenario forbid_redefining_scenario_even_if_forced)
+274 % Hide_errors = true;
+275 % Disable_redefine_checks = true;
+276 def scenario-foo [
+277   1:num <- copy 34
+278 ]
+279 def scenario-foo [
+280   1:num <- copy 35
+281 ]
+282 +error: redefining recipe scenario-foo
+283 
+284 :(scenario scenario_containing_parse_error)
+285 % Hide_errors = true;
+286 scenario foo [
+287   memory-should-contain [
+288     1 <- 0
+289   # missing ']'
+290 ]
+291 # no crash
+292 
+293 :(scenario scenario_containing_transform_error)
+294 % Hide_errors = true;
+295 def main [
+296   local-scope
+297   add x, 1
+298 ]
+299 # no crash
+300 
+301 :(after "bool should_check_for_redefine(const string& recipe_name)")
+302   if (recipe_name.find("scenario-") == 0) return true;
+303 
+304 //:: The special instructions we want to support inside scenarios.
+305 //: These are easy to support in an interpreter, but will require more work
+306 //: when we eventually build a compiler.
+307 
+308 //: 'run' is a purely lexical convenience to separate the code actually being
+309 //: tested from any setup or teardown
+310 
+311 :(scenario run)
+312 def main [
+313   run [
+314     1:num <- copy 13
+315   ]
+316 ]
+317 +mem: storing 13 in location 1
+318 
+319 :(before "End Rewrite Instruction(curr, recipe result)")
+320 if (curr.name == "run") {
+321   // Just inline all instructions inside the run block in the containing
+322   // recipe. 'run' is basically a comment; pretend it doesn't exist.
+323   istringstream in2("[\n"+curr.ingredients.at(0).name+"\n]\n");
+324   slurp_body(in2, result);
+325   curr.clear();
+326 }
+327 
+328 :(scenario run_multiple)
+329 def main [
+330   run [
+331     1:num <- copy 13
+332   ]
+333   run [
+334     2:num <- copy 13
+335   ]
+336 ]
+337 +mem: storing 13 in location 1
+338 +mem: storing 13 in location 2
+339 
+340 //: 'memory-should-contain' raises errors if specific locations aren't as expected
+341 //: Also includes some special support for checking strings.
+342 
+343 :(before "End Globals")
+344 bool Scenario_testing_scenario = false;
+345 :(before "End Setup")
+346 Scenario_testing_scenario = false;
+347 
+348 :(scenario memory_check)
+349 % Scenario_testing_scenario = true;
+350 % Hide_errors = true;
+351 def main [
+352   memory-should-contain [
+353     1 <- 13
+354   ]
+355 ]
+356 +run: checking location 1
+357 +error: expected location '1' to contain 13 but saw 0
+358 
+359 :(before "End Primitive Recipe Declarations")
+360 MEMORY_SHOULD_CONTAIN,
+361 :(before "End Primitive Recipe Numbers")
+362 put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN);
+363 :(before "End Primitive Recipe Checks")
+364 case MEMORY_SHOULD_CONTAIN: {
+365   break;
+366 }
+367 :(before "End Primitive Recipe Implementations")
+368 case MEMORY_SHOULD_CONTAIN: {
+369   if (!Passed) break;
+370   check_memory(current_instruction().ingredients.at(0).name);
+371   break;
+372 }
+373 
+374 :(code)
+375 void check_memory(const string& s) {
+376   istringstream in(s);
+377   in >> std::noskipws;
+378   set<int> locations_checked;
+379   while (true) {
+380     skip_whitespace_and_comments(in);
+381     if (!has_data(in)) break;
+382     string lhs = next_word(in);
+383     if (lhs.empty()) {
+384       assert(!has_data(in));
+385       raise << "incomplete 'memory-should-contain' block at end of file (0)\n" << end();
+386       return;
+387     }
+388     if (!is_integer(lhs)) {
+389       check_type(lhs, in);
+390       continue;
+391     }
+392     int address = to_integer(lhs);
+393     skip_whitespace_and_comments(in);
+394     string _assign;  in >> _assign;  assert(_assign == "<-");
+395     skip_whitespace_and_comments(in);
+396     string rhs = next_word(in);
+397     if (rhs.empty()) {
+398       assert(!has_data(in));
+399       raise << "incomplete 'memory-should-contain' block at end of file (1)\n" << end();
+400       return;
+401     }
+402     if (!is_integer(rhs) && !is_noninteger(rhs)) {
+403       if (Current_scenario && !Scenario_testing_scenario)
+404         // genuine test in a .mu file
+405         raise << "\nF - " << Current_scenario->name << ": location '" << address << "' can't contain non-number " << rhs << "\n" << end();
+406       else
+407         // just testing scenario support
+408         raise << "location '" << address << "' can't contain non-number " << rhs << '\n' << end();
+409       if (!Scenario_testing_scenario) Passed = false;
+410       return;
+411     }
+412     double value = to_double(rhs);
+413     if (contains_key(locations_checked, address))
+414       raise << "duplicate expectation for location '" << address << "'\n" << end();
+415     trace(9999, "run") << "checking location " << address << end();
+416     if (get_or_insert(Memory, address) != value) {
+417       if (Current_scenario && !Scenario_testing_scenario) {
+418         // genuine test in a .mu file
+419         raise << "\nF - " << Current_scenario->name << ": expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
+420       }
+421       else {
+422         // just testing scenario support
+423         raise << "expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
+424       }
+425       if (!Scenario_testing_scenario) Passed = false;
+426       return;
+427     }
+428     locations_checked.insert(address);
+429   }
+430 }
+431 
+432 void check_type(const string& lhs, istream& in) {
+433   reagent x(lhs);
+434   if (is_mu_array(x.type) && is_mu_character(array_element(x.type))) {
+435     x.set_value(to_integer(x.name));
+436     skip_whitespace_and_comments(in);
+437     string _assign = next_word(in);
+438     if (_assign.empty()) {
+439       assert(!has_data(in));
+440       raise << "incomplete 'memory-should-contain' block at end of file (2)\n" << end();
+441       return;
+442     }
+443     assert(_assign == "<-");
+444     skip_whitespace_and_comments(in);
+445     string literal = next_word(in);
+446     if (literal.empty()) {
+447       assert(!has_data(in));
+448       raise << "incomplete 'memory-should-contain' block at end of file (3)\n" << end();
+449       return;
+450     }
+451     int address = x.value;
+452     // exclude quoting brackets
+453     assert(*literal.begin() == '[');  literal.erase(literal.begin());
+454     assert(*--literal.end() == ']');  literal.erase(--literal.end());
+455     check_string(address, literal);
+456     return;
+457   }
+458   // End Scenario Type Special-cases
+459   raise << "don't know how to check memory for '" << lhs << "'\n" << end();
+460 }
+461 
+462 void check_string(int address, const string& literal) {
+463   trace(9999, "run") << "checking string length at " << address << end();
+464   if (get_or_insert(Memory, address) != SIZE(literal)) {
+465     if (Current_scenario && !Scenario_testing_scenario)
+466       raise << "\nF - " << Current_scenario->name << ": expected location '" << address << "' to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << " (" << read_mu_text(address-/*fake refcount*/1) << ")\n" << end();
+467     else
+468       raise << "expected location '" << address << "' to contain length " << SIZE(literal) << " of string [" << literal << "] but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end();
+469     if (!Scenario_testing_scenario) Passed = false;
+470     return;
+471   }
+472   ++address;  // now skip length
+473   for (int i = 0;  i < SIZE(literal);  ++i) {
+474     trace(9999, "run") << "checking location " << address+i << end();
+475     if (get_or_insert(Memory, address+i) != literal.at(i)) {
+476       if (Current_scenario && !Scenario_testing_scenario) {
+477         // genuine test in a .mu file
+478         raise << "\nF - " << Current_scenario->name << ": expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << " ('" << static_cast<char>(get_or_insert(Memory, address+i)) << "')\n" << end();
+479       }
+480       else {
+481         // just testing scenario support
+482         raise << "expected location " << (address+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, address+i)) << '\n' << end();
+483       }
+484       if (!Scenario_testing_scenario) Passed = false;
+485       return;
+486     }
+487   }
+488 }
+489 
+490 :(scenario memory_check_multiple)
+491 % Scenario_testing_scenario = true;
+492 % Hide_errors = true;
+493 def main [
+494   memory-should-contain [
+495     1 <- 0
+496     1 <- 0
+497   ]
+498 ]
+499 +error: duplicate expectation for location '1'
+500 
+501 :(scenario memory_check_string_length)
+502 % Scenario_testing_scenario = true;
+503 % Hide_errors = true;
+504 def main [
+505   1:num <- copy 3
+506   2:num <- copy 97  # 'a'
+507   3:num <- copy 98  # 'b'
+508   4:num <- copy 99  # 'c'
+509   memory-should-contain [
+510     1:array:character <- [ab]
+511   ]
+512 ]
+513 +error: expected location '1' to contain length 2 of string [ab] but saw 3
+514 
+515 :(scenario memory_check_string)
+516 def main [
+517   1:num <- copy 3
+518   2:num <- copy 97  # 'a'
+519   3:num <- copy 98  # 'b'
+520   4:num <- copy 99  # 'c'
+521   memory-should-contain [
+522     1:array:character <- [abc]
+523   ]
+524 ]
+525 +run: checking string length at 1
+526 +run: checking location 2
+527 +run: checking location 3
+528 +run: checking location 4
+529 
+530 :(scenario memory_invalid_string_check)
+531 % Scenario_testing_scenario = true;
+532 % Hide_errors = true;
+533 def main [
+534   memory-should-contain [
+535     1 <- [abc]
+536   ]
+537 ]
+538 +error: location '1' can't contain non-number [abc]
+539 
+540 :(scenario memory_check_with_comment)
+541 % Scenario_testing_scenario = true;
+542 % Hide_errors = true;
+543 def main [
+544   memory-should-contain [
+545     1 <- 34  # comment
+546   ]
+547 ]
+548 -error: location 1 can't contain non-number 34  # comment
+549 # but there'll be an error signalled by memory-should-contain
+550 
+551 //: 'trace-should-contain' is like the '+' lines in our scenarios so far
+552 // Like runs of contiguous '+' lines, order is important. The trace checks
+553 // that the lines are present *and* in the specified sequence. (There can be
+554 // other lines in between.)
+555 
+556 :(scenario trace_check_fails)
+557 % Scenario_testing_scenario = true;
+558 % Hide_errors = true;
+559 def main [
+560   trace-should-contain [
+561     a: b
+562     a: d
+563   ]
+564 ]
+565 +error: missing [b] in trace with label 'a'
+566 
+567 :(before "End Primitive Recipe Declarations")
+568 TRACE_SHOULD_CONTAIN,
+569 :(before "End Primitive Recipe Numbers")
+570 put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN);
+571 :(before "End Primitive Recipe Checks")
+572 case TRACE_SHOULD_CONTAIN: {
+573   break;
+574 }
+575 :(before "End Primitive Recipe Implementations")
+576 case TRACE_SHOULD_CONTAIN: {
+577   if (!Passed) break;
+578   check_trace(current_instruction().ingredients.at(0).name);
+579   break;
+580 }
+581 
+582 :(code)
+583 // simplified version of check_trace_contents() that emits errors rather
+584 // than just printing to stderr
+585 void check_trace(const string& expected) {
+586   Trace_stream->newline();
+587   vector<trace_line> expected_lines = parse_trace(expected);
+588   if (expected_lines.empty()) return;
+589   int curr_expected_line = 0;
+590   for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin();  p != Trace_stream->past_lines.end();  ++p) {
+591     if (expected_lines.at(curr_expected_line).label != p->label) continue;
+592     if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue;
+593     // match
+594     ++curr_expected_line;
+595     if (curr_expected_line == SIZE(expected_lines)) return;
+596   }
+597   if (Current_scenario && !Scenario_testing_scenario)
+598     raise << "\nF - " << Current_scenario->name << ": missing [" << expected_lines.at(curr_expected_line).contents << "] "
+599           << "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end();
+600   else
+601     raise << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
+602           << "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end();
+603   if (!Hide_errors)
+604     DUMP(expected_lines.at(curr_expected_line).label);
+605   if (!Scenario_testing_scenario) Passed = false;
+606 }
+607 
+608 vector<trace_line> parse_trace(const string& expected) {
+609   vector<string> buf = split(expected, "\n");
+610   vector<trace_line> result;
+611   for (int i = 0;  i < SIZE(buf);  ++i) {
+612     buf.at(i) = trim(buf.at(i));
+613     if (buf.at(i).empty()) continue;
+614     int delim = buf.at(i).find(": ");
+615     if (delim == -1) {
+616       raise << Current_scenario->name << ": lines in 'trace-should-contain' should be of the form <label>: <contents>. Both parts are required.\n" << end();
+617       result.clear();
+618       return result;
+619     }
+620     result.push_back(trace_line(trim(buf.at(i).substr(0, delim)),  trim(buf.at(i).substr(delim+2))));
+621   }
+622   return result;
+623 }
+624 
+625 :(scenario trace_check_fails_in_nonfirst_line)
+626 % Scenario_testing_scenario = true;
+627 % Hide_errors = true;
+628 def main [
+629   run [
+630     trace 1, [a], [b]
+631   ]
+632   trace-should-contain [
+633     a: b
+634     a: d
+635   ]
+636 ]
+637 +error: missing [d] in trace with label 'a'
+638 
+639 :(scenario trace_check_passes_silently)
+640 % Scenario_testing_scenario = true;
+641 def main [
+642   run [
+643     trace 1, [a], [b]
+644   ]
+645   trace-should-contain [
+646     a: b
+647   ]
+648 ]
+649 -error: missing [b] in trace with label 'a'
+650 $error: 0
+651 
+652 //: 'trace-should-not-contain' is like the '-' lines in our scenarios so far
+653 //: Each trace line is separately checked for absense. Order is *not*
+654 //: important, so you can't say things like "B should not exist after A."
+655 
+656 :(scenario trace_negative_check_fails)
+657 % Scenario_testing_scenario = true;
+658 % Hide_errors = true;
+659 def main [
+660   run [
+661     trace 1, [a], [b]
+662   ]
+663   trace-should-not-contain [
+664     a: b
+665   ]
+666 ]
+667 +error: unexpected [b] in trace with label 'a'
+668 
+669 :(before "End Primitive Recipe Declarations")
+670 TRACE_SHOULD_NOT_CONTAIN,
+671 :(before "End Primitive Recipe Numbers")
+672 put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN);
+673 :(before "End Primitive Recipe Checks")
+674 case TRACE_SHOULD_NOT_CONTAIN: {
+675   break;
+676 }
+677 :(before "End Primitive Recipe Implementations")
+678 case TRACE_SHOULD_NOT_CONTAIN: {
+679   if (!Passed) break;
+680   check_trace_missing(current_instruction().ingredients.at(0).name);
+681   break;
+682 }
+683 
+684 :(code)
+685 // simplified version of check_trace_contents() that emits errors rather
+686 // than just printing to stderr
+687 bool check_trace_missing(const string& in) {
+688   Trace_stream->newline();
+689   vector<trace_line> lines = parse_trace(in);
+690   for (int i = 0;  i < SIZE(lines);  ++i) {
+691     if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) {
+692       raise << "unexpected [" << lines.at(i).contents << "] in trace with label '" << lines.at(i).label << "'\n" << end();
+693       if (!Scenario_testing_scenario) Passed = false;
+694       return false;
+695     }
+696   }
+697   return true;
+698 }
+699 
+700 :(scenario trace_negative_check_passes_silently)
+701 % Scenario_testing_scenario = true;
+702 def main [
+703   trace-should-not-contain [
+704     a: b
+705   ]
+706 ]
+707 -error: unexpected [b] in trace with label 'a'
+708 $error: 0
+709 
+710 :(scenario trace_negative_check_fails_on_any_unexpected_line)
+711 % Scenario_testing_scenario = true;
+712 % Hide_errors = true;
+713 def main [
+714   run [
+715     trace 1, [a], [d]
+716   ]
+717   trace-should-not-contain [
+718     a: b
+719     a: d
+720   ]
+721 ]
+722 +error: unexpected [d] in trace with label 'a'
+723 
+724 :(scenario trace_count_check)
+725 def main [
+726   run [
+727     trace 1, [a], [foo]
+728   ]
+729   check-trace-count-for-label 1, [a]
+730 ]
+731 # checks are inside scenario
+732 
+733 :(before "End Primitive Recipe Declarations")
+734 CHECK_TRACE_COUNT_FOR_LABEL,
+735 :(before "End Primitive Recipe Numbers")
+736 put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL);
+737 :(before "End Primitive Recipe Checks")
+738 case CHECK_TRACE_COUNT_FOR_LABEL: {
+739   if (SIZE(inst.ingredients) != 2) {
+740     raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
+741     break;
+742   }
+743   if (!is_mu_number(inst.ingredients.at(0))) {
+744     raise << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-count-for-label' should be a number (count), but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
+745     break;
+746   }
+747   if (!is_literal_text(inst.ingredients.at(1))) {
+748     raise << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-count-for-label' should be a literal string (label), but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
+749     break;
+750   }
+751   break;
+752 }
+753 :(before "End Primitive Recipe Implementations")
+754 case CHECK_TRACE_COUNT_FOR_LABEL: {
+755   if (!Passed) break;
+756   int expected_count = ingredients.at(0).at(0);
+757   string label = current_instruction().ingredients.at(1).name;
+758   int count = trace_count(label);
+759   if (count != expected_count) {
+760     if (Current_scenario && !Scenario_testing_scenario) {
+761       // genuine test in a .mu file
+762       raise << "\nF - " << Current_scenario->name << ": " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace: " << end();
+763       DUMP(label);
+764     }
+765     else {
+766       // just testing scenario support
+767       raise << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end();
+768     }
+769     if (!Scenario_testing_scenario) Passed = false;
+770   }
+771   break;
+772 }
+773 
+774 :(scenario trace_count_check_2)
+775 % Scenario_testing_scenario = true;
+776 % Hide_errors = true;
+777 def main [
+778   run [
+779     trace 1, [a], [foo]
+780   ]
+781   check-trace-count-for-label 2, [a]
+782 ]
+783 +error: main: expected 2 lines in trace with label 'a' in trace
+784 
+785 //: Minor detail: ignore 'system' calls in scenarios, since anything we do
+786 //: with them is by definition impossible to test through Mu.
+787 :(after "case _SYSTEM:")
+788   if (Current_scenario) break;
+789 
+790 //:: Warn if people use '_' manually in function names. They're reserved for internal use.
+791 
+792 :(scenario recipe_name_with_underscore)
+793 % Hide_errors = true;
+794 def foo_bar [
+795 ]
+796 +error: foo_bar: don't create recipes with '_' in the name
+797 
+798 :(before "End recipe Fields")
+799 bool is_autogenerated;
+800 :(before "End recipe Constructor")
+801 is_autogenerated = false;
+802 :(code)
+803 void mark_autogenerated(recipe_ordinal r) {
+804   get(Recipe, r).is_autogenerated = true;
+805 }
+806 
+807 :(after "void transform_all()")
+808   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin();  p != Recipe.end();  ++p) {
+809     const recipe& r = p->second;
+810     if (r.name.find('_') == string::npos) continue;
+811     if (r.is_autogenerated) continue;  // created by previous call to transform_all()
+812     raise << r.name << ": don't create recipes with '_' in the name\n" << end();
+813   }
+814 
+815 //:: Helpers
+816 
+817 :(code)
+818 // just for the scenarios running scenarios in C++ layers
+819 void run_mu_scenario(const string& form) {
+820   istringstream in(form);
+821   in >> std::noskipws;
+822   skip_whitespace_and_comments(in);
+823   string _scenario = next_word(in);
+824   if (_scenario.empty()) {
+825     assert(!has_data(in));
+826     raise << "no scenario in string passed into run_mu_scenario()\n" << end();
+827     return;
+828   }
+829   assert(_scenario == "scenario");
+830   scenario s = parse_scenario(in);
+831   run_mu_scenario(s);
+832 }
 
-- cgit 1.4.1-2-gfad0