# Mu - 087file.cc

      1 //: Interacting with the file system.
      2 //:   '$open-file-for-reading' returns a FILE* as a number (ugh)
      3 //:   '$read-from-file' accepts a number, interprets it as a FILE* (double ugh) and reads a character from it
      4 //: Similarly for writing files.
      5 //: These interfaces are ugly and tied to the current (Linux) host Mu happens
      6 //: to be implemented atop. Later layers will wrap them with better, more
      7 //: testable interfaces.
      8 //:
      9 //: Clearly we don't care about performance or any of that so far.
     10 //: todo: reading/writing binary files
     11
     12 :(before "End Primitive Recipe Declarations")
     13 _OPEN_FILE_FOR_READING,
     14 :(before "End Primitive Recipe Numbers")
     15 put(Recipe_ordinal, "$open-file-for-reading", _OPEN_FILE_FOR_READING);
     16 :(before "End Primitive Recipe Checks")
     17 case _OPEN_FILE_FOR_READING: {
     18   if (SIZE(inst.ingredients) != 1) {
     19     raise << maybe(get(Recipe, r).name) << "'$open-file-for-reading' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
     20     break;
     21   }
     22   if (!is_mu_text(inst.ingredients.at(0))) {
     23     raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-file-for-reading' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     24     break;
     25   }
     26   if (SIZE(inst.products) != 1) {
     27     raise << maybe(get(Recipe, r).name) << "'$open-file-for-reading' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
     28     break;
     29   }
     30   if (!is_mu_number(inst.products.at(0))) {
     31     raise << maybe(get(Recipe, r).name) << "first product of '$open-file-for-reading' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
     32     break;
     33   }
     34   break;
     35 }
     36 :(before "End Primitive Recipe Implementations")
     37 case _OPEN_FILE_FOR_READING: {
     38   string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
     39   assert(sizeof(long long int) >= sizeof(FILE*));
     40   FILE* f = fopen(filename.c_str(), "r");
     41   long long int result = reinterpret_cast<long long int>(f);
     42   products.resize(1);
     43   products.at(0).push_back(static_cast<double>(result));
     44   break;
     45 }
     46
     47 :(before "End Primitive Recipe Declarations")
     48 _OPEN_FILE_FOR_WRITING,
     49 :(before "End Primitive Recipe Numbers")
     50 put(Recipe_ordinal, "$open-file-for-writing", _OPEN_FILE_FOR_WRITING);
     51 :(before "End Primitive Recipe Checks")
     52 case _OPEN_FILE_FOR_WRITING: {
     53   if (SIZE(inst.ingredients) != 1) {
     54     raise << maybe(get(Recipe, r).name) << "'$open-file-for-writing' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
     55     break;
     56   }
     57   if (!is_mu_text(inst.ingredients.at(0))) {
     58     raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-file-for-writing' should be a string, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     59     break;
     60   }
     61   if (SIZE(inst.products) != 1) {
     62     raise << maybe(get(Recipe, r).name) << "'$open-file-for-writing' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
     63     break;
     64   }
     65   if (!is_mu_number(inst.products.at(0))) {
     66     raise << maybe(get(Recipe, r).name) << "first product of '$open-file-for-writing' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
     67     break;
     68   }
     69   break;
     70 }
     71 :(before "End Primitive Recipe Implementations")
     72 case _OPEN_FILE_FOR_WRITING: {
     73   string filename = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
     74   assert(sizeof(long long int) >= sizeof(FILE*));
     75   long long int result = reinterpret_cast<long long int>(fopen(filename.c_str(), "w"));
     76   products.resize(1);
     77   products.at(0).push_back(static_cast<double>(result));
     78   break;
     79 }
     80
     81 :(before "End Primitive Recipe Declarations")
     82 _READ_FROM_FILE,
     83 :(before "End Primitive Recipe Numbers")
     84 put(Recipe_ordinal, "$read-from-file", _READ_FROM_FILE);
     85 :(before "End Primitive Recipe Checks")
     86 case _READ_FROM_FILE: {
     87   if (SIZE(inst.ingredients) != 1) {
     88     raise << maybe(get(Recipe, r).name) << "'$read-from-file' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
     89     break;
     90   }
     91   if (!is_mu_number(inst.ingredients.at(0))) {
     92     raise << maybe(get(Recipe, r).name) << "first ingredient of '$read-from-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     93     break;
     94   }
     95   if (SIZE(inst.products) != 2) {
     96     raise << maybe(get(Recipe, r).name) << "'$read-from-file' requires exactly two products, but got '" << to_original_string(inst) << "'\n" << end();
     97     break;
     98   }
     99   if (!is_mu_character(inst.products.at(0))) {
    100     raise << maybe(get(Recipe, r).name) << "first product of '$read-from-file' should be a character, but got '" << to_string(inst.products.at(0)) << "'\n" << end();
    101     break;
    102   }
    103   if (!is_mu_boolean(inst.products.at(1))) {
    104     raise << maybe(get(Recipe, r).name) << "second product of '$read-from-file' should be a boolean, but got '" << to_string(inst.products.at(1)) << "'\n" << end();
    105     break;
    106   }
    107   break;
    108 }
    109 :(before "End Primitive Recipe Implementations")
    110 case _READ_FROM_FILE: {
    111   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    112   FILE* f = reinterpret_cast<FILE*>(x);
    113   if (f == NULL) {
    114     raise << maybe(current_recipe_name()) << "can't read from null file in '" << to_string(current_instruction()) << "'\n" << end();
    115     break;
    116   }
    117   products.resize(2);
    118   if (feof(f)) {
    119     products.at(0).push_back(0);
    120     products.at(1).push_back(1);  // eof
    121     break;
    122   }
    123   if (ferror(f)) {
    124     raise << maybe(current_recipe_name()) << "file in invalid state in '" << to_string(current_instruction()) << "'\n" << end();
    125     break;
    126   }
    127   char c = getc(f);  // todo: unicode
    128   if (c == EOF) {
    129     products.at(0).push_back(0);
    130     products.at(1).push_back(1);  // eof
    131     break;
    132   }
    133   if (ferror(f)) {
    134     raise << maybe(current_recipe_name()) << "couldn't read from file in '" << to_string(current_instruction()) << "'\n" << end();
    135     raise << "  errno: " << errno << '\n' << end();
    136     break;
    137   }
    138   products.at(0).push_back(c);
    139   products.at(1).push_back(0);  // not eof
    140   break;
    141 }
    142 :(before "End Includes")
    143 #include <errno.h>
    144
    145 :(before "End Primitive Recipe Declarations")
    146 _WRITE_TO_FILE,
    147 :(before "End Primitive Recipe Numbers")
    148 put(Recipe_ordinal, "$write-to-file", _WRITE_TO_FILE);
    149 :(before "End Primitive Recipe Checks")
    150 case _WRITE_TO_FILE: {
    151   if (SIZE(inst.ingredients) != 2) {
    152     raise << maybe(get(Recipe, r).name) << "'$write-to-file' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
    153     break;
    154   }
    155   if (!is_mu_number(inst.ingredients.at(0))) {
    156     raise << maybe(get(Recipe, r).name) << "first ingredient of '$write-to-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    157     break;
    158   }
    159   if (!is_mu_character(inst.ingredients.at(1))) {
    160     raise << maybe(get(Recipe, r).name) << "second ingredient of '$write-to-file' should be a character, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    161     break;
    162   }
    163   if (!inst.products.empty()) {
    164     raise << maybe(get(Recipe, r).name) << "'$write-to-file' writes to no products, but got '" << to_original_string(inst) << "'\n" << end();
    165     break;
    166   }
    167   break;
    168 }
    169 :(before "End Primitive Recipe Implementations")
    170 case _WRITE_TO_FILE: {
    171   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    172   FILE* f = reinterpret_cast<FILE*>(x);
    173   if (f == NULL) {
    174     raise << maybe(current_recipe_name()) << "can't write to null file in '" << to_string(current_instruction()) << "'\n" << end();
    175     break;
    176   }
    177   if (feof(f)) break;
    178   if (ferror(f)) {
    179     raise << maybe(current_recipe_name()) << "file in invalid state in '" << to_string(current_instruction()) << "'\n" << end();
    180     break;
    181   }
    182   long long int y = static_cast<long long int>(ingredients.at(1).at(0));
    183   char c = static_cast<char>(y);
    184   putc(c, f);  // todo: unicode
    185   if (ferror(f)) {
    186     raise << maybe(current_recipe_name()) << "couldn't write to file in '" << to_string(current_instruction()) << "'\n" << end();
    187     raise << "  errno: " << errno << '\n' << end();
    188     break;
    189   }
    190   break;
    191 }
    192
    193 :(before "End Primitive Recipe Declarations")
    194 _CLOSE_FILE,
    195 :(before "End Primitive Recipe Numbers")
    196 put(Recipe_ordinal, "$close-file", _CLOSE_FILE);
    197 :(before "End Primitive Recipe Checks")
    198 case _CLOSE_FILE: {
    199   if (SIZE(inst.ingredients) != 1) {
    200     raise << maybe(get(Recipe, r).name) << "'$close-file' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    201     break;
    202   }
    203   if (!is_mu_number(inst.ingredients.at(0))) {
    204     raise << maybe(get(Recipe, r).name) << "first ingredient of '$close-file' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    205     break;
    206   }
    207   if (SIZE(inst.products) != 1) {
    208     raise << maybe(get(Recipe, r).name) << "'$close-file' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
    209     break;
    210   }
    211   if (inst.products.at(0).name != inst.ingredients.at(0).name) {
    212     raise << maybe(get(Recipe, r).name) << "'$close-file' requires its product to be the same as its ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    213     break;
    214   }
    215   break;
    216 }
    217 :(before "End Primitive Recipe Implementations")
    218 case _CLOSE_FILE: {
    219   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    220   FILE* f = reinterpret_cast<FILE*>(x);
    221   fclose(f);
    222   products.resize(1);
    223   products.at(0).push_back(0);  // todo: ensure that caller always resets the ingredient
    224   break;
    225 }