/* ------------------------------------------------------------------------- */
/*   "inform" :  The top level of Inform: switches, pathnames, filenaming    */
/*               conventions, ICL (Inform Command Line) files, main          */
/*                                                                           */
/*   Part of Inform 6    copyright (c) Graham Nelson 1993, 1994, 1995, 1996  */
/*                                                                           */
/* ------------------------------------------------------------------------- */

#define MAIN_INFORM_FILE
#include "header.h"
extern int last_mapped_line;

/* ------------------------------------------------------------------------- */
/*   Compiler progress                                                       */
/* ------------------------------------------------------------------------- */

static int no_compilations;

int endofpass_flag;      /* set to TRUE when an "end" directive is reached
                            (the inputs routines insert one into the stream
                            if necessary)                                    */

/* ------------------------------------------------------------------------- */
/*   Version control                                                         */
/* ------------------------------------------------------------------------- */

int version_number,      /* 3 to 6 (v7 and 8 are treated as v5)              */
    extend_memory_map;   /* extend using function- and string-offsets        */
int32 scale_factor,      /* packed address multiplier                        */
    length_scale_factor; /* length-in-header multiplier                      */

extern void select_version(int vn)
{   version_number = vn;
    extend_memory_map = FALSE;
    if ((version_number==6)||(version_number==7)) extend_memory_map = TRUE;

    scale_factor = 4;
    if (version_number==3) scale_factor = 2;
    if (version_number==8) scale_factor = 8;

    length_scale_factor = scale_factor;
    if ((version_number==6)||(version_number==7)) length_scale_factor = 8;
}

/* ------------------------------------------------------------------------- */
/*   Tracery: output control variables                                       */
/* ------------------------------------------------------------------------- */

int asm_trace_level,     /* trace assembly: 0 for off, 1 for assembly
                            only, 2 for full assembly tracing with hex dumps */
    line_trace_level,    /* line tracing: 0 off, 1 on                        */
    expr_trace_level,    /* expression tracing: 0 off, 1 full, 2 brief       */
    linker_trace_level,  /* set by -y: 0 to 4 levels of tracing              */
    tokens_trace_level;  /* lexer output tracing: 0 off, 1 on                */

/* ------------------------------------------------------------------------- */
/*   On/off switch variables (by default all FALSE); other switch settings   */
/* ------------------------------------------------------------------------- */

int bothpasses_switch,              /* -b */
    concise_switch,                 /* -c */
    economy_switch,                 /* -e */
    frequencies_switch,             /* -f */
    trace_fns_switch,               /* -g */
    ignore_switches_switch,         /* -i */
    listobjects_switch,             /* -j */
    debugfile_switch,               /* -k */
    listing_switch,                 /* -l */
    memout_switch,                  /* -m */
    printprops_switch,              /* -n */
    offsets_switch,                 /* -o */
    percentages_switch,             /* -p */
    obsolete_switch,                /* -q */
    transcript_switch,              /* -r */
    statistics_switch,              /* -s */
    optimise_switch,                /* -u */
    version_set_switch,             /* -v */
    nowarnings_switch,              /* -w */
    hash_switch,                    /* -x */
    memory_map_switch,              /* -z */
    define_DEBUG_switch,            /* -D */
    temporary_files_switch,         /* -F */
    module_switch,                  /* -M */
    define_USE_MODULES_switch;      /* -U */
#ifdef ARC_THROWBACK
int throwback_switch;               /* -T */
#endif
#ifdef ARCHIMEDES
int riscos_file_type_format;        /* set by -R */
#endif

int error_format,                   /* set by -E */
    asm_trace_setting,              /* set by -a and -t: value of
                                       asm_trace_level to use when tracing */
    double_space_setting,           /* set by -d: 0, 1 or 2 */
    linker_trace_setting,           /* set by -y: ditto for linker_... */
    store_the_text;                 /* when set, record game text to a chunk
                                       of memory (used by both -r & -k) */

static void reset_switch_settings(void)
{   asm_trace_setting=0;
    linker_trace_level=0;
    tokens_trace_level=0;

    store_the_text = FALSE;

    bothpasses_switch = FALSE;
    concise_switch = FALSE;
    double_space_setting = 0;
    economy_switch = FALSE;
    frequencies_switch = FALSE;
    trace_fns_switch = FALSE;
    ignore_switches_switch = FALSE;
    listobjects_switch = FALSE;
    debugfile_switch = FALSE;
    listing_switch = FALSE;
    memout_switch = FALSE;
    printprops_switch = FALSE;
    offsets_switch = FALSE;
    percentages_switch = FALSE;
    obsolete_switch = FALSE;
    transcript_switch = FALSE;
    statistics_switch = FALSE;
    optimise_switch = FALSE;
    version_set_switch = FALSE;
    nowarnings_switch = FALSE;
    hash_switch = FALSE;
    memory_map_switch = FALSE;
    define_DEBUG_switch = FALSE;
#ifdef USE_TEMPORARY_FILES
    temporary_files_switch = TRUE;
#else
    temporary_files_switch = FALSE;
#endif
    define_USE_MODULES_switch = FALSE;
    module_switch = FALSE;
#ifdef ARC_THROWBACK
    throwback_switch = FALSE;
#endif
#ifdef ARCHIMEDES
    riscos_file_type_format = 0;
#endif
    error_format=DEFAULT_ERROR_FORMAT;
}

