888 lines · 21.7 KB · C
RawDownload
/* 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.