I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

FileIO.i6t

FileIO contents

Language.

This whole template contains material used only if the "Glulx external files" element is part of Inform's current definition, so:

12#IFDEF PLUGIN_FILES;

Structure.

The I7 kind of value "auxiliary-file" is an --> array, holding a memory structure containing information about external files. The following constants specify memory offsets and values. Note the safety value stored as the first word of the structure: this helps protect the routines below from accidents. (16339, besides being prime, is a number interesting to the author of Inform since it was the examination board identifying number of his school, and so had to be filled in on all of the many papers he sat during his formative years.)

25Constant AUXF_MAGIC = 0; ! First word holds a safety constant 26Constant AUXF_MAGIC_VALUE = 16339; ! Should be first word of any valid file structure 27Constant AUXF_STATUS = 1; ! One of the following: 28    Constant AUXF_STATUS_IS_CLOSED = 1; ! Currently closed, or perhaps doesn't exist 29    Constant AUXF_STATUS_IS_OPEN_FOR_READ = 2; 30    Constant AUXF_STATUS_IS_OPEN_FOR_WRITE = 3; 31    Constant AUXF_STATUS_IS_OPEN_FOR_APPEND = 4; 32Constant AUXF_BINARY = 2; ! False for text files (I7 default), true for binary 33Constant AUXF_STREAM = 3; ! Stream for an open file (meaningless otherwise) 34Constant AUXF_FILENAME = 4; ! Packed address of constant string 35Constant AUXF_IFID_OF_OWNER = 5; ! UUID_ARRAY if owned by this project, or 36    ! string array of IFID of owner wrapped in //...//, or NULL to leave open

Instances.

These structures are not dynamically created: they are precompiled by the NI compiler, already filled in with the necessary values. The following command generates them.

44{-call:PL::Files::arrays}

Errors.

This is used for I/O errors of all kinds: it isn't within the Glulx-only code because one of the errors is to try to use these routines on the Z-machine.

52[ FileIO_Error extf err_text struc; 53    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) { 54        print "^*** Error on unknown file: ", (string) err_text, " ***^"; 55    } else { 56        struc = TableOfExternalFiles-->extf; 57        print "^*** Error on file ", 58            (string) struc-->AUXF_FILENAME, ": ", 59            (string) err_text, " ***^"; 60    } 61    RunTimeProblem(RTP_FILEIOERROR); 62    return 0; 63];

Glulx Material.

68#IFDEF TARGET_GLULX;

Existence.

Determine whether a file exists on disc. Note that we have no concept of directories, or the file system structure on the host machine: indeed, it is entirely up to the Glulx VM what it does when asked to look for a file. By convention, though, files for a project are stored in the same folder as the story file when out in the wild; when a project is developed within the Inform user interface, they are either (for preference) stored in a Files subfolder of the Materials folder for a project, or else stored alongside the Inform project file.

81[ FileIO_Exists extf fref struc rv usage; 82    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) rfalse; 83    struc = TableOfExternalFiles-->extf; 84    if ((struc == 0) || (struc-->AUXF_MAGIC ~= AUXF_MAGIC_VALUE)) rfalse; 85    if (struc-->AUXF_BINARY) usage = fileusage_BinaryMode; 86    else usage = fileusage_TextMode; 87    fref = glk_fileref_create_by_name(fileusage_Data + usage, 88        Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0); 89    rv = glk_fileref_does_file_exist(fref); 90    glk_fileref_destroy(fref); 91    return rv; 92];

Readiness.

One of our problems is that a file might be being used by another application: perhaps even by another story file running in a second incarnation of Glulx, like a parallel world of which we can know nothing. We actually want to allow for this sort of thing, because one use for external files in I7 is as a sort of communications conduit for assisting applications.

Most operating systems solve this problem by means of locking a file, or by creating a second lock-file, the existence of which indicates ownership of the original. We haven't got much access to the file-system, though: what we do is to set the first character of the file to an asterisk to mark it as complete and ready for reading, or to a hyphen to mark it as a work in progress.

FileIO_Ready determines whether or not a file is ready to be read from: it has to exist on disc, and to be openable, and also to be ready in having this marker asterisk.

FileIO_MarkReady changes the readiness state of a file, writing the asterisk or hyphen into the initial character as needed.

