Add Bocfel interpreter
[projects/chimara/chimara.git] / interpreters / bocfel / io.c
1 /*-
2  * Copyright 2010-2012 Chris Spiegel.
3  *
4  * This file is part of Bocfel.
5  *
6  * Bocfel is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License, version
8  * 2 or 3, as published by the Free Software Foundation.
9  *
10  * Bocfel is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with Bocfel.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdlib.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <limits.h>
24
25 #ifdef ZTERP_GLK
26 #include <glk.h>
27 #endif
28
29 #include "io.h"
30 #include "osdep.h"
31 #include "unicode.h"
32
33 #define MAX_PATH        4096
34
35 int use_utf8_io;
36
37 /* Generally speaking, UNICODE_LINEFEED (10) is used as a newline.  Glk
38  * requires this (Glk API 0.7.0 §2.2), and when Unicode is available, we
39  * write characters out by hand even with stdio, so no translation can
40  * be done.  However, when stdio is being used, Unicode is not
41  * available, and the file usage will be for a transcript or
42  * command-script, use '\n' as a newline so translation can be done;
43  * this is the only case where streams are opened in text mode.
44  *
45  * zterp_io_stdio() and zterp_io_stdout() are considered text-mode if
46  * Unicode is not available, binary otherwise.
47  */
48 #define textmode(io)    (!use_utf8_io && ((io->mode) & (ZTERP_IO_TRANS | ZTERP_IO_INPUT)))
49
50 struct zterp_io
51 {
52   enum type { IO_STDIO, IO_GLK } type;
53
54   FILE *fp;
55   int mode;
56 #ifdef ZTERP_GLK
57   strid_t file;
58 #endif
59 };
60
61 /* Glk does not like you to be able to pass a full filename to
62  * glk_fileref_create_by_name(); this means that Glk cannot be used to
63  * open arbitrary files.  However, Glk is still required to prompt for
64  * files, such as in a save game situation.  To allow zterp_io to work
65  * for opening files both with and without a prompt, it will use stdio
66  * when either Glk is not available, or when Glk is available but
67  * prompting is not necessary.
68  *
69  * This is needed because the IFF parser is required for both opening
70  * games (zblorb files) and for saving/restoring.  The former needs to
71  * be able to access any file on the filesystem, and the latter needs to
72  * prompt.  This is a headache.
73  *
74  * Prompting is assumed to be necessary if “filename” is NULL.
75  */
76 zterp_io *zterp_io_open(const char *filename, int mode)
77 {
78   zterp_io *io;
79   char smode[] = "wb";
80
81   fprintf(stderr, "zterp_io_open: '%s'\n", filename);
82
83   io = malloc(sizeof *io);
84   if(io == NULL) goto err;
85   io->mode = mode;
86
87   if     (mode & ZTERP_IO_RDONLY) smode[0] = 'r';
88   else if(mode & ZTERP_IO_APPEND) smode[0] = 'a';
89
90   if(textmode(io)) smode[1] = 0;
91
92 #ifdef ZTERP_GLK
93   int usage = fileusage_BinaryMode, filemode;
94
95   if     (mode & ZTERP_IO_SAVE)  usage |= fileusage_SavedGame;
96   else if(mode & ZTERP_IO_TRANS) usage |= fileusage_Transcript;
97   else if(mode & ZTERP_IO_INPUT) usage |= fileusage_InputRecord;
98   else                           usage |= fileusage_Data;
99
100   if     (mode & ZTERP_IO_RDONLY) filemode = filemode_Read;
101   else if(mode & ZTERP_IO_WRONLY) filemode = filemode_Write;
102   else if(mode & ZTERP_IO_APPEND) filemode = filemode_WriteAppend;
103
104   else goto err;
105 #else
106   const char *prompt;
107
108   if     (mode & ZTERP_IO_SAVE)  prompt = "Enter filename for save game: ";
109   else if(mode & ZTERP_IO_TRANS) prompt = "Enter filename for transcript: ";
110   else if(mode & ZTERP_IO_INPUT) prompt = "Enter filename for command record: ";
111   else                           prompt = "Enter filename for data: ";
112 #endif
113
114   /* No need to prompt. */
115   if(filename != NULL)
116   {
117     io->type = IO_STDIO;
118     io->fp = fopen(filename, smode);
119     if(io->fp == NULL) goto err;
120   }
121   /* Prompt. */
122   else
123   {
124 #ifdef ZTERP_GLK
125     frefid_t ref;
126
127     ref = glk_fileref_create_by_prompt(usage, filemode, 0);
128     if(ref == NULL) goto err;
129
130     io->type = IO_GLK;
131     io->file = glk_stream_open_file(ref, filemode, 0);
132     glk_fileref_destroy(ref);
133     if(io->file == NULL) goto err;
134 #else
135     char fn[MAX_PATH], *p;
136
137     printf("\n%s", prompt);
138     fflush(stdout);
139     if(fgets(fn, sizeof fn, stdin) == NULL || fn[0] == '\n') goto err;
140     p = strchr(fn, '\n');
141     if(p != NULL) *p = 0;
142
143     io->type = IO_STDIO;
144     io->fp = fopen(fn, smode);
145     if(io->fp == NULL) goto err;
146 #endif
147   }
148
149   return io;
150
151 err:
152   free(io);
153
154   return NULL;
155 }
156
157 /* The zterp_os_reopen_binary() calls attempt to reopen stdin/stdout as
158  * binary streams so that reading/writing UTF-8 doesn’t cause unwanted
159  * translations.  The mode of ZTERP_IO_TRANS is set when Unicode is
160  * unavailable as a way to signal that these are text streams.
161  */
162 const zterp_io *zterp_io_stdin(void)
163 {
164   static zterp_io io;
165
166   if(io.fp == NULL)
167   {
168     io.type = IO_STDIO;
169     io.mode = ZTERP_IO_RDONLY;
170     if(use_utf8_io) zterp_os_reopen_binary(stdin);
171     else            io.mode |= ZTERP_IO_TRANS;
172     io.fp = stdin;
173   }
174
175   return &io;
176 }
177
178 const zterp_io *zterp_io_stdout(void)
179 {
180   static zterp_io io;
181
182   if(io.fp == NULL)
183   {
184     io.type = IO_STDIO;
185     io.mode = ZTERP_IO_WRONLY;
186     if(use_utf8_io) zterp_os_reopen_binary(stdout);
187     else            io.mode |= ZTERP_IO_TRANS;
188     io.fp = stdout;
189   }
190
191   return &io;
192 }
193
194 void zterp_io_close(zterp_io *io)
195 {
196 #ifdef ZTERP_GLK
197   if(io->type == IO_GLK)
198   {
199     glk_stream_close(io->file, NULL);
200   }
201   else
202 #endif
203   {
204     fclose(io->fp);
205   }
206
207   free(io);
208 }
209
210 int zterp_io_seek(const zterp_io *io, long offset, int whence)
211 {
212   /* To smooth over differences between Glk and standard I/O, don’t
213    * allow seeking in append-only streams.
214    */
215   if(io->mode & ZTERP_IO_APPEND) return -1;
216
217 #ifdef ZTERP_GLK
218   if(io->type == IO_GLK)
219   {
220     glk_stream_set_position(io->file, offset, whence == SEEK_SET ? seekmode_Start : whence == SEEK_CUR ? seekmode_Current : seekmode_End);
221     return 0; /* dammit */
222   }
223   else
224 #endif
225   {
226     return fseek(io->fp, offset, whence);
227   }
228 }
229
230 long zterp_io_tell(const zterp_io *io)
231 {
232 #ifdef ZTERP_GLK
233   if(io->type == IO_GLK)
234   {
235     return glk_stream_get_position(io->file);
236   }
237   else
238 #endif
239   {
240     return ftell(io->fp);
241   }
242 }
243
244 /* zterp_io_read() and zterp_io_write() always operate in terms of
245  * bytes, whether or not Unicode is available.
246  */
247 size_t zterp_io_read(const zterp_io *io, void *buf, size_t n)
248 {
249 #ifdef ZTERP_GLK
250   if(io->type == IO_GLK)
251   {
252     glui32 s = glk_get_buffer_stream(io->file, buf, n);
253     /* This should only happen if io->file is invalid. */
254     if(s == (glui32)-1) s = 0;
255     return s;
256   }
257   else
258 #endif
259   {
260     return fread(buf, 1, n, io->fp);
261   }
262 }
263
264 size_t zterp_io_write(const zterp_io *io, const void *buf, size_t n)
265 {
266 #ifdef ZTERP_GLK
267   if(io->type == IO_GLK)
268   {
269     glk_put_buffer_stream(io->file, (char *)buf, n);
270     return n; /* dammit */
271   }
272   else
273 #endif
274   {
275     return fwrite(buf, 1, n, io->fp);
276   }
277 }
278
279 int zterp_io_read16(const zterp_io *io, uint16_t *v)
280 {
281   uint8_t buf[2];
282
283   if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0;
284
285   *v = (buf[0] << 8) | buf[1];
286
287   return 1;
288 }
289
290 int zterp_io_read32(const zterp_io *io, uint32_t *v)
291 {
292   uint8_t buf[4];
293
294   if(zterp_io_read(io, buf, sizeof buf) != sizeof buf) return 0;
295
296   *v = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
297
298   return 1;
299 }
300
301 /* Read a byte and make sure it’s part of a valid UTF-8 sequence. */
302 static int read_byte(const zterp_io *io, uint8_t *c)
303 {
304   if(zterp_io_read(io, c, sizeof *c) != sizeof *c) return 0;
305   if((*c & 0x80) != 0x80) return 0;
306
307   return 1;
308 }
309
310 /* zterp_io_getc() and zterp_io_putc() are meant to operate in terms of
311  * characters, not bytes.  That is, unlike C, bytes and characters are
312  * not equivalent as far as Zterp’s I/O system is concerned.
313  */
314
315 /* Read a UTF-8 character, returning it.
316  * -1 is returned on EOF.
317  *
318  * If there is a problem reading the UTF-8 (either from an invalid
319  * sequence or from a too-large value), a question mark is returned.
320  *
321  * If Unicode is not available, read a single byte (assumed to be
322  * Latin-1).
323  * If Unicode is not available, IO_STDIO is in use, and text mode is
324  * set, do newline translation.  Text mode is likely to always be
325  * set—this function really shouldn’t be used in binary mode.
326  */
327 long zterp_io_getc(const zterp_io *io)
328 {
329   long ret;
330
331   if(!use_utf8_io)
332   {
333 #ifdef ZTERP_GLK
334     if(io->type == IO_GLK)
335     {
336       ret = glk_get_char_stream(io->file);
337     }
338     else
339 #endif
340     {
341       int c;
342
343       c = getc(io->fp);
344       if(c == EOF) ret = -1;
345       else         ret = c;
346
347       if(textmode(io) && c == '\n') ret = UNICODE_LINEFEED;
348     }
349   }
350   else
351   {
352     uint8_t c;
353
354     if(zterp_io_read(io, &c, sizeof c) != sizeof c)
355     {
356       ret = -1;
357     }
358     else if((c & 0x80) == 0) /* One byte. */
359     {
360       ret = c;
361     }
362     else if((c & 0xe0) == 0xc0) /* Two bytes. */
363     {
364       ret = (c & 0x1f) << 6;
365
366       if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
367
368       ret |= (c & 0x3f);
369     }
370     else if((c & 0xf0) == 0xe0) /* Three bytes. */
371     {
372       ret = (c & 0x0f) << 12;
373
374       if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
375
376       ret |= ((c & 0x3f) << 6);
377
378       if(!read_byte(io, &c)) return UNICODE_QUESTIONMARK;
379
380       ret |= (c & 0x3f);
381     }
382     else if((c & 0xf8) == 0xf0) /* Four bytes. */
383     {
384       /* The Z-machine doesn’t support Unicode this large, but at
385        * least try not to leave a partial character in the stream.
386        */
387       zterp_io_seek(io, 3, SEEK_CUR);
388
389       ret = UNICODE_QUESTIONMARK;
390     }
391     else /* Invalid value. */
392     {
393       ret = UNICODE_QUESTIONMARK;
394     }
395   }
396
397   if(ret > UINT16_MAX) ret = UNICODE_QUESTIONMARK;
398
399   return ret;
400 }
401
402 /* Write a Unicode character as UTF-8.
403  *
404  * If Unicode is not available, write the value out as a single Latin-1
405  * byte.  If it is too large for a byte, write out a question mark.
406  *
407  * If Unicode is not available, IO_STDIO is in use, and text mode is
408  * set, do newline translation.
409  *
410  * Text mode is likely to always be set—this function really shouldn’t
411  * be used in binary mode.
412  */
413 void zterp_io_putc(const zterp_io *io, uint16_t c)
414 {
415   if(!use_utf8_io)
416   {
417     if(c > UINT8_MAX) c = UNICODE_QUESTIONMARK;
418 #ifdef ZTERP_GLK
419     if(io->type == IO_GLK)
420     {
421       glk_put_char_stream(io->file, c);
422     }
423     else
424 #endif
425     {
426       if(textmode(io) && c == UNICODE_LINEFEED) c = '\n';
427       putc(c, io->fp);
428     }
429   }
430   else
431   {
432     uint8_t hi = c >> 8, lo = c & 0xff;
433
434 #define WRITE(c)        zterp_io_write(io, &(uint8_t){ c }, sizeof (uint8_t))
435     if(c < 128)
436     {
437       WRITE(c);
438     }
439     else if(c < 2048)
440     {
441       WRITE(0xc0 | (hi << 2) | (lo >> 6));
442       WRITE(0x80 | (lo & 0x3f));
443     }
444     else
445     {
446       WRITE(0xe0 | (hi >> 4));
447       WRITE(0x80 | ((hi << 2) & 0x3f) | (lo >> 6));
448       WRITE(0x80 | (lo & 0x3f));
449     }
450 #undef WRITE
451   }
452 }
453
454 long zterp_io_readline(const zterp_io *io, uint16_t *buf, size_t len)
455 {
456   long ret;
457
458   if(len > LONG_MAX) return -1;
459
460   for(ret = 0; ret < len; ret++)
461   {
462     long c = zterp_io_getc(io);
463
464     /* EOF before newline means there was a problem. */
465     if(c == -1) return -1;
466
467     /* Don’t count the newline. */
468     if(c == UNICODE_LINEFEED) break;
469
470     buf[ret] = c;
471   }
472
473   return ret;
474 }
475
476 long zterp_io_filesize(const zterp_io *io)
477 {
478   if(io->type == IO_STDIO && !textmode(io))
479   {
480     return zterp_os_filesize(io->fp);
481   }
482   else
483   {
484     return -1;
485   }
486 }
487
488 void zterp_io_flush(const zterp_io *io)
489 {
490   if(io == NULL || io->type != IO_STDIO || !(io->mode & (ZTERP_IO_WRONLY | ZTERP_IO_APPEND))) return;
491
492   fflush(io->fp);
493 }