static char *rcs_version = "i77.c V$Revision: 1.18 $";
/*
 * imp77_args.c - Command-line argument parser for the imp77 compiler/translator.
 *
 * Supports:
 *   Source files: *.imp, *.c, *.o
 *   Output:       -o <file>
 *   Compile-only: -c
 *   Include dirs: -I<dir> or -I <dir>  (i.e. one argv parameter or two)
 *   Libraries:    -l<lib> or -l <lib>
 *   Library dirs: -L<dir> or -L <dir>
 *   Defines:      -D<name>[=value] or -D <name>[=value]
 *   Boolean flags (--flag / --no-flag):
 *                 --icode, --check, --array, --unass, --bt
 *   Info options: -h / --help, -d / --decode, -v / --verbose
 */

// To do: add perms.c or perms.o or -Li2c  (half done)
//        (build library)
//        Check that gtcpp is available and give more helpful message if not
//        Fix gtcpp to understand imp comments - it fails if there are unbalanced quotes within an imp comment :-(
//        (note that gtcpp is not used if no *.i77 files are given.)
//        add -x option to 'compile and go' (eXecute)
//        gdb and valgrind can be added by i2c but not yet passing
//         options through to make that happen.
//        Added support for gtcpp to allow pre-processing of imp77 sources
//         - had to add uncomment-imp to fix problems with C-style pre-processor
//         but really gtcpp needs to be made imp-aware
//        Add /home/gtoal/src/IPA/bigspell/SpellLib/pathopen.c to handle include paths etc.
//        Implement -m32 to call arm-linux-gnueabihf-gcc on arm or add -m32 on intel (in progress):

// gtoal@pentomino:/tmp $ cat my_program.c
// #include <stdio.h>
// #include <limits.h>
// 
// int main() {
//   printf("Pointer size: %zu bits\n", sizeof(void *) * CHAR_BIT);
//   printf("Long long size: %zu bits\n", sizeof(long long int) * CHAR_BIT);
//   printf("Long size: %zu bits\n", sizeof(long) * CHAR_BIT);
//   return 0;
// }

// gtoal@pentomino:/tmp $ arm-linux-gnueabihf-gcc -o my_program my_program.c
// gtoal@pentomino:/tmp $ ./my_program
// Pointer size: 32 bits
// Long long size: 64 bits
// Long size: 32 bits

//        gcc -o fracpt fracpt.o -L /usr/local/include/i2c -Wl,-rpath=/usr/local/include/i2c -l perms -lbacktrace -lm

// UPDATE: moving from perms.h and perms.c to a restructured perms in a subdirectory.
// - this source has not yet been updated to accomodate the new layout.
// The include file path for perms will have to be updated to point to
// a new 'perms/' subdirectory.  I've also now built a link library
// (libperms.a for static linking, libperms.so for dynamic linking)
// which will require changes to -l and -L parameters, not to mention
// adding -lbacktrace (which may or may not need to be included in all
// linking?)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <limits.h>

#define append(p, format...) p += sprintf(p, format)

char *Progname;

#define MAX_FILES 256
#define MAX_INCDIRS 64
#define MAX_LIBS 64
#define MAX_LIBDIRS 64
#define MAX_DEFINES 64
#define MAX_WARNINGS 64

typedef struct {
  const char *i77_files[MAX_FILES];
  int i77_count;

  const char *imp_files[MAX_FILES];
  int imp_count;

  const char *c_files[MAX_FILES];
  int c_count;

  const char *obj_files[MAX_FILES];
  int obj_count;

  const char *output;

  const char *inc_dirs[MAX_INCDIRS];
  int inc_count;

  const char *libs[MAX_LIBS];
  int lib_count;

  const char *lib_dirs[MAX_LIBDIRS];
  int lib_dir_count;

  const char *defines[MAX_DEFINES];
  int def_count;

  const char *warnings[MAX_WARNINGS];
  int warn_count;

  bool flag_icode;
  bool flag_check;
  bool flag_array;
  bool flag_unass;
  bool flag_backtrace;
//bool flag_bounds;

  bool flag_help;
  bool flag_quiet;
  bool flag_verbose;
  bool flag_gcc_force_m32;
  bool flag_gcc_debug;
  bool flag_developer_debug;
  bool flag_compile_only;
  bool flag_trace;
  bool flag_static_link;
  bool flag_dry_run;
} CompilerOptions;