/* ------------------------------------------------------------------------- */
/*   Number of files given as command line parameters (0, 1 or 2)            */
/* ------------------------------------------------------------------------- */

static int cli_files_specified,
           convert_filename_flag;

char Source_Name[100];                 /* Processed name of first input file */
char Code_Name[100];                   /* Processed name of output file      */

static char *cli_file1, *cli_file2;    /* Unprocessed (and unsafe to alter)  */

/* ========================================================================= */
/*   Data structure management routines                                      */
/* ------------------------------------------------------------------------- */

static void init_vars(void)
{
    init_arrays_vars();
    init_asm_vars();
    init_bpatch_vars();
    init_directs_vars();
    init_errors_vars();
    init_expressc_vars();
    init_expressp_vars();
    init_files_vars();

    last_mapped_line = -1;

    init_lexer_vars();
    init_linker_vars();
    init_memory_vars();
    init_objects_vars();
    init_states_vars();
    init_symbols_vars();
    init_syntax_vars();
    init_tables_vars();
    init_text_vars();
    init_veneer_vars();
    init_verbs_vars();
}

static void begin_pass(void)
{
    arrays_begin_pass();
    asm_begin_pass();
    bpatch_begin_pass();
    directs_begin_pass();
    errors_begin_pass();
    expressc_begin_pass();
    expressp_begin_pass();
    files_begin_pass();

    endofpass_flag = FALSE;
    line_trace_level = 0; expr_trace_level = 0;
    asm_trace_level = asm_trace_setting;
    linker_trace_level = linker_trace_setting;
    if (listing_switch) line_trace_level=1;

    lexer_begin_pass();
    linker_begin_pass();
    memory_begin_pass();
    objects_begin_pass();
    states_begin_pass();
    symbols_begin_pass();
    syntax_begin_pass();
    tables_begin_pass();
    text_begin_pass();
    veneer_begin_pass();
    verbs_begin_pass();

    if (!module_switch)
    {
        /*  Compile a Main__ routine (see "veneer.c")  */

        compile_initial_routine();

        /*  Make the four metaclasses: Class must be object number 1, so
            it must come first  */

        veneer_mode = TRUE;

        make_class("Class");
        make_class("Object");
        make_class("Routine");
        make_class("String");

        veneer_mode = FALSE;
    }
}

extern void allocate_arrays(void)
{
    arrays_allocate_arrays();
    asm_allocate_arrays();
    bpatch_allocate_arrays();
    directs_allocate_arrays();
    errors_allocate_arrays();
    expressc_allocate_arrays();
    expressp_allocate_arrays();
    files_allocate_arrays();

    lexer_allocate_arrays();
    linker_allocate_arrays();
    memory_allocate_arrays();
    objects_allocate_arrays();
    states_allocate_arrays();
    symbols_allocate_arrays();
    syntax_allocate_arrays();
    tables_allocate_arrays();
    text_allocate_arrays();
    veneer_allocate_arrays();
    verbs_allocate_arrays();
}

extern void free_arrays(void)
{
    /*  One array may survive this routine, all_the_text (used to hold
        game text until the abbreviations optimiser begins work on it): this
        array (if it was ever allocated) is freed at the top level.          */

    arrays_free_arrays();
    asm_free_arrays();
    bpatch_free_arrays();
    directs_free_arrays();
    errors_free_arrays();
    expressc_free_arrays();
    expressp_free_arrays();
    files_free_arrays();

    lexer_free_arrays();
    linker_free_arrays();
    memory_free_arrays();
    objects_free_arrays();
    states_free_arrays();
    symbols_free_arrays();
    syntax_free_arrays();
    tables_free_arrays();
    text_free_arrays();
    veneer_free_arrays();
    verbs_free_arrays();
}

/* ------------------------------------------------------------------------- */
/*    Name translation code for filenames                                    */
/* ------------------------------------------------------------------------- */

static char Source_Path[128];
static char Include_Path[128];
static char Code_Path[128];
static char Module_Path[128];
static char Temporary_Path[128];
static char current_source_path[128];
       char Debugging_Name[128];
       char Transcript_Name[128];
static char ICL_Path[128];

static void set_path_value(char *path, char *value)
{   int i;
    strcpy(path, value);
    if ((path==Debugging_Name)||(path==Transcript_Name)) return;
    i=strlen(path)-1;
    if ((i>=0)&&(isalnum(path[i])))
    {   path[i+1]=FN_SEP; path[i+2]=0;
    }
}

static void set_default_paths(void)
{
    set_path_value(Source_Path,     Source_Directory);
    set_path_value(Include_Path,    Include_Directory);
    set_path_value(Code_Path,       Code_Directory);
    set_path_value(Module_Path,     Module_Directory);
    set_path_value(ICL_Path,        ICL_Directory);
    set_path_value(Temporary_Path,  Temporary_Directory);
    set_path_value(Debugging_Name,  Debugging_File);
    set_path_value(Transcript_Name, Transcript_File);
}