116[ FileIO_Ready extf struc fref usage str ch; 117    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) rfalse; 118    struc = TableOfExternalFiles-->extf; 119    if ((struc == 0) || (struc-->AUXF_MAGIC ~= AUXF_MAGIC_VALUE)) rfalse; 120    if (struc-->AUXF_BINARY) usage = fileusage_BinaryMode; 121    else usage = fileusage_TextMode; 122    fref = glk_fileref_create_by_name(fileusage_Data + usage, 123        Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0); 124    if (glk_fileref_does_file_exist(fref) == false) { 125        glk_fileref_destroy(fref); 126        rfalse; 127    } 128    str = glk_stream_open_file(fref, filemode_Read, 0); 129    ch = glk_get_char_stream(str); 130    glk_stream_close(str, 0); 131    glk_fileref_destroy(fref); 132    if (ch ~= '*') rfalse; 133    rtrue; 134]; 135 136[ FileIO_MarkReady extf readiness struc fref str ch usage; 137    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 138        return FileIO_Error(extf, "tried to open a non-file"); 139    struc = TableOfExternalFiles-->extf; 140    if ((struc == 0) || (struc-->AUXF_MAGIC ~= AUXF_MAGIC_VALUE)) rfalse; 141    if (struc-->AUXF_BINARY) usage = fileusage_BinaryMode; 142    else usage = fileusage_TextMode; 143    fref = glk_fileref_create_by_name(fileusage_Data + usage, 144        Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0); 145    if (glk_fileref_does_file_exist(fref) == false) { 146        glk_fileref_destroy(fref); 147        return FileIO_Error(extf, "only existing files can be marked"); 148    } 149    if (struc-->AUXF_STATUS ~= AUXF_STATUS_IS_CLOSED) { 150        glk_fileref_destroy(fref); 151        return FileIO_Error(extf, "only closed files can be marked"); 152    } 153    str = glk_stream_open_file(fref, filemode_ReadWrite, 0); 154    glk_stream_set_position(str, 0, 0); ! seek start 155    if (readiness) ch = '*'; else ch = '-'; 156    glk_put_char_stream(str, ch); ! mark as complete 157    glk_stream_close(str, 0); 158    glk_fileref_destroy(fref); 159];

Open File.

164[ FileIO_Open extf write_flag append_flag 165    struc fref str mode ix ch not_this_ifid owner force_header usage; 166    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 167        return FileIO_Error(extf, "tried to open a non-file"); 168    struc = TableOfExternalFiles-->extf; 169    if ((struc == 0) || (struc-->AUXF_MAGIC ~= AUXF_MAGIC_VALUE)) rfalse; 170    if (struc-->AUXF_STATUS ~= AUXF_STATUS_IS_CLOSED) 171        return FileIO_Error(extf, "tried to open a file already open"); 172    if (struc-->AUXF_BINARY) usage = fileusage_BinaryMode; 173    else usage = fileusage_TextMode; 174    fref = glk_fileref_create_by_name(fileusage_Data + usage, 175        Glulx_ChangeAnyToCString(struc-->AUXF_FILENAME), 0); 176    if (write_flag) { 177        if (append_flag) { 178            mode = filemode_WriteAppend; 179            if (glk_fileref_does_file_exist(fref) == false) 180                force_header = true; 181        } 182        else mode = filemode_Write; 183    } else { 184        mode = filemode_Read; 185        if (glk_fileref_does_file_exist(fref) == false) { 186            glk_fileref_destroy(fref); 187            return FileIO_Error(extf, "tried to open a file which does not exist"); 188        } 189    } 190    str = glk_stream_open_file(fref, mode, 0); 191    glk_fileref_destroy(fref); 192    if (str == 0) return FileIO_Error(extf, "tried to open a file but failed"); 193    struc-->AUXF_STREAM = str; 194    if (write_flag) { 195        if (append_flag) 196            struc-->AUXF_STATUS = AUXF_STATUS_IS_OPEN_FOR_APPEND; 197        else 198            struc-->AUXF_STATUS = AUXF_STATUS_IS_OPEN_FOR_WRITE; 199        glk_stream_set_current(str); 200        if ((append_flag == FALSE) || (force_header)) { 201            print "- "; 202            for (ix=6: ix <= UUID_ARRAY->0: ix++) print (char) UUID_ARRAY->ix; 203            print " ", (string) struc-->AUXF_FILENAME, "^"; 204        } 205    } else { 206        struc-->AUXF_STATUS = AUXF_STATUS_IS_OPEN_FOR_READ; 207        ch = FileIO_GetC(extf); 208        if (ch ~= '-' or '*') { jump BadFile; } 209        if (ch == '-') 210            return FileIO_Error(extf, "tried to open a file which was incomplete"); 211        ch = FileIO_GetC(extf); 212        if (ch ~= ' ') { jump BadFile; } 213        ch = FileIO_GetC(extf); 214        if (ch ~= '/') { jump BadFile; } 215        ch = FileIO_GetC(extf); 216        if (ch ~= '/') { jump BadFile; } 217        owner = struc-->AUXF_IFID_OF_OWNER; 218        ix = 3; 219        if (owner == UUID_ARRAY) ix = 8; 220        if (owner ~= NULL) { 221            for (: ix <= owner->0: ix++) { 222                ch = FileIO_GetC(extf); 223                if (ch == -1) { jump BadFile; } 224                if (ch ~= owner->ix) not_this_ifid = true; 225                if (ch == ' ') break; 226            } 227            if (not_this_ifid == false) { 228                ch = FileIO_GetC(extf); 229                if (ch ~= ' ') { jump BadFile; } 230            } 231        } 232        while (ch ~= -1) { 233            ch = FileIO_GetC(extf); 234            if (ch == 10 or 13) break; 235        } 236        if (not_this_ifid) { 237            struc-->AUXF_STATUS = AUXF_STATUS_IS_CLOSED; 238            glk_stream_close(str, 0); 239            return FileIO_Error(extf, 240                "tried to open a file owned by another project"); 241        } 242    } 243    return struc-->AUXF_STREAM; 244    .BadFile; 245    struc-->AUXF_STATUS = AUXF_STATUS_IS_CLOSED; 246    glk_stream_close(str, 0); 247    return FileIO_Error(extf, "tried to open a file which seems to be malformed"); 248];