static CompilerOptions opts = { 0 }; // set all counts to 0, all booleans to false, all ptrs to NULL, all strs to "".

int sysnprintf(char *out, int maxlen, char *command) {
  FILE *pipe;
  pipe = popen(command, "r");
  for (int i = 0; i < maxlen - 1; i++) {
    int c = fgetc(pipe);
    if (c == EOF) {
      out[i] = '\0';
      pclose(pipe);
      return i;
    }
    out[i] = c;
  }
  return 0;
}

void call(char *command) {
  int rc;

  if (!opts.flag_quiet) fprintf(stderr, "%s\n", command);
  if (opts.flag_dry_run) return;

#ifndef DO_NOT_CALL
  rc = system(command);
#else
  //fprintf(stderr, "Calling: %s\n", command);
  rc  = 0;
#endif
  if (rc == -1) {
    fprintf(stderr, "%s: error invoking '%s' - %s\n", Progname, command, strerror(errno));
  } else {
    //fprintf(stderr, "%s: system returns %d\n", Progname, rc);
    // Check if the command terminated normally
    if (WIFEXITED(rc)) {
      int exit_code = WEXITSTATUS(rc);
      if (exit_code != 0) exit(exit_code);
    } else if (WIFSIGNALED(rc)) {
      // Check if it was killed by a signal instead
      fprintf(stderr, "%s: '%s' killed by signal - %s (%d)\n", Progname, command, strerror(errno), WTERMSIG(rc));
    }
  }
}

static const char *file_ext(const char *filename) {
  const char *base = strrchr(filename, '/');
  const char *dot;

  base = base ? base + 1 : filename;
  dot = strrchr(base, '.');

  return strdup((dot == NULL || dot == base) ? "" : dot);
}

static char *strip_ext(const char *s) {
  char *fname = strdup(s);
  char *start = strrchr(fname, '/');
  char *sep;
  if (start) start += 1; else start = fname;
  sep = strrchr(start, '.');
  if (sep) *sep = '\0';
  return fname;
}

static bool list_add(const char **list, int *count, int max, const char *val) {
  if (*count >= max) return false;
  list[(*count)++] = val;
  return true;
}