static void set_path_command(char *command)
{   int i, j; char *path_to_set = NULL, *new_value;
    for (i=0; (command[i]!=0) && (command[i]!='=');i++) ;

    if (command[i]==0) { new_value=command; path_to_set=Include_Path; }
    else
    {   char pathname[128];
        if (i>=128) i=127;
        new_value = command+i+1;
        for (j=0;j<i;j++)
            if (isupper(command[j])) pathname[j]=tolower(command[j]);
            else pathname[j]=command[j];
        pathname[j]=0;

        if (strcmp(pathname, "source_path")==0)  path_to_set=Source_Path;
        if (strcmp(pathname, "include_path")==0) path_to_set=Include_Path;
        if (strcmp(pathname, "code_path")==0)    path_to_set=Code_Path;
        if (strcmp(pathname, "module_path")==0)  path_to_set=Module_Path;
        if (strcmp(pathname, "icl_path")==0)     path_to_set=ICL_Path;
        if (strcmp(pathname, "temporary_path")==0) path_to_set=Temporary_Path;
        if (strcmp(pathname, "debugging_name")==0) path_to_set=Debugging_Name;
        if (strcmp(pathname, "transcript_name")==0) path_to_set=Transcript_Name;

        if (path_to_set == NULL)
        {   char temp_error[156];
            sprintf(temp_error, "No such path setting as \"%s\"", pathname);
            fatalerror(temp_error);
        }
    }

    set_path_value(path_to_set, new_value);
}

static int contains_separator(char *name)
{   int i;
    for (i=0; name[i]!=0; i++)
        if (name[i] == FN_SEP) return 1;
    return 0;
}

static void write_translated_name(char *new_name, char *old_name,
                                  char *prefix_path, char *extension)
{   if (prefix_path == NULL)
      sprintf(new_name,"%s%s", old_name, extension);
    else
      sprintf(new_name,"%s%s%s", prefix_path, old_name, extension);
}

#ifdef FILE_EXTENSIONS
static char *check_extension(char *name, char *extension)
{   int i;

    /* If a filename ends in '.', remove the dot and add no file extension: */
    i = strlen(name)-1;
    if (name[i] == '.') { name[i]=0; return ""; }

    /* Remove the new extension if it's already got one: */

    for (; (i>=0) && (name[i]!=FN_SEP); i--)
        if (name[i] == '.') return "";
    return extension;
}
#endif

extern void translate_in_filename(char *new_name, char *old_name,
    int same_directory_flag, int command_line_flag)
{   char *prefix_path = NULL;
    char *extension;
    int add_path_flag = 1;
    int i;

    if ((same_directory_flag==0)
        && (contains_separator(old_name)==1)) add_path_flag=0;

    if (add_path_flag==1)
    {   if (command_line_flag == 0)
        {   /* File is opened as a result of an Include directive */

            if (same_directory_flag==1)
                prefix_path = current_source_path;
            else
                if (Include_Path[0]!=0) prefix_path = Include_Path;
        }
        /* Main file being opened from the command line */

        else if (Source_Path[0]!=0) prefix_path = Source_Path;
    }

#ifdef FILE_EXTENSIONS
    /* Which file extension is expected? */

    if ((command_line_flag==1)||(same_directory_flag==1))
        extension = Source_Extension;
    else
        extension = Include_Extension;

    extension = check_extension(old_name, extension);
#else
    extension = "";
#endif

    write_translated_name(new_name, old_name, prefix_path, extension);

    /* Set the "current source path" (for use of Include ">...") */

    if (command_line_flag==1)
    {   strcpy(current_source_path, new_name);
        for (i=strlen(current_source_path)-1;
             ((i>0)&&(current_source_path[i]!=FN_SEP));i--) ;
        if (i!=0) current_source_path[i+1]=0;
    }
}

extern void translate_link_filename(char *new_name, char *old_name)
{   char *prefix_path = NULL;
    char *extension;

    if (contains_separator(old_name)==0)
        if (Module_Path[0]!=0)
            prefix_path = Module_Path;

#ifdef FILE_EXTENSIONS
    extension = check_extension(old_name, Module_Extension);
#else
    extension = "";
#endif

    write_translated_name(new_name, old_name, prefix_path, extension);
}

static void translate_icl_filename(char *new_name, char *old_name)
{   char *prefix_path = NULL;
    char *extension = "";

    if (contains_separator(old_name)==0)
        if (ICL_Path[0]!=0)
            prefix_path = ICL_Path;

#ifdef FILE_EXTENSIONS
    extension = check_extension(old_name, ICL_Extension);
#endif

    write_translated_name(new_name, old_name, prefix_path, extension);
}

extern void translate_out_filename(char *new_name, char *old_name)
{   char *prefix_path;
    char *extension = "";
    int i;

    /* If !convert_filename_flag, then the old_name is just the <file2>
       parameter on the Inform command line, which we leave alone. */

    if (!convert_filename_flag)
    {   strcpy(new_name, old_name); return;
    }

    /* Remove any pathname or extension in <file1>. */

    if (contains_separator(old_name)==1)
    {   for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!=FN_SEP) ;i--) ;
            if (old_name[i]==FN_SEP) i++;
        old_name += i;
    }
#ifdef FILE_EXTENSIONS
    for (i=strlen(old_name)-1; (i>=0)&&(old_name[i]!='.') ;i--) ;
    if (old_name[i] == '.') old_name[i] = 0;
