1 /* ------------------------------------------------------------------------- */
2 /* "inform" : The top level of Inform: switches, pathnames, filenaming */
3 /* conventions, ICL (Inform Command Line) files, main */
5 /* Part of Inform 6.42 */
6 /* copyright (c) Graham Nelson 1993 - 2024 */
8 /* Inform is free software: you can redistribute it and/or modify */
9 /* it under the terms of the GNU General Public License as published by */
10 /* the Free Software Foundation, either version 3 of the License, or */
11 /* (at your option) any later version. */
13 /* Inform is distributed in the hope that it will be useful, */
14 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
15 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
16 /* GNU General Public License for more details. */
18 /* You should have received a copy of the GNU General Public License */
19 /* along with Inform. If not, see https://gnu.org/licenses/ */
21 /* ------------------------------------------------------------------------- */
23 #define MAIN_INFORM_FILE
26 #define CMD_BUF_SIZE (256)
28 /* ------------------------------------------------------------------------- */
29 /* Compiler progress */
30 /* ------------------------------------------------------------------------- */
32 static int no_compilations;
34 int endofpass_flag; /* set to TRUE when an "end" directive is reached
35 (the inputs routines insert one into the stream
38 /* ------------------------------------------------------------------------- */
40 /* ------------------------------------------------------------------------- */
42 int version_number, /* 3 to 8 (Z-code) */
43 instruction_set_number,
44 /* 3 to 6: versions 7 and 8 use instruction set of
46 extend_memory_map; /* extend using function- and string-offsets */
47 int32 scale_factor, /* packed address multiplier */
48 length_scale_factor; /* length-in-header multiplier */
50 int32 requested_glulx_version; /* version requested via -v switch */
51 int32 final_glulx_version; /* requested version combined with game
52 feature requirements */
54 extern void select_version(int vn)
55 { version_number = vn;
56 extend_memory_map = FALSE;
57 if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;
60 if (version_number==3) scale_factor = 2;
61 if (version_number==8) scale_factor = 8;
63 length_scale_factor = scale_factor;
64 if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
66 instruction_set_number = version_number;
67 if ((version_number==7)||(version_number==8)) instruction_set_number = 5;
70 static int select_glulx_version(char *str)
72 /* Parse an "X.Y.Z" style version number, and store it for later use. */
74 int major=0, minor=0, patch=0;
77 major = major*10 + ((*cx++)-'0');
81 minor = minor*10 + ((*cx++)-'0');
85 patch = patch*10 + ((*cx++)-'0');
89 requested_glulx_version = ((major & 0x7FFF) << 16)
90 + ((minor & 0xFF) << 8)
95 /* ------------------------------------------------------------------------- */
96 /* Target: variables which vary between the Z-machine and Glulx */
97 /* ------------------------------------------------------------------------- */
99 int WORDSIZE; /* Size of a machine word: 2 or 4 */
100 int32 MAXINTWORD; /* 0x7FFF or 0x7FFFFFFF */
102 /* The first property number which is an individual property. The
103 eight class-system i-props (create, recreate, ... print_to_array)
104 are numbered from INDIV_PROP_START to INDIV_PROP_START+7.
106 int INDIV_PROP_START;
108 /* The length of an object, as written in tables.c. It's easier to define
109 it here than to repeat the same expression all over the source code.
112 int OBJECT_BYTE_LENGTH;
113 /* The total length of a dict entry, in bytes. Not used in Z-code.
115 int DICT_ENTRY_BYTE_LENGTH;
116 /* The position in a dict entry that the flag values begin.
119 int DICT_ENTRY_FLAG_POS;
121 static void select_target(int targ)
128 MAX_LOCAL_VARIABLES = 16; /* including "sp" */
130 if (INDIV_PROP_START != 64) {
131 INDIV_PROP_START = 64;
132 fatalerror("You cannot change INDIV_PROP_START in Z-code");
134 if (DICT_WORD_SIZE != 6) {
136 fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
138 if (DICT_CHAR_SIZE != 1) {
140 fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
142 if (NUM_ATTR_BYTES != 6) {
144 fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
150 MAXINTWORD = 0x7FFFFFFF;
151 scale_factor = 0; /* It should never even get used in Glulx */
153 /* This could really be 120, since the practical limit is the size
154 of local_variables.keywords. But historically it's been 119. */
155 MAX_LOCAL_VARIABLES = 119; /* including "sp" */
157 if (INDIV_PROP_START < 256) {
158 INDIV_PROP_START = 256;
159 warning_fmt("INDIV_PROP_START should be at least 256 in Glulx; setting to %d", INDIV_PROP_START);
162 if (NUM_ATTR_BYTES % 4 != 3) {
163 NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4));
164 warning_fmt("NUM_ATTR_BYTES must be a multiple of four, plus three; increasing to %d", NUM_ATTR_BYTES);
167 if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
169 warning_fmt("DICT_CHAR_SIZE must be either 1 or 4; setting to %d", DICT_CHAR_SIZE);
173 if (MAX_LOCAL_VARIABLES > MAX_KEYWORD_GROUP_SIZE) {
174 compiler_error("MAX_LOCAL_VARIABLES cannot exceed MAX_KEYWORD_GROUP_SIZE");
175 MAX_LOCAL_VARIABLES = MAX_KEYWORD_GROUP_SIZE;
178 if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
179 NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
181 "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting to %d",
183 /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
186 /* Set up a few more variables that depend on the above values */
190 DICT_WORD_BYTES = DICT_WORD_SIZE;
191 OBJECT_BYTE_LENGTH = 0;
192 DICT_ENTRY_BYTE_LENGTH = ((version_number==3)?7:9) - (ZCODE_LESS_DICT_DATA?1:0);
193 DICT_ENTRY_FLAG_POS = 0;
197 OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
198 DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
199 if (DICT_CHAR_SIZE == 1) {
200 DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
201 DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
204 DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
205 DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
211 /* The Z-machine's 96 abbreviations are used for these two purposes.
212 Make sure they are set consistently. If exactly one has been
213 set non-default, set the other to match. */
214 if (MAX_DYNAMIC_STRINGS == 32 && MAX_ABBREVS != 64) {
215 MAX_DYNAMIC_STRINGS = 96 - MAX_ABBREVS;
217 if (MAX_ABBREVS == 64 && MAX_DYNAMIC_STRINGS != 32) {
218 MAX_ABBREVS = 96 - MAX_DYNAMIC_STRINGS;
220 if (MAX_ABBREVS + MAX_DYNAMIC_STRINGS != 96
222 || MAX_DYNAMIC_STRINGS < 0) {
223 warning("MAX_ABBREVS plus MAX_DYNAMIC_STRINGS must be 96 in Z-code; resetting both");
224 MAX_DYNAMIC_STRINGS = 32;
230 /* ------------------------------------------------------------------------- */
231 /* Tracery: output control variables */
232 /* (These are initially set to foo_trace_setting, but the Trace directive */
233 /* can change them on the fly) */
234 /* ------------------------------------------------------------------------- */
236 int asm_trace_level, /* trace assembly: 0 for off, 1 for assembly
237 only, 2 for full assembly tracing with hex dumps,
238 3 for branch shortening info, 4 for verbose
240 expr_trace_level, /* expression tracing: 0 off, 1 on, 2/3 more */
241 tokens_trace_level; /* lexer output tracing: 0 off, 1 on, 2/3 more */
243 /* ------------------------------------------------------------------------- */
244 /* On/off switch variables (by default all FALSE); other switch settings */
245 /* (Some of these have become numerical settings now) */
246 /* ------------------------------------------------------------------------- */
248 int concise_switch, /* -c */
249 economy_switch, /* -e */
250 frequencies_setting, /* $!FREQ, -f */
251 ignore_switches_switch, /* -i */
252 debugfile_switch, /* -k */
253 memout_switch, /* $!MEM */
254 printprops_switch, /* $!PROPS */
255 printactions_switch, /* $!ACTIONS */
256 obsolete_switch, /* -q */
257 transcript_switch, /* -r */
258 statistics_switch, /* $!STATS, -s */
259 optimise_switch, /* -u */
260 version_set_switch, /* -v */
261 nowarnings_switch, /* -w */
262 hash_switch, /* -x */
263 memory_map_setting, /* $!MAP, -z */
264 oddeven_packing_switch, /* -B */
265 define_DEBUG_switch, /* -D */
266 runtime_error_checking_switch, /* -S */
267 define_INFIX_switch; /* -X */
269 int throwback_switch; /* -T */
272 int riscos_file_type_format; /* set by -R */
274 int compression_switch; /* set by -H */
275 int character_set_setting, /* set by -C0 through -C9 */
276 character_set_unicode, /* set by -Cu */
277 error_format, /* set by -E */
278 asm_trace_setting, /* $!ASM, -a: initial value of
280 bpatch_trace_setting, /* $!BPATCH */
281 symdef_trace_setting, /* $!SYMDEF */
282 expr_trace_setting, /* $!EXPR: initial value of
284 tokens_trace_setting, /* $!TOKENS: initial value of
285 tokens_trace_level */
286 optabbrevs_trace_setting, /* $!FINDABBREVS */
287 double_space_setting, /* set by -d: 0, 1 or 2 */
288 trace_fns_setting, /* $!RUNTIME, -g: 0, 1, 2, or 3 */
289 files_trace_setting, /* $!FILES */
290 list_verbs_setting, /* $!VERBS */
291 list_dict_setting, /* $!DICT */
292 list_objects_setting, /* $!OBJECTS */
293 list_symbols_setting, /* $!SYMBOLS */
294 store_the_text; /* when set, record game text to a chunk
295 of memory (used by -u) */
296 static int r_e_c_s_set; /* has -S been explicitly set? */
298 int glulx_mode; /* -G */
300 static void reset_switch_settings(void)
301 { asm_trace_setting = 0;
302 tokens_trace_setting = 0;
303 expr_trace_setting = 0;
304 bpatch_trace_setting = 0;
305 symdef_trace_setting = 0;
306 list_verbs_setting = 0;
307 list_dict_setting = 0;
308 list_objects_setting = 0;
309 list_symbols_setting = 0;
311 store_the_text = FALSE;
313 concise_switch = FALSE;
314 double_space_setting = 0;
315 economy_switch = FALSE;
316 files_trace_setting = 0;
317 frequencies_setting = 0;
318 trace_fns_setting = 0;
319 ignore_switches_switch = FALSE;
320 debugfile_switch = FALSE;
322 printprops_switch = 0;
323 printactions_switch = 0;
324 obsolete_switch = FALSE;
325 transcript_switch = FALSE;
326 statistics_switch = FALSE;
327 optimise_switch = FALSE;
328 optabbrevs_trace_setting = 0;
329 version_set_switch = FALSE;
330 nowarnings_switch = FALSE;
332 memory_map_setting = 0;
333 oddeven_packing_switch = FALSE;
334 define_DEBUG_switch = FALSE;
336 throwback_switch = FALSE;
338 runtime_error_checking_switch = TRUE;
340 define_INFIX_switch = FALSE;
342 riscos_file_type_format = 0;
344 error_format=DEFAULT_ERROR_FORMAT;
346 character_set_setting = 1; /* Default is ISO Latin-1 */
347 character_set_unicode = FALSE;
349 compression_switch = TRUE;
351 requested_glulx_version = 0;
352 final_glulx_version = 0;
354 /* These aren't switches, but for clarity we reset them too. */
356 expr_trace_level = 0;
357 tokens_trace_level = 0;
360 /* ------------------------------------------------------------------------- */
361 /* Number of files given as command line parameters (0, 1 or 2) */
362 /* ------------------------------------------------------------------------- */
364 static int cli_files_specified,
365 convert_filename_flag;
367 char Source_Name[PATHLEN]; /* Processed name of first input file */
368 char Code_Name[PATHLEN]; /* Processed name of output file */
370 static char *cli_file1, *cli_file2; /* Unprocessed (and unsafe to alter) */
372 /* ========================================================================= */
373 /* Data structure management routines */
374 /* ------------------------------------------------------------------------- */
376 static void init_vars(void)
384 init_expressc_vars();
385 init_expressp_vars();
399 static void begin_pass(void)
405 directs_begin_pass();
407 expressc_begin_pass();
408 expressp_begin_pass();
411 endofpass_flag = FALSE;
412 expr_trace_level = expr_trace_setting;
413 asm_trace_level = asm_trace_setting;
414 tokens_trace_level = tokens_trace_setting;
418 objects_begin_pass();
420 symbols_begin_pass();
427 /* Compile a Main__ routine (see "veneer.c") */
429 compile_initial_routine();
431 /* Make the four metaclasses: Class must be object number 1, so
432 it must come first */
437 make_class("Object");
438 make_class("Routine");
439 make_class("String");
444 extern void allocate_arrays(void)
446 arrays_allocate_arrays();
447 asm_allocate_arrays();
448 bpatch_allocate_arrays();
449 chars_allocate_arrays();
450 directs_allocate_arrays();
451 errors_allocate_arrays();
452 expressc_allocate_arrays();
453 expressp_allocate_arrays();
454 files_allocate_arrays();
456 lexer_allocate_arrays();
457 memory_allocate_arrays();
458 objects_allocate_arrays();
459 states_allocate_arrays();
460 symbols_allocate_arrays();
461 syntax_allocate_arrays();
462 tables_allocate_arrays();
463 text_allocate_arrays();
464 veneer_allocate_arrays();
465 verbs_allocate_arrays();
468 extern void free_arrays(void)
470 /* One array may survive this routine, all_the_text (used to hold
471 game text until the abbreviations optimiser begins work on it): this
472 array (if it was ever allocated) is freed at the top level. */
474 arrays_free_arrays();
476 bpatch_free_arrays();
478 directs_free_arrays();
479 errors_free_arrays();
480 expressc_free_arrays();
481 expressp_free_arrays();
485 memory_free_arrays();
486 objects_free_arrays();
487 states_free_arrays();
488 symbols_free_arrays();
489 syntax_free_arrays();
490 tables_free_arrays();
492 veneer_free_arrays();
496 /* ------------------------------------------------------------------------- */
497 /* Name translation code for filenames */
498 /* ------------------------------------------------------------------------- */
500 static char Source_Path[PATHLEN];
501 static char Include_Path[PATHLEN];
502 static char Code_Path[PATHLEN];
503 static char current_source_path[PATHLEN];
504 char Debugging_Name[PATHLEN];
505 char Transcript_Name[PATHLEN];
506 char Language_Name[PATHLEN];
507 char Charset_Map[PATHLEN];
508 static char ICL_Path[PATHLEN];
510 /* Set one of the above Path buffers to the given location, or list of
511 locations. (A list is comma-separated, and only accepted for Source_Path,
512 Include_Path, ICL_Path.)
514 static void set_path_value(char *path, char *value)
519 if (i >= PATHLEN-1) {
520 printf("A specified path is longer than %d characters.\n",
524 if ((value[j] == FN_ALT) || (value[j] == 0))
525 { if ((value[j] == FN_ALT)
526 && (path != Source_Path) && (path != Include_Path)
527 && (path != ICL_Path))
528 { printf("The character '%c' is used to divide entries in a list \
529 of possible locations, and can only be used in the Include_Path, Source_Path \
530 or ICL_Path variables. Other paths are for output only.\n", FN_ALT);
533 if ((path != Debugging_Name) && (path != Transcript_Name)
534 && (path != Language_Name) && (path != Charset_Map)
535 && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
536 path[i++] = value[j++];
537 if (value[j-1] == 0) return;
539 else path[i++] = value[j++];
543 /* Prepend the given location or list of locations to one of the above
544 Path buffers. This is only permitted for Source_Path, Include_Path,
547 An empty field (in the comma-separated list) means the current
548 directory. If the Path buffer is entirely empty, we assume that
549 we want to search both value and the current directory, so
550 the result will be "value,".
552 static void prepend_path_value(char *path, char *value)
555 int oldlen = strlen(path);
557 char new_path[PATHLEN];
559 if ((path != Source_Path) && (path != Include_Path)
560 && (path != ICL_Path))
561 { printf("The character '+' is used to add to a list \
562 of possible locations, and can only be used in the Include_Path, Source_Path \
563 or ICL_Path variables. Other paths are for output only.\n");
569 if (i >= PATHLEN-1) {
570 printf("A specified path is longer than %d characters.\n",
574 if ((value[j] == FN_ALT) || (value[j] == 0))
575 { if ((path != Debugging_Name) && (path != Transcript_Name)
576 && (path != Language_Name) && (path != Charset_Map)
577 && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
578 new_path[i++] = value[j++];
579 if (value[j-1] == 0) {
584 else new_path[i++] = value[j++];
587 if (newlen+1+oldlen >= PATHLEN-1) {
588 printf("A specified path is longer than %d characters.\n",
594 new_path[i++] = FN_ALT;
596 new_path[i++] = path[j++];
599 strcpy(path, new_path);
602 static void set_default_paths(void)
604 set_path_value(Source_Path, Source_Directory);
605 set_path_value(Include_Path, Include_Directory);
606 set_path_value(Code_Path, Code_Directory);
607 set_path_value(ICL_Path, ICL_Directory);
608 set_path_value(Debugging_Name, Debugging_File);
609 set_path_value(Transcript_Name, Transcript_File);
610 set_path_value(Language_Name, Default_Language);
611 set_path_value(Charset_Map, "");
614 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
615 or "+pathname=dir". If there is no "=", we assume "include_path=...".
616 If the option begins with a "+" the directory is prepended to the
617 existing path instead of replacing it.
619 static void set_path_command(char *command)
620 { int i, j; char *path_to_set = NULL;
623 if (command[0] == '+') {
628 for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
630 path_to_set=Include_Path;
632 if (command[i] == '=') {
633 char pathname[PATHLEN];
634 if (i>=PATHLEN) i=PATHLEN-1;
636 char ch = command[j];
637 if (isupper(ch)) ch=tolower(ch);
641 command = command+i+1;
644 if (strcmp(pathname, "source_path")==0) path_to_set=Source_Path;
645 if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
646 if (strcmp(pathname, "code_path")==0) path_to_set=Code_Path;
647 if (strcmp(pathname, "icl_path")==0) path_to_set=ICL_Path;
648 if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
649 if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
650 if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
651 if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
653 if (path_to_set == NULL)
654 { printf("No such path setting as \"%s\"\n", pathname);
660 set_path_value(path_to_set, command);
662 prepend_path_value(path_to_set, command);
665 static int contains_separator(char *name)
667 for (i=0; name[i]!=0; i++)
668 if (name[i] == FN_SEP) return 1;
672 static int write_translated_name(char *new_name, char *old_name,
673 char *prefix_path, int start_pos,
676 if (strlen(old_name)+strlen(extension) >= PATHLEN) {
677 printf("One of your filenames is longer than %d characters.\n", PATHLEN);
680 if (prefix_path == NULL)
681 { sprintf(new_name,"%s%s", old_name, extension);
684 strcpy(new_name, prefix_path + start_pos);
685 for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
686 if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
687 if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
688 printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
691 sprintf(new_name + x, "%s%s", old_name, extension);
695 #ifdef FILE_EXTENSIONS
696 static char *check_extension(char *name, char *extension)
699 /* If a filename ends in '.', remove the dot and add no file extension: */
701 if (name[i] == '.') { name[i]=0; return ""; }
703 /* Remove the new extension if it's already got one: */
705 for (; (i>=0) && (name[i]!=FN_SEP); i--)
706 if (name[i] == '.') return "";
711 /* ------------------------------------------------------------------------- */
712 /* Three translation routines have to deal with path variables which may */
713 /* contain alternative locations separated by the FN_ALT character. */
714 /* These have the protocol: */
716 /* int translate_*_filename(int last_value, ...) */
718 /* and should first be called with last_value equal to 0. If the */
719 /* translated filename works, fine. Otherwise, if the returned integer */
720 /* was zero, the caller knows that no filename works and can issue an */
721 /* error message. If it was non-zero, the caller should pass it on as */
722 /* the last_value again. */
724 /* As implemented below, last_value is the position in the path variable */
725 /* string at which the next directory name to try begins. */
726 /* ------------------------------------------------------------------------- */
728 extern int translate_in_filename(int last_value,
729 char *new_name, char *old_name,
730 int same_directory_flag, int command_line_flag)
731 { char *prefix_path = NULL;
733 int add_path_flag = 1;
736 if ((same_directory_flag==0)
737 && (contains_separator(old_name)==1)) add_path_flag=0;
739 if (add_path_flag==1)
740 { if (command_line_flag == 0)
741 { /* File is opened as a result of an Include directive */
743 if (same_directory_flag==1)
744 prefix_path = current_source_path;
746 if (Include_Path[0]!=0) prefix_path = Include_Path;
748 /* Main file being opened from the command line */
750 else if (Source_Path[0]!=0) prefix_path = Source_Path;
753 #ifdef FILE_EXTENSIONS
754 /* Which file extension is expected? */
756 if ((command_line_flag==1)||(same_directory_flag==1))
757 extension = Source_Extension;
759 extension = Include_Extension;
761 extension = check_extension(old_name, extension);
766 last_value = write_translated_name(new_name, old_name,
767 prefix_path, last_value, extension);
769 /* Set the "current source path" (for use of Include ">...") */
771 if (command_line_flag==1)
772 { strcpy(current_source_path, new_name);
773 for (i=strlen(current_source_path)-1;
774 ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
776 if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir */
777 else current_source_path[0] = 0; /* Current file at root dir */
783 static int translate_icl_filename(int last_value,
784 char *new_name, char *old_name)
785 { char *prefix_path = NULL;
786 char *extension = "";
788 if (contains_separator(old_name)==0)
790 prefix_path = ICL_Path;
792 #ifdef FILE_EXTENSIONS
793 extension = check_extension(old_name, ICL_Extension);
796 return write_translated_name(new_name, old_name,
797 prefix_path, last_value, extension);
800 extern void translate_out_filename(char *new_name, char *old_name)
802 char *extension = "";
805 /* If !convert_filename_flag, then the old_name is just the <file2>
806 parameter on the Inform command line, which we leave alone. */
808 if (!convert_filename_flag)
809 { strcpy(new_name, old_name); return;
812 /* Remove any pathname or extension in <file1>. */
814 if (contains_separator(old_name)==1)
815 { for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
816 if (old_name[i]==FN_SEP) i++;
819 #ifdef FILE_EXTENSIONS
820 for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
821 if (old_name[i] == '.') old_name[i] = 0;
827 switch(version_number)
828 { case 3: extension = Code_Extension; break;
829 case 4: extension = V4Code_Extension; break;
830 case 5: extension = V5Code_Extension; break;
831 case 6: extension = V6Code_Extension; break;
832 case 7: extension = V7Code_Extension; break;
833 case 8: extension = V8Code_Extension; break;
837 extension = GlulxCode_Extension;
839 if (Code_Path[0]!=0) prefix_path = Code_Path;
841 #ifdef FILE_EXTENSIONS
842 extension = check_extension(old_name, extension);
845 write_translated_name(new_name, old_name, prefix_path, 0, extension);
848 static char *name_or_unset(char *p)
849 { if (p[0]==0) return "(unset)";
853 static void help_on_filenames(void)
854 { char old_name[PATHLEN];
855 char new_name[PATHLEN];
858 printf("Help information on filenames:\n\n");
861 "The command line can take one of two forms:\n\n\
862 inform [commands...] <file1>\n\
863 inform [commands...] <file1> <file2>\n\n\
864 Inform translates <file1> into a source file name (see below) for its input.\n\
865 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
866 by cutting out the name part and translating that (see below).\n\
867 If <file2> is given, however, the output filename is set to just <file2>\n\
868 (not altered in any way).\n\n");
871 "Filenames given in the game source (with commands like Include \"name\")\n\
872 are also translated by the rules below.\n\n");
875 "Rules of translation:\n\n\
876 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
877 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
878 1. If the name contains a '%c' character (so it's already a pathname), it\n\
879 isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
882 " [Exception: when the name is given in an Include command using the >\n\
883 form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
884 of the file doing the inclusion");
885 #ifdef FILE_EXTENSIONS
886 printf(" and a suitable file extension is added");
891 " Filenames must never contain double-quotation marks \". To use filenames\n\
892 which contain spaces, write them in double-quotes: for instance,\n\n\
893 \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
896 "2. The file is looked for at a particular \"path\" (the filename of a\n\
897 directory), depending on what kind of file it is.\n\n\
898 File type Name Current setting\n\n\
899 Source code (in) source_path %s\n\
900 Include file (in) include_path %s\n\
901 Story file (out) code_path %s\n",
902 name_or_unset(Source_Path), name_or_unset(Include_Path),
903 name_or_unset(Code_Path));
906 " ICL command file (in) icl_path %s\n\n",
907 name_or_unset(ICL_Path));
910 " If the path is unset, then the current working directory is used (so\n\
911 the filename doesn't change): if, for instance, include_path is set to\n\
912 \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
913 \"backup%coldlib%cparser\".\n\n\
914 The paths can be set or unset on the Inform command line by, eg,\n\
915 \"inform +code_path=finished jigsaw\" or\n\
916 \"inform +include_path= balances\" (which unsets include_path).\n\n",
917 FN_SEP, FN_SEP, FN_SEP);
920 " The four input path variables can be set to lists of alternative paths\n\
921 separated by '%c' characters: these alternatives are always tried in\n\
922 the order they are specified in, that is, left to right through the text\n\
923 in the path variable.\n\n",
926 " If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
927 the path or paths are added to the existing list.\n\n");
929 " (It is an error to give alternatives at all for purely output paths.)\n\n");
931 #ifdef FILE_EXTENSIONS
932 printf("3. The following file extensions are added:\n\n\
935 Story files: %s (Version 3), %s (v4), %s (v5, the default),\n\
936 %s (v6), %s (v7), %s (v8), %s (Glulx)\n\n",
937 Source_Extension, Include_Extension,
938 Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
939 V7Code_Extension, V8Code_Extension, GlulxCode_Extension);
941 except that any extension you give (on the command line or in a filename\n\
942 used in a program) will override these. If you give the null extension\n\
943 \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
946 printf("Names of four individual files can also be set using the same\n\
947 + command notation (though they aren't really pathnames). These are:\n\n\
948 transcript_name (text written by -r switch): now \"%s\"\n\
949 debugging_name (data written by -k switch): now \"%s\"\n\
950 language_name (library file defining natural language of game):\n\
952 charset_map (file for character set mapping): now \"%s\"\n\n",
953 Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
955 translate_in_filename(0, new_name, "rezrov", 0, 1);
956 printf("Examples: 1. \"inform rezrov\"\n\
957 the source code is read from \"%s\"\n",
959 convert_filename_flag = TRUE;
960 translate_out_filename(new_name, "rezrov");
961 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
963 sprintf(old_name, "demos%cplugh", FN_SEP);
964 printf("2. \"inform %s\"\n", old_name);
965 translate_in_filename(0, new_name, old_name, 0, 1);
966 printf(" the source code is read from \"%s\"\n", new_name);
967 sprintf(old_name, "demos%cplugh", FN_SEP);
968 convert_filename_flag = TRUE;
969 translate_out_filename(new_name, old_name);
970 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
972 printf("3. \"inform plover my_demo\"\n");
973 translate_in_filename(0, new_name, "plover", 0, 1);
974 printf(" the source code is read from \"%s\"\n", new_name);
975 convert_filename_flag = FALSE;
976 translate_out_filename(new_name, "my_demo");
977 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
979 strcpy(old_name, Source_Path);
980 sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
981 FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
982 printf("4. \"inform +source_path=%s zooge\"\n", new_name);
984 " Note that four alternative paths are given, the first being the empty\n\
985 path-name (meaning: where you are now). Inform looks for the source code\n\
986 by trying these four places in turn, stopping when it finds anything:\n\n");
988 set_path_value(Source_Path, new_name);
991 { x = translate_in_filename(x, new_name, "zooge", 0, 1);
992 printf(" \"%s\"\n", new_name);
994 strcpy(Source_Path, old_name);
998 static char riscos_ft_buffer[4];
1000 extern char *riscos_file_type(void)
1002 if (riscos_file_type_format == 1)
1007 sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1008 return(riscos_ft_buffer);
1012 /* ------------------------------------------------------------------------- */
1013 /* The compilation pass */
1014 /* ------------------------------------------------------------------------- */
1016 static void run_pass(void)
1018 lexer_begin_prepass();
1019 files_begin_prepass();
1020 load_sourcefile(Source_Name, 0);
1024 parse_program(NULL);
1027 issue_unused_warnings();
1032 issue_debug_symbol_warnings();
1035 if (hash_switch && hash_printed_since_newline) printf("\n");
1038 if (track_unused_routines)
1039 locate_dead_functions();
1040 locate_dead_grammar_lines();
1041 construct_storyfile();
1044 int output_has_occurred;
1046 static void rennab(float time_taken)
1047 { /* rennab = reverse of banner */
1048 int t = no_warnings + no_suppressed_warnings;
1050 if (memout_switch) print_memory_usage();
1052 if ((no_errors + t)!=0)
1053 { printf("Compiled with ");
1055 { printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1056 if (t > 0) printf(" and ");
1058 if (no_warnings > 0)
1059 printf("%d warning%s", t, (t==1)?"":"s");
1060 if (no_suppressed_warnings > 0)
1061 { if (no_warnings > 0)
1062 printf(" (%d suppressed)", no_suppressed_warnings);
1064 printf("%d suppressed warning%s", no_suppressed_warnings,
1065 (no_suppressed_warnings==1)?"":"s");
1067 if (output_has_occurred == FALSE) printf(" (no output)");
1071 if (no_compiler_errors > 0) print_sorry_message();
1073 if (statistics_switch)
1075 /* Print the duration to a sensible number of decimal places.
1076 (We aim for three significant figures.) */
1077 if (time_taken >= 10.0)
1078 printf("Completed in %.1f seconds\n", time_taken);
1079 else if (time_taken >= 1.0)
1080 printf("Completed in %.2f seconds\n", time_taken);
1082 printf("Completed in %.3f seconds\n", time_taken);
1086 /* ------------------------------------------------------------------------- */
1087 /* The compiler abstracted to a routine. */
1088 /* ------------------------------------------------------------------------- */
1090 static int execute_icl_header(char *file1);
1092 static int compile(int number_of_files_specified, char *file1, char *file2)
1094 TIMEVALUE time_start, time_end;
1097 if (execute_icl_header(file1))
1100 select_target(glulx_mode);
1102 if (define_INFIX_switch && glulx_mode) {
1103 printf("Infix (-X) facilities are not available in Glulx: \
1104 disabling -X switch\n");
1105 define_INFIX_switch = FALSE;
1108 TIMEVALUE_NOW(&time_start);
1112 strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1113 strcpy(Code_Name, file1);
1114 if (number_of_files_specified == 2)
1115 { strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1120 if (debugfile_switch) begin_debug_file();
1124 if (transcript_switch) open_transcript_file(Source_Name);
1128 if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1129 else { output_has_occurred = FALSE; }
1131 if (transcript_switch)
1132 { write_dictionary_to_transcript();
1133 close_transcript_file();
1136 if (debugfile_switch)
1140 if (optimise_switch) {
1141 /* Pull out all_text so that it will not be freed. */
1147 TIMEVALUE_NOW(&time_end);
1148 duration = TIMEVALUE_DIFFERENCE(&time_start, &time_end);
1152 if (optimise_switch) {
1153 optimise_abbreviations();
1157 return (no_errors==0)?0:1;
1160 /* ------------------------------------------------------------------------- */
1161 /* The command line interpreter */
1162 /* ------------------------------------------------------------------------- */
1164 static void cli_print_help(int help_level)
1167 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1168 story files, as well as \"Glulx\" story files:\n\
1169 Copyright (c) Graham Nelson 1993 - 2024.\n\n");
1171 /* For people typing just "inform", a summary only: */
1176 #ifndef PROMPT_INPUT
1177 printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1179 printf("When run, Inform prompts you for commands (and switches),\n\
1180 which are optional, then an input <file1> and an (optional) output\n\
1185 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1186 if given, overrides the filename Inform would normally use for the\n\
1187 compiled output. Try \"inform -h1\" for file-naming conventions.\n\n\
1188 One or more words can be supplied as \"commands\". These may be:\n\n\
1189 -switches a list of compiler switches, 1 or 2 letter\n\
1190 (see \"inform -h2\" for the full range)\n\n\
1191 +dir set Include_Path to this directory\n\
1192 ++dir add this directory to Include_Path\n\
1193 +PATH=dir change the PATH to this directory\n\
1194 ++PATH=dir add this directory to the PATH\n\n\
1195 $... one of the following configuration commands:\n");
1198 " $list list current settings\n\
1199 $?SETTING explain briefly what SETTING is for\n\
1200 $SETTING=number change SETTING to given number\n\
1201 $!TRACEOPT set trace option TRACEOPT\n\
1202 (or $!TRACEOPT=2, 3, etc for more tracing;\n\
1203 $! by itself to list all trace options)\n\
1204 $#SYMBOL=number define SYMBOL as a constant in the story\n\n");
1207 " (filename) read in a list of commands (in the format above)\n\
1208 from this \"setup file\"\n\n");
1210 printf("Alternate command-line formats for the above:\n\
1211 --help (this page)\n\
1212 --path PATH=dir (set path)\n\
1213 --addpath PATH=dir (add to path)\n\
1214 --list (list current settings)\n\
1215 --helpopt SETTING (explain setting)\n\
1216 --opt SETTING=number (change setting)\n\
1217 --helptrace (list all trace options)\n\
1218 --trace TRACEOPT (set trace option)\n\
1219 --trace TRACEOPT=num (more tracing)\n\
1220 --define SYMBOL=number (define constant)\n\
1221 --config filename (read setup file)\n\n");
1223 #ifndef PROMPT_INPUT
1224 printf("For example: \"inform -dexs curses\".\n");
1230 /* The -h1 (filenaming) help information: */
1232 if (help_level == 1) { help_on_filenames(); return; }
1234 /* The -h2 (switches) help information: */
1236 printf("Help on the full list of legal switch commands:\n\n\
1237 a trace assembly-language\n\
1238 a2 trace assembly with hex dumps\n\
1239 c more concise error messages\n\
1240 d contract double spaces after full stops in text\n\
1241 d2 contract double spaces after exclamation and question marks, too\n\
1242 e economy mode (slower): make use of declared abbreviations\n");
1245 f frequencies mode: show how useful abbreviations are\n\
1246 g traces calls to all game functions\n\
1247 g2 traces calls to all game and library functions\n\
1248 g3 traces calls to all functions (including veneer)\n\
1249 h print general help information\n\
1250 h1 print help information on filenames and path options\n\
1251 h2 print help information on switches (this page)\n");
1254 i ignore default switches set within the file\n\
1255 k output debugging information to \"%s\"\n",
1258 q keep quiet about obsolete usages\n\
1259 r record all the text to \"%s\"\n\
1260 s give statistics\n",
1264 u work out most useful abbreviations (very very slowly)\n\
1265 v3 compile to version-3 (\"Standard\"/\"ZIP\") story file\n\
1266 v4 compile to version-4 (\"Plus\"/\"EZIP\") story file\n\
1267 v5 compile to version-5 (\"Advanced\"/\"XZIP\") story file: the default\n\
1268 v6 compile to version-6 (graphical/\"YZIP\") story file\n\
1269 v7 compile to version-7 (expanded \"Advanced\") story file\n\
1270 v8 compile to version-8 (expanded \"Advanced\") story file\n\
1271 w disable warning messages\n\
1272 x print # for every 100 lines compiled\n\
1273 z print memory map of the virtual machine\n\n");
1276 B use big memory model (for large V6/V7 files)\n\
1277 C0 text character set is plain ASCII only\n\
1278 Cu text character set is UTF-8\n\
1279 Cn text character set is ISO 8859-n (n = 1 to 9)\n\
1280 (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1281 7, Greek; 8, Hebrew; 9, Latin5. Default is -C1.)\n");
1282 printf(" D insert \"Constant DEBUG;\" automatically\n");
1283 printf(" E0 Archimedes-style error messages%s\n",
1284 (error_format==0)?" (current setting)":"");
1285 printf(" E1 Microsoft-style error messages%s\n",
1286 (error_format==1)?" (current setting)":"");
1287 printf(" E2 Macintosh MPW-style error messages%s\n",
1288 (error_format==2)?" (current setting)":"");
1289 printf(" G compile a Glulx game file\n");
1290 printf(" H use Huffman encoding to compress Glulx strings\n");
1294 R0 use filetype 060 + version number for games (default)\n\
1295 R1 use official Acorn filetype 11A for all games\n");
1297 printf(" S compile strict error-checking at run-time (on by default)\n");
1298 #ifdef ARC_THROWBACK
1299 printf(" T enable throwback of errors in the DDE\n");
1301 printf(" V print the version and date of this program\n");
1302 printf(" Wn header extension table is at least n words (n = 3 to 99)\n");
1303 printf(" X compile with INFIX debugging facilities present\n");
1307 extern void switches(char *p, int cmode)
1308 { int i, s=1, state;
1309 /* Here cmode is 0 if switches list is from a "Switches" directive
1310 and 1 if from a "-switches" command-line or ICL list */
1315 "Ignoring second word which should be a -list of switches.\n");
1319 for (i=cmode; p[i]!=0; i+=s, s=1)
1327 case 'a': switch(p[i+1])
1328 { case '1': asm_trace_setting=1; s=2; break;
1329 case '2': asm_trace_setting=2; s=2; break;
1330 case '3': asm_trace_setting=3; s=2; break;
1331 case '4': asm_trace_setting=4; s=2; break;
1332 default: asm_trace_setting=1; break;
1335 case 'c': concise_switch = state; break;
1336 case 'd': switch(p[i+1])
1337 { case '1': double_space_setting=1; s=2; break;
1338 case '2': double_space_setting=2; s=2; break;
1339 default: double_space_setting=1; break;
1342 case 'e': economy_switch = state; break;
1343 case 'f': frequencies_setting = (state?1:0); break;
1344 case 'g': switch(p[i+1])
1345 { case '1': trace_fns_setting=1; s=2; break;
1346 case '2': trace_fns_setting=2; s=2; break;
1347 case '3': trace_fns_setting=3; s=2; break;
1348 default: trace_fns_setting=1; break;
1351 case 'h': switch(p[i+1])
1352 { case '1': cli_print_help(1); s=2; break;
1353 case '2': cli_print_help(2); s=2; break;
1355 default: cli_print_help(0); break;
1358 case 'i': ignore_switches_switch = state; break;
1359 case 'k': if (cmode == 0)
1360 error("The switch '-k' can't be set with 'Switches'");
1362 debugfile_switch = state;
1364 case 'q': obsolete_switch = state; break;
1365 case 'r': if (cmode == 0)
1366 error("The switch '-r' can't be set with 'Switches'");
1368 transcript_switch = state; break;
1369 case 's': statistics_switch = state; break;
1370 case 'u': if (cmode == 0) {
1371 error("The switch '-u' can't be set with 'Switches'");
1374 optimise_switch = state; break;
1375 case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1376 if ((cmode==0) && (version_set_switch)) { s=2; break; }
1377 version_set_switch = TRUE; s=2;
1379 { case '3': select_version(3); break;
1380 case '4': select_version(4); break;
1381 case '5': select_version(5); break;
1382 case '6': select_version(6); break;
1383 case '7': select_version(7); break;
1384 case '8': select_version(8); break;
1385 default: printf("-v must be followed by 3 to 8\n");
1386 version_set_switch=0; s=1;
1389 if ((version_number < 5) && (r_e_c_s_set == FALSE))
1390 runtime_error_checking_switch = FALSE;
1392 case 'w': nowarnings_switch = state; break;
1393 case 'x': hash_switch = state; break;
1394 case 'z': memory_map_setting = (state ? 1 : 0); break;
1395 case 'B': oddeven_packing_switch = state; break;
1397 if (p[i+1] == 'u') {
1398 character_set_unicode = TRUE;
1399 /* Leave the set_setting on Latin-1, because that
1400 matches the first block of Unicode. */
1401 character_set_setting = 1;
1404 { character_set_setting=p[i+1]-'0';
1405 if ((character_set_setting < 0)
1406 || (character_set_setting > 9))
1407 { printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1408 character_set_unicode = FALSE;
1409 character_set_setting = 1;
1412 if (cmode == 0) change_character_set();
1414 case 'D': define_DEBUG_switch = state; break;
1415 case 'E': switch(p[i+1])
1416 { case '0': s=2; error_format=0; break;
1417 case '1': s=2; error_format=1; break;
1418 case '2': s=2; error_format=2; break;
1419 default: error_format=1; break;
1423 case 'R': switch(p[i+1])
1424 { case '0': s=2; riscos_file_type_format=0; break;
1425 case '1': s=2; riscos_file_type_format=1; break;
1426 default: riscos_file_type_format=1; break;
1430 #ifdef ARC_THROWBACK
1431 case 'T': throwback_switch = state; break;
1433 case 'S': runtime_error_checking_switch = state;
1434 r_e_c_s_set = TRUE; break;
1435 case 'G': if (cmode == 0)
1436 error("The switch '-G' can't be set with 'Switches'");
1437 else if (version_set_switch)
1438 error("The '-G' switch cannot follow the '-v' switch");
1440 { glulx_mode = state;
1441 adjust_memory_sizes();
1444 case 'H': compression_switch = state; break;
1445 case 'V': exit(0); break;
1446 case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1447 { s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1448 if ((p[i+2]>='0') && (p[i+2]<='9'))
1449 { s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1450 ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1454 case 'X': define_INFIX_switch = state; break;
1456 printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1462 if (optimise_switch)
1464 /* store_the_text is equivalent to optimise_switch; -u sets both.
1465 We could simplify this. */
1466 store_the_text=TRUE;
1470 /* Check whether the string looks like an ICL command. */
1471 static int icl_command(char *p)
1472 { if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1473 || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1477 static void icl_error(char *filename, int line)
1478 { printf("Error in ICL file '%s', line %d:\n", filename, line);
1481 static void icl_header_error(char *filename, int line)
1482 { printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1485 static int copy_icl_word(char *from, char *to, int max)
1487 /* Copies one token from 'from' to 'to', null-terminated:
1488 returns the number of chars in 'from' read past (possibly 0). */
1490 int i, j, quoted_mode, truncated;
1492 i = 0; truncated = 0;
1493 while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1494 || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1497 { while (from[i] != 0) i++;
1498 to[0] = 0; return i;
1501 for (quoted_mode = FALSE, j=0;;)
1502 { if (from[i] == 0) break;
1503 if (from[i] == 10) break;
1504 if (from[i] == 13) break;
1505 if (from[i] == TAB_CHARACTER) break;
1506 if ((from[i] == ' ') && (!quoted_mode)) break;
1507 if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1508 else to[j++] = from[i++];
1516 printf("The following parameter has been truncated:\n%s\n", to);
1520 /* Copy a string, converting to uppercase. The to array should be
1521 (at least) max characters. Result will be null-terminated, so
1522 at most max-1 characters will be copied.
1524 static int strcpyupper(char *to, char *from, int max)
1527 for (ix=0; ix<max-1; ix++) {
1529 if (islower(ch)) ch = toupper(ch);
1536 static void execute_icl_command(char *p);
1537 static int execute_dashdash_command(char *p, char *p2);
1539 /* Open a file and see whether the initial lines match the "!% ..." format
1540 used for ICL commands. Stop when we reach a line that doesn't.
1542 This does not do line break conversion. It just reads to the next
1543 \n (and ignores \r as whitespace). Therefore it will work on Unix and
1544 DOS source files, but fail to cope with Mac-Classic (\r) source files.
1545 I am not going to worry about this, because files from the Mac-Classic
1546 era shouldn't have "!%" lines; that convention was invented well after
1547 Mac switched over to \n format.
1549 static int execute_icl_header(char *argname)
1552 char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1556 char filename[PATHLEN];
1560 { x = translate_in_filename(x, filename, argname, 0, 1);
1561 command_file = fopen(filename,"rb");
1562 } while ((command_file == NULL) && (x != 0));
1563 if (!command_file) {
1564 /* Fail silently. The regular compiler will try to open the file
1565 again, and report the problem. */
1569 while (feof(command_file)==0) {
1570 if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1572 if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1574 i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1575 if (icl_command(fw)) {
1576 execute_icl_command(fw);
1577 copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1578 if ((fw[0] != 0) && (fw[0] != '!')) {
1579 icl_header_error(filename, line);
1581 printf("expected comment or nothing but found '%s'\n", fw);
1586 icl_header_error(filename, line);
1588 printf("Expected command or comment but found '%s'\n", fw);
1592 fclose(command_file);
1594 return (errcount==0)?0:1;
1598 static void run_icl_file(char *filename, FILE *command_file)
1599 { char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1601 printf("[Running ICL file '%s']\n", filename);
1603 while (feof(command_file)==0)
1604 { if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1606 i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1607 if (icl_command(fw))
1608 { execute_icl_command(fw);
1609 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1610 if ((fw[0] != 0) && (fw[0] != '!'))
1611 { icl_error(filename, line);
1612 printf("expected comment or nothing but found '%s'\n", fw);
1616 { if (strcmp(fw, "compile")==0)
1617 { char story_name[PATHLEN], code_name[PATHLEN];
1618 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1619 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1621 if (code_name[0] != 0) x=2;
1622 else if (story_name[0] != 0) x=1;
1626 { case 0: icl_error(filename, line);
1627 printf("No filename given to 'compile'\n");
1629 case 1: printf("[Compiling <%s>]\n", story_name);
1630 compile(x, story_name, code_name);
1632 case 2: printf("[Compiling <%s> to <%s>]\n",
1633 story_name, code_name);
1634 compile(x, story_name, code_name);
1635 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1637 { icl_error(filename, line);
1638 printf("Expected comment or nothing but found '%s'\n",
1646 { icl_error(filename, line);
1647 printf("Expected command or comment but found '%s'\n", fw);
1653 /* This should only be called if the argument has been verified to be
1654 an ICL command, e.g. by checking icl_command().
1656 static void execute_icl_command(char *p)
1657 { char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1662 { case '+': set_path_command(p+1); break;
1663 case '-': switches(p,1); break;
1664 case '$': memory_command(p+1); break;
1665 case '(': len = strlen(p);
1666 if (p[len-1] != ')') {
1667 printf("Error in ICL: (command) missing closing paren\n");
1670 len -= 2; /* omit parens */
1671 if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1672 strncpy(cli_buff, p+1, len);
1676 { x = translate_icl_filename(x, filename, cli_buff);
1677 command_file = fopen(filename,"r");
1678 } while ((command_file == NULL) && (x != 0));
1680 if (command_file == NULL) {
1681 printf("Error in ICL: Couldn't open command file '%s'\n",
1685 run_icl_file(filename, command_file);
1686 fclose(command_file);
1691 /* Convert a --command into the equivalent ICL command and call
1692 execute_icl_command(). The dashes have already been stripped.
1694 The second argument is the following command-line argument
1695 (or NULL if there was none). This may or may not be consumed.
1696 Returns TRUE if it was.
1698 static int execute_dashdash_command(char *p, char *p2)
1700 char cli_buff[CMD_BUF_SIZE];
1701 int consumed2 = FALSE;
1703 if (!strcmp(p, "help")) {
1704 strcpy(cli_buff, "-h");
1706 else if (!strcmp(p, "list")) {
1707 strcpy(cli_buff, "$LIST");
1709 else if (!strcmp(p, "size")) {
1711 /* We accept these arguments even though they've been withdrawn. */
1712 if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1713 printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1716 strcpy(cli_buff, "$");
1717 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1719 else if (!strcmp(p, "opt")) {
1721 if (!p2 || !strchr(p2, '=')) {
1722 printf("--opt must be followed by \"setting=number\"\n");
1725 strcpy(cli_buff, "$");
1726 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1728 else if (!strcmp(p, "helpopt")) {
1731 printf("--helpopt must be followed by \"setting\"\n");
1734 strcpy(cli_buff, "$?");
1735 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1737 else if (!strcmp(p, "define")) {
1740 printf("--define must be followed by \"symbol=number\"\n");
1743 strcpy(cli_buff, "$#");
1744 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1746 else if (!strcmp(p, "path")) {
1748 if (!p2 || !strchr(p2, '=')) {
1749 printf("--path must be followed by \"name=path\"\n");
1752 snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1754 else if (!strcmp(p, "addpath")) {
1756 if (!p2 || !strchr(p2, '=')) {
1757 printf("--addpath must be followed by \"name=path\"\n");
1760 snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1762 else if (!strcmp(p, "config")) {
1765 printf("--config must be followed by \"file.icl\"\n");
1768 snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1770 else if (!strcmp(p, "trace")) {
1773 printf("--trace must be followed by \"traceopt\" or \"traceopt=N\"\n");
1776 snprintf(cli_buff, CMD_BUF_SIZE, "$!%s", p2);
1778 else if (!strcmp(p, "helptrace")) {
1779 strcpy(cli_buff, "$!");
1782 printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1786 execute_icl_command(cli_buff);
1790 /* ------------------------------------------------------------------------- */
1791 /* Opening and closing banners */
1792 /* ------------------------------------------------------------------------- */
1794 char banner_line[CMD_BUF_SIZE];
1796 /* We store the banner text for use elsewhere (see files.c).
1798 static void banner(void)
1801 snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1802 (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1803 #ifdef RELEASE_SUFFIX
1804 len = strlen(banner_line);
1805 snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1807 #ifdef MACHINE_STRING
1808 len = strlen(banner_line);
1809 snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1811 len = strlen(banner_line);
1812 snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1814 printf("%s\n", banner_line);
1817 /* ------------------------------------------------------------------------- */
1818 /* Input from the outside world */
1819 /* ------------------------------------------------------------------------- */
1822 static void read_command_line(int argc, char **argv)
1824 char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1826 printf("Source filename?\n> ");
1827 while (gets(buffer1)==NULL); cli_file1=buffer1;
1828 printf("Output filename (RETURN for the same)?\n> ");
1829 while (gets(buffer2)==NULL); cli_file2=buffer2;
1830 cli_files_specified=1;
1831 if (buffer2[0]!=0) cli_files_specified=2;
1833 { printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1834 while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1835 } while (buffer3[0]!=0);
1838 static void read_command_line(int argc, char **argv)
1840 if (argc==1) switches("-h",1);
1842 for (i=1, cli_files_specified=0; i<argc; i++)
1843 if (argv[i][0] == '-' && argv[i][1] == '-') {
1844 char *nextarg = NULL;
1846 if (i+1 < argc) nextarg = argv[i+1];
1847 consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1848 if (consumed2 && i+1 < argc) {
1852 else if (icl_command(argv[i])) {
1853 execute_icl_command(argv[i]);
1856 switch(++cli_files_specified)
1857 { case 1: cli_file1 = argv[i]; break;
1858 case 2: cli_file2 = argv[i]; break;
1860 printf("Command line error: unknown parameter '%s'\n",
1867 /* ------------------------------------------------------------------------- */
1868 /* M A I N : An outer shell for machine-specific quirks */
1869 /* Omitted altogether if EXTERNAL_SHELL is defined, as for instance is */
1870 /* needed for the Macintosh front end. */
1871 /* ------------------------------------------------------------------------- */
1873 #ifdef EXTERNAL_SHELL
1874 extern int sub_main(int argc, char **argv);
1877 static int sub_main(int argc, char **argv);
1879 int main(int argc, char **argv, char *envp[])
1881 int main(int argc, char **argv)
1885 InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
1887 rcode = sub_main(argc, argv);
1888 #ifdef ARC_THROWBACK
1896 /* ------------------------------------------------------------------------- */
1897 /* M A I N II: Starting up ICL with the command line */
1898 /* ------------------------------------------------------------------------- */
1900 #ifdef EXTERNAL_SHELL
1901 extern int sub_main(int argc, char **argv)
1903 static int sub_main(int argc, char **argv)
1908 ProcessEvents (&g_proc);
1911 if (store_the_text) my_free(&all_text,"transcription text");
1912 longjmp (g_fallback, 1);
1918 set_memory_sizes(); set_default_paths();
1919 reset_switch_settings(); select_version(5);
1921 cli_files_specified = 0; no_compilations = 0;
1922 cli_file1 = "source"; cli_file2 = "output";
1924 read_command_line(argc, argv);
1926 if (cli_files_specified > 0)
1927 { return_code = compile(cli_files_specified, cli_file1, cli_file2);
1929 if (return_code != 0) return(return_code);
1932 if (no_compilations == 0)
1933 printf("\n[No compilation requested]\n");
1934 if (no_compilations > 1)
1935 printf("[%d compilations completed]\n", no_compilations);
1940 /* ========================================================================= */