int parse_args(int argc, char *argv[], CompilerOptions *opts) {
  char *first_file = NULL;
  memset(opts, 0, sizeof(*opts));

  for (int argindex = 1; argindex < argc; argindex++) {
    const char *arg = argv[argindex];

    if (strncmp(arg, "--", 2) == 0) {
      const char *name = arg + 2;

      struct {
        const char *name;
        bool *flag;
      } bool_flags[] = {{"icode", &opts->flag_icode},
                        {"check", &opts->flag_check},
                        {"array", &opts->flag_array},
                        {"unass", &opts->flag_unass},
                        {"bt",    &opts->flag_backtrace},
                        {"dry-run", &opts->flag_dry_run},
                        /*{"bounds", &opts->flag_bounds},*/
                        {NULL, NULL}};

      bool matched = false;
      for (int f = 0; bool_flags[f].name; f++) {
        if (strcmp(name, bool_flags[f].name) == 0) {
          *bool_flags[f].flag = true;
          matched = true;
          break;
        }

        char negated[64];
        snprintf(negated, sizeof(negated), "no-%s", bool_flags[f].name);
        if (strcmp(name, negated) == 0) {
          *bool_flags[f].flag = false;
          matched = true;
          break;
        }
      }
      if (matched) continue;

      if (strcmp(name, "help") == 0) {
        opts->flag_help = true;
        continue;
      }
      if (strcmp(name, "verbose") == 0) {
        opts->flag_verbose = true;
        continue;
      }
      if (strcmp(name, "quiet") == 0) {
        opts->flag_quiet = true;
        continue;
      }
      if (strcmp(name, "debug") == 0) {
        opts->flag_gcc_debug = true;
        continue;
      }
      if (strcmp(name, "ddebug") == 0) {
        opts->flag_developer_debug = true;
        continue;
      }
      if (strcmp(name, "trace") == 0) {
        opts->flag_trace = true;
        continue;
      }
      if (strcmp(name, "static") == 0) {
        opts->flag_static_link = true;
        continue;
      }

      fprintf(stderr, "%s: unknown option '%s'\n", Progname, arg);
      return 1;
    }

    if (arg[0] == '-' && arg[1] != '\0') {
      char sw = arg[1];

      if (sw == 'h' && arg[2] == '\0') {
        opts->flag_help = true;
        continue;
      }
      if (sw == 'v' && arg[2] == '\0') {
        opts->flag_verbose = true;
        continue;
      }
      if (sw == 'q' && arg[2] == '\0') {
        opts->flag_quiet = true;
        continue;
      }
      if (sw == 'g' && arg[2] == '\0') {
        opts->flag_gcc_debug = true;
        continue;
      }
      if (sw == 'd' && arg[2] == '\0') {
        opts->flag_developer_debug = true;
        continue;
      }
      if (sw == 'c' && arg[2] == '\0') {
        opts->flag_compile_only = true;
        continue;
      }

      if (sw == 'o') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -o requires an argument\n", Progname);
          return 1;
        }
        opts->output = val;
        continue;
      }

      if (sw == 'm') {
        // on a 32 bit system -m is ignored, on a 64 bit system -m forces compilation for 32 bit target.
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val || (strcmp(val, "32") && strcmp(val, "64"))) {
          fprintf(stderr, "%s: only -m32 or -m64 are allowed for -m option\n", Progname);
          return 1;
        }
        opts->flag_gcc_force_m32 = true;
        continue;
      }

      if (sw == 'I') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -I requires an argument\n", Progname);
          return 1;
        }
        if (!list_add(opts->inc_dirs, &opts->inc_count, MAX_INCDIRS, val)) {
          fprintf(stderr, "%s: too many -I directories\n", Progname);
          return 1;
        }
        continue;
      }

      if (sw == 'l') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -l requires an argument\n", Progname);
          return 1;
        }
        if (!list_add(opts->libs, &opts->lib_count, MAX_LIBS, val)) {
          fprintf(stderr, "%s: too many -l libraries\n", Progname);
          return 1;
        }
        continue;
      }

      if (sw == 'L') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -L requires an argument\n", Progname);
          return 1;
        }
        if (!list_add(opts->lib_dirs, &opts->lib_dir_count, MAX_LIBDIRS, val)) {
          fprintf(stderr, "%s: too many -L directories\n", Progname);
          return 1;
        }
        continue;
      }

      if (sw == 'W') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -W requires an argument\n", Progname);
          return 1;
        }
        if (!list_add(opts->warnings, &opts->warn_count, MAX_WARNINGS, val)) {
          fprintf(stderr, "%s: too many -W options\n", Progname);
          return 1;
        }
        continue;
      }

      if (sw == 'D') {
        const char *val = (arg[2] != '\0') ? arg + 2 : (++argindex < argc ? argv[argindex] : NULL);
        if (!val) {
          fprintf(stderr, "%s: -D requires an argument\n", Progname);
          return 1;
        }
        if (!list_add(opts->defines, &opts->def_count, MAX_DEFINES, val)) {
          fprintf(stderr, "%s: too many -D defines\n", Progname);
          return 1;
        }
        continue;
      }

      fprintf(stderr, "%s: unknown option '%s'\n", Progname, arg);
      return 1;
    }

    {
      const char *ext = file_ext(arg);

      if (first_file == NULL) first_file = strdup(arg); // to be used for default executable

      if (*ext == '\0') {
        // (-q will only work if it comes before this filename.)
        char tmp[1024];
        FILE *check;
        sprintf(tmp, "%s.i77", arg);
        check = fopen(tmp, "r");
        if (check != NULL) {
          fclose(check);
          if (!opts->flag_quiet) fprintf(stderr, "%s: Assuming %s.i77\n", Progname, arg);
        } else {
          sprintf(tmp, "%s.imp", arg);
          check = fopen(tmp, "r");
          if (check != NULL) {
            fclose(check);
            if (!opts->flag_quiet) fprintf(stderr, "%s: Assuming %s.imp\n", Progname, arg);
          } else {
            fprintf(stderr, "%s: Cannot open %s.i77 or %s.imp - %s\n", Progname, arg, arg, strerror(errno));
            return 1;
          }
        }
        arg = strdup(tmp); ext = file_ext(arg);
      }
      
      if (strcmp(ext, ".i77") == 0) {
        if (!list_add(opts->i77_files, &opts->i77_count, MAX_FILES, arg)) {
          fprintf(stderr, "%s: too many input files\n", Progname);
          return 1;
        }
      } else if (strcmp(ext, ".imp") == 0) {
        if (!list_add(opts->imp_files, &opts->imp_count, MAX_FILES, arg)) {
          fprintf(stderr, "%s: too many input files\n", Progname);
          return 1;
        }
      } else if (strcmp(ext, ".c") == 0) {
        if (!list_add(opts->c_files, &opts->c_count, MAX_FILES, arg)) {
          fprintf(stderr, "%s: too many input files\n", Progname);
          return 1;
        }
      } else if (strcmp(ext, ".o") == 0) {
        if (!list_add(opts->obj_files, &opts->obj_count, MAX_FILES, arg)) {
          fprintf(stderr, "%s: too many input files\n", Progname);
          return 1;
        }
      } else {
        if (*ext == '\0') {
          fprintf(stderr, "%s: %s - no extension given?\n", Progname, arg);
        } else {
          fprintf(stderr, "%s: %s - no compile rule for *%s files\n", Progname, arg, ext);
        }
        return 1;
      }
    }
  }

  int input_count = opts->i77_count + opts->imp_count + opts->c_count + opts->obj_count;
  if (opts->output == NULL) {
    if (opts->flag_compile_only) {
      //
    } else {
      if (input_count == 1) {
        if (opts->i77_count == 1) {
          opts->output = strip_ext(opts->i77_files[0]);
        } else if (opts->imp_count == 1) {
          opts->output = strip_ext(opts->imp_files[0]);
        } else if (opts->c_count == 1) {
          opts->output = strip_ext(opts->c_files[0]);
        } else if (opts->obj_count == 1) {
          opts->output = strip_ext(opts->obj_files[0]);
        } else {
          // internal error
        }
      } else {
        // This is compatible with how cc command-line works:
        //opts->output = "a.out";
        // We *could* if we preferred (for Imp) generate an executable named after the first file parameter:
        if (first_file) opts->output = strip_ext(first_file);
      }
    }
  } else {
    if (opts->flag_compile_only) {
      if (input_count > 1) {
        fprintf(stderr, "%s: with -c and -o, more than one input file was supplied\n", Progname);
        return 1;
      }
      if (strcmp(file_ext(opts->output), ".o") != 0) {
        fprintf(stderr, "%s: with -c, output file '%s' must end in .o\n", Progname, opts->output);
        return 1;
      }
    } else {
      //
    }
  }

  if (opts->flag_compile_only && opts->obj_count != 0) {
    fprintf(stderr, "%s: using -c with *.o files does nothing.\n", Progname);
    return 1;
  }

  if (opts->output != NULL) {
    const char *extension = file_ext(opts->output);
    if (strcmp(extension, ".imp") == 0 || strcmp(extension, ".c") == 0) {
      fprintf(stderr, "%s: -o %s - this is unlikely to be the name of an executable!\n", Progname, opts->output);
      return 1;
    }
    if (*extension != '\0') {
      if (opts->flag_compile_only && strcmp(extension, ".o") == 0) {
      } else {
        fprintf(stderr, "%s: warning: unexpected %s extension on output file %s\n", Progname, extension, opts->output);
      }
    }
  }
  
  return 0;
}