#endif

    prefix_path = NULL;
    if (module_switch)
    {   extension = Module_Extension;
        if (Module_Path[0]!=0) prefix_path = Module_Path;
    }
    else
    {   switch(version_number)
        {   case 3: extension = Code_Extension;   break;
            case 4: extension = V4Code_Extension; break;
            case 5: extension = V5Code_Extension; break;
            case 6: extension = V6Code_Extension; break;
            case 7: extension = V7Code_Extension; break;
            case 8: extension = V8Code_Extension; break;
        }
        if (Code_Path[0]!=0) prefix_path = Code_Path;
    }

#ifdef FILE_EXTENSIONS
    extension = check_extension(old_name, extension);
#endif

    write_translated_name(new_name, old_name, prefix_path, extension);
}

static char *name_or_unset(char *p)
{   if (p[0]==0) return "(unset)";
    return p;
}

static void help_on_filenames(void)
{   char old_name[128];
    char new_name[128];
    int save_mm = module_switch;

    module_switch = FALSE;

    printf("Help information on filenames:\n\n");

    printf(
"The command line can take one of two forms:\n\n\
  inform [commands...] <file1>\n\
  inform [commands...] <file1> <file2>\n\n\
Inform translates <file1> into a source file name (see below) for its input.\n\
<file2> is usually omitted: if so, the output filename is made from <file1>\n\
by cutting out the name part and translating that (see below).\n\
If <file2> is given, however, the output filename is set to just <file2>\n\
(not altered in any way).\n\n");

    printf(
"Filenames given in the game source (with commands like Include \"name\" and\n\
Link \"name\") are also translated by the rules below.\n\n");

    printf(
"Rules of translation:\n\n\
Inform translates plain filenames (such as \"xyzzy\") into full pathnames\n\
(such as \"adventure%cgames%cxyzzy\") according to the following rules.\n\n\
1. If the name contains a '%c' character (so it's already a pathname), it\n\
   isn't changed.\n\n", FN_SEP, FN_SEP, FN_SEP);

    printf(
"   [Exception: when the name is given in an Include command using the >\n\
   form (such as Include \">prologue\"), the \">\" is replaced by the path\n\
   of the file doing the inclusion");
#ifdef FILE_EXTENSIONS
                          printf(" and a suitable file extension is added");
#endif
    printf(".]\n\n");

    printf(
"2. The file is looked for at a particular \"path\" (the filename of a\n\
   directory), depending on what kind of file it is.\n\n\
       File type          Name                Current setting\n\n\
       Source code        source_path         %s\n\
       Include file       include_path        %s\n\
       Story file         code_path           %s\n\
       Temporary file     temporary_path      %s\n\
       ICL command file   icl_path            %s\n\
       Module             module_path         %s\n\n",
   name_or_unset(Source_Path), name_or_unset(Include_Path),
   name_or_unset(Code_Path),   name_or_unset(Temporary_Path),
   name_or_unset(ICL_Path), name_or_unset(Module_Path));

    printf(
"   If the path is unset, then the current working directory is used (so\n\
   the filename doesn't change): if, for instance, include_path is set to\n\
   \"backup%coldlib\" then when \"parser\" is included it is looked for at\n\
   \"backup%coldlib%cparser\".\n\n\
   The paths can be set or unset on the Inform command line by, eg,\n\
   \"inform +code_path=finished jigsaw\" or\n\
   \"inform +include_path= balances\" (which unsets include_path).\n\n",
        FN_SEP, FN_SEP, FN_SEP);

#ifdef FILE_EXTENSIONS
    printf("3. The following file extensions are added:\n\n\
      Source code:     %s\n\
      Include files:   %s\n\
      Story files:     %s (Version 3), %s (v4), %s (v5, the default),\n\
                       %s (v6), %s (v7), %s (v8)\n\
      Temporary files: .tmp\n\
      Modules:         %s\n\n",
      Source_Extension, Include_Extension,
      Code_Extension, V4Code_Extension, V5Code_Extension, V6Code_Extension,
      V7Code_Extension, V8Code_Extension, Module_Extension);
    printf("\
   except that any extension you give (on the command line or in a filename\n\
   used in a program) will override these.  If you give the null extension\n\
   \".\" then Inform uses no file extension at all (removing the \".\").\n\n");
#endif

    printf("You can set the transcript_name and debugging_name filenames\n\
(which are only used with the appropriate switches set) with + commands\n\
like those for setting paths.\n\n");

    translate_in_filename(new_name, "rezrov", 0, 1);
    printf("Examples: 1. \"inform rezrov\"\n\
  the source code is read from \"%s\"\n",
        new_name);
    convert_filename_flag = TRUE;
    translate_out_filename(new_name, "rezrov");
    printf("  and a story file is compiled to \"%s\".\n\n", new_name);

    translate_in_filename(new_name, "frotz", 0, 1);
    printf("2. \"inform -M frotz\"\n\
  the source code is read from \"%s\"\n",
        new_name);
    module_switch = TRUE;
    convert_filename_flag = TRUE;
    translate_out_filename(new_name, "frotz");
    printf("  and a module is compiled to \"%s\".\n\n", new_name);

    module_switch = FALSE;

    sprintf(old_name, "demos%cplugh", FN_SEP);
    printf("3. \"inform %s\"\n", old_name);
    translate_in_filename(new_name, old_name, 0, 1);
    printf("  the source code is read from \"%s\"\n", new_name);
    sprintf(old_name, "demos%cplugh", FN_SEP);
    convert_filename_flag = TRUE;
    translate_out_filename(new_name, old_name);
    printf("  and a story file is compiled to \"%s\".\n\n", new_name);

    printf("4. \"inform plover my_demo\"\n");
    translate_in_filename(new_name, "plover", 0, 1);
    printf("  the source code is read from \"%s\"\n", new_name);
    convert_filename_flag = FALSE;
    translate_out_filename(new_name, "my_demo");
    printf("  and a story file is compiled to \"%s\".\n\n", new_name);

    module_switch = save_mm;
}

