1 //: Clean syntax to manipulate and check the file system in scenarios.
  2 //: Instruction 'assume-resources' implicitly creates a variable called
  3 //: 'resources' that is accessible to later instructions in the scenario.
  4 
  5 :(scenarios run_mu_scenario)
  6 :(scenario simple_filesystem)
  7 scenario simple-filesystem [
  8   local-scope
  9   assume-resources [
 10     # file 'a' containing two lines of data
 11     [a] <- [
 12       |a bc|
 13       |de f|
 14     ]
 15     # directory 'b' containing two files, 'c' and 'd'
 16     [b/c] <- []
 17     [b/d] <- [
 18       |xyz|
 19     ]
 20   ]
 21   data:&:@:resource <- get *resources, data:offset
 22   file1:resource <- index *data, 0
 23   file1-name:text <- get file1, name:offset
 24   10:@:char/raw <- copy *file1-name
 25   file1-contents:text <- get file1, contents:offset
 26   100:@:char/raw <- copy *file1-contents
 27   file2:resource <- index *data, 1
 28   file2-name:text <- get file2, name:offset
 29   30:@:char/raw <- copy *file2-name
 30   file2-contents:text <- get file2, contents:offset
 31   40:@:char/raw <- copy *file2-contents
 32   file3:resource <- index *data, 2
 33   file3-name:text <- get file3, name:offset
 34   50:@:char/raw <- copy *file3-name
 35   file3-contents:text <- get file3, contents:offset
 36   60:@:char/raw <- copy *file3-contents
 37   memory-should-contain [
 38     10:array:character <- [a]
 39     100:array:character <- [a bc
 40 de f
 41 ]
 42     30:array:character <- [b/c]
 43     40:array:character <- []
 44     50:array:character <- [b/d]
 45     60:array:character <- [xyz
 46 ]
 47   ]
 48 ]
 49 
 50 :(scenario escaping_file_contents)
 51 scenario escaping-file-contents [
 52   local-scope
 53   assume-resources [
 54     # file 'a' containing a '|'
 55     # need to escape '\' once for each block
 56     [a] <- [
 57       |x\\\\|yz|
 58     ]
 59   ]
 60   data:&:@:resource <- get *resources, data:offset
 61   file1:resource <- index *data, 0
 62   file1-name:text <- get file1, name:offset
 63   10:@:char/raw <- copy *file1-name
 64   file1-contents:text <- get file1, contents:offset
 65   20:@:char/raw <- copy *file1-contents
 66   memory-should-contain [
 67     10:array:character <- [a]
 68     20:array:character <- [x|yz
 69 ]
 70   ]
 71 ]
 72 
 73 :(before "End Globals")
 74 extern const int RESOURCES = next_predefined_global_for_scenarios(/*size_of(address:resources)*/2);
 75 //: give 'resources' a fixed location in scenarios
 76 :(before "End Special Scenario Variable Names(r)")
 77 Name[r]["resources"] = RESOURCES;
 78 //: make 'resources' always a raw location in scenarios
 79 :(before "End is_special_name Special-cases")
 80 if (s == "resources") return true;
 81 :(before "End Initialize Type Of Special Name In Scenario(r)")
 82 if (r.namor: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
# print msg to screen if a != b, otherwise print "."
fn check-ints-equal _a: int, b: int, msg: (addr array byte) {
  var a/eax: int <- copy _a
  compare a, b
  {
    break-if-!=
    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
    return
  }
  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
  count-test-failure
}

fn test-check-ints-equal {
  check-ints-equal 0, 0, "abc"
}

fn check _a: boolean, msg: (addr array byte) {
  var a/eax: int <- copy _a
  compare a, 0/false
  {
    break-if-=
    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
    return
  }
  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
  count-test-failure
}

fn check-not _a: boolean, msg: (addr array byte) {
  var a/eax: int <- copy _a
  compare a, 0/false
  {
    break-if-!=
    draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, ".", 3/fg/cyan, 0/bg
    return
  }
  draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0/screen, msg, 3/fg/cyan, 0/bg
  count-test-failure
}
n class="Constant">": assume-resources: file contents '" << contents << "' for filename '" << filename << "' must end with a ']'\n" << end(); 163 break; 164 } 165 contents.erase(0, 1); 166 contents.erase(SIZE(contents)-1); 167 put(out, filename, munge_resources_contents(contents, filename, caller)); 168 } 169 } 170 171 string munge_resources_contents(const string& data, const string& filename, const string& caller) { 172 if (data.empty()) return ""; 173 istringstream in(data); 174 in >> std::noskipws; 175 skip_whitespace_and_comments(in); 176 ostringstream out; 177 while (true) { 178 if (!has_data(in)) break; 179 skip_whitespace(in); 180 if (!has_data(in)) break; 181 if (in.peek() != '|') { 182 raise << caller << ": assume-resources: file contents for filename '" << filename << "' must be delimited in '|'s\n" << end(); 183 break; 184 } 185 in.get(); // skip leading '|' 186 string line; 187 getline(in, line); 188 for (int i = 0; i < SIZE(line); ++i) { 189 if (line.at(i) == '|') break; 190 if (line.at(i) == '\\') { 191 ++i; // skip 192 if (i == SIZE(line)) { 193 raise << caller << ": assume-resources: file contents can't end a line with '\\'\n" << end(); 194 break; 195 } 196 } 197 out << line.at(i); 198 } 199 // todo: some way to represent a file without a final newline 200 out << '\n'; 201 } 202 return out.str(); 203 } 204 205 void construct_resources_object(const map<string, string>& contents) { 206 int resources_data_address = allocate(SIZE(contents) * /*size of resource*/4 + /*array length*/1); 207 int curr = resources_data_address + /*skip alloc id*/1 + /*skip array length*/1; 208 for (map<string, string>::const_iterator p = contents.begin(); p != contents.end(); ++p) { 209 ++curr; // skip alloc id of resource.name 210 put(Memory, curr, new_mu_text(p->first)); 211 trace("mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end(); 212 ++curr; 213 ++curr; // skip alloc id of resource.contents 214 put(Memory, curr, new_mu_text(p->second)); 215 trace("mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end(); 216 ++curr; 217 } 218 curr = resources_data_address + /*skip alloc id of resources.data*/1; 219 put(Memory, curr, SIZE(contents)); // array length 220 trace("mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end(); 221 // wrap the resources data in a 'resources' object 222 int resources_address = allocate(size_of_resources()); 223 curr = resources_address+/*alloc id*/1+/*offset of 'data' element*/1+/*skip alloc id of 'data' element*/1; 224 put(Memory, curr, resources_data_address); 225 trace("mem") << "storing resources data address " << resources_data_address << " in location " << curr << end(); 226 // save in product 227 put(Memory, RESOURCES+/*skip alloc id*/1, resources_address); 228 trace("mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end(); 229 } 230 231 int size_of_resources() { 232 // memoize result if already computed 233 static int result = 0; 234 if (result) return result; 235 assert(get(Type_ordinal, "resources")); 236 type_tree* type = new type_tree("resources"); 237 result = size_of(type); 238 delete type; 239 return result; 240 }