static void print_usage(const char *prog) {
  fprintf(stderr, 
      "Usage: %s [options] file...\n"
      "\n"
      "Files:\n"
      "  *.imp          IMP-77 source files (translated to C)\n"
      "  *.c            C source files (passed to the C compiler)\n"
      "  *.o            Object files (passed to the linker)\n"
      "\n"
      "Options:\n"
      "  -c             Compile only; do not link\n"
      "  -g             Enable gdb support\n"
      "  -m32           On a 64 bit system, compile for 32 bit execution\n"
      "  -o <file>      Place output into <file>\n"
      "  -I<dir>        Add <dir> to include search path\n"
      "  -L<dir>        Add <dir> to library search path\n"
      "  -l<lib>        Link with library <lib>\n"
      "  -D<name>[=val] Define preprocessor macro\n"
      "  --static       Use static linking\n"
      "  --trace        Trace procedure calls (implies -g)\n"
      "  --(no-)icode   Keep intermediate-code output\n"
      "  --(no-)check   Enable checks with runtime overhead\n"
      "                 (includes --array, --unass)\n"
      "  --(no-)array   Array bounds checks\n"
      "  --(no-)unass   Uninitialised variable checks\n"
      "  --(no-)bt      Enable backtrace (implies -g)\n"
//    "  --bounds       Enable runtime bounds checking\n"
//    "  --no-bounds    Disable runtime bounds checking\n"
//    "  -h, --help     Show this help message\n"
      "  --dry-run      Don't run subshells\n"
      "  -q, --quiet    No output except from subshells\n"
      "  -v, --verbose  Enable verbose output\n\n",
//    "  -d, --debug    Enable developer debugging\n\n",
      prog);
}