/* ------------------------------------------------------------------------- */
/*  Naming temporary files                                                   */
/*       (Arguably temporary files should be made using "tmpfile" in         */
/*        the ANSI C library, but many supposed ANSI libraries lack it.)     */
/* ------------------------------------------------------------------------- */

extern void translate_temp_filename(int i)
{   char *p;
    switch(i)
    {   case 1: p=Temp1_Name; break;
        case 2: p=Temp2_Name; break;
        case 3: p=Temp3_Name; break;
    }
    sprintf(p,"%s%s%d", Temporary_Path, Temporary_File, i);
#ifdef INCLUDE_TASK_ID
    sprintf(p+strlen(p), "_proc%08lx", (long int) unique_task_id());
#endif
#ifdef FILE_EXTENSIONS
    sprintf(p+strlen(p), ".tmp");
#endif
}

#ifdef ARCHIMEDES
static char riscos_ft_buffer[4];

extern char *riscos_file_type(void)
{
    if (riscos_file_type_format == 1)
    {   if (module_switch) return("data");
        return("11A");
    }

    if (module_switch) return("075");

    sprintf(riscos_ft_buffer, "%03x", 0x60 + version_number);
    return(riscos_ft_buffer);
}
#endif

/* ------------------------------------------------------------------------- */
/*   Counting lines                                                          */
/* ------------------------------------------------------------------------- */

static void begin_debug_file(void)
{   open_debug_file();
    write_debug_byte(5); write_debug_byte(4);
    write_debug_byte(VNUMBER/256); write_debug_byte(VNUMBER%256);
    last_mapped_line=0;
}

/* ------------------------------------------------------------------------- */
/*   The compilation pass                                                    */
/* ------------------------------------------------------------------------- */

static void run_pass(void)
{
    lexer_begin_prepass();
    files_begin_prepass();
    load_sourcefile(Source_Name, 0);

    begin_pass(); keep_debug_linenum();

    parse_program(NULL);
    find_the_actions();
    if (!nowarnings_switch) issue_unused_warnings();
    compile_veneer();

    lexer_endpass();
    if (module_switch) linker_endpass();

    close_all_source();
    if (hash_switch && hash_printed_since_newline) printf("\n");

    if (temporary_files_switch)
    {   if (module_switch) flush_link_data();
        check_temp_files();
    }
    sort_dictionary();
    construct_storyfile();
}

static void rennab(int32 time_taken)
{   /*  rennab = reverse of banner  */

    if (memout_switch) print_memory_usage();

    if ((no_errors+no_warnings)!=0)
        printf("Compiled with %d error%s and %d warning%s%s\n",
            no_errors,(no_errors==1)?"":"s",
            no_warnings,(no_warnings==1)?"":"s",
            (no_errors>0)?" (no output)":"");

    if (statistics_switch)
        printf("Completed in %ld seconds\n", (long int) time_taken);
}

/* ------------------------------------------------------------------------- */
/*   The compiler abstracted to a routine.                                   */
/* ------------------------------------------------------------------------- */

static int compile(int number_of_files_specified, char *file1, char *file2)
{   int32 time_start;

    time_start=time(0); no_compilations++;

    strcpy(Source_Name, file1); convert_filename_flag = TRUE;
    strcpy(Code_Name, file1);
    if (number_of_files_specified == 2)
    {   strcpy(Code_Name, file2); convert_filename_flag = FALSE;
    }

    init_vars();
    allocate_arrays();

    if (debugfile_switch) begin_debug_file();
    if (transcript_switch) open_transcript_file(Source_Name);

    run_pass();

    if (transcript_switch)
    {   write_dictionary_to_transcript();
        close_transcript_file();
    }

    if (no_errors==0) output_file();

    if (debugfile_switch) close_debug_file();

    if (temporary_files_switch && (no_errors>0)) remove_temp_files();

    free_arrays();

    rennab((int32) (time(0)-time_start));

    if (optimise_switch) optimise_abbreviations();

    if (store_the_text) my_free(&all_text,"transcription text");

    return (no_errors==0)?0:1;
}

/* ------------------------------------------------------------------------- */
/*   The command line interpreter                                            */
/* ------------------------------------------------------------------------- */

