1
/* string.c: Glulxe string and text functions.
2
Designed by Andrew Plotkin <erkyrath@eblong.com>
3
http://eblong.com/zarf/glulx/index.html
9
static glui32 iosys_mode;
10
static glui32 iosys_rock;
11
/* These constants are defined in the Glulx spec. */
12
#define iosys_None (0)
13
#define iosys_Filter (1)
17
#define CACHESIZE (1<<CACHEBITS)
18
#define CACHEMASK (15)
20
typedef struct cacheblock_struct {
21
int depth; /* 1 to 4 */
24
struct cacheblock_struct *branches;
31
static int never_cache_stringtable = FALSE;
33
/* The current string-decoding tables, broken out into a fast and
35
static int tablecache_valid = FALSE;
36
static cacheblock_t tablecache;
38
static void stream_setup_unichar(void);
40
static void nopio_char_han(unsigned char ch);
41
static void filio_char_han(unsigned char ch);
42
static void nopio_unichar_han(glui32 ch);
43
static void filio_unichar_han(glui32 ch);
44
static void glkio_unichar_nouni_han(glui32 val);
45
static void (*glkio_unichar_han_ptr)(glui32 val) = NULL;
47
static void dropcache(cacheblock_t *cablist);
48
static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
50
static void dumpcache(cacheblock_t *cablist, int count, int indent);
52
void stream_get_iosys(glui32 *mode, glui32 *rock)
58
static void stream_setup_unichar()
60
#ifdef GLK_MODULE_UNICODE
62
if (glk_gestalt(gestalt_Unicode, 0))
63
glkio_unichar_han_ptr = glk_put_char_uni;
65
glkio_unichar_han_ptr = glkio_unichar_nouni_han;
67
#else /* GLK_MODULE_UNICODE */
69
glkio_unichar_han_ptr = glkio_unichar_nouni_han;
71
#endif /* GLK_MODULE_UNICODE */
74
void stream_set_iosys(glui32 mode, glui32 rock)
79
/* ...and fall through to next case (no-op I/O). */
82
stream_char_handler = nopio_char_han;
83
stream_unichar_handler = nopio_unichar_han;
86
stream_char_handler = filio_char_han;
87
stream_unichar_handler = filio_unichar_han;
90
if (!glkio_unichar_han_ptr)
91
stream_setup_unichar();
93
stream_char_handler = glk_put_char;
94
stream_unichar_handler = glkio_unichar_han_ptr;
102
static void nopio_char_han(unsigned char ch)
106
static void nopio_unichar_han(glui32 ch)
110
static void filio_char_han(unsigned char ch)
114
enter_function(iosys_rock, 1, &val);
117
static void filio_unichar_han(glui32 val)
120
enter_function(iosys_rock, 1, &val);
123
static void glkio_unichar_nouni_han(glui32 val)
125
/* Only used if the Glk library has no Unicode functions */
132
Write a signed integer to the current output stream.
134
void stream_num(glsi32 val, int inmiddle, int charnum)
152
buf[ix] = (ival % 10) + '0';
163
switch (iosys_mode) {
168
glk_put_char(buf[ix]);
174
push_callstub(0x11, 0);
177
res = pop_callstub_string(&jx);
179
fatal_error("String-on-string call stub while printing number.");
182
ival = buf[(ix-1)-charnum] & 0xFF;
184
push_callstub(0x12, charnum+1);
185
enter_function(iosys_rock, 1, &ival);
196
Write a Glulx string object to the current output stream.
197
inmiddle is zero if we are beginning a new string, or
198
nonzero if restarting one (E0/E1/E2, as appropriate for
201
void stream_string(glui32 addr, int inmiddle, int bitnum)
206
int substring = (inmiddle != 0);
210
fatal_error("Called stream_string with null address.");
227
if (tablecache_valid) {
231
cacheblock_t *cablist;
234
/* bitnum is already set right */
238
numbits = (8 - bitnum);
241
if (tablecache.type != 0) {
242
/* This is a bit of a cheat. If the top-level block is not
243
a branch, then it must be a string-terminator -- otherwise
244
the string would be an infinite repetition of that block.
245
We check for this case and bail immediately. */
249
cablist = tablecache.u.branches;
253
if (numbits < CACHEBITS) {
254
/* readahead is certainly false */
255
int newbyte = Mem1(addr+1);
256
bits |= (newbyte << numbits);
261
cab = &(cablist[bits & CACHEMASK]);
262
numbits -= cab->depth;
264
bitnum += cab->depth;
272
int newbyte = Mem1(addr);
273
bits |= (newbyte << numbits);
279
case 0x00: /* non-leaf node */
280
cablist = cab->u.branches;
282
case 0x01: /* string terminator */
285
case 0x02: /* single character */
286
switch (iosys_mode) {
288
glk_put_char(cab->u.ch);
291
ival = cab->u.ch & 0xFF;
293
push_callstub(0x11, 0);
297
push_callstub(0x10, bitnum);
298
enter_function(iosys_rock, 1, &ival);
301
cablist = tablecache.u.branches;
303
case 0x04: /* single Unicode character */
304
switch (iosys_mode) {
306
glkio_unichar_han_ptr(cab->u.uch);
311
push_callstub(0x11, 0);
315
push_callstub(0x10, bitnum);
316
enter_function(iosys_rock, 1, &ival);
319
cablist = tablecache.u.branches;
321
case 0x03: /* C string */
322
switch (iosys_mode) {
324
for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++)
326
cablist = tablecache.u.branches;
330
push_callstub(0x11, 0);
334
push_callstub(0x10, bitnum);
340
cablist = tablecache.u.branches;
344
case 0x05: /* C Unicode string */
345
switch (iosys_mode) {
347
for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4)
348
glkio_unichar_han_ptr(ival);
349
cablist = tablecache.u.branches;
353
push_callstub(0x11, 0);
357
push_callstub(0x10, bitnum);
363
cablist = tablecache.u.branches;
375
if (cab->type >= 0x09)
377
if (cab->type == 0x0B)
381
push_callstub(0x11, 0);
384
if (otype >= 0xE0 && otype <= 0xFF) {
386
push_callstub(0x10, bitnum);
391
else if (otype >= 0xC0 && otype <= 0xDF) {
394
if (cab->type == 0x0A || cab->type == 0x0B) {
395
argc = Mem4(cab->u.addr+4);
396
argv = pop_arguments(argc, cab->u.addr+8);
403
push_callstub(0x10, bitnum);
404
enter_function(oaddr, argc, argv);
408
fatal_error("Unknown object while decoding string indirect reference.");
413
fatal_error("Unknown entity in string decoding (cached).");
418
continue; /* restart the top-level loop */
421
else { /* tablecache not valid */
428
fatal_error("Attempted to print a compressed string with no table set.");
429
/* bitnum is already set right */
433
node = Mem4(stringtable+8);
435
nodetype = Mem1(node);
438
case 0x00: /* non-leaf node */
453
case 0x01: /* string terminator */
456
case 0x02: /* single character */
458
switch (iosys_mode) {
465
push_callstub(0x11, 0);
469
push_callstub(0x10, bitnum);
470
enter_function(iosys_rock, 1, &ival);
473
node = Mem4(stringtable+8);
475
case 0x04: /* single Unicode character */
477
switch (iosys_mode) {
479
glkio_unichar_han_ptr(ival);
483
push_callstub(0x11, 0);
487
push_callstub(0x10, bitnum);
488
enter_function(iosys_rock, 1, &ival);
491
node = Mem4(stringtable+8);
493
case 0x03: /* C string */
494
switch (iosys_mode) {
496
for (; (ch=Mem1(node)) != '\0'; node++)
498
node = Mem4(stringtable+8);
502
push_callstub(0x11, 0);
506
push_callstub(0x10, bitnum);
512
node = Mem4(stringtable+8);
516
case 0x05: /* C Unicode string */
517
switch (iosys_mode) {
519
for (; (ival=Mem4(node)) != 0; node+=4)
520
glkio_unichar_han_ptr(ival);
521
node = Mem4(stringtable+8);
525
push_callstub(0x11, 0);
529
push_callstub(0x10, bitnum);
535
node = Mem4(stringtable+8);
547
if (nodetype == 0x09 || nodetype == 0x0B)
551
push_callstub(0x11, 0);
554
if (otype >= 0xE0 && otype <= 0xFF) {
556
push_callstub(0x10, bitnum);
561
else if (otype >= 0xC0 && otype <= 0xDF) {
564
if (nodetype == 0x0A || nodetype == 0x0B) {
566
argv = pop_arguments(argc, node+8);
573
push_callstub(0x10, bitnum);
574
enter_function(oaddr, argc, argv);
578
fatal_error("Unknown object while decoding string indirect reference.");
583
fatal_error("Unknown entity in string decoding.");
588
continue; /* restart the top-level loop */
592
else if (type == 0xE0) {
593
switch (iosys_mode) {
605
push_callstub(0x11, 0);
613
push_callstub(0x13, 0);
614
enter_function(iosys_rock, 1, &ival);
620
else if (type == 0xE2) {
621
switch (iosys_mode) {
628
glkio_unichar_han_ptr(ival);
633
push_callstub(0x11, 0);
640
push_callstub(0x14, 0);
641
enter_function(iosys_rock, 1, &ival);
647
else if (type >= 0xE0 && type <= 0xFF) {
648
fatal_error("Attempt to print unknown type of string.");
651
fatal_error("Attempt to print non-string.");
655
/* Just get straight out. */
659
/* Pop a stub and see what's to be done. */
660
addr = pop_callstub_string(&bitnum);
671
/* stream_get_table():
672
Get the current table address.
674
glui32 stream_get_table()
679
/* stream_set_table():
680
Set the current table address, and rebuild decoding cache.
682
void stream_set_table(glui32 addr)
684
if (stringtable == addr)
688
if (tablecache_valid) {
689
if (tablecache.type == 0)
690
dropcache(tablecache.u.branches);
691
tablecache.u.branches = NULL;
692
tablecache_valid = FALSE;
698
/* Build cache. We can only do this if the table is entirely in ROM. */
699
glui32 tablelen = Mem4(stringtable);
700
glui32 rootaddr = Mem4(stringtable+8);
701
if (stringtable+tablelen <= ramstart && !never_cache_stringtable) {
702
buildcache(&tablecache, rootaddr, CACHEBITS, 0);
703
/* dumpcache(&tablecache, 1, 0); */
704
tablecache_valid = TRUE;
709
static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
714
type = Mem1(nodeaddr);
716
if (type == 0 && depth == CACHEBITS) {
717
cacheblock_t *list, *cab;
718
list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE);
719
buildcache(list, nodeaddr, 0, 0);
720
cab = &(cablist[mask]);
722
cab->depth = CACHEBITS;
723
cab->u.branches = list;
728
glui32 leftaddr = Mem4(nodeaddr+1);
729
glui32 rightaddr = Mem4(nodeaddr+5);
730
buildcache(cablist, leftaddr, depth+1, mask);
731
buildcache(cablist, rightaddr, depth+1, (mask | (1 << depth)));
737
for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
738
cacheblock_t *cab = &(cablist[ix]);
743
cab->u.ch = Mem1(nodeaddr);
746
cab->u.uch = Mem4(nodeaddr);
752
cab->u.addr = nodeaddr;
756
cab->u.addr = Mem4(nodeaddr);
764
static void dumpcache(cacheblock_t *cablist, int count, int indent)
768
for (ix=0; ix<count; ix++) {
769
cacheblock_t *cab = &(cablist[ix]);
770
for (jx=0; jx<indent; jx++)
776
dumpcache(cab->u.branches, CACHESIZE, indent+1);
782
printf("0x%02X", cab->u.ch);
786
printf(" '%c'\n", cab->u.ch);
789
printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
796
static void dropcache(cacheblock_t *cablist)
799
for (ix=0; ix<CACHESIZE; ix++) {
800
cacheblock_t *cab = &(cablist[ix]);
801
if (cab->type == 0) {
802
dropcache(cab->u.branches);
803
cab->u.branches = NULL;
809
/* This misbehaves if a Glk function has more than one S argument. */
811
#define STATIC_TEMP_BUFSIZE (127)
812
static char temp_buf[STATIC_TEMP_BUFSIZE+1];
814
char *make_temp_string(glui32 addr)
820
if (Mem1(addr) != 0xE0)
821
fatal_error("String argument to a Glk call must be unencoded.");
824
for (addr2=addr; Mem1(addr2); addr2++) { };
825
len = (addr2 - addr);
826
if (len < STATIC_TEMP_BUFSIZE) {
830
res = (char *)glulx_malloc(len+1);
832
fatal_error("Unable to allocate space for string argument to Glk call.");
835
for (ix=0, addr2=addr; ix<len; ix++, addr2++) {
836
res[ix] = Mem1(addr2);
843
glui32 *make_temp_ustring(glui32 addr)
849
if (Mem1(addr) != 0xE2)
850
fatal_error("Ustring argument to a Glk call must be unencoded.");
853
for (addr2=addr; Mem4(addr2); addr2+=4) { };
854
len = (addr2 - addr) / 4;
855
if ((len+1)*4 < STATIC_TEMP_BUFSIZE) {
856
res = (glui32 *)temp_buf;
859
res = (glui32 *)glulx_malloc((len+1)*4);
861
fatal_error("Unable to allocate space for ustring argument to Glk call.");
864
for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
865
res[ix] = Mem4(addr2);
872
void free_temp_string(char *str)
874
if (str && str != temp_buf)
878
void free_temp_ustring(glui32 *str)
880
if (str && str != (glui32 *)temp_buf)