static void print_options(const CompilerOptions *opts) {

  fprintf(stderr, "Flags:\n");
  fprintf(stderr, "  --icode   : %s\n", opts->flag_icode ? "on" : "off");
  fprintf(stderr, "  --check   : %s\n", opts->flag_check ? "on" : "off");
  fprintf(stderr, "  --array   : %s\n", opts->flag_array ? "on" : "off");
  fprintf(stderr, "  --unass   : %s\n", opts->flag_unass ? "on" : "off");
  fprintf(stderr, "  --bt      : %s\n", opts->flag_backtrace ? "on" : "off");
//fprintf(stderr, "  --bounds  : %s\n", opts->flag_bounds ? "on" : "off");
  fprintf(stderr, "  --verbose : %s\n", opts->flag_verbose ? "on" : "off");
  fprintf(stderr, "  --dry-run : %s\n", opts->flag_dry_run ? "on" : "off");
  fprintf(stderr, "  --static  : %s\n", opts->flag_static_link ? "on" : "off");
  fprintf(stderr, "  --trace   : %s\n", opts->flag_static_link ? "on" : "off");
  fprintf(stderr, "  --quiet   : %s\n", opts->flag_quiet ? "on" : "off");
  fprintf(stderr, "  -c        : %s\n", opts->flag_compile_only ? "on" : "off");
  if (opts->flag_gcc_force_m32) {
  fprintf(stderr, "  -m32      : on\n");
  }
  fprintf(stderr, "  -g        : %s\n", opts->flag_gcc_debug ? "on" : "off");
  fprintf(stderr, "  -d        : %s\n", opts->flag_developer_debug ? "on" : "off");
  fprintf(stderr, "\n");

  fprintf(stderr, "Output file       : %s\n", opts->output ? opts->output : "(none)");
  fprintf(stderr, "\n");

  fprintf(stderr, "Include directories (%d):\n", opts->inc_count);
  for (int i = 0; i < opts->inc_count; i++) fprintf(stderr, "  -I %s\n", opts->inc_dirs[i]);

  fprintf(stderr, "Library directories (%d):\n", opts->lib_dir_count);
  for (int i = 0; i < opts->lib_dir_count; i++) fprintf(stderr, "  -L %s\n", opts->lib_dirs[i]);

  fprintf(stderr, "Libraries (%d):\n", opts->lib_count);
  for (int i = 0; i < opts->lib_count; i++) fprintf(stderr, "  -l %s\n", opts->libs[i]);

  fprintf(stderr, "Defines (%d):\n", opts->def_count);
  for (int i = 0; i < opts->def_count; i++) fprintf(stderr, "  -D \"%s\"\n", opts->defines[i]);

  fprintf(stderr, "-W Options (%d):\n", opts->warn_count);
  for (int i = 0; i < opts->warn_count; i++) fprintf(stderr, "  -W \"%s\"\n", opts->warnings[i]);

  fprintf(stderr, "\nIMP-77 source files with pre-processor directives (%d):\n", opts->i77_count);
  for (int i = 0; i < opts->i77_count; i++) fprintf(stderr, "  %s\n", opts->i77_files[i]);

  fprintf(stderr, "Original IMP-77 source files (%d):\n", opts->imp_count);
  for (int i = 0; i < opts->imp_count; i++) fprintf(stderr, "  %s\n", opts->imp_files[i]);

  fprintf(stderr, "C source files (%d):\n", opts->c_count);
  for (int i = 0; i < opts->c_count; i++) fprintf(stderr, "  %s\n", opts->c_files[i]);

  fprintf(stderr, "Object files (%d):\n", opts->obj_count);
  for (int i = 0; i < opts->obj_count; i++) fprintf(stderr, "  %s\n", opts->obj_files[i]);
}