static void cli_print_help(int help_level)
{
    printf(
"\nThis program is a compiler to Infocom format adventure games\n\
and is copyright (c) Graham Nelson 1993, 1994, 1995, 1996.\n\n");

   /* For people typing just "inform", a summary only: */

   if (help_level==0)
   {

#ifndef PROMPT_INPUT
  printf("Usage: \"inform [commands...] <file1> [<file2>]\"\n\n");
#else
  printf("When run, Inform prompts you for commands (and switches),\n\
which are optional, then an input <file1> and an (optional) output\n\
<file2>.\n\n");
#endif

  printf(
"<file1> is the Inform source file of the game to be compiled. <file2>,\n\
if given, overrides the filename Inform would normally use for the\n\
compiled output.  Try \"inform -h1\" for file-naming conventions.\n\n\
One or more words can be supplied as \"commands\". These may be:\n\n\
  -switches     a list of compiler switches, 1 or 2 letter\n\
                (see \"inform -h2\" for the full range)\n\n\
  +dir          set Include_Path to this directory\n\
  +PATH=dir     change the PATH to this directory\n\n\
  $...          one of the following memory commands:\n\
     $list            list current memory allocation settings\n\
     $huge            make standard \"huge game\" settings %s\n\
     $large           make standard \"large game\" settings %s\n\
     $small           make standard \"small game\" settings %s\n\
     $?SETTING        explain briefly what SETTING is for\n\
     $SETTING=number  change SETTING to given number\n\n\
  (filename)    read in a list of commands (in the format above)\n\
                from this \"setup file\"\n\n",
    (DEFAULT_MEMORY_SIZE==HUGE_SIZE)?"(default)":"",
    (DEFAULT_MEMORY_SIZE==LARGE_SIZE)?"(default)":"",
    (DEFAULT_MEMORY_SIZE==SMALL_SIZE)?"(default)":"");

#ifndef PROMPT_INPUT
    printf("For example: \"inform -dexs $huge curses\".\n\n");
#endif

    printf(
"For fuller information, see Chapter V of the Inform Designer's Manual.\n");

       return;
   }

   /* The -h1 (filenaming) help information: */

   if (help_level == 1) { help_on_filenames(); return; }

   /* The -h2 (switches) help information: */

   printf("Help on the full list of legal switch commands:\n\n\
  a   trace assembly-language (without hex dumps; see -t)\n\
  c   more concise error messages\n\
  d   contract double spaces after full stops in text\n\
  e   economy mode (slower): make use of declared abbreviations\n");

   printf("\
  f   frequencies mode: show how useful abbreviations are\n\
  g   with debugging code: traces all function calls\n\
  h   print this information\n");

   printf("\
  i   ignore default switches set within the file\n\
  j   list objects as constructed\n\
  k   output Infix debugging information to \"%s\"\n\
  l   list every statement run through Inform\n\
  m   say how much memory has been allocated\n\
  n   print numbers of properties, attributes and actions\n",
          Debugging_Name);
   printf("\
  o   print offset addresses\n\
  p   give percentage breakdown of story file\n\
  q   keep quiet about obsolete usages\n\
  r   record all the text to \"%s\"\n\
  s   give statistics\n\
  t   trace assembly-language (with full hex dumps; see -a)\n",
      Transcript_Name);

   printf("\
  u   work out most useful abbreviations (very very slowly)\n\
  v3  compile to version-3 (Standard) story file\n\
  v4  compile to version-4 (Plus) story file\n\
  v5  compile to version-5 (Advanced) story file\n\
  v6  compile to version-6 (graphical) story file\n\
  v7  compile to version-7 (*) story file\n\
  v8  compile to version-8 (*) story file\n\
      (*) formats for very large games, requiring\n\
          slightly modified game interpreters to play\n\
  w   disable warning messages\n\
  x   print # for every 100 lines compiled\n\
  y   trace linking system\n\
  z   print memory map of the Z-machine\n\n");

printf("  D   insert \"Constant DEBUG;\" automatically\n");
printf("  E0  Archimedes-style error messages%s\n",
      (error_format==0)?" (current setting)":"");
printf("  E1  Microsoft-style error messages%s\n",
      (error_format==1)?" (current setting)":"");
#ifdef USE_TEMPORARY_FILES
printf("  F0  use extra memory rather than temporary files\n");
#else
printf("  F1  use temporary files to reduce memory consumption\n");
#endif
printf("  M   compile as a Module for future linking\n");

#ifdef ARCHIMEDES
printf("\
  R0  use filetype 060 + version number for games (default)\n\
  R1  use official Acorn filetype 11A for all games\n");
#endif
#ifdef ARC_THROWBACK
printf("  T   enable throwback of errors in the DDE\n");
#endif
printf("  U   insert \"Constant USE_MODULES;\" automatically\n");
  printf("\n");
}