Close File.

Note that a call to the following, in write mode, must be followed by a glk_stream_set_current(), or else the next print statement will run into Glk errors.

256[ FileIO_Close extf struc; 257    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 258        return FileIO_Error(extf, "tried to open a non-file"); 259    struc = TableOfExternalFiles-->extf; 260    if (struc-->AUXF_STATUS ~= 261        AUXF_STATUS_IS_OPEN_FOR_READ or 262        AUXF_STATUS_IS_OPEN_FOR_WRITE or 263        AUXF_STATUS_IS_OPEN_FOR_APPEND) 264        return FileIO_Error(extf, "tried to close a file which is not open"); 265    if (struc-->AUXF_STATUS == 266        AUXF_STATUS_IS_OPEN_FOR_WRITE or 267        AUXF_STATUS_IS_OPEN_FOR_APPEND) { 268        glk_stream_set_position(struc-->AUXF_STREAM, 0, 0); ! seek start 269        glk_put_char_stream(struc-->AUXF_STREAM, '*'); ! mark as complete 270    } 271    glk_stream_close(struc-->AUXF_STREAM, 0); 272    struc-->AUXF_STATUS = AUXF_STATUS_IS_CLOSED; 273];

Get Character.

278[ FileIO_GetC extf struc; 279    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) return -1; 280    struc = TableOfExternalFiles-->extf; 281    if (struc-->AUXF_STATUS ~= AUXF_STATUS_IS_OPEN_FOR_READ) return -1; 282    return glk_get_char_stream(struc-->AUXF_STREAM); 283];

Put Character.

288[ FileIO_PutC extf char struc; 289    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) return -1; 290        return FileIO_Error(extf, "tried to write to a non-file"); 291    struc = TableOfExternalFiles-->extf; 292    if (struc-->AUXF_STATUS ~= 293        AUXF_STATUS_IS_OPEN_FOR_WRITE or 294        AUXF_STATUS_IS_OPEN_FOR_APPEND) 295        return FileIO_Error(extf, 296            "tried to write to a file which is not open for writing"); 297    return glk_put_char_stream(struc-->AUXF_STREAM, char); 298];

Print Line.

We read characters from the supplied file until the next newline character. (We allow for that to be encoded as either a single 0a or a single 0d.) Each character is printed, and at the end we print a newline.

