Update to Inform v6.42
[inform.git] / src / memory.c
1 /* ------------------------------------------------------------------------- */
2 /*   "memory" : Memory management and ICL memory setting commands            */
3 /*                                                                           */
4 /*   Part of Inform 6.42                                                     */
5 /*   copyright (c) Graham Nelson 1993 - 2024                                 */
6 /*                                                                           */
7 /* Inform is free software: you can redistribute it and/or modify            */
8 /* it under the terms of the GNU General Public License as published by      */
9 /* the Free Software Foundation, either version 3 of the License, or         */
10 /* (at your option) any later version.                                       */
11 /*                                                                           */
12 /* Inform is distributed in the hope that it will be useful,                 */
13 /* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
14 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
15 /* GNU General Public License for more details.                              */
16 /*                                                                           */
17 /* You should have received a copy of the GNU General Public License         */
18 /* along with Inform. If not, see https://gnu.org/licenses/                  */
19 /*                                                                           */
20 /* ------------------------------------------------------------------------- */
21
22 #include "header.h"
23
24 size_t malloced_bytes=0;               /* Total amount of memory allocated   */
25
26 /* Wrappers for malloc(), realloc(), etc.
27
28    Note that all of these functions call fatalerror_memory_out() on failure.
29    This is a fatal error and does not return. However, we check my_malloc()
30    return values anyway as a matter of good habit.
31  */
32
33 #ifdef PC_QUICKC
34
35 extern void *my_malloc(size_t size, char *whatfor)
36 {   char _huge *c;
37     if (memout_switch)
38         printf("Allocating %ld bytes for %s\n",size,whatfor);
39     if (size==0) return(NULL);
40     c=(char _huge *)halloc(size,1);
41     malloced_bytes+=size;
42     if (c==0) fatalerror_memory_out(size, 1, whatfor);
43     return(c);
44 }
45
46 extern void my_realloc(void *pointer, size_t oldsize, size_t size, 
47     char *whatfor)
48 {   char _huge *c;
49     if (size==0) {
50         my_free(pointer, whatfor);
51         return;
52     }
53     c=halloc(size,1);
54     malloced_bytes+=(size-oldsize);
55     if (c==0) fatalerror_memory_out(size, 1, whatfor);
56     if (memout_switch)
57         printf("Increasing allocation from %ld to %ld bytes for %s was (%08lx) now (%08lx)\n",
58             (long int) oldsize, (long int) size, whatfor,
59             (long int) (*(int **)pointer), 
60             (long int) c);
61     memcpy(c, *(int **)pointer, MIN(oldsize, size));
62     hfree(*(int **)pointer);
63     *(int **)pointer = c;
64 }
65
66 extern void *my_calloc(size_t size, size_t howmany, char *whatfor)
67 {   void _huge *c;
68     if (memout_switch)
69         printf("Allocating %d bytes: array (%ld entries size %ld) for %s\n",
70             size*howmany,howmany,size,whatfor);
71     if ((size*howmany) == 0) return(NULL);
72     c=(void _huge *)halloc(howmany*size,1);
73     malloced_bytes+=size*howmany;
74     if (c==0) fatalerror_memory_out(size, howmany, whatfor);
75     return(c);
76 }
77
78 extern void my_recalloc(void *pointer, size_t size, size_t oldhowmany, 
79     int32 howmany, char *whatfor)
80 {   void _huge *c;
81     if (size*howmany==0) {
82         my_free(pointer, whatfor);
83         return;
84     }
85     c=(void _huge *)halloc(size*howmany,1);
86     malloced_bytes+=size*(howmany-oldhowmany);
87     if (c==0) fatalerror_memory_out(size, howmany, whatfor);
88     if (memout_switch)
89         printf("Increasing allocation from %ld to %ld bytes: array (%ld entries size %ld) for %s was (%08lx) now (%08lx)\n",
90             ((long int)size) * ((long int)oldhowmany),
91             ((long int)size) * ((long int)howmany),
92             (long int)howmany, (long int)size, whatfor,
93             (long int) *(int **)pointer, (long int) c);
94     memcpy(c, *(int **)pointer, MIN(size*oldhowmany, size*howmany));
95     hfree(*(int **)pointer);
96     *(int **)pointer = c;
97 }
98
99 #else
100
101 extern void *my_malloc(size_t size, char *whatfor)
102 {   char *c;
103     if (size==0) return(NULL);
104     c=malloc(size);
105     malloced_bytes+=size;
106     if (c==0) fatalerror_memory_out(size, 1, whatfor);
107     if (memout_switch)
108         printf("Allocating %ld bytes for %s at (%p)\n",
109             (long int) size, whatfor, c);
110     return(c);
111 }
112
113 extern void my_realloc(void *pointer, size_t oldsize, size_t size, 
114     char *whatfor)
115 {   void *c;
116     if (size==0) {
117         my_free(pointer, whatfor);
118         return;
119     }
120     c=realloc(*(int **)pointer,  size);
121     malloced_bytes+=(size-oldsize);
122     if (c==0) fatalerror_memory_out(size, 1, whatfor);
123     if (memout_switch)
124         printf("Increasing allocation from %ld to %ld bytes for %s was (%p) now (%p)\n",
125             (long int) oldsize, (long int) size, whatfor, pointer, c);
126     *(int **)pointer = c;
127 }
128
129 extern void *my_calloc(size_t size, size_t howmany, char *whatfor)
130 {   void *c;
131     if (size*howmany==0) return(NULL);
132     c=calloc(howmany, size);
133     malloced_bytes+=size*howmany;
134     if (c==0) fatalerror_memory_out(size, howmany, whatfor);
135     if (memout_switch)
136         printf("Allocating %ld bytes: array (%ld entries size %ld) \
137 for %s at (%p)\n",
138             ((long int)size) * ((long int)howmany),
139             (long int)howmany,(long int)size, whatfor, c);
140     return(c);
141 }
142
143 extern void my_recalloc(void *pointer, size_t size, size_t oldhowmany, 
144     size_t howmany, char *whatfor)
145 {   void *c;
146     if (size*howmany==0) {
147         my_free(pointer, whatfor);
148         return;
149     }
150     c=realloc(*(int **)pointer, size*howmany); 
151     malloced_bytes+=size*(howmany-oldhowmany);
152     if (c==0) fatalerror_memory_out(size, howmany, whatfor);
153     if (memout_switch)
154         printf("Increasing allocation from %ld to %ld bytes: array (%ld entries size %ld) for %s was (%p) now (%p)\n",
155             ((long int)size) * ((long int)oldhowmany),
156             ((long int)size) * ((long int)howmany),
157             (long int)howmany, (long int)size, whatfor,
158             pointer, c);
159     *(int **)pointer = c;
160 }
161
162 #endif
163
164 extern void my_free(void *pointer, char *whatitwas)
165 {
166     if (*(int **)pointer != NULL)
167     {   if (memout_switch)
168             printf("Freeing memory for %s at (%p)\n",
169                 whatitwas, pointer);
170 #ifdef PC_QUICKC
171         hfree(*(int **)pointer);
172 #else
173         free(*(int **)pointer);
174 #endif
175         *(int **)pointer = NULL;
176     }
177 }
178
179 /* ------------------------------------------------------------------------- */
180 /*   A dynamic memory array. This grows as needed (but never shrinks).       */
181 /*   Call ensure_memory_list_available(N) before accessing array item N-1.   */
182 /*                                                                           */
183 /*   whatfor must be a static string describing the list. initalloc is       */
184 /*   (optionally) the number of items to allocate right away.                */
185 /*                                                                           */
186 /*   You typically initialise this with extpointer referring to an array of  */
187 /*   structs or whatever type you need. Whenever the memory list grows, the  */
188 /*   external array will be updated to refer to the new data.                */
189 /*                                                                           */
190 /*   Add "#define DEBUG_MEMLISTS" to allocate exactly the number of items    */
191 /*   needed, rather than increasing allocations exponentially. This is very  */
192 /*   slow but it lets us track down array overruns.                          */
193 /* ------------------------------------------------------------------------- */
194
195 void initialise_memory_list(memory_list *ML, size_t itemsize, size_t initalloc, void **extpointer, char *whatfor)
196 {
197     #ifdef DEBUG_MEMLISTS
198     initalloc = 0;          /* No initial allocation */
199     #endif
200     
201     ML->whatfor = whatfor;
202     ML->itemsize = itemsize;
203     ML->count = 0;
204     ML->data = NULL;
205     ML->extpointer = extpointer;
206
207     if (initalloc) {
208         ML->count = initalloc;
209         ML->data = my_calloc(ML->itemsize, ML->count, ML->whatfor);
210         if (ML->data == NULL) return;
211     }
212
213     if (ML->extpointer)
214         *(ML->extpointer) = ML->data;
215 }
216
217 void deallocate_memory_list(memory_list *ML)
218 {
219     ML->itemsize = 0;
220     ML->count = 0;
221     
222     if (ML->data)
223         my_free(&(ML->data), ML->whatfor);
224
225     if (ML->extpointer)
226         *(ML->extpointer) = NULL;
227     ML->extpointer = NULL;
228 }
229
230 /* After this is called, at least count items will be available in the list.
231    That is, you can freely access array[0] through array[count-1]. */
232 void ensure_memory_list_available(memory_list *ML, size_t count)
233 {
234     size_t oldcount;
235     
236     if (ML->itemsize == 0) {
237         /* whatfor is also null! */
238         compiler_error("memory: attempt to access uninitialized memory_list");
239         return;
240     }
241
242     if (ML->count >= count) {
243         return;
244     }
245
246     oldcount = ML->count;
247     ML->count = 2*count+8;  /* Allow headroom for future growth */
248     
249     #ifdef DEBUG_MEMLISTS
250     ML->count = count;      /* No headroom */
251     #endif
252     
253     if (ML->data == NULL)
254         ML->data = my_calloc(ML->itemsize, ML->count, ML->whatfor);
255     else
256         my_recalloc(&(ML->data), ML->itemsize, oldcount, ML->count, ML->whatfor);
257     if (ML->data == NULL) return;
258
259     if (ML->extpointer)
260         *(ML->extpointer) = ML->data;
261 }
262
263 /* ------------------------------------------------------------------------- */
264 /*   Where the memory settings are declared as variables                     */
265 /* ------------------------------------------------------------------------- */
266
267 int HASH_TAB_SIZE;
268 int MAX_ABBREVS;
269 int MAX_DYNAMIC_STRINGS;
270 int MAX_LOCAL_VARIABLES;
271 int DICT_WORD_SIZE; /* number of characters in a dict word */
272 int DICT_CHAR_SIZE; /* (glulx) 1 for one-byte chars, 4 for Unicode chars */
273 int DICT_WORD_BYTES; /* DICT_WORD_SIZE*DICT_CHAR_SIZE */
274 int ZCODE_HEADER_EXT_WORDS; /* (zcode 1.0) requested header extension size */
275 int ZCODE_HEADER_FLAGS_3; /* (zcode 1.1) value to place in Flags 3 word */
276 int ZCODE_LESS_DICT_DATA; /* (zcode) use 2 data bytes per dict word instead of 3 */
277 int ZCODE_MAX_INLINE_STRING; /* (zcode) length of string literals that can be inlined */
278 int NUM_ATTR_BYTES;
279 int GLULX_OBJECT_EXT_BYTES; /* (glulx) extra bytes for each object record */
280 int32 MAX_STACK_SIZE;
281 int32 MEMORY_MAP_EXTENSION;
282 int WARN_UNUSED_ROUTINES; /* 0: no, 1: yes except in system files, 2: yes always */
283 int OMIT_UNUSED_ROUTINES; /* 0: no, 1: yes */
284 int STRIP_UNREACHABLE_LABELS; /* 0: no, 1: yes (default) */
285 int OMIT_SYMBOL_TABLE; /* 0: no, 1: yes */
286 int LONG_DICT_FLAG_BUG; /* 0: no bug, 1: bug (default for historic reasons) */
287 int TRANSCRIPT_FORMAT; /* 0: classic, 1: prefixed */
288
289 /* The way memory sizes are set causes great nuisance for those parameters
290    which have different defaults under Z-code and Glulx. We have to get
291    the defaults right whether the user sets "-G $HUGE" or "$HUGE -G". 
292    And an explicit value set by the user should override both defaults. */
293 static int DICT_WORD_SIZE_z, DICT_WORD_SIZE_g;
294 static int NUM_ATTR_BYTES_z, NUM_ATTR_BYTES_g;
295 static int MAX_DYNAMIC_STRINGS_z, MAX_DYNAMIC_STRINGS_g;
296
297 /* ------------------------------------------------------------------------- */
298 /*   Memory control from the command line                                    */
299 /* ------------------------------------------------------------------------- */
300
301 static void list_memory_sizes(void)
302 {   printf("+--------------------------------------+\n");
303     printf("|  %25s = %-7s |\n","Memory setting","Value");
304     printf("+--------------------------------------+\n");
305     printf("|  %25s = %-7d |\n","MAX_ABBREVS",MAX_ABBREVS);
306     printf("|  %25s = %-7d |\n","NUM_ATTR_BYTES",NUM_ATTR_BYTES);
307     printf("|  %25s = %-7d |\n","DICT_WORD_SIZE",DICT_WORD_SIZE);
308     if (glulx_mode)
309       printf("|  %25s = %-7d |\n","DICT_CHAR_SIZE",DICT_CHAR_SIZE);
310     printf("|  %25s = %-7d |\n","MAX_DYNAMIC_STRINGS",MAX_DYNAMIC_STRINGS);
311     printf("|  %25s = %-7d |\n","HASH_TAB_SIZE",HASH_TAB_SIZE);
312     if (!glulx_mode)
313       printf("|  %25s = %-7d |\n","ZCODE_HEADER_EXT_WORDS",ZCODE_HEADER_EXT_WORDS);
314     if (!glulx_mode)
315       printf("|  %25s = %-7d |\n","ZCODE_HEADER_FLAGS_3",ZCODE_HEADER_FLAGS_3);
316     if (!glulx_mode)
317       printf("|  %25s = %-7d |\n","ZCODE_LESS_DICT_DATA",ZCODE_LESS_DICT_DATA);
318     if (!glulx_mode)
319       printf("|  %25s = %-7d |\n","ZCODE_MAX_INLINE_STRING",ZCODE_MAX_INLINE_STRING);
320     printf("|  %25s = %-7d |\n","INDIV_PROP_START", INDIV_PROP_START);
321     if (glulx_mode)
322       printf("|  %25s = %-7d |\n","MEMORY_MAP_EXTENSION",
323         MEMORY_MAP_EXTENSION);
324     if (glulx_mode)
325       printf("|  %25s = %-7d |\n","GLULX_OBJECT_EXT_BYTES",
326         GLULX_OBJECT_EXT_BYTES);
327     if (glulx_mode)
328       printf("|  %25s = %-7ld |\n","MAX_STACK_SIZE",
329            (long int) MAX_STACK_SIZE);
330     printf("|  %25s = %-7d |\n","TRANSCRIPT_FORMAT",TRANSCRIPT_FORMAT);
331     printf("|  %25s = %-7d |\n","WARN_UNUSED_ROUTINES",WARN_UNUSED_ROUTINES);
332     printf("|  %25s = %-7d |\n","OMIT_UNUSED_ROUTINES",OMIT_UNUSED_ROUTINES);
333     printf("|  %25s = %-7d |\n","STRIP_UNREACHABLE_LABELS",STRIP_UNREACHABLE_LABELS);
334     printf("|  %25s = %-7d |\n","OMIT_SYMBOL_TABLE",OMIT_SYMBOL_TABLE);
335     printf("|  %25s = %-7d |\n","LONG_DICT_FLAG_BUG",LONG_DICT_FLAG_BUG);
336     printf("+--------------------------------------+\n");
337 }
338
339 extern void set_memory_sizes(void)
340 {
341     HASH_TAB_SIZE      = 512;
342     DICT_CHAR_SIZE = 1;
343     DICT_WORD_SIZE_z = 6;
344     DICT_WORD_SIZE_g = 9;
345     NUM_ATTR_BYTES_z = 6;
346     NUM_ATTR_BYTES_g = 7;
347     MAX_ABBREVS = 64;
348     MAX_DYNAMIC_STRINGS_z = 32;
349     MAX_DYNAMIC_STRINGS_g = 100;
350     /* Backwards-compatible behavior: allow for a unicode table
351        whether we need one or not. The user can set this to zero if
352        there's no unicode table. */
353     ZCODE_HEADER_EXT_WORDS = 3;
354     ZCODE_HEADER_FLAGS_3 = 0;
355     ZCODE_LESS_DICT_DATA = 0;
356     ZCODE_MAX_INLINE_STRING = 32;
357     GLULX_OBJECT_EXT_BYTES = 0;
358     MEMORY_MAP_EXTENSION = 0;
359     /* We estimate the default Glulx stack size at 4096. That's about
360        enough for 90 nested function calls with 8 locals each -- the
361        same capacity as the Z-Spec's suggestion for Z-machine stack
362        size. Note that Inform 7 wants more stack; I7-generated code
363        sets MAX_STACK_SIZE to 65536 by default. */
364     MAX_STACK_SIZE = 4096;
365     OMIT_UNUSED_ROUTINES = 0;
366     WARN_UNUSED_ROUTINES = 0;
367     STRIP_UNREACHABLE_LABELS = 1;
368     OMIT_SYMBOL_TABLE = 0;
369     LONG_DICT_FLAG_BUG = 1;
370     TRANSCRIPT_FORMAT = 0;
371
372     adjust_memory_sizes();
373 }
374
375 extern void adjust_memory_sizes()
376 {
377   if (!glulx_mode) {
378     DICT_WORD_SIZE = DICT_WORD_SIZE_z;
379     NUM_ATTR_BYTES = NUM_ATTR_BYTES_z;
380     MAX_DYNAMIC_STRINGS = MAX_DYNAMIC_STRINGS_z;
381     INDIV_PROP_START = 64;
382   }
383   else {
384     DICT_WORD_SIZE = DICT_WORD_SIZE_g;
385     NUM_ATTR_BYTES = NUM_ATTR_BYTES_g;
386     MAX_DYNAMIC_STRINGS = MAX_DYNAMIC_STRINGS_g;
387     INDIV_PROP_START = 256;
388   }
389 }
390
391 static void explain_parameter(char *command)
392 {   printf("\n");
393     if (strcmp(command,"HASH_TAB_SIZE")==0)
394     {   printf(
395 "  HASH_TAB_SIZE is the size of the hash tables used for the heaviest \n\
396   symbols banks.\n");
397         return;
398     }
399     if (strcmp(command,"DICT_WORD_SIZE")==0)
400     {   printf(
401 "  DICT_WORD_SIZE is the number of characters in a dictionary word. In \n\
402   Z-code this is always 6 (only 4 are used in v3 games). In Glulx it \n\
403   can be any number.\n");
404         return;
405     }
406     if (strcmp(command,"DICT_CHAR_SIZE")==0)
407     {   printf(
408 "  DICT_CHAR_SIZE is the byte size of one character in the dictionary. \n\
409   (This is only meaningful in Glulx, since Z-code has compressed dictionary \n\
410   words.) It can be either 1 (the default) or 4 (to enable full Unicode \n\
411   input.)\n");
412         return;
413     }
414     if (strcmp(command,"NUM_ATTR_BYTES")==0)
415     {   printf(
416 "  NUM_ATTR_BYTES is the space used to store attribute flags. Each byte \n\
417   stores eight attributes. In Z-code this is always 6 (only 4 are used in \n\
418   v3 games). In Glulx it can be any number which is a multiple of four, \n\
419   plus three.\n");
420         return;
421     }
422     if (strcmp(command,"ZCODE_HEADER_EXT_WORDS")==0)
423     {   printf(
424 "  ZCODE_HEADER_EXT_WORDS is the number of words in the Z-code header \n\
425   extension table (Z-Spec 1.0). The -W switch also sets this. It defaults \n\
426   to 3, but can be set higher. (It can be set lower if no Unicode \n\
427   translation table is created.)\n");
428         return;
429     }
430     if (strcmp(command,"ZCODE_HEADER_FLAGS_3")==0)
431     {   printf(
432 "  ZCODE_HEADER_FLAGS_3 is the value to store in the Flags 3 word of the \n\
433   header extension table (Z-Spec 1.1).\n");
434         return;
435     }
436     if (strcmp(command,"ZCODE_LESS_DICT_DATA")==0)
437     {   printf(
438 "  ZCODE_LESS_DICT_DATA, if set, provides each dict word with two data bytes\n\
439   rather than three. (Z-code only.)\n");
440         return;
441     }
442     if (strcmp(command,"ZCODE_MAX_INLINE_STRING")==0)
443     {   printf(
444 "  ZCODE_MAX_INLINE_STRING is the length beyond which string literals cannot\n\
445   be inlined in assembly opcodes. (Z-code only.)\n");
446         return;
447     }
448     if (strcmp(command,"GLULX_OBJECT_EXT_BYTES")==0)
449     {   printf(
450 "  GLULX_OBJECT_EXT_BYTES is an amount of additional space to add to each \n\
451   object record. It is initialized to zero bytes, and the game is free to \n\
452   use it as desired. (This is only meaningful in Glulx, since Z-code \n\
453   specifies the object structure.)\n");
454         return;
455     }
456     if (strcmp(command,"MAX_ABBREVS")==0)
457     {   printf(
458 "  MAX_ABBREVS is the maximum number of declared abbreviations.  It is not \n\
459   allowed to exceed 96 in Z-code. (This is not meaningful in Glulx, where \n\
460   there is no limit on abbreviations.)\n");
461         return;
462     }
463     if (strcmp(command,"MAX_DYNAMIC_STRINGS")==0)
464     {   printf(
465 "  MAX_DYNAMIC_STRINGS is the maximum number of string substitution variables\n\
466   (\"@00\" or \"@(0)\").  It is not allowed to exceed 96 in Z-code.\n");
467         return;
468     }
469     if (strcmp(command,"INDIV_PROP_START")==0)
470     {   printf(
471 "  Properties 1 to INDIV_PROP_START-1 are common properties; individual\n\
472   properties are numbered INDIV_PROP_START and up.\n");
473         return;
474     }
475     if (strcmp(command,"MAX_STACK_SIZE")==0)
476     {
477         printf(
478 "  MAX_STACK_SIZE is the maximum size (in bytes) of the interpreter stack \n\
479   during gameplay. (Glulx only)\n");
480         return;
481     }
482     if (strcmp(command,"MEMORY_MAP_EXTENSION")==0)
483     {
484         printf(
485 "  MEMORY_MAP_EXTENSION is the number of bytes (all zeroes) to map into \n\
486   memory after the game file. (Glulx only)\n");
487         return;
488     }
489     if (strcmp(command,"TRANSCRIPT_FORMAT")==0)
490     {
491         printf(
492 "  TRANSCRIPT_FORMAT, if set to 1, adjusts the gametext.txt transcript for \n\
493   easier machine processing; each line will be prefixed by its context.\n");
494         return;
495     }
496     if (strcmp(command,"WARN_UNUSED_ROUTINES")==0)
497     {
498         printf(
499 "  WARN_UNUSED_ROUTINES, if set to 2, will display a warning for each \n\
500   routine in the game file which is never called. (This includes \n\
501   routines called only from uncalled routines, etc.) If set to 1, will warn \n\
502   only about functions in game code, not in the system library.\n");
503         return;
504     }
505     if (strcmp(command,"OMIT_UNUSED_ROUTINES")==0)
506     {
507         printf(
508 "  OMIT_UNUSED_ROUTINES, if set to 1, will avoid compiling unused routines \n\
509   into the game file.\n");
510         return;
511     }
512     if (strcmp(command,"STRIP_UNREACHABLE_LABELS")==0)
513     {
514         printf(
515 "  STRIP_UNREACHABLE_LABELS, if set to 1, will skip labels in unreachable \n\
516   statements. Jumping to a skipped label is an error. If 0, all labels \n\
517   will be compiled, at the cost of less optimized code. The default is 1.\n");
518         return;
519     }
520     if (strcmp(command,"OMIT_SYMBOL_TABLE")==0)
521     {
522         printf(
523 "  OMIT_SYMBOL_TABLE, if set to 1, will skip compiling debug symbol names \n\
524   into the game file.\n");
525         return;
526     }
527     if (strcmp(command,"LONG_DICT_FLAG_BUG")==0)
528     {
529         printf(
530 "  LONG_DICT_FLAG_BUG, if set to 0, will fix the old bug which ignores \n\
531   the '//p' flag in long dictionary words. If 1, the buggy behavior is \n\
532   retained.\n");
533         return;
534     }
535     if (strcmp(command,"SERIAL")==0)
536     {
537         printf(
538 "  SERIAL, if set, will be used as the six digit serial number written into \n\
539   the header of the output file.\n");
540         return;
541     }
542
543     printf("No such memory setting as \"%s\"\n",command);
544
545     return;
546 }
547
548 /* Parse a decimal number as an int32. Return true if a valid number
549    was found; otherwise print a warning and return false.
550
551    Anything over nine digits is considered an overflow; we report a
552    warning but return +/- 999999999 (and true). This is not entirely
553    clever about leading zeroes ("0000000001" is treated as an
554    overflow) but this is better than trying to detect genuine
555    overflows in a long.
556
557    (Some Glulx settings might conceivably want to go up to $7FFFFFFF,
558    which is a ten-digit number, but we're not going to allow that
559    today.)
560
561    This used to rely on atoi(), and we retain the atoi() behavior of
562    ignoring garbage characters after a valid decimal number.
563  */
564 static int parse_memory_setting(char *str, char *label, int32 *result)
565 {
566     char *cx = str;
567     char *ex;
568     long val;
569
570     *result = 0;
571
572     while (*cx == ' ') cx++;
573
574     val = strtol(cx, &ex, 10);    
575
576     if (ex == cx) {
577         printf("Bad numerical setting in $ command \"%s=%s\"\n",
578             label, str);
579         return 0;
580     }
581
582     if (*cx == '-') {
583         if (ex > cx+10) {
584             val = -999999999;
585             printf("Numerical setting underflowed in $ command \"%s=%s\" (limiting to %ld)\n",
586                 label, str, val);
587         }
588     }
589     else {
590         if (ex > cx+9) {
591             val = 999999999;
592             printf("Numerical setting overflowed in $ command \"%s=%s\" (limiting to %ld)\n",
593                 label, str, val);
594         }
595     }
596
597     *result = (int32)val;
598     return 1;
599 }
600
601 static void add_predefined_symbol(char *command)
602 {
603     int ix;
604     
605     int value = 0;
606     char *valpos = NULL;
607     
608     for (ix=0; command[ix]; ix++) {
609         if (command[ix] == '=') {
610             valpos = command+(ix+1);
611             command[ix] = '\0';
612             break;
613         }
614     }
615     
616     for (ix=0; command[ix]; ix++) {
617         if ((ix == 0 && isdigit(command[ix]))
618             || !(isalnum(command[ix]) || command[ix] == '_')) {
619             printf("Attempt to define invalid symbol: %s\n", command);
620             return;
621         }
622     }
623
624     if (valpos) {
625         if (!parse_memory_setting(valpos, command, &value)) {
626             return;
627         };
628     }
629
630     add_config_symbol_definition(command, value);
631 }
632
633 static void set_trace_option(char *command)
634 {
635     char *cx;
636     int value;
637
638     /* Parse options of the form STRING or STRING=NUM. (The $! has already been eaten.) If the string is null or empty, show help. */
639     
640     if (!command || *command == '\0') {
641         printf("The full list of trace options:\n\n");
642         printf("  ACTIONS: show actions defined\n");
643         printf("  ASM: trace assembly (same as -a)\n");
644         printf("    ASM=2: also show hex dumps\n");
645         printf("    ASM=3: also show branch optimization info\n");
646         printf("    ASM=4: more verbose branch info\n");
647         printf("  BPATCH: show backpatch results\n");
648         printf("    BPATCH=2: also show markers added\n");
649         printf("  DICT: display the dictionary table\n");
650         printf("    DICT=2: also the byte encoding of entries\n");
651         printf("  EXPR: show expression trees\n");
652         printf("    EXPR=2: more verbose\n");
653         printf("    EXPR=3: even more verbose\n");
654         printf("  FILES: show files opened\n");
655         printf("  FINDABBREVS: show selection decisions during abbreviation optimization\n    (only meaningful with -u)\n");
656         printf("    FINDABBREVS=2: also show three-letter-block decisions\n");
657         printf("  FREQ: show how efficient abbreviations were (same as -f)\n    (only meaningful with -e)\n");
658         printf("  MAP: print memory map of the virtual machine (same as -z)\n");
659         printf("    MAP=2: also show percentage of VM that each segment occupies\n");
660         printf("    MAP=3: also show number of bytes that each segment occupies\n");
661         printf("  MEM: show internal memory allocations\n");
662         printf("  OBJECTS: display the object table\n");
663         printf("  PROPS: show attributes and properties defined\n");
664         printf("  RUNTIME: show game function calls at runtime (same as -g)\n");
665         printf("    RUNTIME=2: also show library calls (not supported in Glulx)\n");
666         printf("    RUNTIME=3: also show veneer calls (not supported in Glulx)\n");
667         printf("  STATS: give compilation statistics (same as -s)\n");
668         printf("  SYMBOLS: display the symbol table\n");
669         printf("    SYMBOLS=2: also show compiler-defined symbols\n");
670         printf("  SYMDEF: show when symbols are noticed and defined\n");
671         printf("  TOKENS: show token lexing\n");
672         printf("    TOKENS=2: also show token types\n");
673         printf("    TOKENS=3: also show lexical context\n");
674         printf("  VERBS: display the verb grammar table\n");
675         return;
676     }
677
678     for (cx=command; *cx && *cx != '='; cx++) {
679         if (!(*cx >= 'A' && *cx <= 'Z')) {
680             printf("Invalid $! trace command \"%s\"\n", command);
681             return;
682         }
683     }
684
685     value = 1;
686     if (*cx == '=') {
687         char *ex;
688         value = strtol(cx+1, &ex, 10);
689         
690         if (ex == cx+1 || *ex != '\0' || value < 0) {
691             printf("Bad numerical setting in $! trace command \"%s\"\n", command);
692             return;
693         }
694         
695         *cx = '\0';
696     }
697
698     /* We accept some reasonable synonyms, including plausible singular/plural confusion. */
699     
700     if (strcmp(command, "ASSEMBLY")==0 || strcmp(command, "ASM")==0) {
701         asm_trace_setting = value;
702     }
703     else if (strcmp(command, "ACTION")==0 || strcmp(command, "ACTIONS")==0) {
704         printactions_switch = value;
705     }
706     else if (strcmp(command, "BPATCH")==0 || strcmp(command, "BACKPATCH")==0) {
707         bpatch_trace_setting = value;
708     }
709     else if (strcmp(command, "DICTIONARY")==0 || strcmp(command, "DICT")==0) {
710         list_dict_setting = value;
711     }
712     else if (strcmp(command, "EXPR")==0 || strcmp(command, "EXPRESSION")==0 || strcmp(command, "EXPRESSIONS")==0) {
713         expr_trace_setting = value;
714     }
715     else if (strcmp(command, "FILE")==0 || strcmp(command, "FILES")==0) {
716         files_trace_setting = value;
717     }
718     else if (strcmp(command, "FINDABBREV")==0 || strcmp(command, "FINDABBREVS")==0) {
719         optabbrevs_trace_setting = value;
720     }
721     else if (strcmp(command, "FREQUENCY")==0 || strcmp(command, "FREQUENCIES")==0 || strcmp(command, "FREQ")==0) {
722         frequencies_setting = value;
723     }
724     else if (strcmp(command, "MAP")==0) {
725         memory_map_setting = value;
726     }
727     else if (strcmp(command, "MEM")==0 || strcmp(command, "MEMORY")==0) {
728         memout_switch = value;
729     }
730     else if (strcmp(command, "OBJECTS")==0 || strcmp(command, "OBJECT")==0 || strcmp(command, "OBJS")==0 || strcmp(command, "OBJ")==0) {
731         list_objects_setting = value;
732     }
733     else if (strcmp(command, "PROP")==0 || strcmp(command, "PROPERTY")==0 || strcmp(command, "PROPS")==0 || strcmp(command, "PROPERTIES")==0) {
734         printprops_switch = value;
735     }
736     else if (strcmp(command, "RUNTIME")==0) {
737         trace_fns_setting = value;
738     }
739     else if (strcmp(command, "STATISTICS")==0 || strcmp(command, "STATS")==0 || strcmp(command, "STAT")==0) {
740         statistics_switch = value;
741     }
742     else if (strcmp(command, "SYMBOLS")==0 || strcmp(command, "SYMBOL")==0) {
743         list_symbols_setting = value;
744     }
745     else if (strcmp(command, "SYMDEF")==0 || strcmp(command, "SYMBOLDEF")==0) {
746         symdef_trace_setting = value;
747     }
748     else if (strcmp(command, "TOKEN")==0 || strcmp(command, "TOKENS")==0) {
749         tokens_trace_setting = value;
750     }
751     else if (strcmp(command, "VERBS")==0 || strcmp(command, "VERB")==0) {
752         list_verbs_setting = value;
753     }
754     else {
755         printf("Unrecognized $! trace command \"%s\"\n", command);
756     }
757 }
758
759 /* Handle a dollar-sign command option: $LIST, $FOO=VAL, and so on.
760    The option may come from the command line, an ICL file, or a header
761    comment.
762
763    (Unix-style command-line options are converted to dollar-sign format
764    before being sent here.)
765
766    The name of this function is outdated. Many of these settings are not
767    really about memory allocation.
768 */
769 extern void memory_command(char *command)
770 {   int i, k, flag=0; int32 j;
771
772     for (k=0; command[k]!=0; k++)
773         if (islower(command[k])) command[k]=toupper(command[k]);
774
775     if (command[0]=='?') { explain_parameter(command+1); return; }
776     if (command[0]=='#') { add_predefined_symbol(command+1); return; }
777     if (command[0]=='!') { set_trace_option(command+1); return; }
778
779     if (strcmp(command, "HUGE")==0
780         || strcmp(command, "LARGE")==0
781         || strcmp(command, "SMALL")==0) {
782         if (!nowarnings_switch)
783             printf("The Inform 6 memory size commands (\"SMALL, LARGE, HUGE\") are no longer needed and has been withdrawn.\n");
784         return;
785     }
786     
787     if (strcmp(command, "LIST")==0)  { list_memory_sizes(); return; }
788     
789     for (i=0; command[i]!=0; i++)
790     {   if (command[i]=='=')
791         {   command[i]=0;
792             if (!parse_memory_setting(command+i+1, command, &j)) {
793                 return;
794             }
795             if (strcmp(command,"BUFFER_LENGTH")==0)
796                 flag=2;
797             if (strcmp(command,"MAX_QTEXT_SIZE")==0)
798                 flag=3;
799             if (strcmp(command,"MAX_SYMBOLS")==0)
800                 flag=3;
801             if (strcmp(command,"MAX_BANK_SIZE")==0)
802                 flag=2;
803             if (strcmp(command,"SYMBOLS_CHUNK_SIZE")==0)
804                 flag=3;
805             if (strcmp(command,"BANK_CHUNK_SIZE")==0)
806                 flag=2;
807             if (strcmp(command,"HASH_TAB_SIZE")==0)
808                 HASH_TAB_SIZE=j, flag=1;
809             if (strcmp(command,"MAX_OBJECTS")==0)
810                 flag=3;
811             if (strcmp(command,"MAX_ACTIONS")==0)
812                 flag=3;
813             if (strcmp(command,"MAX_ADJECTIVES")==0)
814                 flag=3;
815             if (strcmp(command,"MAX_DICT_ENTRIES")==0)
816                 flag=3;
817             if (strcmp(command,"DICT_WORD_SIZE")==0) 
818             {   DICT_WORD_SIZE=j, flag=1;
819                 DICT_WORD_SIZE_g=DICT_WORD_SIZE_z=j;
820             }
821             if (strcmp(command,"DICT_CHAR_SIZE")==0)
822                 DICT_CHAR_SIZE=j, flag=1;
823             if (strcmp(command,"NUM_ATTR_BYTES")==0) 
824             {   NUM_ATTR_BYTES=j, flag=1;
825                 NUM_ATTR_BYTES_g=NUM_ATTR_BYTES_z=j;
826             }
827             if (strcmp(command,"ZCODE_HEADER_EXT_WORDS")==0)
828                 ZCODE_HEADER_EXT_WORDS=j, flag=1;
829             if (strcmp(command,"ZCODE_HEADER_FLAGS_3")==0)
830                 ZCODE_HEADER_FLAGS_3=j, flag=1;
831             if (strcmp(command,"ZCODE_LESS_DICT_DATA")==0)
832                 ZCODE_LESS_DICT_DATA=j, flag=1;
833             if (strcmp(command,"ZCODE_MAX_INLINE_STRING")==0)
834                 ZCODE_MAX_INLINE_STRING=j, flag=1;
835             if (strcmp(command,"GLULX_OBJECT_EXT_BYTES")==0)
836                 GLULX_OBJECT_EXT_BYTES=j, flag=1;
837             if (strcmp(command,"MAX_STATIC_DATA")==0)
838                 flag=3;
839             if (strcmp(command,"MAX_OLDEPTH")==0)
840                 flag=2;
841             if (strcmp(command,"MAX_ROUTINES")==0)
842                 flag=2;
843             if (strcmp(command,"MAX_GCONSTANTS")==0)
844                 flag=2;
845             if (strcmp(command,"MAX_PROP_TABLE_SIZE")==0)
846                 flag=3;
847             if (strcmp(command,"MAX_FORWARD_REFS")==0)
848                 flag=2;
849             if (strcmp(command,"STACK_SIZE")==0)
850                 flag=2;
851             if (strcmp(command,"STACK_LONG_SLOTS")==0)
852                 flag=2;
853             if (strcmp(command,"STACK_SHORT_LENGTH")==0)
854                 flag=2;
855             if (strcmp(command,"MAX_ABBREVS")==0)
856                 MAX_ABBREVS=j, flag=1;
857             if (strcmp(command,"MAX_DYNAMIC_STRINGS")==0)
858             {   MAX_DYNAMIC_STRINGS=j, flag=1;
859                 MAX_DYNAMIC_STRINGS_g=MAX_DYNAMIC_STRINGS_z=j;
860             }
861             if (strcmp(command,"MAX_ARRAYS")==0)
862                 flag=3;
863             if (strcmp(command,"MAX_EXPRESSION_NODES")==0)
864                 flag=3;
865             if (strcmp(command,"MAX_VERBS")==0)
866                 flag=3;
867             if (strcmp(command,"MAX_VERBSPACE")==0)
868                 flag=3;
869             if (strcmp(command,"MAX_LABELS")==0)
870                 flag=3;
871             if (strcmp(command,"MAX_LINESPACE")==0)
872                 flag=3;
873             if (strcmp(command,"MAX_NUM_STATIC_STRINGS")==0)
874                 flag=3;
875             if (strcmp(command,"MAX_STATIC_STRINGS")==0)
876                 flag=3;
877             if (strcmp(command,"MAX_ZCODE_SIZE")==0)
878                 flag=3;
879             if (strcmp(command,"MAX_LINK_DATA_SIZE")==0)
880                 flag=3;
881             if (strcmp(command,"MAX_LOW_STRINGS")==0)
882                 flag=3;
883             if (strcmp(command,"MAX_TRANSCRIPT_SIZE")==0)
884                 flag=3;
885             if (strcmp(command,"MAX_CLASSES")==0)
886                 flag=3;
887             if (strcmp(command,"MAX_INCLUSION_DEPTH")==0)
888                 flag=3;
889             if (strcmp(command,"MAX_SOURCE_FILES")==0)
890                 flag=3;
891             if (strcmp(command,"MAX_INDIV_PROP_TABLE_SIZE")==0)
892                 flag=3;
893             if (strcmp(command,"INDIV_PROP_START")==0)
894                 INDIV_PROP_START=j, flag=1;
895             if (strcmp(command,"MAX_OBJ_PROP_TABLE_SIZE")==0)
896                 flag=3;
897             if (strcmp(command,"MAX_OBJ_PROP_COUNT")==0)
898                 flag=3;
899             if (strcmp(command,"MAX_LOCAL_VARIABLES")==0)
900                 flag=3;
901             if (strcmp(command,"MAX_GLOBAL_VARIABLES")==0)
902                 flag=3;
903             if (strcmp(command,"ALLOC_CHUNK_SIZE")==0)
904                 flag=3;
905             if (strcmp(command,"MAX_UNICODE_CHARS")==0)
906                 flag=3;
907             if (strcmp(command,"MAX_STACK_SIZE")==0)
908             {
909                 MAX_STACK_SIZE=j, flag=1;
910                 /* Adjust up to a 256-byte boundary. */
911                 MAX_STACK_SIZE = (MAX_STACK_SIZE + 0xFF) & (~0xFF);
912             }
913             if (strcmp(command,"MEMORY_MAP_EXTENSION")==0)
914             {
915                 MEMORY_MAP_EXTENSION=j, flag=1;
916                 /* Adjust up to a 256-byte boundary. */
917                 MEMORY_MAP_EXTENSION = (MEMORY_MAP_EXTENSION + 0xFF) & (~0xFF);
918             }
919             if (strcmp(command,"TRANSCRIPT_FORMAT")==0)
920             {
921                 TRANSCRIPT_FORMAT=j, flag=1;
922                 if (TRANSCRIPT_FORMAT > 1 || TRANSCRIPT_FORMAT < 0)
923                     TRANSCRIPT_FORMAT = 1;
924             }
925             if (strcmp(command,"WARN_UNUSED_ROUTINES")==0)
926             {
927                 WARN_UNUSED_ROUTINES=j, flag=1;
928                 if (WARN_UNUSED_ROUTINES > 2 || WARN_UNUSED_ROUTINES < 0)
929                     WARN_UNUSED_ROUTINES = 2;
930             }
931             if (strcmp(command,"OMIT_UNUSED_ROUTINES")==0)
932             {
933                 OMIT_UNUSED_ROUTINES=j, flag=1;
934                 if (OMIT_UNUSED_ROUTINES > 1 || OMIT_UNUSED_ROUTINES < 0)
935                     OMIT_UNUSED_ROUTINES = 1;
936             }
937             if (strcmp(command,"STRIP_UNREACHABLE_LABELS")==0)
938             {
939                 STRIP_UNREACHABLE_LABELS=j, flag=1;
940                 if (STRIP_UNREACHABLE_LABELS > 1 || STRIP_UNREACHABLE_LABELS < 0)
941                     STRIP_UNREACHABLE_LABELS = 1;
942             }
943             if (strcmp(command,"OMIT_SYMBOL_TABLE")==0)
944             {
945                 OMIT_SYMBOL_TABLE=j, flag=1;
946                 if (OMIT_SYMBOL_TABLE > 1 || OMIT_SYMBOL_TABLE < 0)
947                     OMIT_SYMBOL_TABLE = 1;
948             }
949             if (strcmp(command,"LONG_DICT_FLAG_BUG")==0)
950             {
951                 LONG_DICT_FLAG_BUG=j, flag=1;
952                 if (LONG_DICT_FLAG_BUG > 1 || LONG_DICT_FLAG_BUG < 0)
953                     LONG_DICT_FLAG_BUG = 1;
954             }
955             if (strcmp(command,"SERIAL")==0)
956             {
957                 if (j >= 0 && j <= 999999)
958                 {
959                     sprintf(serial_code_buffer,"%06d",j);
960                     serial_code_given_in_program = TRUE;
961                     flag=1;
962                 }
963             }
964
965             if (flag==0)
966                 printf("No such memory setting as \"%s\"\n", command);
967             if (flag==2 && !nowarnings_switch)
968                 printf("The Inform 5 memory setting \"%s\" has been withdrawn.\n", command);
969             if (flag==3 && !nowarnings_switch)
970                 printf("The Inform 6 memory setting \"%s\" is no longer needed and has been withdrawn.\n", command);
971             return;
972         }
973     }
974     printf("No such memory $ command as \"%s\"\n",command);
975 }
976
977 extern void print_memory_usage(void)
978 {
979     printf("Properties table used %d\n",
980         properties_table_size);
981     printf("Allocated a total of %ld bytes of memory\n",
982         (long int) malloced_bytes);
983 }
984
985 /* ========================================================================= */
986 /*   Data structure management routines                                      */
987 /* ------------------------------------------------------------------------- */
988
989 extern void init_memory_vars(void)
990 {   malloced_bytes = 0;
991 }
992
993 extern void memory_begin_pass(void) { }
994
995 extern void memory_allocate_arrays(void) { }
996
997 extern void memory_free_arrays(void) { }
998
999 /* ========================================================================= */