extern void switches(char *p, int cmode)
{   int i, s=1, state;
    if (cmode==1)
    {   if (p[0]!='-')
        {   printf(
                "Ignoring second word which should be a -list of switches.\n");
            return;
        }
    }
    for (i=cmode; p[i]!=0; i+=s, s=1)
    {   state = TRUE;
        if (p[i] == '~')
        {   state = FALSE;
            i++;
        }
        switch(p[i])
        {
        case 'a': asm_trace_setting = 1; break;
        case 'b': bothpasses_switch = state; break;
        case 'c': concise_switch = state; break;
        case 'd': switch(p[i+1])
                  {   case '1': double_space_setting=1; s=2; break;
                      case '2': double_space_setting=2; s=2; break;
                      default: double_space_setting=1; break;
                  }
                  break;
        case 'e': economy_switch = state; break;
        case 'f': frequencies_switch = state; break;
        case 'g': trace_fns_switch = state; break;
        case 'h': switch(p[i+1])
                  {   case '1': cli_print_help(1); s=2; break;
                      case '2': cli_print_help(2); s=2; break;
                      case '0': s=2;
                      default:  cli_print_help(0); break;
                  }
                  break;
        case 'i': ignore_switches_switch = state; break;
        case 'j': listobjects_switch = state; break;
        case 'k': debugfile_switch = state; break;
        case 'l': listing_switch = state; break;
        case 'm': memout_switch = state; break;
        case 'n': printprops_switch = state; break;
        case 'o': offsets_switch = state; break;
        case 'p': percentages_switch = state; break;
        case 'q': obsolete_switch = state; break;
        case 'r': transcript_switch = state; break;
        case 's': statistics_switch = state; break;
        case 't': asm_trace_setting=2; break;
        case 'u': optimise_switch = state; break;
        case 'v': if ((cmode==0) && (version_set_switch)) { s=2; break; }
                  version_set_switch = TRUE; s=2;
                  switch(p[i+1])
                  {   case '3': select_version(3); break;
                      case '4': select_version(4); break;
                      case '5': select_version(5); break;
                      case '6': select_version(6); break;
                      case '7': select_version(7); break;
                      case '8': select_version(8); break;
                      default:  printf("-v must be followed by 3 to 8\n");
                                version_set_switch=0; s=1;
                                break;
                  }
                  break;
        case 'w': nowarnings_switch = state; break;
        case 'x': hash_switch = state; break;
        case 'y': s=2; linker_trace_setting=p[i+1]-'0'; break;
        case 'z': memory_map_switch = state; break;

        case 'D': define_DEBUG_switch = state; break;
        case 'E': switch(p[i+1])
                  {   case '0': s=2; error_format=0; break;
                      case '1': s=2; error_format=1; break;
                      default:  error_format=1; break;
                  }
                  break;
        case 'F': switch(p[i+1])
                  {   case '0': s=2; temporary_files_switch = FALSE; break;
                      case '1': s=2; temporary_files_switch = TRUE; break;
                      default:  temporary_files_switch = state; break;
                  }
                  break;
        case 'M': module_switch = state; break;
#ifdef ARCHIMEDES
        case 'R': switch(p[i+1])
                  {   case '0': s=2; riscos_file_type_format=0; break;
                      case '1': s=2; riscos_file_type_format=1; break;
                      default:  riscos_file_type_format=1; break;
                  }
                  break;
#endif
#ifdef ARC_THROWBACK
        case 'T': throwback_switch = state; break;
#endif
        case 'U': define_USE_MODULES_switch = state; break;
        default:
          printf("Switch \"-%c\" unknown (try \"inform -h2\" for the list)\n",
              p[i]);
          break;
        }
    }

    if (optimise_switch && (!store_the_text))
    {   store_the_text=TRUE;
#ifdef PC_QUICKC
        if (memout_switch)
            printf("Allocation %ld bytes for transcription text\n",
                (long) MAX_TRANSCRIPT_SIZE);
        all_text = halloc(MAX_TRANSCRIPT_SIZE,1);
        malloced_bytes += MAX_TRANSCRIPT_SIZE;
        if (all_text==NULL)
         fatalerror("Can't hallocate memory for transcription text.  Darn.");
#else
        all_text=my_malloc(MAX_TRANSCRIPT_SIZE,"transcription text");
#endif
    }
}

static int icl_command(char *p)
{   if ((p[0]=='+')||(p[0]=='-')||(p[0]=='$')
        || ((p[0]=='(')&&(p[strlen(p)-1]==')')) ) return TRUE;
    return FALSE;
}

static void icl_error(char *filename, int line)
{   printf("Error in ICL file '%s', line %d:\n", filename, line);
}

static int copy_icl_word(char *from, char *to)
{
    /*  Copies one token from 'from' to 'to', null-terminated:
        returns the number of chars in 'from' read past (possibly 0).  */

    int i, j;

    i = 0;
    while ((from[i] == ' ') || (from[i] == TAB_CHARACTER)
           || (from[i] == (char) 10) || (from[i] == (char) 13)) i++;

    if (from[i] == '!')
    {   while (from[i] != 0) i++;
        to[0] = 0; return i;
    }

    j = 0;
    while ((from[i] != 0) && (from[i] != ' ') && (from[i] != TAB_CHARACTER)
        && (from[i] != (char) 10) && (from[i] != (char) 13))
        to[j++] = from[i++];
    to[j] = 0; return i;
}

