Update Andrew Plotkin's unit tests
[projects/chimara/chimara.git] / interpreters / glulxe / string.c
1 /* string.c: Glulxe string and text functions.
2     Designed by Andrew Plotkin <erkyrath@eblong.com>
3     http://eblong.com/zarf/glulx/index.html
4 */
5
6 #include "glk.h"
7 #include "glulxe.h"
8
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)
14 #define iosys_Glk (2)
15
16 #define CACHEBITS (4)
17 #define CACHESIZE (1<<CACHEBITS) 
18 #define CACHEMASK (15)
19
20 typedef struct cacheblock_struct {
21   int depth; /* 1 to 4 */
22   int type;
23   union {
24     struct cacheblock_struct *branches;
25     unsigned char ch;
26     glui32 uch;
27     glui32 addr;
28   } u;
29 } cacheblock_t;
30
31 /* The current string-decoding tables, broken out into a fast and
32    easy-to-use form. */
33 static int tablecache_valid = FALSE;
34 static cacheblock_t tablecache;
35
36 static void stream_setup_unichar(void);
37
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;
44
45 static void dropcache(cacheblock_t *cablist);
46 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
47   int mask);
48 static void dumpcache(cacheblock_t *cablist, int count, int indent);
49
50 void stream_get_iosys(glui32 *mode, glui32 *rock)
51 {
52   *mode = iosys_mode;
53   *rock = iosys_rock;
54 }
55
56 static void stream_setup_unichar()
57 {
58 #ifdef GLK_MODULE_UNICODE
59
60   if (glk_gestalt(gestalt_Unicode, 0))
61     glkio_unichar_han_ptr = glk_put_char_uni;
62   else
63     glkio_unichar_han_ptr = glkio_unichar_nouni_han;
64
65 #else /* GLK_MODULE_UNICODE */
66
67   glkio_unichar_han_ptr = glkio_unichar_nouni_han;
68
69 #endif /* GLK_MODULE_UNICODE */
70 }
71
72 void stream_set_iosys(glui32 mode, glui32 rock)
73 {
74   switch (mode) {
75   default:
76     mode = 0;
77     /* ...and fall through to next case (no-op I/O). */
78   case iosys_None:
79     rock = 0;
80     stream_char_handler = nopio_char_han;
81     stream_unichar_handler = nopio_unichar_han;
82     break;
83   case iosys_Filter:
84     stream_char_handler = filio_char_han;
85     stream_unichar_handler = filio_unichar_han;
86     break;
87   case iosys_Glk:
88     if (!glkio_unichar_han_ptr)
89       stream_setup_unichar();
90     rock = 0;
91     stream_char_handler = glk_put_char;
92     stream_unichar_handler = glkio_unichar_han_ptr;
93     break;
94   }
95
96   iosys_mode = mode;
97   iosys_rock = rock;
98 }
99
100 static void nopio_char_han(unsigned char ch)
101 {
102 }
103
104 static void nopio_unichar_han(glui32 ch)
105 {
106 }
107
108 static void filio_char_han(unsigned char ch)
109 {
110   glui32 val = ch;
111   push_callstub(0, 0);
112   enter_function(iosys_rock, 1, &val);
113 }
114
115 static void filio_unichar_han(glui32 val)
116 {
117   push_callstub(0, 0);
118   enter_function(iosys_rock, 1, &val);
119 }
120
121 static void glkio_unichar_nouni_han(glui32 val)
122 {
123   /* Only used if the Glk library has no Unicode functions */
124   if (val > 0xFF)
125     val = '?';
126   glk_put_char(val);
127 }
128
129 /* stream_num():
130    Write a signed integer to the current output stream.
131 */
132 void stream_num(glsi32 val, int inmiddle, int charnum)
133 {
134   int ix = 0;
135   int res, jx;
136   char buf[16];
137   glui32 ival;
138
139   if (val == 0) {
140     buf[ix] = '0';
141     ix++;
142   }
143   else {
144     if (val < 0) 
145       ival = -val;
146     else 
147       ival = val;
148
149     while (ival != 0) {
150       buf[ix] = (ival % 10) + '0';
151       ix++;
152       ival /= 10;
153     }
154
155     if (val < 0) {
156       buf[ix] = '-';
157       ix++;
158     }
159   }
160
161   switch (iosys_mode) {
162
163   case iosys_Glk:
164     ix -= charnum;
165     while (ix > 0) {
166       ix--;
167       glk_put_char(buf[ix]);
168     }
169     break;
170
171   case iosys_Filter:
172     if (!inmiddle) {
173       push_callstub(0x11, 0);
174       inmiddle = TRUE;
175     }
176     if (charnum < ix) {
177       ival = buf[(ix-1)-charnum] & 0xFF;
178       pc = val;
179       push_callstub(0x12, charnum+1);
180       enter_function(iosys_rock, 1, &ival);
181       return;
182     }
183     break;
184
185   default:
186     break;
187
188   }
189
190   if (inmiddle) {
191     res = pop_callstub_string(&jx);
192     if (res) 
193       fatal_error("String-on-string call stub while printing number.");
194   }
195 }
196
197 /* stream_string():
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
201    the string type).
202 */
203 void stream_string(glui32 addr, int inmiddle, int bitnum)
204 {
205   int ch;
206   int type;
207   int alldone = FALSE;
208   int substring = (inmiddle != 0);
209   glui32 ival;
210
211   if (!addr)
212     fatal_error("Called stream_string with null address.");
213   
214   while (!alldone) {
215
216     if (inmiddle == 0) {
217       type = Mem1(addr);
218       if (type == 0xE2)
219         addr+=4;
220       else
221         addr++;
222       bitnum = 0;
223     }
224     else {
225       type = inmiddle;
226     }
227
228     if (type == 0xE1) {
229       if (tablecache_valid) {
230         int bits, numbits;
231         int readahead;
232         glui32 tmpaddr;
233         cacheblock_t *cablist;
234         int done = 0;
235
236         /* bitnum is already set right */
237         bits = Mem1(addr); 
238         if (bitnum)
239           bits >>= bitnum;
240         numbits = (8 - bitnum);
241         readahead = FALSE;
242
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. */
248           done = 1;
249         }
250
251         cablist = tablecache.u.branches;
252         while (!done) {
253           cacheblock_t *cab;
254
255           if (numbits < CACHEBITS) {
256             /* readahead is certainly false */
257             int newbyte = Mem1(addr+1);
258             bits |= (newbyte << numbits);
259             numbits += 8;
260             readahead = TRUE;
261           }
262
263           cab = &(cablist[bits & CACHEMASK]);
264           numbits -= cab->depth;
265           bits >>= cab->depth;
266           bitnum += cab->depth;
267           if (bitnum >= 8) {
268             addr += 1;
269             bitnum -= 8;
270             if (readahead) {
271               readahead = FALSE;
272             }
273             else {
274               int newbyte = Mem1(addr);
275               bits |= (newbyte << numbits);
276               numbits += 8;
277             }
278           }
279
280           switch (cab->type) {
281           case 0x00: /* non-leaf node */
282             cablist = cab->u.branches;
283             break;
284           case 0x01: /* string terminator */
285             done = 1;
286             break;
287           case 0x02: /* single character */
288             switch (iosys_mode) {
289             case iosys_Glk:
290               glk_put_char(cab->u.ch);
291               break;
292             case iosys_Filter: 
293               ival = cab->u.ch & 0xFF;
294               if (!substring) {
295                 push_callstub(0x11, 0);
296                 substring = TRUE;
297               }
298               pc = addr;
299               push_callstub(0x10, bitnum);
300               enter_function(iosys_rock, 1, &ival);
301               return;
302             }
303             cablist = tablecache.u.branches;
304             break;
305           case 0x04: /* single Unicode character */
306             switch (iosys_mode) {
307             case iosys_Glk:
308               glkio_unichar_han_ptr(cab->u.uch);
309               break;
310             case iosys_Filter: 
311               ival = cab->u.uch;
312               if (!substring) {
313                 push_callstub(0x11, 0);
314                 substring = TRUE;
315               }
316               pc = addr;
317               push_callstub(0x10, bitnum);
318               enter_function(iosys_rock, 1, &ival);
319               return;
320             }
321             cablist = tablecache.u.branches;
322             break;
323           case 0x03: /* C string */
324             switch (iosys_mode) {
325             case iosys_Glk:
326               for (tmpaddr=cab->u.addr; (ch=Mem1(tmpaddr)) != '\0'; tmpaddr++) 
327                 glk_put_char(ch);
328               cablist = tablecache.u.branches; 
329               break;
330             case iosys_Filter:
331               if (!substring) {
332                 push_callstub(0x11, 0);
333                 substring = TRUE;
334               }
335               pc = addr;
336               push_callstub(0x10, bitnum);
337               inmiddle = 0xE0;
338               addr = cab->u.addr;
339               done = 2;
340               break;
341             default:
342               cablist = tablecache.u.branches; 
343               break;
344             }
345             break;
346           case 0x05: /* C Unicode string */
347             switch (iosys_mode) {
348             case iosys_Glk:
349               for (tmpaddr=cab->u.addr; (ival=Mem4(tmpaddr)) != 0; tmpaddr+=4) 
350                 glkio_unichar_han_ptr(ival);
351               cablist = tablecache.u.branches; 
352               break;
353             case iosys_Filter:
354               if (!substring) {
355                 push_callstub(0x11, 0);
356                 substring = TRUE;
357               }
358               pc = addr;
359               push_callstub(0x10, bitnum);
360               inmiddle = 0xE2;
361               addr = cab->u.addr;
362               done = 2;
363               break;
364             default:
365               cablist = tablecache.u.branches; 
366               break;
367             }
368             break;
369           case 0x08:
370           case 0x09:
371           case 0x0A:
372           case 0x0B: 
373             {
374               glui32 oaddr;
375               int otype;
376               oaddr = cab->u.addr;
377               if (cab->type >= 0x09)
378                 oaddr = Mem4(oaddr);
379               if (cab->type == 0x0B)
380                 oaddr = Mem4(oaddr);
381               otype = Mem1(oaddr);
382               if (!substring) {
383                 push_callstub(0x11, 0);
384                 substring = TRUE;
385               }
386               if (otype >= 0xE0 && otype <= 0xFF) {
387                 pc = addr;
388                 push_callstub(0x10, bitnum);
389                 inmiddle = 0;
390                 addr = oaddr;
391                 done = 2;
392               }
393               else if (otype >= 0xC0 && otype <= 0xDF) {
394                 glui32 argc;
395                 glui32 *argv;
396                 if (cab->type == 0x0A || cab->type == 0x0B) {
397                   argc = Mem4(cab->u.addr+4);
398                   argv = pop_arguments(argc, cab->u.addr+8);
399                 }
400                 else {
401                   argc = 0;
402                   argv = NULL;
403                 }
404                 pc = addr;
405                 push_callstub(0x10, bitnum);
406                 enter_function(oaddr, argc, argv);
407                 return;
408               }
409               else {
410                 fatal_error("Unknown object while decoding string indirect reference.");
411               }
412             }
413             break;
414           default:
415             fatal_error("Unknown entity in string decoding (cached).");
416             break;
417           }
418         }
419         if (done > 1) {
420           continue; /* restart the top-level loop */
421         }
422       }
423       else { /* tablecache not valid */
424         glui32 node;
425         int byte;
426         int nodetype;
427         int done = 0;
428
429         if (!stringtable)
430           fatal_error("Attempted to print a compressed string with no table set.");
431         /* bitnum is already set right */
432         byte = Mem1(addr); 
433         if (bitnum)
434           byte >>= bitnum;
435         node = Mem4(stringtable+8);
436         while (!done) {
437           nodetype = Mem1(node);
438           node++;
439           switch (nodetype) {
440           case 0x00: /* non-leaf node */
441             if (byte & 1) 
442               node = Mem4(node+4);
443             else
444               node = Mem4(node+0);
445             if (bitnum == 7) {
446               bitnum = 0;
447               addr++;
448               byte = Mem1(addr);
449             }
450             else {
451               bitnum++;
452               byte >>= 1;
453             }
454             break;
455           case 0x01: /* string terminator */
456             done = 1;
457             break;
458           case 0x02: /* single character */
459             ch = Mem1(node);
460             switch (iosys_mode) {
461             case iosys_Glk:
462               glk_put_char(ch);
463               break;
464             case iosys_Filter: 
465               ival = ch & 0xFF;
466               if (!substring) {
467                 push_callstub(0x11, 0);
468                 substring = TRUE;
469               }
470               pc = addr;
471               push_callstub(0x10, bitnum);
472               enter_function(iosys_rock, 1, &ival);
473               return;
474             }
475             node = Mem4(stringtable+8);
476             break;
477           case 0x04: /* single Unicode character */
478             ival = Mem4(node);
479             switch (iosys_mode) {
480             case iosys_Glk:
481               glkio_unichar_han_ptr(ival);
482               break;
483             case iosys_Filter: 
484               if (!substring) {
485                 push_callstub(0x11, 0);
486                 substring = TRUE;
487               }
488               pc = addr;
489               push_callstub(0x10, bitnum);
490               enter_function(iosys_rock, 1, &ival);
491               return;
492             }
493             node = Mem4(stringtable+8);
494             break;
495           case 0x03: /* C string */
496             switch (iosys_mode) {
497             case iosys_Glk:
498               for (; (ch=Mem1(node)) != '\0'; node++) 
499                 glk_put_char(ch);
500               node = Mem4(stringtable+8);
501               break;
502             case iosys_Filter:
503               if (!substring) {
504                 push_callstub(0x11, 0);
505                 substring = TRUE;
506               }
507               pc = addr;
508               push_callstub(0x10, bitnum);
509               inmiddle = 0xE0;
510               addr = node;
511               done = 2;
512               break;
513             default:
514               node = Mem4(stringtable+8);
515               break;
516             }
517             break;
518           case 0x05: /* C Unicode string */
519             switch (iosys_mode) {
520             case iosys_Glk:
521               for (; (ival=Mem4(node)) != 0; node+=4) 
522                 glkio_unichar_han_ptr(ival);
523               node = Mem4(stringtable+8);
524               break;
525             case iosys_Filter:
526               if (!substring) {
527                 push_callstub(0x11, 0);
528                 substring = TRUE;
529               }
530               pc = addr;
531               push_callstub(0x10, bitnum);
532               inmiddle = 0xE2;
533               addr = node;
534               done = 2;
535               break;
536             default:
537               node = Mem4(stringtable+8);
538               break;
539             }
540             break;
541           case 0x08:
542           case 0x09:
543           case 0x0A:
544           case 0x0B: 
545             {
546               glui32 oaddr;
547               int otype;
548               oaddr = Mem4(node);
549               if (nodetype == 0x09 || nodetype == 0x0B)
550                 oaddr = Mem4(oaddr);
551               otype = Mem1(oaddr);
552               if (!substring) {
553                 push_callstub(0x11, 0);
554                 substring = TRUE;
555               }
556               if (otype >= 0xE0 && otype <= 0xFF) {
557                 pc = addr;
558                 push_callstub(0x10, bitnum);
559                 inmiddle = 0;
560                 addr = oaddr;
561                 done = 2;
562               }
563               else if (otype >= 0xC0 && otype <= 0xDF) {
564                 glui32 argc;
565                 glui32 *argv;
566                 if (nodetype == 0x0A || nodetype == 0x0B) {
567                   argc = Mem4(node+4);
568                   argv = pop_arguments(argc, node+8);
569                 }
570                 else {
571                   argc = 0;
572                   argv = NULL;
573                 }
574                 pc = addr;
575                 push_callstub(0x10, bitnum);
576                 enter_function(oaddr, argc, argv);
577                 return;
578               }
579               else {
580                 fatal_error("Unknown object while decoding string indirect reference.");
581               }
582             }
583             break;
584           default:
585             fatal_error("Unknown entity in string decoding.");
586             break;
587           }
588         }
589         if (done > 1) {
590           continue; /* restart the top-level loop */
591         }
592       }
593     }
594     else if (type == 0xE0) {
595       switch (iosys_mode) {
596       case iosys_Glk:
597         while (1) {
598           ch = Mem1(addr);
599           addr++;
600           if (ch == '\0')
601             break;
602           glk_put_char(ch);
603         }
604         break;
605       case iosys_Filter:
606         if (!substring) {
607           push_callstub(0x11, 0);
608           substring = TRUE;
609         }
610         ch = Mem1(addr);
611         addr++;
612         if (ch != '\0') {
613           ival = ch & 0xFF;
614           pc = addr;
615           push_callstub(0x13, 0);
616           enter_function(iosys_rock, 1, &ival);
617           return;
618         }
619         break;
620       }
621     }
622     else if (type == 0xE2) {
623       switch (iosys_mode) {
624       case iosys_Glk:
625         while (1) {
626           ival = Mem4(addr);
627           addr+=4;
628           if (ival == 0)
629             break;
630           glkio_unichar_han_ptr(ival);
631         }
632         break;
633       case iosys_Filter:
634         if (!substring) {
635           push_callstub(0x11, 0);
636           substring = TRUE;
637         }
638         ival = Mem4(addr);
639         addr+=4;
640         if (ival != 0) {
641           pc = addr;
642           push_callstub(0x14, 0);
643           enter_function(iosys_rock, 1, &ival);
644           return;
645         }
646         break;
647       }
648     }
649     else if (type >= 0xE0 && type <= 0xFF) {
650       fatal_error("Attempt to print unknown type of string.");
651     }
652     else {
653       fatal_error("Attempt to print non-string.");
654     }
655
656     if (!substring) {
657       /* Just get straight out. */
658       alldone = TRUE;
659     }
660     else {
661       /* Pop a stub and see what's to be done. */
662       addr = pop_callstub_string(&bitnum);
663       if (addr == 0) {
664         alldone = TRUE;
665       }
666       else {
667         inmiddle = 0xE1;
668       }
669     }
670   }
671 }
672
673 /* stream_get_table():
674    Get the current table address. 
675 */
676 glui32 stream_get_table()
677 {
678   return stringtable;
679 }
680
681 /* stream_set_table():
682    Set the current table address, and rebuild decoding cache. 
683 */
684 void stream_set_table(glui32 addr)
685 {
686   if (stringtable == addr)
687     return;
688
689   /* Drop cache. */
690   if (tablecache_valid) {
691     if (tablecache.type == 0)
692       dropcache(tablecache.u.branches);
693     tablecache.u.branches = NULL;
694     tablecache_valid = FALSE;
695   }
696
697   stringtable = addr;
698
699   if (stringtable) {
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;
710     }
711   }
712 }
713
714 static void buildcache(cacheblock_t *cablist, glui32 nodeaddr, int depth,
715   int mask)
716 {
717   int ix, type;
718
719   type = Mem1(nodeaddr);
720
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]);
726     cab->type = 0;
727     cab->depth = CACHEBITS;
728     cab->u.branches = list;
729     return;
730   }
731
732   if (type == 0) {
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)));
737     return;
738   }
739
740   /* Leaf node. */
741   nodeaddr++;
742   for (ix = mask; ix < CACHESIZE; ix += (1 << depth)) {
743     cacheblock_t *cab = &(cablist[ix]);
744     cab->type = type;
745     cab->depth = depth;
746     switch (type) {
747     case 0x02:
748       cab->u.ch = Mem1(nodeaddr);
749       break;
750     case 0x04:
751       cab->u.uch = Mem4(nodeaddr);
752       break;
753     case 0x03:
754     case 0x05:
755     case 0x0A:
756     case 0x0B:
757       cab->u.addr = nodeaddr;
758       break;
759     case 0x08:
760     case 0x09:
761       cab->u.addr = Mem4(nodeaddr);
762       break;
763     }
764   }
765 }
766
767 #if 0
768 #include <stdio.h>
769 static void dumpcache(cacheblock_t *cablist, int count, int indent)
770 {
771   int ix, jx;
772
773   for (ix=0; ix<count; ix++) {
774     cacheblock_t *cab = &(cablist[ix]); 
775     for (jx=0; jx<indent; jx++)
776       printf("  ");
777     printf("%X: ", ix);
778     switch (cab->type) {
779     case 0:
780       printf("...\n");
781       dumpcache(cab->u.branches, CACHESIZE, indent+1);
782       break;
783     case 1:
784       printf("<EOS>\n");
785       break;
786     case 2:
787       printf("0x%02X", cab->u.ch);
788       if (cab->u.ch < 32)
789         printf(" ''\n");
790       else
791         printf(" '%c'\n", cab->u.ch);
792       break;
793     default:
794       printf("type %02X, address %06lX\n", cab->type, cab->u.addr);
795       break;
796     }
797   }
798 }
799 #endif /* 0 */
800
801 static void dropcache(cacheblock_t *cablist)
802 {
803   int ix;
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;
809     }
810   }
811   glulx_free(cablist);
812 }
813
814 /* This misbehaves if a Glk function has more than one S argument. */
815
816 #define STATIC_TEMP_BUFSIZE (127)
817 static char temp_buf[STATIC_TEMP_BUFSIZE+1];
818
819 char *make_temp_string(glui32 addr)
820 {
821   int ix, len;
822   glui32 addr2;
823   char *res;
824
825   if (Mem1(addr) != 0xE0)
826     fatal_error("String argument to a Glk call must be unencoded.");
827   addr++;
828
829   for (addr2=addr; Mem1(addr2); addr2++) { };
830   len = (addr2 - addr);
831   if (len < STATIC_TEMP_BUFSIZE) {
832     res = temp_buf;
833   }
834   else {
835     res = (char *)glulx_malloc(len+1);
836     if (!res) 
837       fatal_error("Unable to allocate space for string argument to Glk call.");
838   }
839   
840   for (ix=0, addr2=addr; ix<len; ix++, addr2++) {
841     res[ix] = Mem1(addr2);
842   }
843   res[len] = '\0';
844
845   return res;
846 }
847
848 glui32 *make_temp_ustring(glui32 addr)
849 {
850   int ix, len;
851   glui32 addr2;
852   glui32 *res;
853
854   if (Mem1(addr) != 0xE2)
855     fatal_error("Ustring argument to a Glk call must be unencoded.");
856   addr+=4;
857
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;
862   }
863   else {
864     res = (glui32 *)glulx_malloc((len+1)*4);
865     if (!res) 
866       fatal_error("Unable to allocate space for ustring argument to Glk call.");
867   }
868   
869   for (ix=0, addr2=addr; ix<len; ix++, addr2+=4) {
870     res[ix] = Mem4(addr2);
871   }
872   res[len] = 0;
873
874   return res;
875 }
876
877 void free_temp_string(char *str)
878 {
879   if (str && str != temp_buf) 
880     glulx_free(str);
881 }
882
883 void free_temp_ustring(glui32 *str)
884 {
885   if (str && str != (glui32 *)temp_buf) 
886     glulx_free(str);
887 }
888