int main(int argc, char *argv[]) {
  char *p;
  char COMPILER_COMMAND[128];
  
  Progname = strdup(argv[0]);
  p = strrchr(Progname, '/');
  if (p) Progname = p+1;
  
  if (argc == 1) {
    fprintf(stderr, "Try %s -h for help with parameters.\n\n", Progname);
    exit(EXIT_FAILURE);
  }
  if (parse_args(argc, argv, &opts) != 0) exit(EXIT_FAILURE);

  if (opts.flag_help) {
    print_usage(Progname);
    exit(EXIT_SUCCESS);
  }

  // Still to add perms.c or perms.o to the compilation, and set up
  // a default path for Imp77 system include files (esp. perms.inc)
  
  if (opts.flag_developer_debug)  print_options(&opts);
  if (!opts.flag_quiet) fprintf(stderr, "\n");
  if (opts.flag_backtrace || opts.flag_trace) opts.flag_gcc_debug = 1; // --bt or --trace implies -g (needed for -lbacktrace)

  if (opts.i77_count+opts.imp_count+opts.c_count+opts.obj_count == 0) {
    fprintf(stderr, "No files given.\n");
    exit(0);
  }
  
  char include_path[2048];
  {
    strcpy(include_path, ".");
    int N = 0;
    while (opts.inc_count --> 0) {
      strcat(include_path, ":");
      strcat(include_path, opts.inc_dirs[N]);
      N += 1;
    }
    // During transition period, point to new perms for C headers but leave default in place for .inc file
    strcat(include_path, ":/usr/local/include/i2c");
    //fprintf(stderr, "setenv IMP_INC_PATH %s\n", include_path);
    setenv("IMP_INC_PATH", include_path, 1);
  }
  char *cp, command[1024];
  char obj[128];
  if (sizeof(void *) == 4) {
    // __i386__ or __arm__ (or other) ...
    sprintf(COMPILER_COMMAND, "gcc");
    //fprintf(stderr, "32-bit system\n");
  } else if (sizeof(void *) == 8) {
    //fprintf(stderr, "64-bit system\n");
#ifdef __aarch64__
    // at this point we might consider a quick runtime check to see if arm-linux-gnueabihf-gcc
    // is available, and if not, output some help info along the lines of:
    
    // To generate 32 bit binaries you will need to install a cross-compiler for architecture 'armhf'
    // sudo apt install gcc-arm-linux-gnueabihf
    // sudo dpkg --add-architecture armhf
    // sudo apt-get update
    // sudo apt-get install libc6:armhf
    if (opts.flag_gcc_force_m32) {
      char cmd[256];
      int rc = sysnprintf(cmd, sizeof(cmd), "which arm-linux-gnueabihf-gcc");
      // sudo apt install gcc-arm-linux-gnueabihf
      if (!(rc && (strlen(cmd) > 3))) {
        fprintf(stderr, "To compile for 32-bit arm architectures, you'll need to:\n");
        fprintf(stderr, "   sudo apt install gcc-arm-linux-gnueabihf\n");
        fprintf(stderr, "   sudo dpkg --add-architecture armhf\n");
        fprintf(stderr, "   sudo apt-get update\n");
        fprintf(stderr, "   sudo apt-get install libc6:armhf\n");
        exit(EXIT_FAILURE);
      }
      sprintf(COMPILER_COMMAND, "arm-linux-gnueabihf-gcc -std=c99");  // or maybe a specific version like arm-linux-gnueabihf-gcc-12 ?
    } else {
      sprintf(COMPILER_COMMAND, "gcc");
    }
#else
  #ifdef __x86_64__
    if (opts.flag_gcc_force_m32) {
      sprintf(COMPILER_COMMAND, "gcc -m32");
    } else {
      sprintf(COMPILER_COMMAND, "gcc");
    }
  #else
    if (opts.flag_gcc_force_m32) {
      fprintf(stderr, "i77: This is an unknown 64-bit system.  I'll try forcing gcc -m32 but no promises that it will work.\n");
      sprintf(COMPILER_COMMAND, "gcc -m32");
    } else {
      sprintf(COMPILER_COMMAND, "gcc");
    }
  #endif
#endif
  } else {
    fprintf(stderr, "Are you seriously trying to compile Imp77 for a %lu-bit target?  OK, good luck!\n", (long unsigned int)sizeof(void *)*CHAR_BIT);
    sprintf(COMPILER_COMMAND, "gcc");
  }
  
  while (opts.i77_count --> 0) {
    FILE *check;
    check = fopen(opts.i77_files[opts.i77_count], "r");
    if (check == NULL) {
      fprintf(stderr, "%s: Cannot open %s - %s\n", Progname, opts.i77_files[opts.i77_count], strerror(errno));
      exit(EXIT_FAILURE);
    } else {
      fclose(check);
    }
    cp = command;
    append(cp, "uncomment-imp");
    append(cp, " < %s.i77", strip_ext(opts.i77_files[opts.i77_count]));
    append(cp, " | ");
    append(cp, "gtcpp -P");
    for (int i = 0; i < opts.def_count; i++) append(cp, " -D \"%s\"", opts.defines[i]);
    append(cp, " > %s.imp", strip_ext(opts.i77_files[opts.i77_count]));
    call(command);
    cp = command;
    char tmp[1024];
    sprintf(tmp, "%s.imp", strip_ext(opts.i77_files[opts.i77_count]));
    (void)list_add(opts.imp_files, &opts.imp_count, MAX_FILES, tmp);
  }
  
  while (opts.imp_count --> 0) {
    cp = command;
    append(cp, "i2c -c --perms /usr/local/include/i2c/perms.inc");
    if (opts.flag_verbose) append(cp, " -v");
    if (opts.flag_developer_debug) append(cp, " -d");
    if (opts.flag_icode) append(cp, " --icode");
    if (opts.flag_check) append(cp, " --check");
    if (opts.flag_array) append(cp, " --array");
    if (opts.flag_unass) append(cp, " --unass");
    // if (opts.flag_backtrace) append(cp, " --bt"); // Only needed by gcc, not implemented by i2c
//  if (opts.flag_bounds) fprintf(stderr, " --bounds");
    // -I for Imp files is used for *.inc files
    for (int i = 0; i < opts.inc_count; i++) append(cp, " -I %s", opts.inc_dirs[i]);
    append(cp, " %s", opts.imp_files[opts.imp_count]);
    call(command);

    cp = command;

    //append(cp, "ecce `dirname %s`/`basename %s .imp`.c -command \"(v/__label__/\\m,m-2(v/ /r)0v/_imp_/km2,m3)0;%%c\" 2> /dev/null  # this is a temporary hack", opts.imp_files[opts.imp_count], opts.imp_files[opts.imp_count]);

    // (changed from 'ecce' to 'ecce8' to avoid clashes with any different ecce that user may already have installed)
    // NOTE: this was to handle explicit user %label declarations, *not* the _imp_endofblock labels that I was
    // generating before changing begin/end blocks into anonymous procedures... so this is still needed for now.
    append(cp, "ecce8 %s.c -command \"(v/__label__/\\m,m-2(v/ /r)0v/_imp_/km2,m3)0;%%c\" 2> /dev/null  # this is a temporary hack", strip_ext(opts.imp_files[opts.imp_count]));

    call(command);

    // *Or* Could add the generated .c file to the list of C files.
    cp = command;
    append(cp, "%s", COMPILER_COMMAND);
    if (opts.flag_gcc_debug) append(cp, " -g");
    for (int i = 0; i < opts.def_count; i++) append(cp, " -D \"%s\"", opts.defines[i]);
    for (int i = 0; i < opts.inc_count; i++) append(cp, " -I %s", opts.inc_dirs[i]);
    append(cp, " -I /usr/local/include/i2c");
    if (opts.flag_compile_only) {
      if (opts.output) {
        sprintf(obj, "%s.o", opts.output);
      } else {
        sprintf(obj, "%s.o", strip_ext(opts.imp_files[opts.imp_count]));
      }
    } else {
      sprintf(obj, "%s.o", strip_ext(opts.imp_files[opts.imp_count]));
    }
    append(cp, " -o %s", obj);
    if (opts.flag_trace) append(cp, "%s", " -finstrument-functions");
    append(cp, " -Wno-discarded-qualifiers");
    append(cp, " -c %s.c", strip_ext(opts.imp_files[opts.imp_count]));
    call(command);
    
    opts.obj_files[opts.obj_count++] = strdup(obj);
  }

  while (opts.c_count --> 0) {
    cp = command;
    append(cp, "%s", COMPILER_COMMAND);
    if (opts.flag_gcc_debug) append(cp, " -g");
    for (int i = 0; i < opts.def_count; i++) append(cp, " -D \"%s\"", opts.defines[i]);
    if (opts.flag_compile_only) {
      if (opts.output) append(cp, " -o %s", opts.output);
      sprintf(obj, "%s.o", opts.output);
    } else {
      sprintf(obj, "%s.o", strip_ext(opts.c_files[opts.c_count]));
    }
    // -I for Imp files is used for *.c files
    for (int i = 0; i < opts.inc_count; i++) append(cp, " -I %s", opts.inc_dirs[i]);
    append(cp, " -I /usr/local/include/i2c");
    if (opts.flag_trace) append(cp, "%s", " -finstrument-functions");
    append(cp, " -Wno-discarded-qualifiers");
    append(cp, " -c %s.c", strip_ext(opts.c_files[opts.c_count]));
    call(command);
    
    opts.obj_files[opts.obj_count++] = strdup(obj);
  }

  if (!opts.flag_compile_only) {
    cp = command;
    append(cp, "%s", COMPILER_COMMAND);
    if (opts.flag_static_link) append(cp, "%s", " --static");
    if (opts.flag_gcc_debug) append(cp, " -g");
    if (opts.flag_trace) append(cp, "%s", " -finstrument-functions -rdynamic");
    if (opts.output != NULL) append(cp, " -o %s", opts.output);
    while (opts.obj_count --> 0) {
      append(cp, " %s", opts.obj_files[opts.obj_count]);
    }
    for (int i = 0; i < opts.lib_dir_count; i++) append(cp, " -L%s", opts.lib_dirs[i]);
    // The default library (64 or 32 bit) is /usr/local/lib/i2c but on a 64 bit system,
    // there will also be a forced 32 bit library in /usr/local/lib/i2c/m32 ...
    if (!opts.flag_static_link) {
      append(cp, " -Wl,-rpath=/usr/local/lib/i2c");
    }
    if (opts.flag_gcc_force_m32) {
      //append(cp, " -L /usr/local/lib/i2c/m32 -L /usr/local/lib/i2c /usr/local/lib/i2c/m32/perms.o");
      append(cp, " -L /usr/local/lib/i2c/m32 -L /usr/local/lib/i2c -l perms");
    } else {
      // in transition to new library:  append(cp, " -L /usr/local/lib/i2c /usr/local/lib/i2c/perms.o");
      append(cp, " -L /usr/local/lib/i2c -l perms");
    }
    for (int i = 0; i < opts.lib_count; i++) {
      if (strcmp(opts.libs[i], "m") != 0) {     // For obscure reasons, -lm must be moved to the end...
        append(cp, " -l%s", opts.libs[i]);
      }
    }
    /* if (opts.flag_backtrace) */ append(cp, " -lbacktrace"); // in the process of adding backtrace facility.  Always requires -g on initial compilation

    // for procedure tracing to stderr.  Was initially always required, but no longer now that I'm using ELF and libbacktrace rather than DWARF for tracing.
    // /*if (opts.flag_trace)*/ append(cp, " -ldl");

    
    append(cp, " -lm"); // we always want -lm, not just when the user asks for it.
    call(command);
  }
  
  exit(EXIT_SUCCESS);
  return EXIT_FAILURE;
}
