about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-08-13 22:57:13 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-08-13 22:57:13 -0700
commitdff1abb2e10ca2dd064a505dfb3d67635f680a9a (patch)
treefaa8b754d0faba8b33491ccd2e07732efd8679b5
parent0e6a3c0d21006a31bf45138ca8e7470d97a0e139 (diff)
downloadmu-dff1abb2e10ca2dd064a505dfb3d67635f680a9a.tar.gz
3182 - primitives for manipulating the file system
I don't know why this took so long to gel. I just needed to copy my
approach to screen management:

1. primitives layer (C++): simple, non-testable, non-safe operations.
2. wrappers layer (Mu): wrap operations with dependency-injected
versions that can take a fake file system.
3. scenario layer (C++): implement assume-filesystem that constructs a
fake file system.
4. scenario test layer (Mu): test out assume-filesystem in a test.

This commit implements step 1.
-rw-r--r--087file.cc169
-rw-r--r--real-file.mu16
2 files changed, 176 insertions, 9 deletions
diff --git a/087file.cc b/087file.cc
index 2448370e..767dc0ab 100644
--- a/087file.cc
+++ b/087file.cc
@@ -1,27 +1,178 @@
-//:: Interacting with the file-system
+//: Interacting with the file system.
+//:   'real-open-file-for-reading' returns a FILE* as a number (ugh)
+//:   'real-read-from-file' accepts a number, interprets it as a FILE* (double ugh) and reads a character from it
+//: Similarly for writing files.
+//:
+//: Clearly we don't care about performance or any of that so far.
+//: todo: reading/writing binary files
 
 :(before "End Primitive Recipe Declarations")
-OPEN_FILE_FOR_READING,
+REAL_OPEN_FILE_FOR_READING,
 :(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "open-file-for-reading", OPEN_FILE_FOR_READING);
+put(Recipe_ordinal, "real-open-file-for-reading", REAL_OPEN_FILE_FOR_READING);
 :(before "End Primitive Recipe Checks")
-case OPEN_FILE_FOR_READING: {
+case REAL_OPEN_FILE_FOR_READING: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'real-open-file-for-reading' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'real-open-file-for-reading' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
   break;
 }
 :(before "End Primitive Recipe Implementations")
