https://github.com/akkartik/mu/blob/master/089scenario_filesystem.cc

      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.name == "resources") r.type = new_type_tree("address:resources");
     83
     84 :(before "End initialize_transform_rewrite_literal_string_to_text()")
     85 recipes_taking_literal_strings.insert("assume-resources");
     86
     87 //: screen-should-contain is a regular instruction
     88 :(before "End Primitive Recipe Declarations")
     89 ASSUME_RESOURCES,
     90 :(before "End Primitive Recipe Numbers")
     91 put(Recipe_ordinal, "assume-resources", ASSUME_RESOURCES);
     92 :(before "End Primitive Recipe Checks")
     93 case ASSUME_RESOURCES: {
     94   break;
     95 }
     96 :(before "End Primitive Recipe Implementations")
     97 case ASSUME_RESOURCES: {
     98   assert(scalar(ingredients.at(0)));
     99   assume_resources(current_instruction().ingredients.at(0).name, current_recipe_name());
    100   break;
    101 }
    102
    103 :(code)
    104 void assume_resources(const string& data, const string& caller) {
    105   map<string, string> contents;
    106   parse_resources(data, contents, caller);
    107   construct_resources_object(contents);
    108 }
    109
    110 void parse_resources(const string& data, map<string, string>& out, const string& caller) {
    111   istringstream in(data);
    112   in >> std::noskipws;
    113   while (true) {
    114     if (!has_data(in)) break;
    115     skip_whitespace_and_comments(in);
    116     if (!has_data(in)) break;
    117     string filename = next_word(in);
    118     if (filename.empty()) {
    119       assert(!has_data(in));
    120       raise << "incomplete 'resources' block at end of file (0)\n" << end();
    121       return;
    122     }
    123     if (*filename.begin() != '[') {
    124       raise << caller << ": assume-resources: filename '" << filename << "' must begin with a '['\n" << end();
    125       break;
    126     }
    127     if (*filename.rbegin() != ']') {
    128       raise << caller << ": assume-resources: filename '" << filename << "' must end with a ']'\n" << end();
    129       break;
    130     }
    131     filename.erase(0, 1);
    132     filename.erase(SIZE(filename)-1);
    133     if (!has_data(in)) {
    134       raise << caller << ": assume-resources: no data for filename '" << filename << "'\n" << end();
    135       break;
    136     }
    137     string arrow = next_word(in);
    138     if (arrow.empty()) {
    139       assert(!has_data(in));
    140       raise << "incomplete 'resources' block at end of file (1)\n" << end();
    141       return;
    142     }
    143     if (arrow != "<-") {
    144       raise << caller << ": assume-resources: expected '<-' after filename '" << filename << "' but got '" << arrow << "'\n" << end();
    145       break;
    146     }
    147     if (!has_data(in)) {
    148       raise << caller << ": assume-resources: no data for filename '" << filename << "' after '<-'\n" << end();
    149       break;
    150     }
    151     string contents = next_word(in);
    152     if (contents.empty()) {
    153       assert(!has_data(in));
    154       raise << "incomplete 'resources' block at end of file (2)\n" << end();
    155       return;
    156     }
    157     if (*contents.begin() != '[') {
    158       raise << caller << ": assume-resources: file contents '" << contents << "' for filename '" << filename << "' must begin with a '['\n" << end();
    159       break;
    160     }
    161     if (*contents.rbegin() != ']') {
    162       raise << caller << ": 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 }