/* string.c: Glulxe string and text functions. Designed by Andrew Plotkin <erkyrath@eblong.com> http://eblong.com/zarf/glulx/index.html*/ #include "glk.h"#include "glulxe.h" static glui32 iosys_mode;static glui32 iosys_rock;/* These constants are defined in the Glulx spec. */#define iosys_None (0)#define iosys_Filter (1)#define iosys_Glk (2) #define CACHEBITS (4)#define CACHESIZE (1<<CACHEBITS) #define CACHEMASK (15) typedef struct cacheblock_struct { int depth; /* 1 to 4 */ int type; union { struct cacheblock_struct *branches; unsigned char ch; glui32 uch; glui32 addr; } u;} cacheblock_t; /* The current string-decoding tables, broken out into a fast and easy-to-use form. */static int tablecache_valid = FALSE;static cacheblock_t tablecache; static void stream_setup_unichar(void); static void nopio_char_han(unsigned char ch);static void filio_char_han(unsigned char ch);static void nopio_unichar_han(glui32 ch);static void filio_unichar_han(glui32 ch);static void glkio_unichar_nouni_han(glui32 val);static void (*glkio_unichar_han_ptr)(glui32 val) = NULL; static void dropcache(cacheblock_t *cablist);static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth, int mask);static void dumpcache(cacheblock_t *cablist, int count, int indent); void stream_get_iosys(glui32 *mode, glui32 *rock){ *mode = iosys_mode; *rock = iosys_rock;} static void stream_setup_unichar(){#ifdef GLK_MODULE_UNICODE if (glk_gestalt(gestalt_Unicode, 0)) glkio_unichar_han_ptr = glk_put_char_uni; else glkio_unichar_han_ptr = glkio_unichar_nouni_han; #else /* GLK_MODULE_UNICODE */ glkio_unichar_han_ptr = glkio_unichar_nouni_han; #endif /* GLK_MODULE_UNICODE */} void stream_set_iosys(glui32 mode, glui32 rock){ switch (mode) { default: mode = 0; /* ...and fall through to next case (no-op I/O). */ case iosys_None: rock = 0; stream_char_handler = nopio_char_han; stream_unichar_handler = nopio_unichar_han; break; case iosys_Filter: stream_char_handler = filio_char_han; stream_unichar_handler = filio_unichar_han; break; case iosys_Glk: if (!glkio_unichar_han_ptr) stream_setup_unichar(); rock = 0; stream_char_handler = glk_put_char; stream_unichar_handler = glkio_unichar_han_ptr; break; } iosys_mode = mode; iosys_rock = rock;} static void nopio_char_han(unsigned char ch){} static void nopio_unichar_han(glui32 ch){} static void filio_char_han(unsigned char ch){ glui32 val = ch; push_callstub(0, 0); enter_function(iosys_rock, 1, &val);} static void filio_unichar_han(glui32 val){ push_callstub(0, 0); enter_function(iosys_rock, 1, &val);} static void glkio_unichar_nouni_han(glui32 val){ /* Only used if the Glk library has no Unicode functions */ if (val > 0xFF) val = '?'; glk_put_char(val);} /* stream_num(): Write a signed integer to the current output stream.*/void stream_num(glsi32 val, int inmiddle, int charnum){ int ix = 0; int res, jx; char buf[16]; glui32 ival; if (val == 0) { buf[ix] = '0'; ix++; } else { if (val < 0) ival = -val; else ival = val; while (ival != 0) { buf[ix] = (ival % 10) + '0'; ix++; ival /= 10; } if (val < 0) { buf[ix] = '-'; ix++; } } switch (iosys_mode) { case iosys_Glk: ix -= charnum; while (ix > 0) { ix--; glk_put_char(buf[ix]); } break; case iosys_Filter: if (!inmiddle) { push_callstub(0x11, 0); inmiddle = TRUE; } if (charnum < ix) { ival = buf[(ix-1)-charnum] & 0xFF; pc = val; push_callstub(0x12, charnum+1); enter_function(iosys_rock, 1, &ival); return; } break; default: break; } if (inmiddle) { res = pop_callstub_string(&jx); if (res) fatal_error("String-on-string call stub while printing number."); }} /* stream_string(): Write a Glulx string object to the current output stream. inmiddle is zero if we are beginning a new string, or nonzero if restarting one (E0/E1/E2, as appropriate for the string type).*/void stream_string(glui32 addr, int inmiddle, int bitnum){ int ch; int type; int alldone = FALSE; int substring = (inmiddle != 0); glui32 ival; if (!addr) fatal_error("Called stream_string with null address."); while (!alldone) { if (inmiddle == 0) { type = Mem1(addr); if (type == 0xE2) addr+=4; else addr++; bitnum = 0; } else { type = inmiddle; } if (type == 0xE1) { if (tablecache_valid) { int bits, numbits; int readahead; glui32 tmpaddr; cacheblock_t *cablist; int done = 0; /* bitnum is already set right */ bits = Mem1(addr); if (bitnum) bits >>= bitnum; numbits = (8 - bitnum); readahead = FALSE; if (tablecache.type != 0) { /* This is a bit of a cheat. If the top-level block is not a branch, then it must be a string-terminator -- otherwise the string would be an infinite repetition of that block. We check for this case and bail immediately. */ done = 1; } cablist = tablecache.u.branches; while (!done) { cacheblock_t *cab; if (numbits < CACHEBITS) { /* readahead is certainly false */ int newbyte = Mem1(addr+1); bits |= (newbyte << numbits); numbits += 8; readahead = TRUE; } cab = &(cablist[bits & CACHEMASK]); numbits -= cab->depth; bits >>= cab->depth; bitnum += cab->depth; if (bitnum >= 8) { addr += 1; bitnum -= 8; if (readahead) { readahead = FALSE; } else { int newbyte = Mem1(addr); bits |= (newbyte << numbits); numbits += 8; } } switch (cab->type) { case 0x00: /* non-leaf node */ cablist = cab->u.branches; break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ switch (iosys_mode) { case iosys_Glk: glk_put_char(cab->u.ch); break; case iosys_Filter: ival = cab->u.ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x04: /* single Unicode character */ switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(cab->u.uch); break; case iosys_Filter: ival = cab->u.uch; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++) glk_put_char(ch); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE0; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x05: /* C Unicode string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4) glkio_unichar_han_ptr(ival); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE2; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x08: case 0x09: case 0x0A: case 0x0B: { glui32 oaddr; int otype; oaddr = cab->u.addr; if (cab->type >= 0x09) oaddr = Mem4(oaddr); if (cab->type == 0x0B) oaddr = Mem4(oaddr); otype = Mem1(oaddr); if (!substring) { push_callstub(0x11, 0); substring = TRUE; } if (otype >= 0xE0 && otype <= 0xFF) { pc = addr; push_callstub(0x10, bitnum); inmiddle = 0; addr = oaddr; done = 2; } else if (otype >= 0xC0 && otype <= 0xDF) { glui32 argc; glui32 *argv; if (cab->type == 0x0A || cab->type == 0x0B) { argc = Mem4(cab->u.addr+4); argv = pop_arguments(argc, cab->u.addr+8); } else { argc = 0; argv = NULL; } pc = addr; push_callstub(0x10, bitnum); enter_function(oaddr, argc, argv); return; } else { fatal_error("Unknown object while decoding string indirect reference."); } } break; default: fatal_error("Unknown entity in string decoding (cached)."); break; } } if (done > 1) { continue; /* restart the top-level loop */ } } else { /* tablecache not valid */ glui32 node; int byte; int nodetype; int done = 0; if (!stringtable) fatal_error("Attempted to print a compressed string with no table set."); /* bitnum is already set right */ byte = Mem1(addr); if (bitnum) byte >>= bitnum; node = Mem4(stringtable+8); while (!done) { nodetype = Mem1(node); node++; switch (nodetype) { case 0x00: /* non-leaf node */ if (byte & 1) node = Mem4(node+4); else node = Mem4(node+0); if (bitnum == 7) { bitnum = 0; addr++; byte = Mem1(addr); } else { bitnum++; byte >>= 1; } break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ ch = Mem1(node); switch (iosys_mode) { case iosys_Glk: glk_put_char(ch); break; case iosys_Filter: ival = ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x04: /* single Unicode character */ ival = Mem4(node); switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(ival); break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (; (ch=Mem1(node)) != '\0'; node++) glk_put_char(ch); node = Mem4(stringtable+8);Lines 1–500 of 888. Load more
/* string.c: Glulxe string and text functions. Designed by Andrew Plotkin <erkyrath@eblong.com> http://eblong.com/zarf/glulx/index.html */ #include "glk.h" #include "glulxe.h" static glui32 iosys_mode; static glui32 iosys_rock; /* These constants are defined in the Glulx spec. */ #define iosys_None (0) #define iosys_Filter (1) #define iosys_Glk (2) #define CACHEBITS (4) #define CACHESIZE (1<<CACHEBITS) #define CACHEMASK (15) typedef struct cacheblock_struct { int depth; /* 1 to 4 */ int type; union { struct cacheblock_struct *branches; unsigned char ch; glui32 uch; glui32 addr; } u; } cacheblock_t; /* The current string-decoding tables, broken out into a fast and easy-to-use form. */ static int tablecache_valid = FALSE; static cacheblock_t tablecache; static void stream_setup_unichar(void); static void nopio_char_han(unsigned char ch); static void filio_char_han(unsigned char ch); static void nopio_unichar_han(glui32 ch); static void filio_unichar_han(glui32 ch); static void glkio_unichar_nouni_han(glui32 val); static void (*glkio_unichar_han_ptr)(glui32 val) = NULL; static void dropcache(cacheblock_t *cablist); static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth, int mask); static void dumpcache(cacheblock_t *cablist, int count, int indent); void stream_get_iosys(glui32 *mode, glui32 *rock) { *mode = iosys_mode; *rock = iosys_rock; } static void stream_setup_unichar() { #ifdef GLK_MODULE_UNICODE if (glk_gestalt(gestalt_Unicode, 0)) glkio_unichar_han_ptr = glk_put_char_uni; else glkio_unichar_han_ptr = glkio_unichar_nouni_han; #else /* GLK_MODULE_UNICODE */ glkio_unichar_han_ptr = glkio_unichar_nouni_han; #endif /* GLK_MODULE_UNICODE */ } void stream_set_iosys(glui32 mode, glui32 rock) { switch (mode) { default: mode = 0; /* ...and fall through to next case (no-op I/O). */ case iosys_None: rock = 0; stream_char_handler = nopio_char_han; stream_unichar_handler = nopio_unichar_han; break; case iosys_Filter: stream_char_handler = filio_char_han; stream_unichar_handler = filio_unichar_han; break; case iosys_Glk: if (!glkio_unichar_han_ptr) stream_setup_unichar(); rock = 0; stream_char_handler = glk_put_char; stream_unichar_handler = glkio_unichar_han_ptr; break; } iosys_mode = mode; iosys_rock = rock; } static void nopio_char_han(unsigned char ch) { } static void nopio_unichar_han(glui32 ch) { } static void filio_char_han(unsigned char ch) { glui32 val = ch; push_callstub(0, 0); enter_function(iosys_rock, 1, &val); } static void filio_unichar_han(glui32 val) { push_callstub(0, 0); enter_function(iosys_rock, 1, &val); } static void glkio_unichar_nouni_han(glui32 val) { /* Only used if the Glk library has no Unicode functions */ if (val > 0xFF) val = '?'; glk_put_char(val); } /* stream_num(): Write a signed integer to the current output stream. */ void stream_num(glsi32 val, int inmiddle, int charnum) { int ix = 0; int res, jx; char buf[16]; glui32 ival; if (val == 0) { buf[ix] = '0'; ix++; } else { if (val < 0) ival = -val; else ival = val; while (ival != 0) { buf[ix] = (ival % 10) + '0'; ix++; ival /= 10; } if (val < 0) { buf[ix] = '-'; ix++; } } switch (iosys_mode) { case iosys_Glk: ix -= charnum; while (ix > 0) { ix--; glk_put_char(buf[ix]); } break; case iosys_Filter: if (!inmiddle) { push_callstub(0x11, 0); inmiddle = TRUE; } if (charnum < ix) { ival = buf[(ix-1)-charnum] & 0xFF; pc = val; push_callstub(0x12, charnum+1); enter_function(iosys_rock, 1, &ival); return; } break; default: break; } if (inmiddle) { res = pop_callstub_string(&jx); if (res) fatal_error("String-on-string call stub while printing number."); } } /* stream_string(): Write a Glulx string object to the current output stream. inmiddle is zero if we are beginning a new string, or nonzero if restarting one (E0/E1/E2, as appropriate for the string type). */ void stream_string(glui32 addr, int inmiddle, int bitnum) { int ch; int type; int alldone = FALSE; int substring = (inmiddle != 0); glui32 ival; if (!addr) fatal_error("Called stream_string with null address."); while (!alldone) { if (inmiddle == 0) { type = Mem1(addr); if (type == 0xE2) addr+=4; else addr++; bitnum = 0; } else { type = inmiddle; } if (type == 0xE1) { if (tablecache_valid) { int bits, numbits; int readahead; glui32 tmpaddr; cacheblock_t *cablist; int done = 0; /* bitnum is already set right */ bits = Mem1(addr); if (bitnum) bits >>= bitnum; numbits = (8 - bitnum); readahead = FALSE; if (tablecache.type != 0) { /* This is a bit of a cheat. If the top-level block is not a branch, then it must be a string-terminator -- otherwise the string would be an infinite repetition of that block. We check for this case and bail immediately. */ done = 1; } cablist = tablecache.u.branches; while (!done) { cacheblock_t *cab; if (numbits < CACHEBITS) { /* readahead is certainly false */ int newbyte = Mem1(addr+1); bits |= (newbyte << numbits); numbits += 8; readahead = TRUE; } cab = &(cablist[bits & CACHEMASK]); numbits -= cab->depth; bits >>= cab->depth; bitnum += cab->depth; if (bitnum >= 8) { addr += 1; bitnum -= 8; if (readahead) { readahead = FALSE; } else { int newbyte = Mem1(addr); bits |= (newbyte << numbits); numbits += 8; } } switch (cab->type) { case 0x00: /* non-leaf node */ cablist = cab->u.branches; break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ switch (iosys_mode) { case iosys_Glk: glk_put_char(cab->u.ch); break; case iosys_Filter: ival = cab->u.ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x04: /* single Unicode character */ switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(cab->u.uch); break; case iosys_Filter: ival = cab->u.uch; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } cablist = tablecache.u.branches; break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++) glk_put_char(ch); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE0; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x05: /* C Unicode string */ switch (iosys_mode) { case iosys_Glk: for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4) glkio_unichar_han_ptr(ival); cablist = tablecache.u.branches; break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); inmiddle = 0xE2; addr = cab->u.addr; done = 2; break; default: cablist = tablecache.u.branches; break; } break; case 0x08: case 0x09: case 0x0A: case 0x0B: { glui32 oaddr; int otype; oaddr = cab->u.addr; if (cab->type >= 0x09) oaddr = Mem4(oaddr); if (cab->type == 0x0B) oaddr = Mem4(oaddr); otype = Mem1(oaddr); if (!substring) { push_callstub(0x11, 0); substring = TRUE; } if (otype >= 0xE0 && otype <= 0xFF) { pc = addr; push_callstub(0x10, bitnum); inmiddle = 0; addr = oaddr; done = 2; } else if (otype >= 0xC0 && otype <= 0xDF) { glui32 argc; glui32 *argv; if (cab->type == 0x0A || cab->type == 0x0B) { argc = Mem4(cab->u.addr+4); argv = pop_arguments(argc, cab->u.addr+8); } else { argc = 0; argv = NULL; } pc = addr; push_callstub(0x10, bitnum); enter_function(oaddr, argc, argv); return; } else { fatal_error("Unknown object while decoding string indirect reference."); } } break; default: fatal_error("Unknown entity in string decoding (cached)."); break; } } if (done > 1) { continue; /* restart the top-level loop */ } } else { /* tablecache not valid */ glui32 node; int byte; int nodetype; int done = 0; if (!stringtable) fatal_error("Attempted to print a compressed string with no table set."); /* bitnum is already set right */ byte = Mem1(addr); if (bitnum) byte >>= bitnum; node = Mem4(stringtable+8); while (!done) { nodetype = Mem1(node); node++; switch (nodetype) { case 0x00: /* non-leaf node */ if (byte & 1) node = Mem4(node+4); else node = Mem4(node+0); if (bitnum == 7) { bitnum = 0; addr++; byte = Mem1(addr); } else { bitnum++; byte >>= 1; } break; case 0x01: /* string terminator */ done = 1; break; case 0x02: /* single character */ ch = Mem1(node); switch (iosys_mode) { case iosys_Glk: glk_put_char(ch); break; case iosys_Filter: ival = ch & 0xFF; if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x04: /* single Unicode character */ ival = Mem4(node); switch (iosys_mode) { case iosys_Glk: glkio_unichar_han_ptr(ival); break; case iosys_Filter: if (!substring) { push_callstub(0x11, 0); substring = TRUE; } pc = addr; push_callstub(0x10, bitnum); enter_function(iosys_rock, 1, &ival); return; } node = Mem4(stringtable+8); break; case 0x03: /* C string */ switch (iosys_mode) { case iosys_Glk: for (; (ch=Mem1(node)) != '\0'; node++) glk_put_char(ch); node = Mem4(stringtable+8);