From 8e7b4429787bc2b7fe289f264d09a4b1f5f6b081 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sat, 20 Aug 2016 20:01:25 -0700 Subject: 3235 --- html/001help.cc.html | 2 +- html/014literal_string.cc.html | 30 +- html/075channel.mu.html | 2 + html/082scenario_screen.cc.html | 9 +- html/085scenario_console.cc.html | 4 +- html/088file.mu.html | 58 +++- html/089scenario_filesystem.cc.html | 276 +++++++++++++++ html/090scenario_filesystem_test.mu.html | 57 ++++ html/090trace_browser.cc.html | 285 ---------------- html/091run_sandboxed.cc.html | 556 ------------------------------- html/092persist.cc.html | 153 --------- html/100trace_browser.cc.html | 285 ++++++++++++++++ html/101run_sandboxed.cc.html | 556 +++++++++++++++++++++++++++++++ html/102persist.cc.html | 153 +++++++++ html/edit/011-errors.mu.html | 7 +- index.html | 2 +- 16 files changed, 1418 insertions(+), 1017 deletions(-) create mode 100644 html/089scenario_filesystem.cc.html create mode 100644 html/090scenario_filesystem_test.mu.html delete mode 100644 html/090trace_browser.cc.html delete mode 100644 html/091run_sandboxed.cc.html delete mode 100644 html/092persist.cc.html create mode 100644 html/100trace_browser.cc.html create mode 100644 html/101run_sandboxed.cc.html create mode 100644 html/102persist.cc.html diff --git a/html/001help.cc.html b/html/001help.cc.html index cf4c82ec..2c8bcce4 100644 --- a/html/001help.cc.html +++ b/html/001help.cc.html @@ -273,7 +273,7 @@ feenableexcept(FE_OVERFLOW | FE_UNDERFLOWusing std::cerr; #include <iomanip> -#include <cstring> +#include <string.h> #include <string> using std::string; diff --git a/html/014literal_string.cc.html b/html/014literal_string.cc.html index c917373b..6aa8050a 100644 --- a/html/014literal_string.cc.html +++ b/html/014literal_string.cc.html @@ -98,7 +98,7 @@ string slurp_quoted(istream& inwhile (has_data(in)) { char c = in.get(); if (c == '\\') { - out << static_cast<char>(in.get()); + slurp_one_past_backslashes(in, out); continue; } out << c; @@ -117,7 +117,7 @@ string slurp_quoted(istream& inchar c; while (in >> c) { if (c == '\\') { - out << static_cast<char>(in.get()); + slurp_one_past_backslashes(in, out); continue; } if (c == '#') { @@ -179,6 +179,28 @@ string emit_literal_string(string nameif (!s.empty()) s.erase(SIZE(s)-1); } +void slurp_one_past_backslashes(istream& in, ostream& out) { + // When you encounter a backslash, strip it out and pass through any + // following run of backslashes. If we 'escaped' a single following + // character, then the character '\' would be: + // '\\' escaped once + // '\\\\' escaped twice + // '\\\\\\\\' escaped thrice (8 backslashes) + // ..and so on. With our approach it'll be: + // '\\' escaped once + // '\\\' escaped twice + // '\\\\' escaped thrice + // This only works as long as backslashes aren't also overloaded to create + // special characters. So Mu doesn't follow C's approach of overloading + // backslashes both to escape quote characters and also as a notation for + // unprintable characters like '\n'. + while (has_data(in)) { + char c = in.get(); + out << c; + if (c != '\\') break; + } +} + :(scenario string_literal_nested) def main [ 1:address:array:character <- copy [abc [def]] @@ -191,10 +213,10 @@ def main [ ] +parse: ingredient: {"abc [def": "literal-string"} -:(scenario string_literal_escaped_comment_aware) +:(scenario string_literal_escaped_twice) def main [ 1:address:array:character <- copy [ -abc \\\[def] +abc \\[def] ] +parse: ingredient: {"\nabc \[def": "literal-string"} diff --git a/html/075channel.mu.html b/html/075channel.mu.html index 49bb5fb3..54fda434 100644 --- a/html/075channel.mu.html +++ b/html/075channel.mu.html @@ -102,6 +102,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color def write out:address:sink:_elem, val:_elem -> out:address:sink:_elem [ local-scope load-ingredients + assert out, [write to null channel] chan:address:channel:_elem <- get *out, chan:offset <channel-write-initial> { @@ -132,6 +133,7 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color def read in:address:source:_elem -> result:_elem, eof?:boolean, in:address:source:_elem [ local-scope load-ingredients + assert in, [read on null channel] eof? <- copy 0/false # default result chan:address:channel:_elem <- get *in, chan:offset { diff --git a/html/082scenario_screen.cc.html b/html/082scenario_screen.cc.html index df06104d..d66b7407 100644 --- a/html/082scenario_screen.cc.html +++ b/html/082scenario_screen.cc.html @@ -151,10 +151,6 @@ scenario screen-in-scenario-color [ ] +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_fail_when_mixing_special_names_and_numeric_locations) % Scenario_testing_scenario = true; @@ -183,8 +179,13 @@ assert(Name[tmp_recipe.// Scenario Globals. const int SCREEN = Next_predefined_global_for_scenarios++; // End Scenario Globals. + +//: give 'screen' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") Name[r]["screen"] = SCREEN; +//: make 'screen' always a raw location in scenarios +:(before "End is_special_name Cases") +if (s == "screen") return true; :(before "End Rewrite Instruction(curr, recipe result)") // rewrite `assume-screen width, height` to diff --git a/html/085scenario_console.cc.html b/html/085scenario_console.cc.html index bfa387e2..f2e79b3c 100644 --- a/html/085scenario_console.cc.html +++ b/html/085scenario_console.cc.html @@ -68,10 +68,10 @@ scenario keyboard-in-scenario [ :(before "End Scenario Globals") const int CONSOLE = Next_predefined_global_for_scenarios++; +//: give 'console' a fixed location in scenarios :(before "End Special Scenario Variable Names(r)") Name[r]["console"] = CONSOLE; - -//: allow naming just for 'console' +//: make 'console' always a raw location in scenarios :(before "End is_special_name Cases") if (s == "console") return true; diff --git a/html/088file.mu.html b/html/088file.mu.html index e2dec15c..6a194227 100644 --- a/html/088file.mu.html +++ b/html/088file.mu.html @@ -15,10 +15,10 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color * { font-size: 12pt; font-size: 1em; } .muRecipe { color: #ff8700; } .muData { color: #ffff00; } -.Comment { color: #9090ff; } .Delimiter { color: #800080; } -.Special { color: #c00000; } +.Comment { color: #9090ff; } .Constant { color: #00a0a0; } +.Special { color: #c00000; } .muControl { color: #c0a020; } --> @@ -35,18 +35,46 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color # are thus easier to test. container filesystem [ - {data: (address table (address array character) (address array character))} + data:address:array:file-mapping +] + +container file-mapping [ + name:address:array:character + contents:address:array:character ] def start-reading fs:address:filesystem, filename:address:array:character -> contents:address:source:character [ local-scope load-ingredients - file:number <- $open-file-for-reading filename - contents:address:source:character, sink:address:sink:character <- new-channel 30 - start-running transmit-from-file file, sink + { + break-if fs + # real file-system + file:number <- $open-file-for-reading filename + assert file, [file not found] + contents:address:source:character, sink:address:sink:character <- new-channel 30 + start-running transmit-from-file file, sink + return + } + # fake file system + i:number <- copy 0 + data:address:array:file-mapping <- get *fs, data:offset + len:number <- length *data + { + done?:boolean <- greater-or-equal i, len + break-if done? + tmp:file-mapping <- index *data, i + curr-filename:address:array:character <- get tmp, name:offset + found?:boolean <- equal filename, curr-filename + loop-unless found? + contents:address:source:character, sink:address:sink:character <- new-channel 30 + curr-contents:address:array:character <- get tmp, contents:offset + start-running transmit-from-text curr-contents, sink + return + } + return 0/not-found ] -def transmit-from-file file:number, sink:address:sink:character -> file:number, sink:address:sink:character [ +def transmit-from-file file:number, sink:address:sink:character -> sink:address:sink:character [ local-scope load-ingredients { @@ -59,6 +87,22 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color $close-file file ] +def transmit-from-text contents:address:array:character, sink:address:sink:character -> sink:address:sink:character [ + local-scope + load-ingredients + i:number <- copy 0 + len:number <- length *contents + { + done?:boolean <- greater-or-equal i, len + break-if done? + c:character <- index *contents, i + sink <- write sink, c + i <- add i, 1 + loop + } + sink <- close sink +] + def start-writing fs:address:filesystem, filename:address:array:character -> sink:address:sink:character, routine-id:number [ local-scope load-ingredients diff --git a/html/089scenario_filesystem.cc.html b/html/089scenario_filesystem.cc.html new file mode 100644 index 00000000..6dc1faa3 --- /dev/null +++ b/html/089scenario_filesystem.cc.html @@ -0,0 +1,276 @@ + + + + +Mu - 089scenario_filesystem.cc + + + + + + + + + + +
+//: Clean syntax to manipulate and check the file system in scenarios.
+//: Instructions 'assume-filesystem' and 'filesystem-should-contain' implicitly create
+//: a variable called 'filesystem' that is accessible to later instructions in
+//: the scenario. 'filesystem-should-contain' can check unicode characters in
+//: the fake filesystem
+
+:(scenarios run_mu_scenario)
+:(scenario simple_filesystem)
+scenario assume-filesystem [
+  local-scope
+  assume-filesystem [
+    # file 'a' containing two lines of data
+    [a] <- [
+      |a bc|
+      |de f|
+    ]
+    # directory 'b' containing two files, 'c' and 'd'
+    [b/c] <- []
+    [b/d] <- [
+      |xyz|
+    ]
+  ]
+  data:address:array:file-mapping <- get *filesystem:address:filesystem, data:offset
+  file1:file-mapping <- index *data, 0
+  file1-name:address:array:character <- get file1, name:offset
+  10:array:character/raw <- copy *file1-name
+  file1-contents:address:array:character <- get file1, contents:offset
+  100:array:character/raw <- copy *file1-contents
+  file2:file-mapping <- index *data, 1
+  file2-name:address:array:character <- get file2, name:offset
+  30:array:character/raw <- copy *file2-name
+  file2-contents:address:array:character <- get file2, contents:offset
+  40:array:character/raw <- copy *file2-contents
+  file3:file-mapping <- index *data, 2
+  file3-name:address:array:character <- get file3, name:offset
+  50:array:character/raw <- copy *file3-name
+  file3-contents:address:array:character <- get file3, contents:offset
+  60:array:character/raw <- copy *file3-contents
+  memory-should-contain [
+    10:array:character <- [a]
+    100:array:character <- [a bc
+de f
+]
+    30:array:character <- [b/c]
+    40:array:character <- []
+    50:array:character <- [b/d]
+    60:array:character <- [xyz
+]
+  ]
+]
+
+:(scenario escaping_file_contents)
+scenario assume-filesystem [
+  local-scope
+  assume-filesystem [
+    # file 'a' containing a '|'
+    # need to escape '\' once for each block
+    [a] <- [
+      |x\\\\|yz|
+    ]
+  ]
+  data:address:array:file-mapping <- get *filesystem:address:filesystem, data:offset
+  file1:file-mapping <- index *data, 0
+  file1-name:address:array:character <- get file1, name:offset
+  10:array:character/raw <- copy *file1-name
+  file1-contents:address:array:character <- get file1, contents:offset
+  20:array:character/raw <- copy *file1-contents
+  memory-should-contain [
+    10:array:character <- [a]
+    20:array:character <- [x|yz
+]
+  ]
+]
+
+:(before "End Globals")
+const int FILESYSTEM = Next_predefined_global_for_scenarios++;
+//: give 'filesystem' a fixed location in scenarios
+:(before "End Special Scenario Variable Names(r)")
+Name[r]["filesystem"] = FILESYSTEM;
+//: make 'filesystem' always a raw location in scenarios
+:(before "End is_special_name Cases")
+if (s == "filesystem") return true;
+
+:(before "End initialize_transform_rewrite_literal_string_to_text()")
+recipes_taking_literal_strings.insert("assume-filesystem");
+
+//: screen-should-contain is a regular instruction
+:(before "End Primitive Recipe Declarations")
+ASSUME_FILESYSTEM,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "assume-filesystem", ASSUME_FILESYSTEM);
+:(before "End Primitive Recipe Checks")
+case ASSUME_FILESYSTEM: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case ASSUME_FILESYSTEM: {
+  assert(scalar(ingredients.at(0)));
+  assume_filesystem(current_instruction().ingredients.at(0).name, current_recipe_name());
+  break;
+}
+
+:(code)
+void assume_filesystem(const string& data, const string& caller) {
+  map<string, string> contents;
+  parse_filesystem(data, contents, caller);
+  construct_filesystem_object(contents);
+}
+
+void parse_filesystem(const string& data, map<string, string>& out, const string& caller) {
+  istringstream in(data);
+  in >> std::noskipws;
+  while (true) {
+    if (!has_data(in)) break;
+    skip_whitespace_and_comments(in);
+    if (!has_data(in)) break;
+    string filename = next_filesystem_word(in);
+    if (*filename.begin() != '[') {
+      raise << caller << ": assume-filesystem: filename '" << filename << "' must begin with a '['\n" << end();
+      break;
+    }
+    if (*filename.rbegin() != ']') {
+      raise << caller << ": assume-filesystem: filename '" << filename << "' must end with a ']'\n" << end();
+      break;
+    }
+    filename.erase(0, 1);
+    filename.erase(SIZE(filename)-1);
+    if (!has_data(in)) {
+      raise << caller << ": assume-filesystem: no data for filename '" << filename << "'\n" << end();
+      break;
+    }
+    string arrow = next_filesystem_word(in);
+    if (arrow != "<-") {
+      raise << caller << ": assume-filesystem: expected '<-' after filename '" << filename << "' but got '" << arrow << "'\n" << end();
+      break;
+    }
+    if (!has_data(in)) {
+      raise << caller << ": assume-filesystem: no data for filename '" << filename << "' after '<-'\n" << end();
+      break;
+    }
+    string contents = next_filesystem_word(in);
+    if (*contents.begin() != '[') {
+      raise << caller << ": assume-filesystem: file contents '" << contents << "' for filename '" << filename << "' must begin with a '['\n" << end();
+      break;
+    }
+    if (*contents.rbegin() != ']') {
+      raise << caller << ": assume-filesystem: file contents '" << contents << "' for filename '" << filename << "' must end with a ']'\n" << end();
+      break;
+    }
+    contents.erase(0, 1);
+    contents.erase(SIZE(contents)-1);
+    put(out, filename, munge_filesystem_contents(contents, filename, caller));
+  }
+}
+
+string munge_filesystem_contents(const string& data, const string& filename, const string& caller) {
+  if (data.empty()) return "";
+  istringstream in(data);
+  in >> std::noskipws;
+  skip_whitespace_and_comments(in);
+  ostringstream out;
+  while (true) {
+    if (!has_data(in)) break;
+    skip_whitespace(in);
+    if (!has_data(in)) break;
+    if (in.peek() != '|') {
+      raise << caller << ": assume-filesystem: file contents for filename '" << filename << "' must be delimited in '|'s\n" << end();
+      break;
+    }
+    in.get();  // skip leading '|'
+    string line;
+    getline(in, line);
+    for (int i = 0; i < SIZE(line); ++i) {
+      if (line.at(i) == '|') break;
+      if (line.at(i) == '\\') {
+        ++i;  // skip
+        if (i == SIZE(line)) {
+          raise << caller << ": assume-filesystem: file contents can't end a line with '\\'\n" << end();
+          break;
+        }
+      }
+      out << line.at(i);
+    }
+    // todo: some way to represent a file without a final newline
+    out << '\n';
+  }
+  return out.str();
+}
+
+void construct_filesystem_object(const map<string, string>& contents) {
+  int filesystem_data_address = allocate(SIZE(contents)*2 + /*array length*/1);
+  int curr = filesystem_data_address + /*skip refcount*/1 + /*skip array length*/1;
+  for (map<string, string>::const_iterator p = contents.begin(); p != contents.end(); ++p) {
+    put(Memory, curr, new_mu_string(p->first));
+    curr++;
+    put(Memory, curr, new_mu_string(p->second));
+    curr++;
+  }
+  put(Memory, filesystem_data_address+/*skip refcount*/1, SIZE(contents));  // size of array
+  put(Memory, filesystem_data_address, 1);  // initialize refcount
+  // wrap the filesystem data in a filesystem object
+  int filesystem_address = allocate(size_of_filesystem());
+  put(Memory, filesystem_address+/*skip refcount*/1, filesystem_data_address);
+  put(Memory, filesystem_address, 1);  // initialize refcount
+  // save in product
+  put(Memory, FILESYSTEM, filesystem_address);
+}
+
+int size_of_filesystem() {
+  // memoize result if already computed
+  static int result = 0;
+  if (result) return result;
+  assert(get(Type_ordinal, "filesystem"));
+  type_tree* type = new type_tree("filesystem");
+  result = size_of(type)+/*refcount*/1;
+  delete type;
+  return result;
+}
+
+string next_filesystem_word(istream& in) {
+  skip_whitespace_and_comments(in);
+  if (in.peek() == '[') {
+    string result = slurp_quoted(in);
+    skip_whitespace_and_comments_but_not_newline(in);
+    return result;
+  }
+  ostringstream out;
+  slurp_word(in, out);
+  skip_whitespace_and_comments(in);
+  return out.str();
+}
+
+void skip_whitespace(istream& in) {
+  while (true) {
+    if (!has_data(in)) break;
+    if (isspace(in.peek())) in.get();
+    else break;
+  }
+}
+
+ + + diff --git a/html/090scenario_filesystem_test.mu.html b/html/090scenario_filesystem_test.mu.html new file mode 100644 index 00000000..1188f1dd --- /dev/null +++ b/html/090scenario_filesystem_test.mu.html @@ -0,0 +1,57 @@ + + + + +Mu - 090scenario_filesystem_test.mu + + + + + + + + + + +
+# Check our support for fake file systems in scenarios.
+
+scenario read-from-fake-file [
+  local-scope
+  assume-filesystem [
+    [a] <- [
+      |xyz|
+    ]
+  ]
+  contents:address:source:character <- start-reading filesystem:address:filesystem, [a]
+  1:character/raw <- read contents
+  2:character/raw <- read contents
+  3:character/raw <- read contents
+  4:character/raw <- read contents
+  _, 5:boolean/raw <- read contents
+  memory-should-contain [
+    1 <- 120  # x
+    2 <- 121  # y
+    3 <- 122  # z
+    4 <- 10  # newline
+    5 <- 1  # eof
+  ]
+]
+
+ + + diff --git a/html/090trace_browser.cc.html b/html/090trace_browser.cc.html deleted file mode 100644 index 7fe976a3..00000000 --- a/html/090trace_browser.cc.html +++ /dev/null @@ -1,285 +0,0 @@ - - - - -Mu - 090trace_browser.cc - - - - - - - - - - -
-//: A debugging helper that lets you zoom in/out on a trace.
-
-//: browse the trace we just created
-:(before "End Primitive Recipe Declarations")
-_BROWSE_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
-:(before "End Primitive Recipe Checks")
-case _BROWSE_TRACE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _BROWSE_TRACE: {
-  start_trace_browser();
-  break;
-}
-
-//: browse a trace loaded from a file
-:(after "Commandline Parsing")
-if (argc == 3 && is_equal(argv[1], "browse-trace")) {
-  load_trace(argv[2]);
-  start_trace_browser();
-  return 0;
-}
-
-:(before "End Globals")
-set<int> Visible;
-int Top_of_screen = 0;
-int Last_printed_row = 0;
-map<int, int> Trace_index;  // screen row -> trace index
-
-:(code)
-void start_trace_browser() {
-  if (!Trace_stream) return;
-  cerr << "computing min depth to display\n";
-  int min_depth = 9999;
-  for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    trace_line& curr_line = Trace_stream->past_lines.at(i);
-    if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-  }
-  cerr << "min depth is " << min_depth << '\n';
-  cerr << "computing lines to display\n";
-  for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
-    if (Trace_stream->past_lines.at(i).depth == min_depth)
-      Visible.insert(i);
-  }
-  tb_init();
-  Display_row = Display_column = 0;
-  tb_event event;
-  Top_of_screen = 0;
-  refresh_screen_rows();
-  while (true) {
-    render();
-    do {
-      tb_poll_event(&event);
-    } while (event.type != TB_EVENT_KEY);
-    int key = event.key ? event.key : event.ch;
-    if (key == 'q' || key == 'Q') break;
-    if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
-      // move cursor one line down
-      if (Display_row < Last_printed_row) ++Display_row;
-    }
-    if (key == 'k' || key == TB_KEY_ARROW_UP) {
-      // move cursor one line up
-      if (Display_row > 0) --Display_row;
-    }
-    if (key == 'H') {
-      // move cursor to top of screen
-      Display_row = 0;
-    }
-    if (key == 'M') {
-      // move cursor to center of screen
-      Display_row = tb_height()/2;
-    }
-    if (key == 'L') {
-      // move cursor to bottom of screen
-      Display_row = tb_height()-1;
-    }
-    if (key == 'J' || key == TB_KEY_PGDN) {
-      // page-down
-      if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
-        Top_of_screen = get(Trace_index, tb_height()-1) + 1;
-        refresh_screen_rows();
-      }
-    }
-    if (key == 'K' || key == TB_KEY_PGUP) {
-      // page-up is more convoluted
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      if (Top_of_screen >= 0)
-        refresh_screen_rows();
-    }
-    if (key == 'G') {
-      // go to bottom of screen; largely like page-up, interestingly
-      Top_of_screen = SIZE(Trace_stream->past_lines)-1;
-      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
-        --Top_of_screen;
-        if (Top_of_screen <= 0) break;
-        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
-          --Top_of_screen;
-      }
-      refresh_screen_rows();
-      // move cursor to bottom
-      Display_row = Last_printed_row;
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_CARRIAGE_RETURN) {
-      // expand lines under current by one level
-      assert(contains_key(Trace_index, Display_row));
-      int start_index = get(Trace_index, Display_row);
-      int index = 0;
-      // simultaneously compute end_index and min_depth
-      int min_depth = 9999;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (contains_key(Visible, index)) break;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
-        if (curr_line.depth < min_depth) min_depth = curr_line.depth;
-      }
-      int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index; index < end_index; ++index) {
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth == min_depth) {
-          Visible.insert(index);
-        }
-      }
-      refresh_screen_rows();
-    }
-    if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
-      // collapse all lines under current
-      assert(contains_key(Trace_index, Display_row));
-      int start_index = get(Trace_index, Display_row);
-      int index = 0;
-      // end_index is the next line at a depth same as or lower than start_index
-      int initial_depth = Trace_stream->past_lines.at(start_index).depth;
-      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
-        if (!contains_key(Visible, index)) continue;
-        trace_line& curr_line = Trace_stream->past_lines.at(index);
-        if (curr_line.depth <= initial_depth) break;
-      }
-      int end_index = index;
-      // mark as visible all intervening indices at min_depth
-      for (index = start_index+1; index < end_index; ++index) {
-        Visible.erase(index);
-      }
-      refresh_screen_rows();
-    }
-  }
-  tb_shutdown();
-}
-
-// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
-void refresh_screen_rows() {
-  int screen_row = 0, index = 0;
-  Trace_index.clear();
-  for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
-    // skip lines without depth for now
-    while (!contains_key(Visible, index)) {
-      ++index;
-      if (index >= SIZE(Trace_stream->past_lines)) goto done;
-    }
-    assert(index < SIZE(Trace_stream->past_lines));
-    put(Trace_index, screen_row, index);
-  }
-done:;
-}
-
-void render() {
-  int screen_row = 0;
-  for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
-    if (!contains_key(Trace_index, screen_row)) break;
-    trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
-    ostringstream out;
-    out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
-    if (screen_row < tb_height()-1) {
-      int delta = lines_hidden(screen_row);
-      // home-brew escape sequence for red
-      if (delta > 999) out << "{";
-      out << " (" << delta << ")";
-      if (delta > 999) out << "}";
-    }
-    render_line(screen_row, out.str());
-  }
-  // clear rest of screen
-  Last_printed_row = screen_row-1;
-  for (; screen_row < tb_height(); ++screen_row) {
-    render_line(screen_row, "~");
-  }
-  // move cursor back to display row at the end
-  tb_set_cursor(0, Display_row);
-  tb_present();
-}
-
-int lines_hidden(int screen_row) {
-  assert(contains_key(Trace_index, screen_row));
-  if (!contains_key(Trace_index, screen_row+1))
-    return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
-  else
-    return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
-}
-
-void render_line(int screen_row, const string& s) {
-  int col = 0;
-  int color = TB_WHITE;
-  for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
-    char c = s.at(col);  // todo: unicode
-    if (c == '\n') c = ';';  // replace newlines with semi-colons
-    // escapes. hack: can't start a line with them.
-    if (c == '{') { color = /*red*/1; c = ' '; }
-    if (c == '}') { color = TB_WHITE; c = ' '; }
-    tb_change_cell(col, screen_row, c, color, TB_BLACK);
-  }
-  for (; col < tb_width(); ++col) {
-    tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
-  }
-}
-
-void load_trace(const char* filename) {
-  ifstream tin(filename);
-  if (!tin) {
-    cerr << "no such file: " << filename << '\n';
-    exit(1);
-  }
-  Trace_stream = new trace_stream;
-  while (has_data(tin)) {
-    tin >> std::noskipws;
-      skip_whitespace_but_not_newline(tin);
-      if (!isdigit(tin.peek())) {
-        string dummy;
-        getline(tin, dummy);
-        continue;
-      }
-    tin >> std::skipws;
-    int depth;
-    tin >> depth;
-    string label;
-    tin >> label;
-    if (*--label.end() == ':') label.erase(--label.end());
-    string line;
-    getline(tin, line);
-    Trace_stream->past_lines.push_back(trace_line(depth, label, line));
-  }
-  cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
-}
-
- - - diff --git a/html/091run_sandboxed.cc.html b/html/091run_sandboxed.cc.html deleted file mode 100644 index cd7cd455..00000000 --- a/html/091run_sandboxed.cc.html +++ /dev/null @@ -1,556 +0,0 @@ - - - - -Mu - 091run_sandboxed.cc - - - - - - - - - - -
-//: Helper for various programming environments: run arbitrary mu code and
-//: return some result in string form.
-
-:(scenario run_interactive_code)
-def main [
-  1:number/raw <- copy 0
-  2:address:array:character <- new [1:number/raw <- copy 34]
-  run-sandboxed 2:address:array:character
-  3:number/raw <- copy 1:number/raw
-]
-+mem: storing 34 in location 3
-
-:(scenario run_interactive_empty)
-def main [
-  1:address:array:character <- copy 0/unsafe
-  2:address:array:character <- run-sandboxed 1:address:array:character
-]
-# result is null
-+mem: storing 0 in location 2
-
-//: As the name suggests, 'run-sandboxed' will prevent certain operations that
-//: regular Mu code can perform.
-:(before "End Globals")
-bool Sandbox_mode = false;
-//: for starters, users can't override 'main' when the environment is running
-:(before "End Load Recipe Name")
-if (Sandbox_mode && result.name == "main") {
-  slurp_balanced_bracket(in);
-  return -1;
-}
-
-//: run code in 'interactive mode', i.e. with errors off and return:
-//:   stringified output in case we want to print it to screen
-//:   any errors encountered
-//:   simulated screen any prints went to
-//:   any 'app' layer traces generated
-:(before "End Primitive Recipe Declarations")
-RUN_SANDBOXED,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "run-sandboxed", RUN_SANDBOXED);
-:(before "End Primitive Recipe Checks")
-case RUN_SANDBOXED: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'run-sandboxed' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(0))) {
-    raise << maybe(get(Recipe, r).name) << "first ingredient of 'run-sandboxed' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RUN_SANDBOXED: {
-  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
-  if (!new_code_pushed_to_stack) {
-    products.resize(5);
-    products.at(0).push_back(0);
-    products.at(1).push_back(trace_error_contents());
-    products.at(2).push_back(0);
-    products.at(3).push_back(trace_app_contents());
-    products.at(4).push_back(1);  // completed
-    run_code_end();
-    break;  // done with this instruction
-  }
-  else {
-    continue;  // not done with caller; don't increment current_step_index()
-  }
-}
-
-:(before "End Globals")
-bool Track_most_recent_products = false;
-:(before "End Tracing")
-trace_stream* Save_trace_stream = NULL;
-string Save_trace_file;
-:(before "End Setup")
-Track_most_recent_products = false;
-:(code)
-// reads a string, tries to call it as code (treating it as a test), saving
-// all errors.
-// returns true if successfully called (no errors found during load and transform)
-bool run_interactive(int address) {
-  assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
-  // try to sandbox the run as best you can
-  // todo: test this
-  if (!Current_scenario) {
-    for (int i = 1; i < Reserved_for_tests; ++i)
-      Memory.erase(i);
-  }
-  string command = trim(strip_comments(read_mu_string(address)));
-  Name[get(Recipe_ordinal, "interactive")].clear();
-  run_code_begin(/*should_stash_snapshots*/true);
-  if (command.empty()) return false;
-  // don't kill the current routine on parse errors
-  routine* save_current_routine = Current_routine;
-  Current_routine = NULL;
-  // call run(string) but without the scheduling
-  load(string("recipe! interactive [\n") +
-          "new-default-space\n" +  // disable automatic abandon so tests can see changes
-          "screen:address:screen <- next-ingredient\n" +
-          "$start-tracking-products\n" +
-          command + "\n" +
-          "$stop-tracking-products\n" +
-          "return screen\n" +
-       "]\n");
-  transform_all();
-  Current_routine = save_current_routine;
-  if (trace_count("error") > 0) return false;
-  // now call 'sandbox' which will run 'interactive' in a separate routine,
-  // and wait for it
-  if (Save_trace_stream) {
-    ++Save_trace_stream->callstack_depth;
-    trace(9999, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
-    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
-  }
-  Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
-  return true;
-}
-
-//: Carefully update all state to exactly how it was -- including snapshots.
-
-:(before "End Globals")
-map<string, recipe_ordinal> Recipe_ordinal_snapshot_stash;
-map<recipe_ordinal, recipe> Recipe_snapshot_stash;
-map<string, type_ordinal> Type_ordinal_snapshot_stash;
-map<type_ordinal, type_info> Type_snapshot_stash;
-map<recipe_ordinal, map<string, int> > Name_snapshot_stash;
-map<string, vector<recipe_ordinal> > Recipe_variants_snapshot_stash;
-
-:(code)
-void run_code_begin(bool should_stash_snapshots) {
-  // stuff to undo later, in run_code_end()
-  Hide_errors = true;
-  Disable_redefine_checks = true;
-  if (should_stash_snapshots)
-    stash_snapshots();
-  Save_trace_stream = Trace_stream;
-  Trace_stream = new trace_stream;
-  Trace_stream->collect_depth = App_depth;
-}
-
-void run_code_end() {
-  Hide_errors = false;
-  Disable_redefine_checks = false;
-  delete Trace_stream;
-  Trace_stream = Save_trace_stream;
-  Save_trace_stream = NULL;
-  Save_trace_file.clear();
-  Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
-  if (!Recipe_snapshot_stash.empty())
-    unstash_snapshots();
-}
-
-// keep sync'd with save_snapshots and restore_snapshots
-void stash_snapshots() {
-  assert(Recipe_ordinal_snapshot_stash.empty());
-  Recipe_ordinal_snapshot_stash = Recipe_ordinal_snapshot;
-  assert(Recipe_snapshot_stash.empty());
-  Recipe_snapshot_stash = Recipe_snapshot;
-  assert(Type_ordinal_snapshot_stash.empty());
-  Type_ordinal_snapshot_stash = Type_ordinal_snapshot;
-  assert(Type_snapshot_stash.empty());
-  Type_snapshot_stash = Type_snapshot;
-  assert(Name_snapshot_stash.empty());
-  Name_snapshot_stash = Name_snapshot;
-  assert(Recipe_variants_snapshot_stash.empty());
-  Recipe_variants_snapshot_stash = Recipe_variants_snapshot;
-  save_snapshots();
-}
-void unstash_snapshots() {
-  restore_snapshots();
-  Recipe_ordinal_snapshot = Recipe_ordinal_snapshot_stash;  Recipe_ordinal_snapshot_stash.clear();
-  Recipe_snapshot = Recipe_snapshot_stash;  Recipe_snapshot_stash.clear();
-  Type_ordinal_snapshot = Type_ordinal_snapshot_stash;  Type_ordinal_snapshot_stash.clear();
-  Type_snapshot = Type_snapshot_stash;  Type_snapshot_stash.clear();
-  Name_snapshot = Name_snapshot_stash;  Name_snapshot_stash.clear();
-  Recipe_variants_snapshot = Recipe_variants_snapshot_stash;  Recipe_variants_snapshot_stash.clear();
-}
-
-:(before "End Load Recipes")
-load(string(
-"recipe interactive [\n") +  // just a dummy version to initialize the Recipe_ordinal and so on
-"]\n" +
-"recipe sandbox [\n" +
-  "local-scope\n" +
-  "screen:address:screen <- new-fake-screen 30, 5\n" +
-  "routine-id:number <- start-running interactive, screen\n" +
-  "limit-time routine-id, 100000/instructions\n" +
-  "wait-for-routine routine-id\n" +
-  "instructions-run:number <- number-of-instructions routine-id\n" +
-  "stash instructions-run [instructions run]\n" +
-  "sandbox-state:number <- routine-state routine-id\n" +
-  "completed?:boolean <- equal sandbox-state, 1/completed\n" +
-  "output:address:array:character <- $most-recent-products\n" +
-  "errors:address:array:character <- save-errors\n" +
-  "stashes:address:array:character <- save-app-trace\n" +
-  "$cleanup-run-sandboxed\n" +
-  "return output, errors, screen, stashes, completed?\n" +
-"]\n");
-
-//: adjust errors in the sandbox
-:(after "string maybe(string s)")
-  if (s == "interactive") return "";
-
-:(scenario run_interactive_comments)
-def main [
-  1:address:array:character <- new [# ab
-add 2, 2]
-  2:address:array:character <- run-sandboxed 1:address:array:character
-  3:array:character <- copy *2:address:array:character
-]
-+mem: storing 52 in location 4
-
-:(before "End Primitive Recipe Declarations")
-_START_TRACKING_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _START_TRACKING_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _START_TRACKING_PRODUCTS: {
-  Track_most_recent_products = true;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_STOP_TRACKING_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _STOP_TRACKING_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _STOP_TRACKING_PRODUCTS: {
-  Track_most_recent_products = false;
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_MOST_RECENT_PRODUCTS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
-:(before "End Primitive Recipe Checks")
-case _MOST_RECENT_PRODUCTS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _MOST_RECENT_PRODUCTS: {
-  products.resize(1);
-  products.at(0).push_back(new_mu_string(Most_recent_products));
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE_ERRORS,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save-errors", SAVE_ERRORS);
-:(before "End Primitive Recipe Checks")
-case SAVE_ERRORS: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE_ERRORS: {
-  products.resize(1);
-  products.at(0).push_back(trace_error_contents());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE_APP_TRACE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
-:(before "End Primitive Recipe Checks")
-case SAVE_APP_TRACE: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE_APP_TRACE: {
-  products.resize(1);
-  products.at(0).push_back(trace_app_contents());
-  break;
-}
-
-:(before "End Primitive Recipe Declarations")
-_CLEANUP_RUN_SANDBOXED,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "$cleanup-run-sandboxed", _CLEANUP_RUN_SANDBOXED);
-:(before "End Primitive Recipe Checks")
-case _CLEANUP_RUN_SANDBOXED: {
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case _CLEANUP_RUN_SANDBOXED: {
-  run_code_end();
-  break;
-}
-
-:(scenario "run_interactive_converts_result_to_text")
-def main [
-  # try to interactively add 2 and 2
-  1:address:array:character <- new [add 2, 2]
-  2:address:array:character <- run-sandboxed 1:address:array:character
-  10:array:character <- copy 2:address:array:character/lookup
-]
-# first letter in the output should be '4' in unicode
-+mem: storing 52 in location 11
-
-:(scenario "run_interactive_returns_text")
-def main [
-  # try to interactively add 2 and 2
-  1:address:array:character <- new [
-    x:address:array:character <- new [a]
-    y:address:array:character <- new [b]
-    z:address:array:character <- append x:address:array:character, y:address:array:character
-  ]
-  2:address:array:character <- run-sandboxed 1:address:array:character
-  10:array:character <- copy 2:address:array:character/lookup
-]
-# output contains "ab"
-+mem: storing 97 in location 11
-+mem: storing 98 in location 12
-
-:(scenario "run_interactive_returns_errors")
-def main [
-  # 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-sandboxed 1:address:array:character
-  10:array:character <- copy 3:address:array:character/lookup
-]
-# 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
-+mem: storing 110 in location 14
-# ...
-
-:(scenario run_interactive_with_comment)
-def main [
-  # 2 instructions, with a comment after the first
-  1:address:array:number <- new [a:number <- copy 0  # abc
-b:number <- copy 0
-]
-  2:address:array:character, 3:address:array:character <- run-sandboxed 1:address:array:character
-]
-# no errors
-+mem: storing 0 in location 3
-
-:(before "End Globals")
-string Most_recent_products;
-:(before "End Setup")
-Most_recent_products = "";
-:(before "End Running One Instruction")
-if (Track_most_recent_products) {
-  track_most_recent_products(current_instruction(), products);
-}
-:(code)
-void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
-  ostringstream out;
-  for (int i = 0; i < SIZE(products); ++i) {
-    // string
-    if (i < SIZE(instruction.products)) {
-      if (is_mu_string(instruction.products.at(i))) {
-        if (!scalar(products.at(i))) continue;  // error handled elsewhere
-        out << read_mu_string(products.at(i).at(0)) << '\n';
-        continue;
-      }
-      // End Record Product Special-cases
-    }
-    for (int j = 0; j < SIZE(products.at(i)); ++j)
-      out << no_scientific(products.at(i).at(j)) << ' ';
-    out << '\n';
-  }
-  Most_recent_products = out.str();
-}
-
-:(code)
-string strip_comments(string in) {
-  ostringstream result;
-  for (int i = 0; i < SIZE(in); ++i) {
-    if (in.at(i) != '#') {
-      result << in.at(i);
-    }
-    else {
-      while (i+1 < SIZE(in) && in.at(i+1) != '\n')
-        ++i;
-    }
-  }
-  return result.str();
-}
-
-int stringified_value_of_location(int address) {
-  // convert to string
-  ostringstream out;
-  out << no_scientific(get_or_insert(Memory, address));
-  return new_mu_string(out.str());
-}
-
-int trace_error_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 != "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);
-}
-
-int trace_app_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->depth != App_depth) 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);
-}
-
-void truncate(string& x) {
-  if (SIZE(x) > 1024) {
-    x.erase(1024);
-    *x.rbegin() = '\n';
-    *++x.rbegin() = '.';
-    *++++x.rbegin() = '.';
-  }
-}
-
-//: simpler version of run-sandboxed: doesn't do any running, just loads
-//: recipes and reports errors.
-
-:(before "End Primitive Recipe Declarations")
-RELOAD,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "reload", RELOAD);
-:(before "End Primitive Recipe Checks")
-case RELOAD: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(0))) {
-    raise << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RELOAD: {
-  // conundrum: need to support repeated reloads of the same code, but not
-  // wipe out state for the current test
-  // hacky solution: subset of restore_snapshots() without restoring recipes {
-  // can't yet define containers in a test that runs 'reload'
-  Type_ordinal = Type_ordinal_snapshot;
-  Type = Type_snapshot;
-  // can't yet create new specializations of shape-shifting recipes in a test
-  // that runs 'reload'
-  Recipe_variants = Recipe_variants_snapshot;
-  Name = Name_snapshot;
-  // }
-  string code = read_mu_string(ingredients.at(0).at(0));
-  run_code_begin(/*should_stash_snapshots*/false);
-  routine* save_current_routine = Current_routine;
-  Current_routine = NULL;
-  Sandbox_mode = true;
-  vector<recipe_ordinal> recipes_reloaded = load(code);
-  transform_all();
-  Trace_stream->newline();  // flush trace
-  Sandbox_mode = false;
-  Current_routine = save_current_routine;
-  products.resize(1);
-  products.at(0).push_back(trace_error_contents());
-  run_code_end();  // wait until we're done with the trace contents
-  break;
-}
-
-:(scenario reload_continues_past_error)
-def main [
-  local-scope
-  x:address:array:character <- new [recipe foo [
-  get 1234:number, foo:offset
-]]
-  reload x
-  1:number/raw <- copy 34
-]
-+mem: storing 34 in location 1
-
-:(scenario reload_can_repeatedly_load_container_definitions)
-# define a container and try to create it (merge requires knowing container size)
-def main [
-  local-scope
-  x:address:array:character <- new [
-    container foo [
-      x:number
-      y:number
-    ]
-    recipe bar [
-      local-scope
-      x:foo <- merge 34, 35
-    ]
-  ]
-  # save warning addresses in locations of type 'number' to avoid spurious changes to them due to 'abandon'
-  1:number/raw <- reload x
-  2:number/raw <- reload x
-]
-# no errors on either load
-+mem: storing 0 in location 1
-+mem: storing 0 in location 2
-
- - - diff --git a/html/092persist.cc.html b/html/092persist.cc.html deleted file mode 100644 index 6c1e722c..00000000 --- a/html/092persist.cc.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - -Mu - 092persist.cc - - - - - - - - - - -
-//: Dead simple persistence.
-//:   'restore' - reads string from a file
-//:   'save' - writes string to a file
-
-:(before "End Primitive Recipe Declarations")
-RESTORE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "restore", RESTORE);
-:(before "End Primitive Recipe Checks")
-case RESTORE: {
-  if (SIZE(inst.ingredients) != 1) {
-    raise << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  string filename;
-  if (is_literal_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else if (is_mu_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else {
-    raise << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case RESTORE: {
-  string filename;
-  if (is_literal_string(current_instruction().ingredients.at(0))) {
-    filename = current_instruction().ingredients.at(0).name;
-  }
-  else if (is_mu_string(current_instruction().ingredients.at(0))) {
-    filename = read_mu_string(ingredients.at(0).at(0));
-  }
-  if (Current_scenario) {
-    // do nothing in tests
-    products.resize(1);
-    products.at(0).push_back(0);
-    break;
-  }
-  string contents = slurp("lesson/"+filename);
-  products.resize(1);
-  if (contents.empty())
-    products.at(0).push_back(0);
-  else
-    products.at(0).push_back(new_mu_string(contents));
-  break;
-}
-
-:(code)
-// http://cpp.indi.frih.net/blog/2014/09/how-to-read-an-entire-file-into-memory-in-cpp
-string slurp(const string& filename) {
-  ifstream fin(filename.c_str());
-  fin.peek();
-  if (!fin) return "";  // don't bother checking errno
-  ostringstream result;
-  result << fin.rdbuf();
-  fin.close();
-  return result.str();
-}
-
-:(before "End Primitive Recipe Declarations")
-SAVE,
-:(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "save", SAVE);
-:(before "End Primitive Recipe Checks")
-case SAVE: {
-  if (SIZE(inst.ingredients) != 2) {
-    raise << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
-    break;
-  }
-  if (is_literal_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else if (is_mu_string(inst.ingredients.at(0))) {
-    ;
-  }
-  else {
-    raise << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
-    break;
-  }
-  if (!is_mu_string(inst.ingredients.at(1))) {
-    raise << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got '" << to_string(inst.ingredients.at(1)) << "'\n" << end();
-    break;
-  }
-  break;
-}
-:(before "End Primitive Recipe Implementations")
-case SAVE: {
-  if (Current_scenario) break;  // do nothing in tests
-  string filename;
-  if (is_literal_string(current_instruction().ingredients.at(0))) {
-    filename = current_instruction().ingredients.at(0).name;
-  }
-  else if (is_mu_string(current_instruction().ingredients.at(0))) {
-    filename = read_mu_string(ingredients.at(0).at(0));
-  }
-  ofstream fout(("lesson/"+filename).c_str());
-  string contents = read_mu_string(ingredients.at(1).at(0));
-  fout << contents;
-  fout.close();
-  if (!exists("lesson/.git")) break;
-  // bug in git: git diff -q messes up --exit-code
-  // 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();
-  break;
-}
-
-:(code)
-bool exists(const string& filename) {
-  struct stat dummy;
-  return 0 == stat(filename.c_str(), &dummy);
-}
-
- - - diff --git a/html/100trace_browser.cc.html b/html/100trace_browser.cc.html new file mode 100644 index 00000000..a2aee17e --- /dev/null +++ b/html/100trace_browser.cc.html @@ -0,0 +1,285 @@ + + + + +Mu - 100trace_browser.cc + + + + + + + + + + +
+//: A debugging helper that lets you zoom in/out on a trace.
+
+//: browse the trace we just created
+:(before "End Primitive Recipe Declarations")
+_BROWSE_TRACE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$browse-trace", _BROWSE_TRACE);
+:(before "End Primitive Recipe Checks")
+case _BROWSE_TRACE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _BROWSE_TRACE: {
+  start_trace_browser();
+  break;
+}
+
+//: browse a trace loaded from a file
+:(after "Commandline Parsing")
+if (argc == 3 && is_equal(argv[1], "browse-trace")) {
+  load_trace(argv[2]);
+  start_trace_browser();
+  return 0;
+}
+
+:(before "End Globals")
+set<int> Visible;
+int Top_of_screen = 0;
+int Last_printed_row = 0;
+map<int, int> Trace_index;  // screen row -> trace index
+
+:(code)
+void start_trace_browser() {
+  if (!Trace_stream) return;
+  cerr << "computing min depth to display\n";
+  int min_depth = 9999;
+  for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
+    trace_line& curr_line = Trace_stream->past_lines.at(i);
+    if (curr_line.depth < min_depth) min_depth = curr_line.depth;
+  }
+  cerr << "min depth is " << min_depth << '\n';
+  cerr << "computing lines to display\n";
+  for (int i = 0; i < SIZE(Trace_stream->past_lines); ++i) {
+    if (Trace_stream->past_lines.at(i).depth == min_depth)
+      Visible.insert(i);
+  }
+  tb_init();
+  Display_row = Display_column = 0;
+  tb_event event;
+  Top_of_screen = 0;
+  refresh_screen_rows();
+  while (true) {
+    render();
+    do {
+      tb_poll_event(&event);
+    } while (event.type != TB_EVENT_KEY);
+    int key = event.key ? event.key : event.ch;
+    if (key == 'q' || key == 'Q') break;
+    if (key == 'j' || key == TB_KEY_ARROW_DOWN) {
+      // move cursor one line down
+      if (Display_row < Last_printed_row) ++Display_row;
+    }
+    if (key == 'k' || key == TB_KEY_ARROW_UP) {
+      // move cursor one line up
+      if (Display_row > 0) --Display_row;
+    }
+    if (key == 'H') {
+      // move cursor to top of screen
+      Display_row = 0;
+    }
+    if (key == 'M') {
+      // move cursor to center of screen
+      Display_row = tb_height()/2;
+    }
+    if (key == 'L') {
+      // move cursor to bottom of screen
+      Display_row = tb_height()-1;
+    }
+    if (key == 'J' || key == TB_KEY_PGDN) {
+      // page-down
+      if (Trace_index.find(tb_height()-1) != Trace_index.end()) {
+        Top_of_screen = get(Trace_index, tb_height()-1) + 1;
+        refresh_screen_rows();
+      }
+    }
+    if (key == 'K' || key == TB_KEY_PGUP) {
+      // page-up is more convoluted
+      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
+        --Top_of_screen;
+        if (Top_of_screen <= 0) break;
+        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
+          --Top_of_screen;
+      }
+      if (Top_of_screen >= 0)
+        refresh_screen_rows();
+    }
+    if (key == 'G') {
+      // go to bottom of screen; largely like page-up, interestingly
+      Top_of_screen = SIZE(Trace_stream->past_lines)-1;
+      for (int screen_row = tb_height(); screen_row > 0 && Top_of_screen > 0; --screen_row) {
+        --Top_of_screen;
+        if (Top_of_screen <= 0) break;
+        while (Top_of_screen > 0 && !contains_key(Visible, Top_of_screen))
+          --Top_of_screen;
+      }
+      refresh_screen_rows();
+      // move cursor to bottom
+      Display_row = Last_printed_row;
+      refresh_screen_rows();
+    }
+    if (key == TB_KEY_CARRIAGE_RETURN) {
+      // expand lines under current by one level
+      assert(contains_key(Trace_index, Display_row));
+      int start_index = get(Trace_index, Display_row);
+      int index = 0;
+      // simultaneously compute end_index and min_depth
+      int min_depth = 9999;
+      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
+        if (contains_key(Visible, index)) break;
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        assert(curr_line.depth > Trace_stream->past_lines.at(start_index).depth);
+        if (curr_line.depth < min_depth) min_depth = curr_line.depth;
+      }
+      int end_index = index;
+      // mark as visible all intervening indices at min_depth
+      for (index = start_index; index < end_index; ++index) {
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        if (curr_line.depth == min_depth) {
+          Visible.insert(index);
+        }
+      }
+      refresh_screen_rows();
+    }
+    if (key == TB_KEY_BACKSPACE || key == TB_KEY_BACKSPACE2) {
+      // collapse all lines under current
+      assert(contains_key(Trace_index, Display_row));
+      int start_index = get(Trace_index, Display_row);
+      int index = 0;
+      // end_index is the next line at a depth same as or lower than start_index
+      int initial_depth = Trace_stream->past_lines.at(start_index).depth;
+      for (index = start_index+1; index < SIZE(Trace_stream->past_lines); ++index) {
+        if (!contains_key(Visible, index)) continue;
+        trace_line& curr_line = Trace_stream->past_lines.at(index);
+        if (curr_line.depth <= initial_depth) break;
+      }
+      int end_index = index;
+      // mark as visible all intervening indices at min_depth
+      for (index = start_index+1; index < end_index; ++index) {
+        Visible.erase(index);
+      }
+      refresh_screen_rows();
+    }
+  }
+  tb_shutdown();
+}
+
+// update Trace_indices for each screen_row on the basis of Top_of_screen and Visible
+void refresh_screen_rows() {
+  int screen_row = 0, index = 0;
+  Trace_index.clear();
+  for (screen_row = 0, index = Top_of_screen; screen_row < tb_height() && index < SIZE(Trace_stream->past_lines); ++screen_row, ++index) {
+    // skip lines without depth for now
+    while (!contains_key(Visible, index)) {
+      ++index;
+      if (index >= SIZE(Trace_stream->past_lines)) goto done;
+    }
+    assert(index < SIZE(Trace_stream->past_lines));
+    put(Trace_index, screen_row, index);
+  }
+done:;
+}
+
+void render() {
+  int screen_row = 0;
+  for (screen_row = 0; screen_row < tb_height(); ++screen_row) {
+    if (!contains_key(Trace_index, screen_row)) break;
+    trace_line& curr_line = Trace_stream->past_lines.at(get(Trace_index, screen_row));
+    ostringstream out;
+    out << std::setw(4) << curr_line.depth << ' ' << curr_line.label << ": " << curr_line.contents;
+    if (screen_row < tb_height()-1) {
+      int delta = lines_hidden(screen_row);
+      // home-brew escape sequence for red
+      if (delta > 999) out << "{";
+      out << " (" << delta << ")";
+      if (delta > 999) out << "}";
+    }
+    render_line(screen_row, out.str());
+  }
+  // clear rest of screen
+  Last_printed_row = screen_row-1;
+  for (; screen_row < tb_height(); ++screen_row) {
+    render_line(screen_row, "~");
+  }
+  // move cursor back to display row at the end
+  tb_set_cursor(0, Display_row);
+  tb_present();
+}
+
+int lines_hidden(int screen_row) {
+  assert(contains_key(Trace_index, screen_row));
+  if (!contains_key(Trace_index, screen_row+1))
+    return SIZE(Trace_stream->past_lines) - get(Trace_index, screen_row);
+  else
+    return get(Trace_index, screen_row+1) - get(Trace_index, screen_row);
+}
+
+void render_line(int screen_row, const string& s) {
+  int col = 0;
+  int color = TB_WHITE;
+  for (col = 0; col < tb_width() && col < SIZE(s); ++col) {
+    char c = s.at(col);  // todo: unicode
+    if (c == '\n') c = ';';  // replace newlines with semi-colons
+    // escapes. hack: can't start a line with them.
+    if (c == '{') { color = /*red*/1; c = ' '; }
+    if (c == '}') { color = TB_WHITE; c = ' '; }
+    tb_change_cell(col, screen_row, c, color, TB_BLACK);
+  }
+  for (; col < tb_width(); ++col) {
+    tb_change_cell(col, screen_row, ' ', TB_WHITE, TB_BLACK);
+  }
+}
+
+void load_trace(const char* filename) {
+  ifstream tin(filename);
+  if (!tin) {
+    cerr << "no such file: " << filename << '\n';
+    exit(1);
+  }
+  Trace_stream = new trace_stream;
+  while (has_data(tin)) {
+    tin >> std::noskipws;
+      skip_whitespace_but_not_newline(tin);
+      if (!isdigit(tin.peek())) {
+        string dummy;
+        getline(tin, dummy);
+        continue;
+      }
+    tin >> std::skipws;
+    int depth;
+    tin >> depth;
+    string label;
+    tin >> label;
+    if (*--label.end() == ':') label.erase(--label.end());
+    string line;
+    getline(tin, line);
+    Trace_stream->past_lines.push_back(trace_line(depth, label, line));
+  }
+  cerr << "lines read: " << Trace_stream->past_lines.size() << '\n';
+}
+
+ + + diff --git a/html/101run_sandboxed.cc.html b/html/101run_sandboxed.cc.html new file mode 100644 index 00000000..a0b9b043 --- /dev/null +++ b/html/101run_sandboxed.cc.html @@ -0,0 +1,556 @@ + + + + +Mu - 101run_sandboxed.cc + + + + + + + + + + +
+//: Helper for various programming environments: run arbitrary mu code and
+//: return some result in string form.
+
+:(scenario run_interactive_code)
+def main [
+  1:number/raw <- copy 0
+  2:address:array:character <- new [1:number/raw <- copy 34]
+  run-sandboxed 2:address:array:character
+  3:number/raw <- copy 1:number/raw
+]
++mem: storing 34 in location 3
+
+:(scenario run_interactive_empty)
+def main [
+  1:address:array:character <- copy 0/unsafe
+  2:address:array:character <- run-sandboxed 1:address:array:character
+]
+# result is null
++mem: storing 0 in location 2
+
+//: As the name suggests, 'run-sandboxed' will prevent certain operations that
+//: regular Mu code can perform.
+:(before "End Globals")
+bool Sandbox_mode = false;
+//: for starters, users can't override 'main' when the environment is running
+:(before "End Load Recipe Name")
+if (Sandbox_mode && result.name == "main") {
+  slurp_balanced_bracket(in);
+  return -1;
+}
+
+//: run code in 'interactive mode', i.e. with errors off and return:
+//:   stringified output in case we want to print it to screen
+//:   any errors encountered
+//:   simulated screen any prints went to
+//:   any 'app' layer traces generated
+:(before "End Primitive Recipe Declarations")
+RUN_SANDBOXED,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "run-sandboxed", RUN_SANDBOXED);
+:(before "End Primitive Recipe Checks")
+case RUN_SANDBOXED: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'run-sandboxed' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'run-sandboxed' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RUN_SANDBOXED: {
+  bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0));
+  if (!new_code_pushed_to_stack) {
+    products.resize(5);
+    products.at(0).push_back(0);
+    products.at(1).push_back(trace_error_contents());
+    products.at(2).push_back(0);
+    products.at(3).push_back(trace_app_contents());
+    products.at(4).push_back(1);  // completed
+    run_code_end();
+    break;  // done with this instruction
+  }
+  else {
+    continue;  // not done with caller; don't increment current_step_index()
+  }
+}
+
+:(before "End Globals")
+bool Track_most_recent_products = false;
+:(before "End Tracing")
+trace_stream* Save_trace_stream = NULL;
+string Save_trace_file;
+:(before "End Setup")
+Track_most_recent_products = false;
+:(code)
+// reads a string, tries to call it as code (treating it as a test), saving
+// all errors.
+// returns true if successfully called (no errors found during load and transform)
+bool run_interactive(int address) {
+  assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0);
+  // try to sandbox the run as best you can
+  // todo: test this
+  if (!Current_scenario) {
+    for (int i = 1; i < Reserved_for_tests; ++i)
+      Memory.erase(i);
+  }
+  string command = trim(strip_comments(read_mu_string(address)));
+  Name[get(Recipe_ordinal, "interactive")].clear();
+  run_code_begin(/*should_stash_snapshots*/true);
+  if (command.empty()) return false;
+  // don't kill the current routine on parse errors
+  routine* save_current_routine = Current_routine;
+  Current_routine = NULL;
+  // call run(string) but without the scheduling
+  load(string("recipe! interactive [\n") +
+          "new-default-space\n" +  // disable automatic abandon so tests can see changes
+          "screen:address:screen <- next-ingredient\n" +
+          "$start-tracking-products\n" +
+          command + "\n" +
+          "$stop-tracking-products\n" +
+          "return screen\n" +
+       "]\n");
+  transform_all();
+  Current_routine = save_current_routine;
+  if (trace_count("error") > 0) return false;
+  // now call 'sandbox' which will run 'interactive' in a separate routine,
+  // and wait for it
+  if (Save_trace_stream) {
+    ++Save_trace_stream->callstack_depth;
+    trace(9999, "trace") << "run-sandboxed: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end();
+    assert(Save_trace_stream->callstack_depth < 9000);  // 9998-101 plus cushion
+  }
+  Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox")));
+  return true;
+}
+
+//: Carefully update all state to exactly how it was -- including snapshots.
+
+:(before "End Globals")
+map<string, recipe_ordinal> Recipe_ordinal_snapshot_stash;
+map<recipe_ordinal, recipe> Recipe_snapshot_stash;
+map<string, type_ordinal> Type_ordinal_snapshot_stash;
+map<type_ordinal, type_info> Type_snapshot_stash;
+map<recipe_ordinal, map<string, int> > Name_snapshot_stash;
+map<string, vector<recipe_ordinal> > Recipe_variants_snapshot_stash;
+
+:(code)
+void run_code_begin(bool should_stash_snapshots) {
+  // stuff to undo later, in run_code_end()
+  Hide_errors = true;
+  Disable_redefine_checks = true;
+  if (should_stash_snapshots)
+    stash_snapshots();
+  Save_trace_stream = Trace_stream;
+  Trace_stream = new trace_stream;
+  Trace_stream->collect_depth = App_depth;
+}
+
+void run_code_end() {
+  Hide_errors = false;
+  Disable_redefine_checks = false;
+  delete Trace_stream;
+  Trace_stream = Save_trace_stream;
+  Save_trace_stream = NULL;
+  Save_trace_file.clear();
+  Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
+  if (!Recipe_snapshot_stash.empty())
+    unstash_snapshots();
+}
+
+// keep sync'd with save_snapshots and restore_snapshots
+void stash_snapshots() {
+  assert(Recipe_ordinal_snapshot_stash.empty());
+  Recipe_ordinal_snapshot_stash = Recipe_ordinal_snapshot;
+  assert(Recipe_snapshot_stash.empty());
+  Recipe_snapshot_stash = Recipe_snapshot;
+  assert(Type_ordinal_snapshot_stash.empty());
+  Type_ordinal_snapshot_stash = Type_ordinal_snapshot;
+  assert(Type_snapshot_stash.empty());
+  Type_snapshot_stash = Type_snapshot;
+  assert(Name_snapshot_stash.empty());
+  Name_snapshot_stash = Name_snapshot;
+  assert(Recipe_variants_snapshot_stash.empty());
+  Recipe_variants_snapshot_stash = Recipe_variants_snapshot;
+  save_snapshots();
+}
+void unstash_snapshots() {
+  restore_snapshots();
+  Recipe_ordinal_snapshot = Recipe_ordinal_snapshot_stash;  Recipe_ordinal_snapshot_stash.clear();
+  Recipe_snapshot = Recipe_snapshot_stash;  Recipe_snapshot_stash.clear();
+  Type_ordinal_snapshot = Type_ordinal_snapshot_stash;  Type_ordinal_snapshot_stash.clear();
+  Type_snapshot = Type_snapshot_stash;  Type_snapshot_stash.clear();
+  Name_snapshot = Name_snapshot_stash;  Name_snapshot_stash.clear();
+  Recipe_variants_snapshot = Recipe_variants_snapshot_stash;  Recipe_variants_snapshot_stash.clear();
+}
+
+:(before "End Load Recipes")
+load(string(
+"recipe interactive [\n") +  // just a dummy version to initialize the Recipe_ordinal and so on
+"]\n" +
+"recipe sandbox [\n" +
+  "local-scope\n" +
+  "screen:address:screen <- new-fake-screen 30, 5\n" +
+  "routine-id:number <- start-running interactive, screen\n" +
+  "limit-time routine-id, 100000/instructions\n" +
+  "wait-for-routine routine-id\n" +
+  "instructions-run:number <- number-of-instructions routine-id\n" +
+  "stash instructions-run [instructions run]\n" +
+  "sandbox-state:number <- routine-state routine-id\n" +
+  "completed?:boolean <- equal sandbox-state, 1/completed\n" +
+  "output:address:array:character <- $most-recent-products\n" +
+  "errors:address:array:character <- save-errors\n" +
+  "stashes:address:array:character <- save-app-trace\n" +
+  "$cleanup-run-sandboxed\n" +
+  "return output, errors, screen, stashes, completed?\n" +
+"]\n");
+
+//: adjust errors in the sandbox
+:(after "string maybe(string s)")
+  if (s == "interactive") return "";
+
+:(scenario run_interactive_comments)
+def main [
+  1:address:array:character <- new [# ab
+add 2, 2]
+  2:address:array:character <- run-sandboxed 1:address:array:character
+  3:array:character <- copy *2:address:array:character
+]
++mem: storing 52 in location 4
+
+:(before "End Primitive Recipe Declarations")
+_START_TRACKING_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _START_TRACKING_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _START_TRACKING_PRODUCTS: {
+  Track_most_recent_products = true;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_STOP_TRACKING_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _STOP_TRACKING_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _STOP_TRACKING_PRODUCTS: {
+  Track_most_recent_products = false;
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_MOST_RECENT_PRODUCTS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS);
+:(before "End Primitive Recipe Checks")
+case _MOST_RECENT_PRODUCTS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _MOST_RECENT_PRODUCTS: {
+  products.resize(1);
+  products.at(0).push_back(new_mu_string(Most_recent_products));
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE_ERRORS,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save-errors", SAVE_ERRORS);
+:(before "End Primitive Recipe Checks")
+case SAVE_ERRORS: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE_ERRORS: {
+  products.resize(1);
+  products.at(0).push_back(trace_error_contents());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE_APP_TRACE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE);
+:(before "End Primitive Recipe Checks")
+case SAVE_APP_TRACE: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE_APP_TRACE: {
+  products.resize(1);
+  products.at(0).push_back(trace_app_contents());
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+_CLEANUP_RUN_SANDBOXED,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "$cleanup-run-sandboxed", _CLEANUP_RUN_SANDBOXED);
+:(before "End Primitive Recipe Checks")
+case _CLEANUP_RUN_SANDBOXED: {
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case _CLEANUP_RUN_SANDBOXED: {
+  run_code_end();
+  break;
+}
+
+:(scenario "run_interactive_converts_result_to_text")
+def main [
+  # try to interactively add 2 and 2
+  1:address:array:character <- new [add 2, 2]
+  2:address:array:character <- run-sandboxed 1:address:array:character
+  10:array:character <- copy 2:address:array:character/lookup
+]
+# first letter in the output should be '4' in unicode
++mem: storing 52 in location 11
+
+:(scenario "run_interactive_returns_text")
+def main [
+  # try to interactively add 2 and 2
+  1:address:array:character <- new [
+    x:address:array:character <- new [a]
+    y:address:array:character <- new [b]
+    z:address:array:character <- append x:address:array:character, y:address:array:character
+  ]
+  2:address:array:character <- run-sandboxed 1:address:array:character
+  10:array:character <- copy 2:address:array:character/lookup
+]
+# output contains "ab"
++mem: storing 97 in location 11
++mem: storing 98 in location 12
+
+:(scenario "run_interactive_returns_errors")
+def main [
+  # 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-sandboxed 1:address:array:character
+  10:array:character <- copy 3:address:array:character/lookup
+]
+# 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
++mem: storing 110 in location 14
+# ...
+
+:(scenario run_interactive_with_comment)
+def main [
+  # 2 instructions, with a comment after the first
+  1:address:array:number <- new [a:number <- copy 0  # abc
+b:number <- copy 0
+]
+  2:address:array:character, 3:address:array:character <- run-sandboxed 1:address:array:character
+]
+# no errors
++mem: storing 0 in location 3
+
+:(before "End Globals")
+string Most_recent_products;
+:(before "End Setup")
+Most_recent_products = "";
+:(before "End Running One Instruction")
+if (Track_most_recent_products) {
+  track_most_recent_products(current_instruction(), products);
+}
+:(code)
+void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) {
+  ostringstream out;
+  for (int i = 0; i < SIZE(products); ++i) {
+    // string
+    if (i < SIZE(instruction.products)) {
+      if (is_mu_string(instruction.products.at(i))) {
+        if (!scalar(products.at(i))) continue;  // error handled elsewhere
+        out << read_mu_string(products.at(i).at(0)) << '\n';
+        continue;
+      }
+      // End Record Product Special-cases
+    }
+    for (int j = 0; j < SIZE(products.at(i)); ++j)
+      out << no_scientific(products.at(i).at(j)) << ' ';
+    out << '\n';
+  }
+  Most_recent_products = out.str();
+}
+
+:(code)
+string strip_comments(string in) {
+  ostringstream result;
+  for (int i = 0; i < SIZE(in); ++i) {
+    if (in.at(i) != '#') {
+      result << in.at(i);
+    }
+    else {
+      while (i+1 < SIZE(in) && in.at(i+1) != '\n')
+        ++i;
+    }
+  }
+  return result.str();
+}
+
+int stringified_value_of_location(int address) {
+  // convert to string
+  ostringstream out;
+  out << no_scientific(get_or_insert(Memory, address));
+  return new_mu_string(out.str());
+}
+
+int trace_error_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 != "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);
+}
+
+int trace_app_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->depth != App_depth) 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);
+}
+
+void truncate(string& x) {
+  if (SIZE(x) > 1024) {
+    x.erase(1024);
+    *x.rbegin() = '\n';
+    *++x.rbegin() = '.';
+    *++++x.rbegin() = '.';
+  }
+}
+
+//: simpler version of run-sandboxed: doesn't do any running, just loads
+//: recipes and reports errors.
+
+:(before "End Primitive Recipe Declarations")
+RELOAD,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "reload", RELOAD);
+:(before "End Primitive Recipe Checks")
+case RELOAD: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RELOAD: {
+  // conundrum: need to support repeated reloads of the same code, but not
+  // wipe out state for the current test
+  // hacky solution: subset of restore_snapshots() without restoring recipes {
+  // can't yet define containers in a test that runs 'reload'
+  Type_ordinal = Type_ordinal_snapshot;
+  Type = Type_snapshot;
+  // can't yet create new specializations of shape-shifting recipes in a test
+  // that runs 'reload'
+  Recipe_variants = Recipe_variants_snapshot;
+  Name = Name_snapshot;
+  // }
+  string code = read_mu_string(ingredients.at(0).at(0));
+  run_code_begin(/*should_stash_snapshots*/false);
+  routine* save_current_routine = Current_routine;
+  Current_routine = NULL;
+  Sandbox_mode = true;
+  vector<recipe_ordinal> recipes_reloaded = load(code);
+  transform_all();
+  Trace_stream->newline();  // flush trace
+  Sandbox_mode = false;
+  Current_routine = save_current_routine;
+  products.resize(1);
+  products.at(0).push_back(trace_error_contents());
+  run_code_end();  // wait until we're done with the trace contents
+  break;
+}
+
+:(scenario reload_continues_past_error)
+def main [
+  local-scope
+  x:address:array:character <- new [recipe foo [
+  get 1234:number, foo:offset
+]]
+  reload x
+  1:number/raw <- copy 34
+]
++mem: storing 34 in location 1
+
+:(scenario reload_can_repeatedly_load_container_definitions)
+# define a container and try to create it (merge requires knowing container size)
+def main [
+  local-scope
+  x:address:array:character <- new [
+    container foo [
+      x:number
+      y:number
+    ]
+    recipe bar [
+      local-scope
+      x:foo <- merge 34, 35
+    ]
+  ]
+  # save warning addresses in locations of type 'number' to avoid spurious changes to them due to 'abandon'
+  1:number/raw <- reload x
+  2:number/raw <- reload x
+]
+# no errors on either load
++mem: storing 0 in location 1
++mem: storing 0 in location 2
+
+ + + diff --git a/html/102persist.cc.html b/html/102persist.cc.html new file mode 100644 index 00000000..d16622e4 --- /dev/null +++ b/html/102persist.cc.html @@ -0,0 +1,153 @@ + + + + +Mu - 102persist.cc + + + + + + + + + + +
+//: Dead simple persistence.
+//:   'restore' - reads string from a file
+//:   'save' - writes string to a file
+
+:(before "End Primitive Recipe Declarations")
+RESTORE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "restore", RESTORE);
+:(before "End Primitive Recipe Checks")
+case RESTORE: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'restore' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (is_literal_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else if (is_mu_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'restore' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case RESTORE: {
+  string filename;
+  if (is_literal_string(current_instruction().ingredients.at(0))) {
+    filename = current_instruction().ingredients.at(0).name;
+  }
+  else if (is_mu_string(current_instruction().ingredients.at(0))) {
+    filename = read_mu_string(ingredients.at(0).at(0));
+  }
+  if (Current_scenario) {
+    // do nothing in tests
+    products.resize(1);
+    products.at(0).push_back(0);
+    break;
+  }
+  string contents = slurp("lesson/"+filename);
+  products.resize(1);
+  if (contents.empty())
+    products.at(0).push_back(0);
+  else
+    products.at(0).push_back(new_mu_string(contents));
+  break;
+}
+
+:(code)
+// http://cpp.indi.frih.net/blog/2014/09/how-to-read-an-entire-file-into-memory-in-cpp
+string slurp(const string& filename) {
+  ifstream fin(filename.c_str());
+  fin.peek();
+  if (!fin) return "";  // don't bother checking errno
+  ostringstream result;
+  result << fin.rdbuf();
+  fin.close();
+  return result.str();
+}
+
+:(before "End Primitive Recipe Declarations")
+SAVE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "save", SAVE);
+:(before "End Primitive Recipe Checks")
+case SAVE: {
+  if (SIZE(inst.ingredients) != 2) {
+    raise << maybe(get(Recipe, r).name) << "'save' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  if (is_literal_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else if (is_mu_string(inst.ingredients.at(0))) {
+    ;
+  }
+  else {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'save' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  if (!is_mu_string(inst.ingredients.at(1))) {
+    raise << maybe(get(Recipe, r).name) << "second ingredient of 'save' should be an address:array:character, but got '" << to_string(inst.ingredients.at(1)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case SAVE: {
+  if (Current_scenario) break;  // do nothing in tests
+  string filename;
+  if (is_literal_string(current_instruction().ingredients.at(0))) {
+    filename = current_instruction().ingredients.at(0).name;
+  }
+  else if (is_mu_string(current_instruction().ingredients.at(0))) {
+    filename = read_mu_string(ingredients.at(0).at(0));
+  }
+  ofstream fout(("lesson/"+filename).c_str());
+  string contents = read_mu_string(ingredients.at(1).at(0));
+  fout << contents;
+  fout.close();
+  if (!exists("lesson/.git")) break;
+  // bug in git: git diff -q messes up --exit-code
+  // 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();
+  break;
+}
+
+:(code)
+bool exists(const string& filename) {
+  struct stat dummy;
+  return 0 == stat(filename.c_str(), &dummy);
+}
+
+ + + diff --git a/html/edit/011-errors.mu.html b/html/edit/011-errors.mu.html index b9db2f50..b9f46f1b 100644 --- a/html/edit/011-errors.mu.html +++ b/html/edit/011-errors.mu.html @@ -412,10 +412,9 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color assume-screen 100/width, 15/height # recipe is incomplete (unbalanced '[') 1:address:array:character <- new [ -recipe foo « +recipe foo \\[ x <- copy 0 ] - replace 1:address:array:character, 171/«, 91 # '[' 2:address:array:character <- new [foo] 3:address:programming-environment-data <- new-programming-environment screen:address:screen, 1:address:array:character, 2:address:array:character assume-console [ @@ -427,10 +426,10 @@ body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color screen-should-contain [ . errors found run (F4) . . ┊foo . - .recipe foo \\\[ ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. + .recipe foo \\[ ┊━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━. . x <- copy 0 ┊ . . ┊ . - .9: unbalanced '\\\[' for recipe ┊ . + .9: unbalanced '\\[' for recipe ┊ . .┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┊ . . ┊ . ] diff --git a/index.html b/index.html index 5bc909aa..fb8f2c47 100644 --- a/index.html +++ b/index.html @@ -234,7 +234,7 @@ writing tests for keyboard and mouse using the fakes.
029tools.cc: various primitive operations to help with testing and debugging. -
090trace_browser.cc: a +
100trace_browser.cc: a zoomable UI for inspecting traces generated by Mu programs. Allows both scanning a high-level view and drilling down into selective details. -- cgit 1.4.1-2-gfad0