-case OPEN_FILE_FOR_READING: {
+case REAL_OPEN_FILE_FOR_READING: {
+  string filename = read_mu_string(ingredients.at(0).at(0));
+  assert(sizeof(long long int) >= sizeof(FILE*));
+  FILE* f = fopen(filename.c_str(), "r");
+  long long int result = reinterpret_cast<long long int>(f);
+  products.resize(1);
+  products.at(0).push_back(static_cast<double>(result));
   break;
 }
 
 :(before "End Primitive Recipe Declarations")
-OPEN_FILE_FOR_WRITING,
+REAL_OPEN_FILE_FOR_WRITING,
 :(before "End Primitive Recipe Numbers")
-put(Recipe_ordinal, "open-file-for-reading", OPEN_FILE_FOR_WRITING);
+put(Recipe_ordinal, "real-open-file-for-writing", REAL_OPEN_FILE_FOR_WRITING);
 :(before "End Primitive Recipe Checks")
-case OPEN_FILE_FOR_WRITING: {
+case REAL_OPEN_FILE_FOR_WRITING: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'real-open-file-for-writing' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (!is_mu_string(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'real-open-file-for-writing' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
   break;
 }
 :(before "End Primitive Recipe Implementations")
-case OPEN_FILE_FOR_WRITING: {
+case REAL_OPEN_FILE_FOR_WRITING: {
+  string filename = read_mu_string(ingredients.at(0).at(0));
+  assert(sizeof(long long int) >= sizeof(FILE*));
+  long long int result = reinterpret_cast<long long int>(fopen(filename.c_str(), "w"));
+  products.resize(1);
+  products.at(0).push_back(static_cast<double>(result));
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+REAL_READ_FROM_FILE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "real-read-from-file", REAL_READ_FROM_FILE);
+:(before "End Primitive Recipe Checks")
+case REAL_READ_FROM_FILE: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'real-read-from-file' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'real-read-from-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case REAL_READ_FROM_FILE: {
+  long long int x = static_cast<long long int>(ingredients.at(0).at(0));
+  FILE* f = reinterpret_cast<FILE*>(x);
+  if (f == NULL) {
+    raise << maybe(current_recipe_name()) << "can't read from null file in '" << to_string(current_instruction()) << "'\n" << end();
+    break;
+  }
+  products.resize(1);
+  if (feof(f)) {
+    products.at(0).push_back(0);
+    break;
+  }
+  if (ferror(f)) {
+    raise << maybe(current_recipe_name()) << "file in invalid state in '" << to_string(current_instruction()) << "'\n" << end();
+    break;
+  }
+  char c = getc(f);  // todo: unicode
+  if (ferror(f)) {
+    raise << maybe(current_recipe_name()) << "couldn't read to file in '" << to_string(current_instruction()) << "'\n" << end();
+    raise << "  errno: " << errno << '\n' << end();
+    break;
+  }
+  products.at(0).push_back(c);
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+REAL_WRITE_TO_FILE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "real-write-to-file", REAL_WRITE_TO_FILE);
+:(before "End Primitive Recipe Checks")
+case REAL_WRITE_TO_FILE: {
+  if (SIZE(inst.ingredients) != 2) {
+    raise << maybe(get(Recipe, r).name) << "'real-write-to-file' requires exactly two ingredients, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'real-write-to-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  if (!is_mu_number(inst.ingredients.at(1))) {
+    raise << maybe(get(Recipe, r).name) << "second ingredient of 'real-write-to-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case REAL_WRITE_TO_FILE: {
+  long long int x = static_cast<long long int>(ingredients.at(0).at(0));
+  FILE* f = reinterpret_cast<FILE*>(x);
+  if (f == NULL) {
+    raise << maybe(current_recipe_name()) << "can't write to null file in '" << to_string(current_instruction()) << "'\n" << end();
+    break;
+  }
+  if (feof(f)) break;
+  if (ferror(f)) {
+    raise << maybe(current_recipe_name()) << "file in invalid state in '" << to_string(current_instruction()) << "'\n" << end();
+    break;
+  }
+  long long int y = static_cast<long long int>(ingredients.at(1).at(0));
+  char c = static_cast<char>(y);
+  putc(c, f);  // todo: unicode
+  if (ferror(f)) {
+    raise << maybe(current_recipe_name()) << "couldn't write to file in '" << to_string(current_instruction()) << "'\n" << end();
+    raise << "  errno: " << errno << '\n' << end();
+    break;
+  }
+  break;
+}
+
+:(before "End Primitive Recipe Declarations")
+REAL_CLOSE_FILE,
+:(before "End Primitive Recipe Numbers")
+put(Recipe_ordinal, "real-close-file", REAL_CLOSE_FILE);
+:(before "End Primitive Recipe Checks")
+case REAL_CLOSE_FILE: {
+  if (SIZE(inst.ingredients) != 1) {
+    raise << maybe(get(Recipe, r).name) << "'real-close-file' requires exactly one ingredient, but got '" << inst.original_string << "'\n" << end();
+    break;
+  }
+  string filename;
+  if (!is_mu_number(inst.ingredients.at(0))) {
+    raise << maybe(get(Recipe, r).name) << "first ingredient of 'real-close-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
+    break;
+  }
+  break;
+}
+:(before "End Primitive Recipe Implementations")
+case REAL_CLOSE_FILE: {
+  long long int x = static_cast<long long int>(ingredients.at(0).at(0));
+  FILE* f = reinterpret_cast<FILE*>(x);
+  fclose(f);
+  products.resize(1);
+  products.at(0).push_back(0);  // todo: ensure that caller always resets the ingredient
   break;
 }
diff --git a/real-file.mu b/real-file.mu
new file mode 100644
index 00000000..07390696
--- /dev/null
+++ b/real-file.mu
@@ -0,0 +1,16 @@
+# example program: read a character from one file and write it to another
+# before running it, put a character into /tmp/mu-x
+
+def main [
+  local-scope
+  f:number/file <- real-open-file-for-reading [/tmp/mu-x]
+  $print [file to read from: ], f, 10/newline
+  c:character <- real-read-from-file f
+  $print [copying ], c, 10/newline
+  f <- real-close-file f
+  $print [file after closing: ], f, 10/newline
+  f <- real-open-file-for-writing [/tmp/mu-y]
+  $print [file to write to: ], f, 10/newline
+  real-write-to-file f, c
+  f <- real-close-file f
+]