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;
26Constant AUXF_MAGIC_VALUE = 16339;
27Constant AUXF_STATUS = 1;
28 Constant AUXF_STATUS_IS_CLOSED = 1;
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;
33Constant AUXF_STREAM = 3;
34Constant AUXF_FILENAME = 4;
35Constant AUXF_IFID_OF_OWNER = 5;
36
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];
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);
155 if (readiness) ch = '*'; else ch = '-';
156 glk_put_char_stream(str, ch);
157 glk_stream_close(str, 0);
158 glk_fileref_destroy(fref);
159];
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);
269 glk_put_char_stream(struc-->AUXF_STREAM, '*');
270 }
271 glk_stream_close(struc-->AUXF_STREAM, 0);
272 struc-->AUXF_STATUS = AUXF_STATUS_IS_CLOSED;
273];
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];
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;
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;
416#IFNOT;
417[ FileIO_GetC extf; return -1; ];
418#ENDIF;