1 /* ------------------------------------------------------------------------- */
2 /* "inform" : The top level of Inform: switches, pathnames, filenaming */
3 /* conventions, ICL (Inform Command Line) files, main */
5 /* Part of Inform 6.41 */
6 /* copyright (c) Graham Nelson 1993 - 2022 */
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;
52 extern void select_version(int vn)
53 { version_number = vn;
54 extend_memory_map = FALSE;
55 if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;
58 if (version_number==3) scale_factor = 2;
59 if (version_number==8) scale_factor = 8;
61 length_scale_factor = scale_factor;
62 if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
64 instruction_set_number = version_number;
65 if ((version_number==7)||(version_number==8)) instruction_set_number = 5;
68 static int select_glulx_version(char *str)
70 /* Parse an "X.Y.Z" style version number, and store it for later use. */
72 int major=0, minor=0, patch=0;
75 major = major*10 + ((*cx++)-'0');
79 minor = minor*10 + ((*cx++)-'0');
83 patch = patch*10 + ((*cx++)-'0');
87 requested_glulx_version = ((major & 0x7FFF) << 16)
88 + ((minor & 0xFF) << 8)
93 /* ------------------------------------------------------------------------- */
94 /* Target: variables which vary between the Z-machine and Glulx */
95 /* ------------------------------------------------------------------------- */
97 int WORDSIZE; /* Size of a machine word: 2 or 4 */
98 int32 MAXINTWORD; /* 0x7FFF or 0x7FFFFFFF */
100 /* The first property number which is an individual property. The
101 eight class-system i-props (create, recreate, ... print_to_array)
102 are numbered from INDIV_PROP_START to INDIV_PROP_START+7.
104 int INDIV_PROP_START;
106 /* The length of an object, as written in tables.c. It's easier to define
107 it here than to repeat the same expression all over the source code.
110 int OBJECT_BYTE_LENGTH;
111 /* The total length of a dict entry, in bytes. Not used in Z-code.
113 int DICT_ENTRY_BYTE_LENGTH;
114 /* The position in a dict entry that the flag values begin.
117 int DICT_ENTRY_FLAG_POS;
119 static void select_target(int targ)
126 MAX_LOCAL_VARIABLES = 16; /* including "sp" */
128 if (INDIV_PROP_START != 64) {
129 INDIV_PROP_START = 64;
130 fatalerror("You cannot change INDIV_PROP_START in Z-code");
132 if (DICT_WORD_SIZE != 6) {
134 fatalerror("You cannot change DICT_WORD_SIZE in Z-code");
136 if (DICT_CHAR_SIZE != 1) {
138 fatalerror("You cannot change DICT_CHAR_SIZE in Z-code");
140 if (NUM_ATTR_BYTES != 6) {
142 fatalerror("You cannot change NUM_ATTR_BYTES in Z-code");
148 MAXINTWORD = 0x7FFFFFFF;
149 scale_factor = 0; /* It should never even get used in Glulx */
151 /* This could really be 120, since the practical limit is the size
152 of local_variables.keywords. But historically it's been 119. */
153 MAX_LOCAL_VARIABLES = 119; /* including "sp" */
155 if (INDIV_PROP_START < 256) {
156 INDIV_PROP_START = 256;
157 warning_numbered("INDIV_PROP_START should be at least 256 in Glulx. Setting to", INDIV_PROP_START);
160 if (NUM_ATTR_BYTES % 4 != 3) {
161 NUM_ATTR_BYTES += (3 - (NUM_ATTR_BYTES % 4));
162 warning_numbered("NUM_ATTR_BYTES must be a multiple of four, plus three. Increasing to", NUM_ATTR_BYTES);
165 if (DICT_CHAR_SIZE != 1 && DICT_CHAR_SIZE != 4) {
167 warning_numbered("DICT_CHAR_SIZE must be either 1 or 4. Setting to", DICT_CHAR_SIZE);
171 if (MAX_LOCAL_VARIABLES > MAX_KEYWORD_GROUP_SIZE) {
172 compiler_error("MAX_LOCAL_VARIABLES cannot exceed MAX_KEYWORD_GROUP_SIZE");
173 MAX_LOCAL_VARIABLES = MAX_KEYWORD_GROUP_SIZE;
176 if (DICT_WORD_SIZE > MAX_DICT_WORD_SIZE) {
177 DICT_WORD_SIZE = MAX_DICT_WORD_SIZE;
179 "DICT_WORD_SIZE cannot exceed MAX_DICT_WORD_SIZE; resetting",
181 /* MAX_DICT_WORD_SIZE can be increased in header.h without fear. */
183 if (NUM_ATTR_BYTES > MAX_NUM_ATTR_BYTES) {
184 NUM_ATTR_BYTES = MAX_NUM_ATTR_BYTES;
186 "NUM_ATTR_BYTES cannot exceed MAX_NUM_ATTR_BYTES; resetting",
188 /* MAX_NUM_ATTR_BYTES can be increased in header.h without fear. */
191 /* Set up a few more variables that depend on the above values */
195 DICT_WORD_BYTES = DICT_WORD_SIZE;
196 OBJECT_BYTE_LENGTH = 0;
197 DICT_ENTRY_BYTE_LENGTH = ((version_number==3)?7:9) - (ZCODE_LESS_DICT_DATA?1:0);
198 DICT_ENTRY_FLAG_POS = 0;
202 OBJECT_BYTE_LENGTH = (1 + (NUM_ATTR_BYTES) + 6*4 + (GLULX_OBJECT_EXT_BYTES));
203 DICT_WORD_BYTES = DICT_WORD_SIZE*DICT_CHAR_SIZE;
204 if (DICT_CHAR_SIZE == 1) {
205 DICT_ENTRY_BYTE_LENGTH = (7+DICT_WORD_BYTES);
206 DICT_ENTRY_FLAG_POS = (1+DICT_WORD_BYTES);
209 DICT_ENTRY_BYTE_LENGTH = (12+DICT_WORD_BYTES);
210 DICT_ENTRY_FLAG_POS = (4+DICT_WORD_BYTES);
216 /* The Z-machine's 96 abbreviations are used for these two purposes.
217 Make sure they are set consistently. If exactly one has been
218 set non-default, set the other to match. */
219 if (MAX_DYNAMIC_STRINGS == 32 && MAX_ABBREVS != 64) {
220 MAX_DYNAMIC_STRINGS = 96 - MAX_ABBREVS;
222 if (MAX_ABBREVS == 64 && MAX_DYNAMIC_STRINGS != 32) {
223 MAX_ABBREVS = 96 - MAX_DYNAMIC_STRINGS;
225 if (MAX_ABBREVS + MAX_DYNAMIC_STRINGS != 96
227 || MAX_DYNAMIC_STRINGS < 0) {
228 warning("MAX_ABBREVS plus MAX_DYNAMIC_STRINGS must be 96 in Z-code; resetting both");
229 MAX_DYNAMIC_STRINGS = 32;
235 /* ------------------------------------------------------------------------- */
236 /* Tracery: output control variables */
237 /* (These are initially set to foo_trace_setting, but the Trace directive */
238 /* can change them on the fly) */
239 /* ------------------------------------------------------------------------- */
241 int asm_trace_level, /* trace assembly: 0 for off, 1 for assembly
242 only, 2 for full assembly tracing with hex dumps,
243 3 for branch shortening info, 4 for verbose
245 expr_trace_level, /* expression tracing: 0 off, 1 on, 2/3 more */
246 tokens_trace_level; /* lexer output tracing: 0 off, 1 on, 2/3 more */
248 /* ------------------------------------------------------------------------- */
249 /* On/off switch variables (by default all FALSE); other switch settings */
250 /* (Some of these have become numerical settings now) */
251 /* ------------------------------------------------------------------------- */
253 int concise_switch, /* -c */
254 economy_switch, /* -e */
255 frequencies_setting, /* $!FREQ, -f */
256 ignore_switches_switch, /* -i */
257 debugfile_switch, /* -k */
258 memout_switch, /* $!MEM */
259 printprops_switch, /* $!PROPS */
260 printactions_switch, /* $!ACTIONS */
261 obsolete_switch, /* -q */
262 transcript_switch, /* -r */
263 statistics_switch, /* $!STATS, -s */
264 optimise_switch, /* -u */
265 version_set_switch, /* -v */
266 nowarnings_switch, /* -w */
267 hash_switch, /* -x */
268 memory_map_setting, /* $!MAP, -z */
269 oddeven_packing_switch, /* -B */
270 define_DEBUG_switch, /* -D */
271 runtime_error_checking_switch, /* -S */
272 define_INFIX_switch; /* -X */
274 int throwback_switch; /* -T */
277 int riscos_file_type_format; /* set by -R */
279 int compression_switch; /* set by -H */
280 int character_set_setting, /* set by -C0 through -C9 */
281 character_set_unicode, /* set by -Cu */
282 error_format, /* set by -E */
283 asm_trace_setting, /* $!ASM, -a: initial value of
285 bpatch_trace_setting, /* $!BPATCH */
286 symdef_trace_setting, /* $!SYMDEF */
287 expr_trace_setting, /* $!EXPR: initial value of
289 tokens_trace_setting, /* $!TOKENS: initial value of
290 tokens_trace_level */
291 optabbrevs_trace_setting, /* $!FINDABBREVS */
292 double_space_setting, /* set by -d: 0, 1 or 2 */
293 trace_fns_setting, /* $!RUNTIME, -g: 0, 1, 2, or 3 */
294 files_trace_setting, /* $!FILES */
295 list_verbs_setting, /* $!VERBS */
296 list_dict_setting, /* $!DICT */
297 list_objects_setting, /* $!OBJECTS */
298 list_symbols_setting, /* $!SYMBOLS */
299 store_the_text; /* when set, record game text to a chunk
300 of memory (used by -u) */
301 static int r_e_c_s_set; /* has -S been explicitly set? */
303 int glulx_mode; /* -G */
305 static void reset_switch_settings(void)
306 { asm_trace_setting = 0;
307 tokens_trace_setting = 0;
308 expr_trace_setting = 0;
309 bpatch_trace_setting = 0;
310 symdef_trace_setting = 0;
311 list_verbs_setting = 0;
312 list_dict_setting = 0;
313 list_objects_setting = 0;
314 list_symbols_setting = 0;
316 store_the_text = FALSE;
318 concise_switch = FALSE;
319 double_space_setting = 0;
320 economy_switch = FALSE;
321 files_trace_setting = 0;
322 frequencies_setting = 0;
323 trace_fns_setting = 0;
324 ignore_switches_switch = FALSE;
325 debugfile_switch = FALSE;
327 printprops_switch = 0;
328 printactions_switch = 0;
329 obsolete_switch = FALSE;
330 transcript_switch = FALSE;
331 statistics_switch = FALSE;
332 optimise_switch = FALSE;
333 optabbrevs_trace_setting = 0;
334 version_set_switch = FALSE;
335 nowarnings_switch = FALSE;
337 memory_map_setting = 0;
338 oddeven_packing_switch = FALSE;
339 define_DEBUG_switch = FALSE;
341 throwback_switch = FALSE;
343 runtime_error_checking_switch = TRUE;
345 define_INFIX_switch = FALSE;
347 riscos_file_type_format = 0;
349 error_format=DEFAULT_ERROR_FORMAT;
351 character_set_setting = 1; /* Default is ISO Latin-1 */
352 character_set_unicode = FALSE;
354 compression_switch = TRUE;
356 requested_glulx_version = 0;
358 /* These aren't switches, but for clarity we reset them too. */
360 expr_trace_level = 0;
361 tokens_trace_level = 0;
364 /* ------------------------------------------------------------------------- */
365 /* Number of files given as command line parameters (0, 1 or 2) */
366 /* ------------------------------------------------------------------------- */
368 static int cli_files_specified,
369 convert_filename_flag;
371 char Source_Name[PATHLEN]; /* Processed name of first input file */
372 char Code_Name[PATHLEN]; /* Processed name of output file */
374 static char *cli_file1, *cli_file2; /* Unprocessed (and unsafe to alter) */
376 /* ========================================================================= */
377 /* Data structure management routines */
378 /* ------------------------------------------------------------------------- */
380 static void init_vars(void)
388 init_expressc_vars();
389 init_expressp_vars();
403 static void begin_pass(void)
409 directs_begin_pass();
411 expressc_begin_pass();
412 expressp_begin_pass();
415 endofpass_flag = FALSE;
416 expr_trace_level = expr_trace_setting;
417 asm_trace_level = asm_trace_setting;
418 tokens_trace_level = tokens_trace_setting;
422 objects_begin_pass();
424 symbols_begin_pass();
431 /* Compile a Main__ routine (see "veneer.c") */
433 compile_initial_routine();
435 /* Make the four metaclasses: Class must be object number 1, so
436 it must come first */
441 make_class("Object");
442 make_class("Routine");
443 make_class("String");
448 extern void allocate_arrays(void)
450 arrays_allocate_arrays();
451 asm_allocate_arrays();
452 bpatch_allocate_arrays();
453 chars_allocate_arrays();
454 directs_allocate_arrays();
455 errors_allocate_arrays();
456 expressc_allocate_arrays();
457 expressp_allocate_arrays();
458 files_allocate_arrays();
460 lexer_allocate_arrays();
461 memory_allocate_arrays();
462 objects_allocate_arrays();
463 states_allocate_arrays();
464 symbols_allocate_arrays();
465 syntax_allocate_arrays();
466 tables_allocate_arrays();
467 text_allocate_arrays();
468 veneer_allocate_arrays();
469 verbs_allocate_arrays();
472 extern void free_arrays(void)
474 /* One array may survive this routine, all_the_text (used to hold
475 game text until the abbreviations optimiser begins work on it): this
476 array (if it was ever allocated) is freed at the top level. */
478 arrays_free_arrays();
480 bpatch_free_arrays();
482 directs_free_arrays();
483 errors_free_arrays();
484 expressc_free_arrays();
485 expressp_free_arrays();
489 memory_free_arrays();
490 objects_free_arrays();
491 states_free_arrays();
492 symbols_free_arrays();
493 syntax_free_arrays();
494 tables_free_arrays();
496 veneer_free_arrays();
500 /* ------------------------------------------------------------------------- */
501 /* Name translation code for filenames */
502 /* ------------------------------------------------------------------------- */
504 static char Source_Path[PATHLEN];
505 static char Include_Path[PATHLEN];
506 static char Code_Path[PATHLEN];
507 static char current_source_path[PATHLEN];
508 char Debugging_Name[PATHLEN];
509 char Transcript_Name[PATHLEN];
510 char Language_Name[PATHLEN];
511 char Charset_Map[PATHLEN];
512 static char ICL_Path[PATHLEN];
514 /* Set one of the above Path buffers to the given location, or list of
515 locations. (A list is comma-separated, and only accepted for Source_Path,
516 Include_Path, ICL_Path.)
518 static void set_path_value(char *path, char *value)
523 if (i >= PATHLEN-1) {
524 printf("A specified path is longer than %d characters.\n",
528 if ((value[j] == FN_ALT) || (value[j] == 0))
529 { if ((value[j] == FN_ALT)
530 && (path != Source_Path) && (path != Include_Path)
531 && (path != ICL_Path))
532 { printf("The character '%c' is used to divide entries in a list \
533 of possible locations, and can only be used in the Include_Path, Source_Path \
534 or ICL_Path variables. Other paths are for output only.\n", FN_ALT);
537 if ((path != Debugging_Name) && (path != Transcript_Name)
538 && (path != Language_Name) && (path != Charset_Map)
539 && (i>0) && (isalnum(path[i-1]))) path[i++] = FN_SEP;
540 path[i++] = value[j++];
541 if (value[j-1] == 0) return;
543 else path[i++] = value[j++];
547 /* Prepend the given location or list of locations to one of the above
548 Path buffers. This is only permitted for Source_Path, Include_Path,
551 An empty field (in the comma-separated list) means the current
552 directory. If the Path buffer is entirely empty, we assume that
553 we want to search both value and the current directory, so
554 the result will be "value,".
556 static void prepend_path_value(char *path, char *value)
559 int oldlen = strlen(path);
561 char new_path[PATHLEN];
563 if ((path != Source_Path) && (path != Include_Path)
564 && (path != ICL_Path))
565 { printf("The character '+' is used to add to a list \
566 of possible locations, and can only be used in the Include_Path, Source_Path \
567 or ICL_Path variables. Other paths are for output only.\n");
573 if (i >= PATHLEN-1) {
574 printf("A specified path is longer than %d characters.\n",
578 if ((value[j] == FN_ALT) || (value[j] == 0))
579 { if ((path != Debugging_Name) && (path != Transcript_Name)
580 && (path != Language_Name) && (path != Charset_Map)
581 && (i>0) && (isalnum(new_path[i-1]))) new_path[i++] = FN_SEP;
582 new_path[i++] = value[j++];
583 if (value[j-1] == 0) {
588 else new_path[i++] = value[j++];
591 if (newlen+1+oldlen >= PATHLEN-1) {
592 printf("A specified path is longer than %d characters.\n",
598 new_path[i++] = FN_ALT;
600 new_path[i++] = path[j++];
603 strcpy(path, new_path);
606 static void set_default_paths(void)
608 set_path_value(Source_Path, Source_Directory);
609 set_path_value(Include_Path, Include_Directory);
610 set_path_value(Code_Path, Code_Directory);
611 set_path_value(ICL_Path, ICL_Directory);
612 set_path_value(Debugging_Name, Debugging_File);
613 set_path_value(Transcript_Name, Transcript_File);
614 set_path_value(Language_Name, Default_Language);
615 set_path_value(Charset_Map, "");
618 /* Parse a path option which looks like "dir", "+dir", "pathname=dir",
619 or "+pathname=dir". If there is no "=", we assume "include_path=...".
620 If the option begins with a "+" the directory is prepended to the
621 existing path instead of replacing it.
623 static void set_path_command(char *command)
624 { int i, j; char *path_to_set = NULL;
627 if (command[0] == '+') {
632 for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;
634 path_to_set=Include_Path;
636 if (command[i] == '=') {
637 char pathname[PATHLEN];
638 if (i>=PATHLEN) i=PATHLEN-1;
640 char ch = command[j];
641 if (isupper(ch)) ch=tolower(ch);
645 command = command+i+1;
648 if (strcmp(pathname, "source_path")==0) path_to_set=Source_Path;
649 if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
650 if (strcmp(pathname, "code_path")==0) path_to_set=Code_Path;
651 if (strcmp(pathname, "icl_path")==0) path_to_set=ICL_Path;
652 if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
653 if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;
654 if (strcmp(pathname, "language_name")==0) path_to_set=Language_Name;
655 if (strcmp(pathname, "charset_map")==0) path_to_set=Charset_Map;
657 if (path_to_set == NULL)
658 { printf("No such path setting as \"%s\"\n", pathname);
664 set_path_value(path_to_set, command);
666 prepend_path_value(path_to_set, command);
669 static int contains_separator(char *name)
671 for (i=0; name[i]!=0; i++)
672 if (name[i] == FN_SEP) return 1;
676 static int write_translated_name(char *new_name, char *old_name,
677 char *prefix_path, int start_pos,
680 if (strlen(old_name)+strlen(extension) >= PATHLEN) {
681 printf("One of your filenames is longer than %d characters.\n", PATHLEN);
684 if (prefix_path == NULL)
685 { sprintf(new_name,"%s%s", old_name, extension);
688 strcpy(new_name, prefix_path + start_pos);
689 for (x=0; (new_name[x]!=0) && (new_name[x]!=FN_ALT); x++) ;
690 if (new_name[x] == 0) start_pos = 0; else start_pos += x+1;
691 if (x+strlen(old_name)+strlen(extension) >= PATHLEN) {
692 printf("One of your pathnames is longer than %d characters.\n", PATHLEN);
695 sprintf(new_name + x, "%s%s", old_name, extension);
699 #ifdef FILE_EXTENSIONS
700 static char *check_extension(char *name, char *extension)
703 /* If a filename ends in '.', remove the dot and add no file extension: */
705 if (name[i] == '.') { name[i]=0; return ""; }
707 /* Remove the new extension if it's already got one: */
709 for (; (i>=0) && (name[i]!=FN_SEP); i--)
710 if (name[i] == '.') return "";
715 /* ------------------------------------------------------------------------- */
716 /* Three translation routines have to deal with path variables which may */
717 /* contain alternative locations separated by the FN_ALT character. */
718 /* These have the protocol: */
720 /* int translate_*_filename(int last_value, ...) */
722 /* and should first be called with last_value equal to 0. If the */
723 /* translated filename works, fine. Otherwise, if the returned integer */
724 /* was zero, the caller knows that no filename works and can issue an */
725 /* error message. If it was non-zero, the caller should pass it on as */
726 /* the last_value again. */
728 /* As implemented below, last_value is the position in the path variable */
729 /* string at which the next directory name to try begins. */
730 /* ------------------------------------------------------------------------- */
732 extern int translate_in_filename(int last_value,
733 char *new_name, char *old_name,
734 int same_directory_flag, int command_line_flag)
735 { char *prefix_path = NULL;
737 int add_path_flag = 1;
740 if ((same_directory_flag==0)
741 && (contains_separator(old_name)==1)) add_path_flag=0;
743 if (add_path_flag==1)
744 { if (command_line_flag == 0)
745 { /* File is opened as a result of an Include directive */
747 if (same_directory_flag==1)
748 prefix_path = current_source_path;
750 if (Include_Path[0]!=0) prefix_path = Include_Path;
752 /* Main file being opened from the command line */
754 else if (Source_Path[0]!=0) prefix_path = Source_Path;
757 #ifdef FILE_EXTENSIONS
758 /* Which file extension is expected? */
760 if ((command_line_flag==1)||(same_directory_flag==1))
761 extension = Source_Extension;
763 extension = Include_Extension;
765 extension = check_extension(old_name, extension);
770 last_value = write_translated_name(new_name, old_name,
771 prefix_path, last_value, extension);
773 /* Set the "current source path" (for use of Include ">...") */
775 if (command_line_flag==1)
776 { strcpy(current_source_path, new_name);
777 for (i=strlen(current_source_path)-1;
778 ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
780 if (i!=0) current_source_path[i+1] = 0; /* Current file in subdir */
781 else current_source_path[0] = 0; /* Current file at root dir */
787 static int translate_icl_filename(int last_value,
788 char *new_name, char *old_name)
789 { char *prefix_path = NULL;
790 char *extension = "";
792 if (contains_separator(old_name)==0)
794 prefix_path = ICL_Path;
796 #ifdef FILE_EXTENSIONS
797 extension = check_extension(old_name, ICL_Extension);
800 return write_translated_name(new_name, old_name,
801 prefix_path, last_value, extension);
804 extern void translate_out_filename(char *new_name, char *old_name)
806 char *extension = "";
809 /* If !convert_filename_flag, then the old_name is just the <file2>
810 parameter on the Inform command line, which we leave alone. */
812 if (!convert_filename_flag)
813 { strcpy(new_name, old_name); return;
816 /* Remove any pathname or extension in <file1>. */
818 if (contains_separator(old_name)==1)
819 { for (i=strlen(old_name)-1; (i>0)&&(old_name[i]!=FN_SEP) ;i--) { };
820 if (old_name[i]==FN_SEP) i++;
823 #ifdef FILE_EXTENSIONS
824 for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
825 if (old_name[i] == '.') old_name[i] = 0;
831 switch(version_number)
832 { case 3: extension = Code_Extension; break;
833 case 4: extension = V4Code_Extension; break;
834 case 5: extension = V5Code_Extension; break;
835 case 6: extension = V6Code_Extension; break;
836 case 7: extension = V7Code_Extension; break;
837 case 8: extension = V8Code_Extension; break;
841 extension = GlulxCode_Extension;
843 if (Code_Path[0]!=0) prefix_path = Code_Path;
845 #ifdef FILE_EXTENSIONS
846 extension = check_extension(old_name, extension);
849 write_translated_name(new_name, old_name, prefix_path, 0, extension);
852 static char *name_or_unset(char *p)
853 { if (p[0]==0) return "(unset)";
857 static void help_on_filenames(void)
858 { char old_name[PATHLEN];
859 char new_name[PATHLEN];
862 printf("Help information on filenames:\n\n");
865 "The command line can take one of two forms:\n\n\
866 inform [commands...] <file1>\n\
867 inform [commands...] <file1> <file2>\n\n\
868 Inform translates <file1> into a source file name (see below) for its input.\n\
869 <file2> is usually omitted: if so, the output filename is made from <file1>\n\
870 by cutting out the name part and translating that (see below).\n\
871 If <file2> is given, however, the output filename is set to just <file2>\n\
872 (not altered in any way).\n\n");
875 "Filenames given in the game source (with commands like Include \"name\")\n\
876 are also translated by the rules below.\n\n");
879 "Rules of translation:\n\n\
880 Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
881 (such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
882 1. If the name contains a '%c' character (so it's already a pathname), it\n\
883 isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);
886 " [Exception: when the name is given in an Include command using the >\n\
887 form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
888 of the file doing the inclusion");
889 #ifdef FILE_EXTENSIONS
890 printf(" and a suitable file extension is added");
895 " Filenames must never contain double-quotation marks \". To use filenames\n\
896 which contain spaces, write them in double-quotes: for instance,\n\n\
897 \"inform +code_path=\"Jigsaw Final Version\" jigsaw\".\n\n");
900 "2. The file is looked for at a particular \"path\" (the filename of a\n\
901 directory), depending on what kind of file it is.\n\n\
902 File type Name Current setting\n\n\
903 Source code (in) source_path %s\n\
904 Include file (in) include_path %s\n\
905 Story file (out) code_path %s\n",
906 name_or_unset(Source_Path), name_or_unset(Include_Path),
907 name_or_unset(Code_Path));
910 " ICL command file (in) icl_path %s\n\n",
911 name_or_unset(ICL_Path));
914 " If the path is unset, then the current working directory is used (so\n\
915 the filename doesn't change): if, for instance, include_path is set to\n\
916 \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
917 \"backup%coldlib%cparser\".\n\n\
918 The paths can be set or unset on the Inform command line by, eg,\n\
919 \"inform +code_path=finished jigsaw\" or\n\
920 \"inform +include_path= balances\" (which unsets include_path).\n\n",
921 FN_SEP, FN_SEP, FN_SEP);
924 " The four input path variables can be set to lists of alternative paths\n\
925 separated by '%c' characters: these alternatives are always tried in\n\
926 the order they are specified in, that is, left to right through the text\n\
927 in the path variable.\n\n",
930 " If two '+' signs are used (\"inform ++include_path=dir jigsaw\") then\n\
931 the path or paths are added to the existing list.\n\n");
933 " (It is an error to give alternatives at all for purely output paths.)\n\n");
935 #ifdef FILE_EXTENSIONS
936 printf("3. The following file extensions are added:\n\n\
939 Story files: %s (Version 3), %s (v4), %s (v5, the default),\n\
940 %s (v6), %s (v7), %s (v8), %s (Glulx)\n\n",
941 Source_Extension, Include_Extension,
942 Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
943 V7Code_Extension, V8Code_Extension, GlulxCode_Extension);
945 except that any extension you give (on the command line or in a filename\n\
946 used in a program) will override these. If you give the null extension\n\
947 \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
950 printf("Names of four individual files can also be set using the same\n\
951 + command notation (though they aren't really pathnames). These are:\n\n\
952 transcript_name (text written by -r switch): now \"%s\"\n\
953 debugging_name (data written by -k switch): now \"%s\"\n\
954 language_name (library file defining natural language of game):\n\
956 charset_map (file for character set mapping): now \"%s\"\n\n",
957 Transcript_Name, Debugging_Name, Language_Name, Charset_Map);
959 translate_in_filename(0, new_name, "rezrov", 0, 1);
960 printf("Examples: 1. \"inform rezrov\"\n\
961 the source code is read from \"%s\"\n",
963 convert_filename_flag = TRUE;
964 translate_out_filename(new_name, "rezrov");
965 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
967 sprintf(old_name, "demos%cplugh", FN_SEP);
968 printf("2. \"inform %s\"\n", old_name);
969 translate_in_filename(0, new_name, old_name, 0, 1);
970 printf(" the source code is read from \"%s\"\n", new_name);
971 sprintf(old_name, "demos%cplugh", FN_SEP);
972 convert_filename_flag = TRUE;
973 translate_out_filename(new_name, old_name);
974 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
976 printf("3. \"inform plover my_demo\"\n");
977 translate_in_filename(0, new_name, "plover", 0, 1);
978 printf(" the source code is read from \"%s\"\n", new_name);
979 convert_filename_flag = FALSE;
980 translate_out_filename(new_name, "my_demo");
981 printf(" and a story file is compiled to \"%s\".\n\n", new_name);
983 strcpy(old_name, Source_Path);
984 sprintf(new_name, "%cnew%cold%crecent%cold%cancient",
985 FN_ALT, FN_ALT, FN_SEP, FN_ALT, FN_SEP);
986 printf("4. \"inform +source_path=%s zooge\"\n", new_name);
988 " Note that four alternative paths are given, the first being the empty\n\
989 path-name (meaning: where you are now). Inform looks for the source code\n\
990 by trying these four places in turn, stopping when it finds anything:\n\n");
992 set_path_value(Source_Path, new_name);
995 { x = translate_in_filename(x, new_name, "zooge", 0, 1);
996 printf(" \"%s\"\n", new_name);
998 strcpy(Source_Path, old_name);
1002 static char riscos_ft_buffer[4];
1004 extern char *riscos_file_type(void)
1006 if (riscos_file_type_format == 1)
1011 sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
1012 return(riscos_ft_buffer);
1016 /* ------------------------------------------------------------------------- */
1017 /* The compilation pass */
1018 /* ------------------------------------------------------------------------- */
1020 static void run_pass(void)
1022 lexer_begin_prepass();
1023 files_begin_prepass();
1024 load_sourcefile(Source_Name, 0);
1028 parse_program(NULL);
1031 issue_unused_warnings();
1036 issue_debug_symbol_warnings();
1039 if (hash_switch && hash_printed_since_newline) printf("\n");
1042 if (track_unused_routines)
1043 locate_dead_functions();
1044 construct_storyfile();
1047 int output_has_occurred;
1049 static void rennab(float time_taken)
1050 { /* rennab = reverse of banner */
1051 int t = no_warnings + no_suppressed_warnings;
1053 if (memout_switch) print_memory_usage();
1055 if ((no_errors + t)!=0)
1056 { printf("Compiled with ");
1058 { printf("%d error%s", no_errors,(no_errors==1)?"":"s");
1059 if (t > 0) printf(" and ");
1061 if (no_warnings > 0)
1062 printf("%d warning%s", t, (t==1)?"":"s");
1063 if (no_suppressed_warnings > 0)
1064 { if (no_warnings > 0)
1065 printf(" (%d suppressed)", no_suppressed_warnings);
1067 printf("%d suppressed warning%s", no_suppressed_warnings,
1068 (no_suppressed_warnings==1)?"":"s");
1070 if (output_has_occurred == FALSE) printf(" (no output)");
1074 if (no_compiler_errors > 0) print_sorry_message();
1076 if (statistics_switch)
1078 /* Print the duration to a sensible number of decimal places.
1079 (We aim for three significant figures.) */
1080 if (time_taken >= 10.0)
1081 printf("Completed in %.1f seconds\n", time_taken);
1082 else if (time_taken >= 1.0)
1083 printf("Completed in %.2f seconds\n", time_taken);
1085 printf("Completed in %.3f seconds\n", time_taken);
1089 /* ------------------------------------------------------------------------- */
1090 /* The compiler abstracted to a routine. */
1091 /* ------------------------------------------------------------------------- */
1093 static int execute_icl_header(char *file1);
1095 static int compile(int number_of_files_specified, char *file1, char *file2)
1097 TIMEVALUE time_start, time_end;
1100 if (execute_icl_header(file1))
1103 select_target(glulx_mode);
1105 if (define_INFIX_switch && glulx_mode) {
1106 printf("Infix (-X) facilities are not available in Glulx: \
1107 disabling -X switch\n");
1108 define_INFIX_switch = FALSE;
1111 TIMEVALUE_NOW(&time_start);
1115 strcpy(Source_Name, file1); convert_filename_flag = TRUE;
1116 strcpy(Code_Name, file1);
1117 if (number_of_files_specified == 2)
1118 { strcpy(Code_Name, file2); convert_filename_flag = FALSE;
1123 if (debugfile_switch) begin_debug_file();
1127 if (transcript_switch) open_transcript_file(Source_Name);
1131 if (transcript_switch)
1132 { write_dictionary_to_transcript();
1133 close_transcript_file();
1136 if (no_errors==0) { output_file(); output_has_occurred = TRUE; }
1137 else { output_has_occurred = FALSE; }
1139 if (debugfile_switch)
1143 if (optimise_switch) {
1144 /* Pull out all_text so that it will not be freed. */
1150 TIMEVALUE_NOW(&time_end);
1151 duration = TIMEVALUE_DIFFERENCE(&time_start, &time_end);
1155 if (optimise_switch) {
1156 optimise_abbreviations();
1160 return (no_errors==0)?0:1;
1163 /* ------------------------------------------------------------------------- */
1164 /* The command line interpreter */
1165 /* ------------------------------------------------------------------------- */
1167 static void cli_print_help(int help_level)
1170 "\nThis program is a compiler of Infocom format (also called \"Z-machine\")\n\
1171 story files, as well as \"Glulx\" story files:\n\
1172 Copyright (c) Graham Nelson 1993 - 2022.\n\n");
1174 /* For people typing just "inform", a summary only: */
1179 #ifndef PROMPT_INPUT
1180 printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
1182 printf("When run, Inform prompts you for commands (and switches),\n\
1183 which are optional, then an input <file1> and an (optional) output\n\
1188 "<file1> is the Inform source file of the game to be compiled. <file2>,\n\
1189 if given, overrides the filename Inform would normally use for the\n\
1190 compiled output. Try \"inform -h1\" for file-naming conventions.\n\n\
1191 One or more words can be supplied as \"commands\". These may be:\n\n\
1192 -switches a list of compiler switches, 1 or 2 letter\n\
1193 (see \"inform -h2\" for the full range)\n\n\
1194 +dir set Include_Path to this directory\n\
1195 ++dir add this directory to Include_Path\n\
1196 +PATH=dir change the PATH to this directory\n\
1197 ++PATH=dir add this directory to the PATH\n\n\
1198 $... one of the following configuration commands:\n");
1201 " $list list current settings\n\
1202 $?SETTING explain briefly what SETTING is for\n\
1203 $SETTING=number change SETTING to given number\n\
1204 $!TRACEOPT set trace option TRACEOPT\n\
1205 (or $!TRACEOPT=2, 3, etc for more tracing;\n\
1206 $! by itself to list all trace options)\n\
1207 $#SYMBOL=number define SYMBOL as a constant in the story\n\n");
1210 " (filename) read in a list of commands (in the format above)\n\
1211 from this \"setup file\"\n\n");
1213 printf("Alternate command-line formats for the above:\n\
1214 --help (this page)\n\
1215 --path PATH=dir (set path)\n\
1216 --addpath PATH=dir (add to path)\n\
1217 --list (list current settings)\n\
1218 --helpopt SETTING (explain setting)\n\
1219 --opt SETTING=number (change setting)\n\
1220 --helptrace (list all trace options)\n\
1221 --trace TRACEOPT (set trace option)\n\
1222 --trace TRACEOPT=num (more tracing)\n\
1223 --define SYMBOL=number (define constant)\n\
1224 --config filename (read setup file)\n\n");
1226 #ifndef PROMPT_INPUT
1227 printf("For example: \"inform -dexs curses\".\n");
1233 /* The -h1 (filenaming) help information: */
1235 if (help_level == 1) { help_on_filenames(); return; }
1237 /* The -h2 (switches) help information: */
1239 printf("Help on the full list of legal switch commands:\n\n\
1240 a trace assembly-language\n\
1241 a2 trace assembly with hex dumps\n\
1242 c more concise error messages\n\
1243 d contract double spaces after full stops in text\n\
1244 d2 contract double spaces after exclamation and question marks, too\n\
1245 e economy mode (slower): make use of declared abbreviations\n");
1248 f frequencies mode: show how useful abbreviations are\n\
1249 g traces calls to all game functions\n\
1250 g2 traces calls to all game and library functions\n\
1251 g3 traces calls to all functions (including veneer)\n\
1252 h print general help information\n\
1253 h1 print help information on filenames and path options\n\
1254 h2 print help information on switches (this page)\n");
1257 i ignore default switches set within the file\n\
1258 k output debugging information to \"%s\"\n",
1261 q keep quiet about obsolete usages\n\
1262 r record all the text to \"%s\"\n\
1263 s give statistics\n",
1267 u work out most useful abbreviations (very very slowly)\n\
1268 v3 compile to version-3 (\"Standard\"/\"ZIP\") story file\n\
1269 v4 compile to version-4 (\"Plus\"/\"EZIP\") story file\n\
1270 v5 compile to version-5 (\"Advanced\"/\"XZIP\") story file: the default\n\
1271 v6 compile to version-6 (graphical/\"YZIP\") story file\n\
1272 v7 compile to version-7 (expanded \"Advanced\") story file\n\
1273 v8 compile to version-8 (expanded \"Advanced\") story file\n\
1274 w disable warning messages\n\
1275 x print # for every 100 lines compiled\n\
1276 z print memory map of the virtual machine\n\n");
1279 B use big memory model (for large V6/V7 files)\n\
1280 C0 text character set is plain ASCII only\n\
1281 Cu text character set is UTF-8\n\
1282 Cn text character set is ISO 8859-n (n = 1 to 9)\n\
1283 (1 to 4, Latin1 to Latin4; 5, Cyrillic; 6, Arabic;\n\
1284 7, Greek; 8, Hebrew; 9, Latin5. Default is -C1.)\n");
1285 printf(" D insert \"Constant DEBUG;\" automatically\n");
1286 printf(" E0 Archimedes-style error messages%s\n",
1287 (error_format==0)?" (current setting)":"");
1288 printf(" E1 Microsoft-style error messages%s\n",
1289 (error_format==1)?" (current setting)":"");
1290 printf(" E2 Macintosh MPW-style error messages%s\n",
1291 (error_format==2)?" (current setting)":"");
1292 printf(" G compile a Glulx game file\n");
1293 printf(" H use Huffman encoding to compress Glulx strings\n");
1297 R0 use filetype 060 + version number for games (default)\n\
1298 R1 use official Acorn filetype 11A for all games\n");
1300 printf(" S compile strict error-checking at run-time (on by default)\n");
1301 #ifdef ARC_THROWBACK
1302 printf(" T enable throwback of errors in the DDE\n");
1304 printf(" V print the version and date of this program\n");
1305 printf(" Wn header extension table is at least n words (n = 3 to 99)\n");
1306 printf(" X compile with INFIX debugging facilities present\n");
1310 extern void switches(char *p, int cmode)
1311 { int i, s=1, state;
1312 /* Here cmode is 0 if switches list is from a "Switches" directive
1313 and 1 if from a "-switches" command-line or ICL list */
1318 "Ignoring second word which should be a -list of switches.\n");
1322 for (i=cmode; p[i]!=0; i+=s, s=1)
1330 case 'a': switch(p[i+1])
1331 { case '1': asm_trace_setting=1; s=2; break;
1332 case '2': asm_trace_setting=2; s=2; break;
1333 case '3': asm_trace_setting=3; s=2; break;
1334 case '4': asm_trace_setting=4; s=2; break;
1335 default: asm_trace_setting=1; break;
1338 case 'c': concise_switch = state; break;
1339 case 'd': switch(p[i+1])
1340 { case '1': double_space_setting=1; s=2; break;
1341 case '2': double_space_setting=2; s=2; break;
1342 default: double_space_setting=1; break;
1345 case 'e': economy_switch = state; break;
1346 case 'f': frequencies_setting = (state?1:0); break;
1347 case 'g': switch(p[i+1])
1348 { case '1': trace_fns_setting=1; s=2; break;
1349 case '2': trace_fns_setting=2; s=2; break;
1350 case '3': trace_fns_setting=3; s=2; break;
1351 default: trace_fns_setting=1; break;
1354 case 'h': switch(p[i+1])
1355 { case '1': cli_print_help(1); s=2; break;
1356 case '2': cli_print_help(2); s=2; break;
1358 default: cli_print_help(0); break;
1361 case 'i': ignore_switches_switch = state; break;
1362 case 'k': if (cmode == 0)
1363 error("The switch '-k' can't be set with 'Switches'");
1365 debugfile_switch = state;
1367 case 'q': obsolete_switch = state; break;
1368 case 'r': if (cmode == 0)
1369 error("The switch '-r' can't be set with 'Switches'");
1371 transcript_switch = state; break;
1372 case 's': statistics_switch = state; break;
1373 case 'u': if (cmode == 0) {
1374 error("The switch '-u' can't be set with 'Switches'");
1377 optimise_switch = state; break;
1378 case 'v': if (glulx_mode) { s = select_glulx_version(p+i+1)+1; break; }
1379 if ((cmode==0) && (version_set_switch)) { s=2; break; }
1380 version_set_switch = TRUE; s=2;
1382 { case '3': select_version(3); break;
1383 case '4': select_version(4); break;
1384 case '5': select_version(5); break;
1385 case '6': select_version(6); break;
1386 case '7': select_version(7); break;
1387 case '8': select_version(8); break;
1388 default: printf("-v must be followed by 3 to 8\n");
1389 version_set_switch=0; s=1;
1392 if ((version_number < 5) && (r_e_c_s_set == FALSE))
1393 runtime_error_checking_switch = FALSE;
1395 case 'w': nowarnings_switch = state; break;
1396 case 'x': hash_switch = state; break;
1397 case 'z': memory_map_setting = (state ? 1 : 0); break;
1398 case 'B': oddeven_packing_switch = state; break;
1400 if (p[i+1] == 'u') {
1401 character_set_unicode = TRUE;
1402 /* Leave the set_setting on Latin-1, because that
1403 matches the first block of Unicode. */
1404 character_set_setting = 1;
1407 { character_set_setting=p[i+1]-'0';
1408 if ((character_set_setting < 0)
1409 || (character_set_setting > 9))
1410 { printf("-C must be followed by 'u' or 0 to 9. Defaulting to ISO-8859-1.\n");
1411 character_set_unicode = FALSE;
1412 character_set_setting = 1;
1415 if (cmode == 0) change_character_set();
1417 case 'D': define_DEBUG_switch = state; break;
1418 case 'E': switch(p[i+1])
1419 { case '0': s=2; error_format=0; break;
1420 case '1': s=2; error_format=1; break;
1421 case '2': s=2; error_format=2; break;
1422 default: error_format=1; break;
1426 case 'R': switch(p[i+1])
1427 { case '0': s=2; riscos_file_type_format=0; break;
1428 case '1': s=2; riscos_file_type_format=1; break;
1429 default: riscos_file_type_format=1; break;
1433 #ifdef ARC_THROWBACK
1434 case 'T': throwback_switch = state; break;
1436 case 'S': runtime_error_checking_switch = state;
1437 r_e_c_s_set = TRUE; break;
1438 case 'G': if (cmode == 0)
1439 error("The switch '-G' can't be set with 'Switches'");
1440 else if (version_set_switch)
1441 error("The '-G' switch cannot follow the '-v' switch");
1443 { glulx_mode = state;
1444 adjust_memory_sizes();
1447 case 'H': compression_switch = state; break;
1448 case 'V': exit(0); break;
1449 case 'W': if ((p[i+1]>='0') && (p[i+1]<='9'))
1450 { s=2; ZCODE_HEADER_EXT_WORDS = p[i+1]-'0';
1451 if ((p[i+2]>='0') && (p[i+2]<='9'))
1452 { s=3; ZCODE_HEADER_EXT_WORDS *= 10;
1453 ZCODE_HEADER_EXT_WORDS += p[i+2]-'0';
1457 case 'X': define_INFIX_switch = state; break;
1459 printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
1465 if (optimise_switch)
1467 /* store_the_text is equivalent to optimise_switch; -u sets both.
1468 We could simplify this. */
1469 store_the_text=TRUE;
1473 /* Check whether the string looks like an ICL command. */
1474 static int icl_command(char *p)
1475 { if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
1476 || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
1480 static void icl_error(char *filename, int line)
1481 { printf("Error in ICL file '%s', line %d:\n", filename, line);
1484 static void icl_header_error(char *filename, int line)
1485 { printf("Error in ICL header of file '%s', line %d:\n", filename, line);
1488 static int copy_icl_word(char *from, char *to, int max)
1490 /* Copies one token from 'from' to 'to', null-terminated:
1491 returns the number of chars in 'from' read past (possibly 0). */
1493 int i, j, quoted_mode, truncated;
1495 i = 0; truncated = 0;
1496 while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
1497 || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;
1500 { while (from[i] != 0) i++;
1501 to[0] = 0; return i;
1504 for (quoted_mode = FALSE, j=0;;)
1505 { if (from[i] == 0) break;
1506 if (from[i] == 10) break;
1507 if (from[i] == 13) break;
1508 if (from[i] == TAB_CHARACTER) break;
1509 if ((from[i] == ' ') && (!quoted_mode)) break;
1510 if (from[i] == '\"') { quoted_mode = !quoted_mode; i++; }
1511 else to[j++] = from[i++];
1519 printf("The following parameter has been truncated:\n%s\n", to);
1523 /* Copy a string, converting to uppercase. The to array should be
1524 (at least) max characters. Result will be null-terminated, so
1525 at most max-1 characters will be copied.
1527 static int strcpyupper(char *to, char *from, int max)
1530 for (ix=0; ix<max-1; ix++) {
1532 if (islower(ch)) ch = toupper(ch);
1539 static void execute_icl_command(char *p);
1540 static int execute_dashdash_command(char *p, char *p2);
1542 static int execute_icl_header(char *argname)
1545 char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1549 char filename[PATHLEN];
1553 { x = translate_in_filename(x, filename, argname, 0, 1);
1554 command_file = fopen(filename,"r");
1555 } while ((command_file == NULL) && (x != 0));
1556 if (!command_file) {
1557 /* Fail silently. The regular compiler will try to open the file
1558 again, and report the problem. */
1562 while (feof(command_file)==0) {
1563 if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1565 if (!(cli_buff[0] == '!' && cli_buff[1] == '%'))
1567 i = copy_icl_word(cli_buff+2, fw, CMD_BUF_SIZE);
1568 if (icl_command(fw)) {
1569 execute_icl_command(fw);
1570 copy_icl_word(cli_buff+2 + i, fw, CMD_BUF_SIZE);
1571 if ((fw[0] != 0) && (fw[0] != '!')) {
1572 icl_header_error(filename, line);
1574 printf("expected comment or nothing but found '%s'\n", fw);
1579 icl_header_error(filename, line);
1581 printf("Expected command or comment but found '%s'\n", fw);
1585 fclose(command_file);
1587 return (errcount==0)?0:1;
1591 static void run_icl_file(char *filename, FILE *command_file)
1592 { char cli_buff[CMD_BUF_SIZE], fw[CMD_BUF_SIZE];
1594 printf("[Running ICL file '%s']\n", filename);
1596 while (feof(command_file)==0)
1597 { if (fgets(cli_buff,CMD_BUF_SIZE,command_file)==0) break;
1599 i = copy_icl_word(cli_buff, fw, CMD_BUF_SIZE);
1600 if (icl_command(fw))
1601 { execute_icl_command(fw);
1602 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1603 if ((fw[0] != 0) && (fw[0] != '!'))
1604 { icl_error(filename, line);
1605 printf("expected comment or nothing but found '%s'\n", fw);
1609 { if (strcmp(fw, "compile")==0)
1610 { char story_name[PATHLEN], code_name[PATHLEN];
1611 i += copy_icl_word(cli_buff + i, story_name, PATHLEN);
1612 i += copy_icl_word(cli_buff + i, code_name, PATHLEN);
1614 if (code_name[0] != 0) x=2;
1615 else if (story_name[0] != 0) x=1;
1619 { case 0: icl_error(filename, line);
1620 printf("No filename given to 'compile'\n");
1622 case 1: printf("[Compiling <%s>]\n", story_name);
1623 compile(x, story_name, code_name);
1625 case 2: printf("[Compiling <%s> to <%s>]\n",
1626 story_name, code_name);
1627 compile(x, story_name, code_name);
1628 copy_icl_word(cli_buff + i, fw, CMD_BUF_SIZE);
1630 { icl_error(filename, line);
1631 printf("Expected comment or nothing but found '%s'\n",
1639 { icl_error(filename, line);
1640 printf("Expected command or comment but found '%s'\n", fw);
1646 /* This should only be called if the argument has been verified to be
1647 an ICL command, e.g. by checking icl_command().
1649 static void execute_icl_command(char *p)
1650 { char filename[PATHLEN], cli_buff[CMD_BUF_SIZE];
1655 { case '+': set_path_command(p+1); break;
1656 case '-': switches(p,1); break;
1657 case '$': memory_command(p+1); break;
1658 case '(': len = strlen(p);
1659 if (p[len-1] != ')') {
1660 printf("Error in ICL: (command) missing closing paren\n");
1663 len -= 2; /* omit parens */
1664 if (len > CMD_BUF_SIZE-1) len = CMD_BUF_SIZE-1;
1665 strncpy(cli_buff, p+1, len);
1669 { x = translate_icl_filename(x, filename, cli_buff);
1670 command_file = fopen(filename,"r");
1671 } while ((command_file == NULL) && (x != 0));
1673 if (command_file == NULL) {
1674 printf("Error in ICL: Couldn't open command file '%s'\n",
1678 run_icl_file(filename, command_file);
1679 fclose(command_file);
1684 /* Convert a --command into the equivalent ICL command and call
1685 execute_icl_command(). The dashes have already been stripped.
1687 The second argument is the following command-line argument
1688 (or NULL if there was none). This may or may not be consumed.
1689 Returns TRUE if it was.
1691 static int execute_dashdash_command(char *p, char *p2)
1693 char cli_buff[CMD_BUF_SIZE];
1694 int consumed2 = FALSE;
1696 if (!strcmp(p, "help")) {
1697 strcpy(cli_buff, "-h");
1699 else if (!strcmp(p, "list")) {
1700 strcpy(cli_buff, "$LIST");
1702 else if (!strcmp(p, "size")) {
1704 /* We accept these arguments even though they've been withdrawn. */
1705 if (!(p2 && (!strcmpcis(p2, "HUGE") || !strcmpcis(p2, "LARGE") || !strcmpcis(p2, "SMALL")))) {
1706 printf("--size must be followed by \"huge\", \"large\", or \"small\"\n");
1709 strcpy(cli_buff, "$");
1710 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1712 else if (!strcmp(p, "opt")) {
1714 if (!p2 || !strchr(p2, '=')) {
1715 printf("--opt must be followed by \"setting=number\"\n");
1718 strcpy(cli_buff, "$");
1719 strcpyupper(cli_buff+1, p2, CMD_BUF_SIZE-1);
1721 else if (!strcmp(p, "helpopt")) {
1724 printf("--helpopt must be followed by \"setting\"\n");
1727 strcpy(cli_buff, "$?");
1728 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1730 else if (!strcmp(p, "define")) {
1733 printf("--define must be followed by \"symbol=number\"\n");
1736 strcpy(cli_buff, "$#");
1737 strcpyupper(cli_buff+2, p2, CMD_BUF_SIZE-2);
1739 else if (!strcmp(p, "path")) {
1741 if (!p2 || !strchr(p2, '=')) {
1742 printf("--path must be followed by \"name=path\"\n");
1745 snprintf(cli_buff, CMD_BUF_SIZE, "+%s", p2);
1747 else if (!strcmp(p, "addpath")) {
1749 if (!p2 || !strchr(p2, '=')) {
1750 printf("--addpath must be followed by \"name=path\"\n");
1753 snprintf(cli_buff, CMD_BUF_SIZE, "++%s", p2);
1755 else if (!strcmp(p, "config")) {
1758 printf("--config must be followed by \"file.icl\"\n");
1761 snprintf(cli_buff, CMD_BUF_SIZE, "(%s)", p2);
1763 else if (!strcmp(p, "trace")) {
1766 printf("--trace must be followed by \"traceopt\" or \"traceopt=N\"\n");
1769 snprintf(cli_buff, CMD_BUF_SIZE, "$!%s", p2);
1771 else if (!strcmp(p, "helptrace")) {
1772 strcpy(cli_buff, "$!");
1775 printf("Option \"--%s\" unknown (try \"inform -h\")\n", p);
1779 execute_icl_command(cli_buff);
1783 /* ------------------------------------------------------------------------- */
1784 /* Opening and closing banners */
1785 /* ------------------------------------------------------------------------- */
1787 char banner_line[CMD_BUF_SIZE];
1789 /* We store the banner text for use elsewhere (see files.c).
1791 static void banner(void)
1794 snprintf(banner_line, CMD_BUF_SIZE, "Inform %d.%d%d",
1795 (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10);
1796 #ifdef RELEASE_SUFFIX
1797 len = strlen(banner_line);
1798 snprintf(banner_line+len, CMD_BUF_SIZE-len, "%s", RELEASE_SUFFIX);
1800 #ifdef MACHINE_STRING
1801 len = strlen(banner_line);
1802 snprintf(banner_line+len, CMD_BUF_SIZE-len, " for %s", MACHINE_STRING);
1804 len = strlen(banner_line);
1805 snprintf(banner_line+len, CMD_BUF_SIZE-len, " (%s)", RELEASE_DATE);
1807 printf("%s\n", banner_line);
1810 /* ------------------------------------------------------------------------- */
1811 /* Input from the outside world */
1812 /* ------------------------------------------------------------------------- */
1815 static void read_command_line(int argc, char **argv)
1817 char buffer1[PATHLEN], buffer2[PATHLEN], buffer3[PATHLEN];
1819 printf("Source filename?\n> ");
1820 while (gets(buffer1)==NULL); cli_file1=buffer1;
1821 printf("Output filename (RETURN for the same)?\n> ");
1822 while (gets(buffer2)==NULL); cli_file2=buffer2;
1823 cli_files_specified=1;
1824 if (buffer2[0]!=0) cli_files_specified=2;
1826 { printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
1827 while (gets(buffer3)==NULL); execute_icl_command(buffer3);
1828 } while (buffer3[0]!=0);
1831 static void read_command_line(int argc, char **argv)
1833 if (argc==1) switches("-h",1);
1835 for (i=1, cli_files_specified=0; i<argc; i++)
1836 if (argv[i][0] == '-' && argv[i][1] == '-') {
1837 char *nextarg = NULL;
1839 if (i+1 < argc) nextarg = argv[i+1];
1840 consumed2 = execute_dashdash_command(argv[i]+2, nextarg);
1841 if (consumed2 && i+1 < argc) {
1845 else if (icl_command(argv[i])) {
1846 execute_icl_command(argv[i]);
1849 switch(++cli_files_specified)
1850 { case 1: cli_file1 = argv[i]; break;
1851 case 2: cli_file2 = argv[i]; break;
1853 printf("Command line error: unknown parameter '%s'\n",
1860 /* ------------------------------------------------------------------------- */
1861 /* M A I N : An outer shell for machine-specific quirks */
1862 /* Omitted altogether if EXTERNAL_SHELL is defined, as for instance is */
1863 /* needed for the Macintosh front end. */
1864 /* ------------------------------------------------------------------------- */
1866 #ifdef EXTERNAL_SHELL
1867 extern int sub_main(int argc, char **argv);
1870 static int sub_main(int argc, char **argv);
1872 int main(int argc, char **argv, char *envp[])
1874 int main(int argc, char **argv)
1878 InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
1880 rcode = sub_main(argc, argv);
1881 #ifdef ARC_THROWBACK
1889 /* ------------------------------------------------------------------------- */
1890 /* M A I N II: Starting up ICL with the command line */
1891 /* ------------------------------------------------------------------------- */
1893 #ifdef EXTERNAL_SHELL
1894 extern int sub_main(int argc, char **argv)
1896 static int sub_main(int argc, char **argv)
1901 ProcessEvents (&g_proc);
1904 if (store_the_text) my_free(&all_text,"transcription text");
1905 longjmp (g_fallback, 1);
1911 set_memory_sizes(); set_default_paths();
1912 reset_switch_settings(); select_version(5);
1914 cli_files_specified = 0; no_compilations = 0;
1915 cli_file1 = "source"; cli_file2 = "output";
1917 read_command_line(argc, argv);
1919 if (cli_files_specified > 0)
1920 { return_code = compile(cli_files_specified, cli_file1, cli_file2);
1922 if (return_code != 0) return(return_code);
1925 if (no_compilations == 0)
1926 printf("\n[No compilation requested]\n");
1927 if (no_compilations > 1)
1928 printf("[%d compilations completed]\n", no_compilations);
1933 /* ========================================================================= */