diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2016-08-13 22:57:13 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2016-08-13 22:57:13 -0700 |
commit | dff1abb2e10ca2dd064a505dfb3d67635f680a9a (patch) | |
tree | faa8b754d0faba8b33491ccd2e07732efd8679b5 | |
parent | 0e6a3c0d21006a31bf45138ca8e7470d97a0e139 (diff) | |
download | mu-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.cc | 169 | ||||
-rw-r--r-- | real-file.mu | 16 |
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 +] |