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 /* The current string-decoding tables, broken out into a fast and
33 static int tablecache_valid = FALSE;
34 static cacheblock_t tablecache;
36 static void stream_setup_unichar(void);
38 static void nopio_char_han(unsigned char ch);
39 static void filio_char_han(unsigned char ch);
40 static void nopio_unichar_han(glui32 ch);
41 static void filio_unichar_han(glui32 ch);
42 static void glkio_unichar_nouni_han(glui32 val);
43 static void (*glkio_unichar_han_ptr)(glui32 val) = NULL;
45 static void dropcache(cacheblock_t *cablist);
46 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
48 static void dumpcache(cacheblock_t *cablist, int count, int indent);
50 void stream_get_iosys(glui32 *mode, glui32 *rock)
56 static void stream_setup_unichar()
58 #ifdef GLK_MODULE_UNICODE
60 if (glk_gestalt(gestalt_Unicode, 0))
61 glkio_unichar_han_ptr = glk_put_char_uni;
63 glkio_unichar_han_ptr = glkio_unichar_nouni_han;
65 #else /* GLK_MODULE_UNICODE */
67 glkio_unichar_han_ptr = glkio_unichar_nouni_han;
69 #endif /* GLK_MODULE_UNICODE */
72 void stream_set_iosys(glui32 mode, glui32 rock)
77 /* ...and fall through to next case (no-op I/O). */
80 stream_char_handler = nopio_char_han;
81 stream_unichar_handler = nopio_unichar_han;
84 stream_char_handler = filio_char_han;
85 stream_unichar_handler = filio_unichar_han;
88 if (!glkio_unichar_han_ptr)
89 stream_setup_unichar();
91 stream_char_handler = glk_put_char;
92 stream_unichar_handler = glkio_unichar_han_ptr;
100 static void nopio_char_han(unsigned char ch)
104 static void nopio_unichar_han(glui32 ch)
108 static void filio_char_han(unsigned char ch)
112 enter_function(iosys_rock, 1, &val);
115 static void filio_unichar_han(glui32 val)
118 enter_function(iosys_rock, 1, &val);
121 static void glkio_unichar_nouni_han(glui32 val)
123 /* Only used if the Glk library has no Unicode functions */
130 Write a signed integer to the current output stream.
132 void stream_num(glsi32 val, int inmiddle, int charnum)
150 buf[ix] = (ival % 10) + '0';
161 switch (iosys_mode) {
167 glk_put_char(buf[ix]);
173 push_callstub(0x11, 0);
177 ival = buf[(ix-1)-charnum] & 0xFF;
179 push_callstub(0x12, charnum+1);
180 enter_function(iosys_rock, 1, &ival);
191 res = pop_callstub_string(&jx);
193 fatal_error("String-on-string call stub while printing number.");
198 Write a Glulx string object to the current output stream.
199 inmiddle is zero if we are beginning a new string, or
200 nonzero if restarting one (E0/E1/E2, as appropriate for
203 void stream_string(glui32 addr, int inmiddle, int bitnum)
208 int substring = (inmiddle != 0);
212 fatal_error("Called stream_string with null address.");
229 if (tablecache_valid) {
233 cacheblock_t *cablist;
236 /* bitnum is already set right */
240 numbits = (8 - bitnum);
243 if (tablecache.type != 0) {
244 /* This is a bit of a cheat. If the top-level block is not
245 a branch, then it must be a string-terminator -- otherwise
246 the string would be an infinite repetition of that block.
247 We check for this case and bail immediately. */
251 cablist = tablecache.u.branches;
255 if (numbits < CACHEBITS) {
256 /* readahead is certainly false */
257 int newbyte = Mem1(addr+1);
258 bits |= (newbyte << numbits);
263 cab = &(cablist[bits & CACHEMASK]);
264 numbits -= cab->depth;
266 bitnum += cab->depth;
274 int newbyte = Mem1(addr);
275 bits |= (newbyte << numbits);
281 case 0x00: /* non-leaf node */
282 cablist = cab->u.branches;
284 case 0x01: /* string terminator */
287 case 0x02: /* single character */
288 switch (iosys_mode) {
290 glk_put_char(cab->u.ch);
293 ival = cab->u.ch & 0xFF;
295 push_callstub(0x11, 0);
299 push_callstub(0x10, bitnum);
300 enter_function(iosys_rock, 1, &ival);
303 cablist = tablecache.u.branches;
305 case 0x04: /* single Unicode character */
306 switch (iosys_mode) {
308 glkio_unichar_han_ptr(cab->u.uch);
313 push_callstub(0x11, 0);
317 push_callstub(0x10, bitnum);
318 enter_function(iosys_rock, 1, &ival);
321 cablist = tablecache.u.branches;
323 case 0x03: /* C string */
324 switch (iosys_mode) {
326 for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++)
328 cablist = tablecache.u.branches;
332 push_callstub(0x11, 0);
336 push_callstub(0x10, bitnum);
342 cablist = tablecache.u.branches;
346 case 0x05: /* C Unicode string */
347 switch (iosys_mode) {
349 for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4)
350 glkio_unichar_han_ptr(ival);
351 cablist = tablecache.u.branches;
355 push_callstub(0x11, 0);
359 push_callstub(0x10, bitnum);
365 cablist = tablecache.u.branches;
377 if (cab->type >= 0x09)
379 if (cab->type == 0x0B)
383 push_callstub(0x11, 0);
386 if (otype >= 0xE0 && otype <= 0xFF) {
388 push_callstub(0x10, bitnum);
393 else if (otype >= 0xC0 && otype <= 0xDF) {
396 if (cab->type == 0x0A || cab->type == 0x0B) {
397 argc = Mem4(cab->u.addr+4);
398 argv = pop_arguments(argc, cab->u.addr+8);
405 push_callstub(0x10, bitnum);
406 enter_function(oaddr, argc, argv);
410 fatal_error("Unknown object while decoding string indirect reference.");
415 fatal_error("Unknown entity in string decoding (cached).");
420 continue; /* restart the top-level loop */
423 else { /* tablecache not valid */
430 fatal_error("Attempted to print a compressed string with no table set.");
431 /* bitnum is already set right */
435 node = Mem4(stringtable+8);
437 nodetype = Mem1(node);
440 case 0x00: /* non-leaf node */
455 case 0x01: /* string terminator */
458 case 0x02: /* single character */
460 switch (iosys_mode) {
467 push_callstub(0x11, 0);
471 push_callstub(0x10, bitnum);
472 enter_function(iosys_rock, 1, &ival);
475 node = Mem4(stringtable+8);
477 case 0x04: /* single Unicode character */
479 switch (iosys_mode) {
481 glkio_unichar_han_ptr(ival);
485 push_callstub(0x11, 0);
489 push_callstub(0x10, bitnum);
490 enter_function(iosys_rock, 1, &ival);
493 node = Mem4(stringtable+8);
495 case 0x03: /* C string */
496 switch (iosys_mode) {
498 for (; (ch=Mem1(node)) != '\0'; node++)
500 node = Mem4(stringtable+8);
504 push_callstub(0x11, 0);
508 push_callstub(0x10, bitnum);
514 node = Mem4(stringtable+8);
518 case 0x05: /* C Unicode string */
519 switch (iosys_mode) {
521 for (; (ival=Mem4(node)) != 0; node+=4)
522 glkio_unichar_han_ptr(ival);
523 node = Mem4(stringtable+8);
527 push_callstub(0x11, 0);
531 push_callstub(0x10, bitnum);
537 node = Mem4(stringtable+8);
549 if (nodetype == 0x09 || nodetype == 0x0B)
553 push_callstub(0x11, 0);
556 if (otype >= 0xE0 && otype <= 0xFF) {
558 push_callstub(0x10, bitnum);
563 else if (otype >= 0xC0 && otype <= 0xDF) {
566 if (nodetype == 0x0A || nodetype == 0x0B) {
568 argv = pop_arguments(argc, node+8);
575 push_callstub(0x10, bitnum);
576 enter_function(oaddr, argc, argv);
580 fatal_error("Unknown object while decoding string indirect reference.");
585 fatal_error("Unknown entity in string decoding.");
590 continue; /* restart the top-level loop */
594 else if (type == 0xE0) {
595 switch (iosys_mode) {
607 push_callstub(0x11, 0);
615 push_callstub(0x13, 0);
616 enter_function(iosys_rock, 1, &ival);
622 else if (type == 0xE2) {
623 switch (iosys_mode) {
630 glkio_unichar_han_ptr(ival);
635 push_callstub(0x11, 0);
642 push_callstub(0x14, 0);
643 enter_function(iosys_rock, 1, &ival);
649 else if (type >= 0xE0 && type <= 0xFF) {
650 fatal_error("Attempt to print unknown type of string.");
653 fatal_error("Attempt to print non-string.");
657 /* Just get straight out. */
661 /* Pop a stub and see what's to be done. */
662 addr = pop_callstub_string(&bitnum);
673 /* stream_get_table():
674 Get the current table address.
676 glui32 stream_get_table()
681 /* stream_set_table():
682 Set the current table address, and rebuild decoding cache.
684 void stream_set_table(glui32 addr)
686 if (stringtable == addr)
690 if (tablecache_valid) {
691 if (tablecache.type == 0)
692 dropcache(tablecache.u.branches);
693 tablecache.u.branches = NULL;
694 tablecache_valid = FALSE;
700 /* Build cache. We can only do this if the table is entirely in ROM. */
701 glui32 tablelen = Mem4(stringtable);
702 glui32 rootaddr = Mem4(stringtable+8);
703 int cache_stringtable = (stringtable+tablelen <= ramstart);
704 /* cache_stringtable = TRUE; ...for testing only */
705 /* cache_stringtable = FALSE; ...for testing only */
706 if (cache_stringtable) {
707 buildcache(&tablecache, rootaddr, CACHEBITS, 0);
708 /* dumpcache(&tablecache, 1, 0); */
709 tablecache_valid = TRUE;
714 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
719 type = Mem1(nodeaddr);
721 if (type == 0 && depth == CACHEBITS) {
722 cacheblock_t *list, *cab;
723 list = (cacheblock_t *)glulx_malloc(sizeof(cacheblock_t) * CACHESIZE);
724 buildcache(list, nodeaddr, 0, 0);
725 cab = &(cablist[mask]);
727 cab->depth = CACHEBITS;
728 cab->u.branches = list;
733 glui32 leftaddr = Mem4(nodeaddr+1);
734 glui32 rightaddr = Mem4(nodeaddr+5);
735 buildcache(cablist, leftaddr, depth+1, mask);
736 buildcache(cablist, rightaddr, depth+1, (mask | (1 << depth)));
742 for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
743 cacheblock_t *cab = &(cablist[ix]);
748 cab->u.ch = Mem1(nodeaddr);
751 cab->u.uch = Mem4(nodeaddr);
757 cab->u.addr = nodeaddr;
761 cab->u.addr = Mem4(nodeaddr);
769 static void dumpcache(cacheblock_t *cablist, int count, int indent)
773 for (ix=0; ix<count; ix++) {
774 cacheblock_t *cab = &(cablist[ix]);
775 for (jx=0; jx<indent; jx++)
781 dumpcache(cab->u.branches, CACHESIZE, indent+1);
787 printf("0x%02X", cab->u.ch);
791 printf(" '%c'\n", cab->u.ch);
794 printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
801 static void dropcache(cacheblock_t *cablist)
804 for (ix=0; ix<CACHESIZE; ix++) {
805 cacheblock_t *cab = &(cablist[ix]);
806 if (cab->type == 0) {
807 dropcache(cab->u.branches);
808 cab->u.branches = NULL;
814 /* This misbehaves if a Glk function has more than one S argument. */
816 #define STATIC_TEMP_BUFSIZE (127)
817 static char temp_buf[STATIC_TEMP_BUFSIZE+1];
819 char *make_temp_string(glui32 addr)
825 if (Mem1(addr) != 0xE0)
826 fatal_error("String argument to a Glk call must be unencoded.");
829 for (addr2=addr; Mem1(addr2); addr2++) { };
830 len = (addr2 - addr);
831 if (len < STATIC_TEMP_BUFSIZE) {
835 res = (char *)glulx_malloc(len+1);
837 fatal_error("Unable to allocate space for string argument to Glk call.");
840 for (ix=0, addr2=addr; ix<len; ix++, addr2++) {
841 res[ix] = Mem1(addr2);
848 glui32 *make_temp_ustring(glui32 addr)
854 if (Mem1(addr) != 0xE2)
855 fatal_error("Ustring argument to a Glk call must be unencoded.");
858 for (addr2=addr; Mem4(addr2); addr2+=4) { };
859 len = (addr2 - addr) / 4;
860 if ((len+1)*4 < STATIC_TEMP_BUFSIZE) {
861 res = (glui32 *)temp_buf;
864 res = (glui32 *)glulx_malloc((len+1)*4);
866 fatal_error("Unable to allocate space for ustring argument to Glk call.");
869 for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
870 res[ix] = Mem4(addr2);
877 void free_temp_string(char *str)
879 if (str && str != temp_buf)
883 void free_temp_ustring(glui32 *str)
885 if (str && str != (glui32 *)temp_buf)