306[ FileIO_PrintLine extf ch struc; 307    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 308        return FileIO_Error(extf, "tried to write to a non-file"); 309    struc = TableOfExternalFiles-->extf; 310    for (::) { 311        ch = FileIO_GetC(extf); 312        if (ch == -1) rfalse; 313        if (ch == 10 or 13) { print "^"; rtrue; } 314        print (char) ch; 315    } 316];

Print Contents.

Repeating this until the file runs out is equivalent to the Unix command cat, that is, it copies the stream of characters from the file to the output stream. (This might well be another file, just as with cat, in which case we have a copy utility.)

325[ FileIO_PrintContents extf tab struc; 326    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 327        return FileIO_Error(extf, "tried to access a non-file"); 328    struc = TableOfExternalFiles-->extf; 329    if (struc-->AUXF_BINARY) 330        return FileIO_Error(extf, "printing text will not work with binary files"); 331    if (FileIO_Open(extf, false) == 0) rfalse; 332    while (FileIO_PrintLine(extf)) ; 333    FileIO_Close(extf); 334    rtrue; 335];

Print Text.

The following writes a given piece of text as the new content of the file, either as the whole file (if append_flag is false) or adding only to the end (if true).

343[ FileIO_PutContents extf text append_flag struc str ch oldstream; 344    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 345        return FileIO_Error(extf, "tried to access a non-file"); 346    struc = TableOfExternalFiles-->extf; 347    if (struc-->AUXF_BINARY) 348        return FileIO_Error(extf, "writing text will not work with binary files"); 349    oldstream = glk_stream_get_current(); 350    str = FileIO_Open(extf, true, append_flag); 351    if (str == 0) rfalse; 352    @push say__p; @push say__pc; 353    ClearParagraphing(19); 354    TEXT_TY_Say(text); 355    FileIO_Close(extf); 356    if (oldstream) glk_stream_set_current(oldstream); 357    @pull say__pc; @pull say__p; 358    rfalse; 359];

Serialising Tables.

The most important data structures to "serialise" – that is, to convert from their binary representations in memory into text representations in an external file – are Tables. Here we only carry out the file-handling; the actual translations are in Tables.i6t.

368[ FileIO_PutTable extf tab rv struc oldstream; 369    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 370        return FileIO_Error(extf, "tried to write table to a non-file"); 371    struc = TableOfExternalFiles-->extf; 372    if (struc-->AUXF_BINARY) 373        return FileIO_Error(extf, "writing a table will not work with binary files"); 374    oldstream = glk_stream_get_current(); 375    if (FileIO_Open(extf, true) == 0) rfalse; 376    rv = TablePrint(tab); 377    FileIO_Close(extf); 378    if (oldstream) glk_stream_set_current(oldstream); 379    if (rv) return RunTimeProblem(RTP_TABLE_CANTSAVE, tab); 380    rtrue; 381]; 382 383[ FileIO_GetTable extf tab struc; 384    if ((extf < 1) || (extf > NO_EXTERNAL_FILES)) 385        return FileIO_Error(extf, "tried to read table from a non-file"); 386    struc = TableOfExternalFiles-->extf; 387    if (struc-->AUXF_BINARY) 388        return FileIO_Error(extf, "reading a table will not work with binary files"); 389    if (FileIO_Open(extf, false) == 0) rfalse; 390    TableRead(tab, extf); 391    FileIO_Close(extf); 392    rtrue; 393];

Z-Machine Stubs.

These routines do the minimum possible, but equally, they only generate a run-time problem when there is no alternative.

400#IFNOT; ! TARGET_GLULX 401[ FileIO_Exists extf; rfalse; ]; 402[ FileIO_Ready extf; rfalse; ]; 403[ FileIO_GetC extf; return -1; ]; 404[ FileIO_PutTable extf tab; 405    return FileIO_Error(extf, "external files can only be used under Glulx"); 406]; 407[ FileIO_MarkReady extf status; FileIO_PutTable(extf); ]; 408[ FileIO_GetTable extf tab; FileIO_PutTable(extf); ]; 409[ FileIO_PrintContents extf; FileIO_PutTable(extf); ]; 410[ FileIO_PutContents extf; FileIO_PutTable(extf); ]; 411#ENDIF; ! TARGET_GLULX

Back To Core.

416#IFNOT; ! PLUGIN_FILES 417[ FileIO_GetC extf; return -1; ]; 418#ENDIF; ! PLUGIN_FILES