static void execute_icl_command(char *p);
static void run_icl_file(char *filename, FILE *command_file)
{   char cli_buff[256], fw[256];
    int i, x, line = 0;
    printf("[Running ICL file '%s']\n", filename);

    while (feof(command_file)==0)
    {   if (fgets(cli_buff,256,command_file)==0) break;
        line++;
        i = copy_icl_word(cli_buff, fw);
        if (icl_command(fw))
        {   execute_icl_command(fw);
            copy_icl_word(cli_buff + i, fw);
            if ((fw[0] != 0) && (fw[0] != '!'))
            {   icl_error(filename, line);
                printf("expected comment or nothing but found '%s'\n", fw);
            }
        }
        else
        {   if (strcmp(fw, "compile")==0)
            {   char story_name[128], code_name[128];
                i += copy_icl_word(cli_buff + i, story_name);
                i += copy_icl_word(cli_buff + i, code_name);

                if (code_name[0] != 0) x=2;
                else if (story_name[0] != 0) x=1;
                else x=0;

                switch(x)
                {   case 0: icl_error(filename, line);
                            printf("No filename given to 'compile'\n");
                            break;
                    case 1: printf("[Compiling <%s>]\n", story_name);
                            compile(x, story_name, code_name);
                            break;
                    case 2: printf("[Compiling <%s> to <%s>]\n",
                                story_name, code_name);
                            compile(x, story_name, code_name);
                            copy_icl_word(cli_buff + i, fw);
                            if (fw[0]!=0)
                            {   icl_error(filename, line);
                        printf("Expected comment or nothing but found '%s'\n",
                                fw);
                            }
                            break;
                }
            }
            else
            if (fw[0]!=0)
            {   icl_error(filename, line);
                printf("Expected command or comment but found '%s'\n", fw);
            }
        }
    }
}

static void execute_icl_command(char *p)
{   char filename[128], cli_buff[256];
    FILE *command_file;

    switch(p[0])
    {   case '+': set_path_command(p+1); break;
        case '-': switches(p,1); break;
        case '$': memory_command(p+1); break;
        case '(': strcpy(cli_buff,p+1); cli_buff[strlen(cli_buff)-1]=0;
                  translate_icl_filename(filename, cli_buff);
                  command_file = fopen(filename,"r");
                  if (command_file == NULL)
                  {   sprintf(cli_buff, "Unable to open command file '%s'",
                          filename);
                      fatalerror(cli_buff);
                  }
                  run_icl_file(filename, command_file);
                  fclose(command_file);
                  break;
    }
}

/* ------------------------------------------------------------------------- */
/*   Opening and closing banners                                             */
/* ------------------------------------------------------------------------- */

char banner_line[80];

static void banner(void)
{
    sprintf(banner_line, MACHINE_STRING);
    sprintf(banner_line+strlen(banner_line), " Inform %d.%d%d (%s)",
        (VNUMBER/100)%10, (VNUMBER/10)%10, VNUMBER%10,
        RELEASE_DATE);
    printf("%s\n", banner_line);
}

/* ------------------------------------------------------------------------- */
/*   Input from the outside world                                            */
/* ------------------------------------------------------------------------- */

#ifdef PROMPT_INPUT
static void read_command_line(int argc, char **argv)
{   int i;
    char buffer1[100], buffer2[100], buffer3[100];
    i=0;
    printf("Source filename?\n> ");
    while (gets(buffer1)==NULL); cli_file1=buffer1;
    printf("Output filename (RETURN for the same)?\n> ");
    while (gets(buffer2)==NULL); cli_file2=buffer2;
    cli_files_specified=1;
    if (buffer2[0]!=0) cli_files_specified=2;
    do
    {   printf("List of commands (RETURN to finish; \"-h\" for help)?\n> ");
        while (gets(buffer3)==NULL); execute_icl_command(buffer3);
    } while (buffer3[0]!=0);
}
#else
static void read_command_line(int argc, char **argv)
{   int i;
    if (argc==1) switches("-h",1);

    for (i=1, cli_files_specified=0; i<argc; i++)
        if (icl_command(argv[i]))
            execute_icl_command(argv[i]);
        else
            switch(++cli_files_specified)
            {   case 1: cli_file1 = argv[i]; break;
                case 2: cli_file2 = argv[i]; break;
                default:
                    printf("Command line error: unknown parameter '%s'\n",
                        argv[i]); return;
            }
}
#endif

/* ------------------------------------------------------------------------- */
/*   M A I N : An outer shell for machine-specific quirks                    */
/* ------------------------------------------------------------------------- */

static int sub_main(int argc, char **argv);

#ifdef MAC_MPW
int main(int argc, char **argv, char *envp[])
#else
int main(int argc, char **argv)
#endif
{   int rcode;
#ifdef MAC_MPW
    InitCursorCtl((acurHandle)NULL); Show_Cursor(WATCH_CURSOR);
#endif
    rcode = sub_main(argc, argv);
#ifdef ARC_THROWBACK
    throwback_end();
#endif
    return rcode;
}

/* ------------------------------------------------------------------------- */
/*   M A I N  II:  Starting up ICL with the command line                     */
/* ------------------------------------------------------------------------- */

static int sub_main(int argc, char **argv)
{   int return_code;

    banner();

    set_memory_sizes(DEFAULT_MEMORY_SIZE); set_default_paths();
    reset_switch_settings(); select_version(5);

    cli_files_specified = 0; no_compilations = 0;
    cli_file1 = "source"; cli_file2 = "output";

    read_command_line(argc, argv);

    if (cli_files_specified > 0)
    {   return_code = compile(cli_files_specified, cli_file1, cli_file2);

        if (return_code != 0) return(return_code);
    }

    if (no_compilations == 0)
        printf("\n[No compilation requested]\n");
    if (no_compilations > 1)
        printf("[%d compilations completed]\n", no_compilations);

    return(0);
}

/* ========================================================================= */
