summary refs log tree commit diff stats
path: root/compiler.nimble
Commit message (Expand)AuthorAgeFilesLines
* Add nimsuggest to installDirs (#10035)Yuriy Glukhov2018-12-181-1/+1
* attempt to make the build green againAraq2017-01-071-1/+0
* use new nimble file format to make Nim's version DRYAraq2017-01-071-5/+4
* Fix compiler.nimble namespace violations.Dominik Picheta2017-01-031-1/+1
* updated development versionAraq2016-10-311-1/+1
* version bumpAndreas Rumpf2016-10-211-1/+1
* develop version is 0.15.1Araq2016-10-081-1/+1
* version changed to 0.15.0Araq2016-10-011-2/+2
* version bump for developmentAndreas Rumpf2016-06-111-1/+1
* version bump to 0.14.2Araq2016-06-081-1/+1
* update the version number in more placesAraq2016-06-071-2/+2
* updated version to 0.13.0Araq2016-01-181-1/+1
* updated compiler.nimble fileAraq2015-10-281-2/+2
* Update version in compiler.nimbleDominik Picheta2015-06-051-2/+2
* Add compiler.nimble file.Dominik Picheta2015-01-161-0/+11
id='n167' href='#n167'>167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
//: Clean syntax to manipulate and check the file system in scenarios.
//: Instruction 'assume-resources' implicitly creates a variable called
//: 'resources' that is accessible to later instructions in the scenario.

:(scenarios run_mu_scenario)
:(scenario simple_filesystem)
scenario simple-filesystem [
  local-scope
  assume-resources [
    # 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:&:@:resource <- get *resources, data:offset
  file1:resource <- index *data, 0
  file1-name:text <- get file1, name:offset
  10:@:char/raw <- copy *file1-name
  file1-contents:text <- get file1, contents:offset
  100:@:char/raw <- copy *file1-contents
  file2:resource <- index *data, 1
  file2-name:text <- get file2, name:offset
  30:@:char/raw <- copy *file2-name
  file2-contents:text <- get file2, contents:offset
  40:@:char/raw <- copy *file2-contents
  file3:resource <- index *data, 2
  file3-name:text <- get file3, name:offset
  50:@:char/raw <- copy *file3-name
  file3-contents:text <- get file3, contents:offset
  60:@:char/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 escaping-file-contents [
  local-scope
  assume-resources [
    # file 'a' containing a '|'
    # need to escape '\' once for each block
    [a] <- [
      |x\\\\|yz|
    ]
  ]
  data:&:@:resource <- get *resources, data:offset
  file1:resource <- index *data, 0
  file1-name:text <- get file1, name:offset
  10:@:char/raw <- copy *file1-name
  file1-contents:text <- get file1, contents:offset
  20:@:char/raw <- copy *file1-contents
  memory-should-contain [
    10:array:character <- [a]
    20:array:character <- [x|yz
]
  ]
]

:(before "End Globals")
extern const int RESOURCES = Next_predefined_global_for_scenarios++;
//: give 'resources' a fixed location in scenarios
:(before "End Special Scenario Variable Names(r)")
Name[r]["resources"] = RESOURCES;
//: make 'resources' always a raw location in scenarios
:(before "End is_special_name Cases")
if (s == "resources") return true;
:(before "End Initialize Type Of Special Name In Scenario(r)")
if (r.name == "resources") r.type = new_type_tree("address:resources");

:(before "End initialize_transform_rewrite_literal_string_to_text()")
recipes_taking_literal_strings.insert("assume-resources");

//: screen-should-contain is a regular instruction
:(before "End Primitive Recipe Declarations")
ASSUME_RESOURCES,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "assume-resources", ASSUME_RESOURCES);
:(before "End Primitive Recipe Checks")
case ASSUME_RESOURCES: {
  break;
}
:(before "End Primitive Recipe Implementations")
case ASSUME_RESOURCES: {
  assert(scalar(ingredients.at(0)));
  assume_resources(current_instruction().ingredients.at(0).name, current_recipe_name());
  break;
}

:(code)
void assume_resources(const string& data, const string& caller) {
  map<string, string> contents;
  parse_resources(data, contents, caller);
  construct_resources_object(contents);
}

void parse_resources(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_word(in);
    if (*filename.begin() != '[') {
      raise << caller << ": assume-resources: filename '" << filename << "' must begin with a '['\n" << end();
      break;
    }
    if (*filename.rbegin() != ']') {
      raise << caller << ": assume-resources: 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-resources: no data for filename '" << filename << "'\n" << end();
      break;
    }
    string arrow = next_word(in);
    if (arrow != "<-") {
      raise << caller << ": assume-resources: expected '<-' after filename '" << filename << "' but got '" << arrow << "'\n" << end();
      break;
    }
    if (!has_data(in)) {
      raise << caller << ": assume-resources: no data for filename '" << filename << "' after '<-'\n" << end();
      break;
    }
    string contents = next_word(in);
    if (*contents.begin() != '[') {
      raise << caller << ": assume-resources: file contents '" << contents << "' for filename '" << filename << "' must begin with a '['\n" << end();
      break;
    }
    if (*contents.rbegin() != ']') {
      raise << caller << ": assume-resources: 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_resources_contents(contents, filename, caller));
  }
}

string munge_resources_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-resources: 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-resources: 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_resources_object(const map<string, string>& contents) {
  int resources_data_address = allocate(SIZE(contents)*2 + /*array length*/1);
  int curr = resources_data_address + /*skip refcount and length*/2;
  for (map<string, string>::const_iterator p = contents.begin();  p != contents.end();  ++p) {
    put(Memory, curr, new_mu_text(p->first));
    trace(9999, "mem") << "storing file name " << get(Memory, curr) << " in location " << curr << end();
    put(Memory, get(Memory, curr), 1);
    trace(9999, "mem") << "storing refcount 1 in location " << get(Memory, curr) << end();
    ++curr;
    put(Memory, curr, new_mu_text(p->second));
    trace(9999, "mem") << "storing file contents " << get(Memory, curr) << " in location " << curr << end();
    put(Memory, get(Memory, curr), 1);
    trace(9999, "mem") << "storing refcount 1 in location " << get(Memory, curr) << end();
    ++curr;
  }
  curr = resources_data_address+/*skip refcount*/1;
  put(Memory, curr, SIZE(contents));  // size of array
  trace(9999, "mem") << "storing resources size " << get(Memory, curr) << " in location " << curr << end();
  put(Memory, resources_data_address, 1);  // initialize refcount
  trace(9999, "mem") << "storing refcount 1 in location " << resources_data_address << end();
  // wrap the resources data in a 'resources' object
  int resources_address = allocate(size_of_resources());
  curr = resources_address+/*skip refcount*/1;
  put(Memory, curr, resources_data_address);
  trace(9999, "mem") << "storing resources data address " << resources_data_address << " in location " << curr << end();
  put(Memory, resources_address, 1);  // initialize refcount
  trace(9999, "mem") << "storing refcount 1 in location " << resources_address << end();
  // save in product
  put(Memory, RESOURCES, resources_address);
  trace(9999, "mem") << "storing resources address " << resources_address << " in location " << RESOURCES << end();
}

int size_of_resources() {
  // memoize result if already computed
  static int result = 0;
  if (result) return result;
  assert(get(Type_ordinal, "resources"));
  type_tree* type = new type_tree("resources");
  result = size_of(type)+/*refcount*/1;
  delete type;
  return result;
}

void skip_whitespace(istream& in) {
  while (true) {
    if (!has_data(in)) break;
    if (isspace(in.peek())) in.get();
    else break;
  }
}