/* Jitter: driver.

   Copyright (C) 2017 Luca Saiu
   Updated in 2019, 2021 and 2022 by Luca Saiu
   Written by Luca Saiu

   This file is part of GNU Jitter.

   GNU Jitter is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   GNU Jitter is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Jitter.  If not, see <https://www.gnu.org/licenses/>. */


/* Include the Gnulib header. */
#include <config.h>

#include <argp.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#include <gl_xlist.h>

#include <jitter/jitter-cpp.h>
#include <jitter/jitter-parse-int.h>

#include "jitterc-vm.h"
#include "jitterc-generate.h"
#include "jitterc-utility.h"
#include "jitterc-parser.h"


/* The directory where flags are.  Of course this is only useful if the
   package is installed, but this default can be changed with the command-line
   option --flag-directory .
   The macro definition is convenient as it is, without surrounding parens,
   since it can be directly concatenated as a string literal. */
#define JITTERC_DEFAULT_FLAG_DIRECTORY  \
  JITTER_FLAGDIR

/* The directory where templates are.  Of course this is only useful if the
   package is installed, but this default can be changed with the command-line
   option --template-directory .
   The macro definition is convenient as it is, without surrounding parens,
   since it can be directly concatenated as a string literal. */
#define JITTERC_DEFAULT_TEMPLATE_DIRECTORY  \
  JITTER_TEMPLATEDIR

/* All the information encoded by the user in the command line. */
struct jitterc_command_line
{
  bool verbose, count_specialized_instructions_and_exit;
  bool generate_line;
  bool generate_frontend;
  char *input_file;
  char *flag_directory;
  char *template_directory;
  char *output_directory;
  int max_fast_register_no_per_class; /* -1 means unlimited. */
  int max_nonresidual_literal_no;  /* -1 means unlimited. */
};

/* Numeric identifiers for --no-* , or more in general "default" options having
   no short equivalent and specifying the default behavior; these are
   particularly convenient for interactive use where a complex command line is
   modified frequently.  Each case must have a value which is not an ASCII
   character. */
enum jitterc_negative_option
  {
    jitterc_negative_option_no_count_specialized_instructions = -1,
    jitterc_negative_option_no_frontend = -2,
    jitterc_negative_option_no_verbose = -3,
    jitterc_negative_option_generate_line = -4
  };

/* Numeric keys for options having only a long format.  These must not conflict
   with any value in enum jitterc_negative_option . */
enum jitterc_long_only_option
  {
    jitterc_long_only_option_dump_version = -6
  };

/* Command-line option specification. */
static struct argp_option jitterc_option_specification[] =
  {/* Mandatory options. */
   {NULL, '\0', NULL, OPTION_DOC, "Mandatory options:", 10},
   {"output", 'o', "PATH", 0,
    "Directory to write to"},
   {"output-directory", '\0', NULL, OPTION_ALIAS },

   /* Frequently used options. */
   {NULL, '\0', NULL, OPTION_DOC, "Frequently used options:", 20},
   {"max-fast-register-no", 'r', "N", 0,
    "Fast registers number limit per class -- any register past the N-th"
    " will not be fast, in any class; or -1 for no limit (default: -1)"},
   {"max-nonresidual-no",'n', "N", 0,
    "Maximum nonresidual number per meta-instruction, or -1 for unlimited"},
   {"frontend", 'f', NULL, 0,
    "Generate a vm-main.c file as well, containing a main function for the "
    "default frontend (default: no)"},
   {"main", '\0', NULL, OPTION_ALIAS },
   /* Frequently used negative options. */
   {NULL, '\0', NULL, OPTION_DOC, "", 21},
   {"no-frontend", jitterc_negative_option_no_frontend, NULL, 0,
    "Don't generate vm-main.c (default)"},
   {"no-main", '\0', NULL, OPTION_ALIAS,
    "Don't generate vm-main.c (default)"},

   /* Tuning options. */
   {NULL, '\0', NULL, OPTION_DOC, "Tuning options:", 30},
   {"count-specialized-instructions", 'c', NULL, 0,
    "Display the number of specialized instruction and exit without "
    "generating C files"},
   {"count", '\0', NULL, OPTION_ALIAS},
   /* Tuning negative options. */
   {NULL, '\0', NULL, OPTION_DOC, "", 31},
   {"no-count-specialized-instructions",
    jitterc_negative_option_no_count_specialized_instructions,
    NULL, 0, "Generate files instead of counting and exiting (default)"},
   {"no-count", '\0', NULL, OPTION_ALIAS},

   /* Debugging options. */
   {NULL, '\0', NULL, OPTION_DOC, "Debugging options:", 40},
   {"verbose", 'v', NULL, 0,
    "Show progress information at run time" },
   {"no-line", 'l', NULL, 0,
    "Don't generate #line directives referring the .jitter file.  This is "
    "useful for locating errors in the VM specification such as fogotten "
    "braces, and for debugging Jitter itself"},
   /* Debugging negative options. */
   {NULL, '\0', NULL, OPTION_DOC, "", 41},
   {"no-verbose", jitterc_negative_option_no_verbose, NULL, 0,
    "Don't show progress information (default)"},
   {"line", jitterc_negative_option_generate_line, NULL, 0,
    "Generate #line directives (default)"},

   /* Debugging/scripting options. */
   {NULL, '\0', NULL, OPTION_DOC, "Debugging and scripting options:", 50},
   {"dump-version", jitterc_long_only_option_dump_version, NULL, 0,
    "Print the Jitter version only, without any surrounding text; this "
    "is convenient for scripts" },

   /* Rarely used options. */
   {NULL, '\0', NULL, OPTION_DOC,
    "Specialized, rarely used options (for an non-installed jitter):", 60},
   {"template-directory", 'T', "PATH", 0,
    "Directory holding template files (default "
    JITTERC_DEFAULT_TEMPLATE_DIRECTORY
    " ).  Useful when Jitter is not installed" },
   {"flag-directory", 'F', "PATH", 0,
    "Directory holding architecture-specific flag files (default "
    JITTERC_DEFAULT_FLAG_DIRECTORY
    " ).  Useful when Jitter is not installed" },

   /* Common GNU-style options. */
   {NULL, '\0', NULL, OPTION_DOC, "Common GNU-style options:", -1},
   /* These are automatically generated. */

   /* Option terminator. */
   { 0 }};

/* Short help text to print on --help and --version .  Defaults are
   supplied by the library if these are not defined; I guess they
   use weak symbols on reasonable platforms. */
const char *argp_program_version
  = "jitter (" PACKAGE_NAME ") " PACKAGE_VERSION "\n"
    "Copyright (C) 2023-2025 Luca Saiu.\n"
    "GNU Jitter comes with ABSOLUTELY NO WARRANTY.\n"
    "You may redistribute copies of GNU Jitter under the terms of the GNU\n"
    "General Public License, version 3 or any later version published\n"
    "by the Free Software Foundation.  For more information see the\n"
    "file named COPYING.\n"
    "\n"
    "Written by Luca Saiu <https://ageinghacker.net>.";
const char *argp_program_bug_address = "<" PACKAGE_BUGREPORT ">";

/* Forward-declaration.  I like having argp defined here, before parse_opt which
   is quite long. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state);

/* The parser main data structure. */
static struct argp argp =
  { jitterc_option_specification,
    parse_opt,
    "-o OUTPUTDIRECTORY FILE.jitter",
    "Generate virtual machine C code."
    "\v"
    JITTER_PACKAGE_NAME " home page: <" JITTER_PACKAGE_URL ">.\n"
    "General help using GNU software: <https://www.gnu.org/gethelp>."
  };

/* Update our option state with the information from a single command-line
   option. */
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
  struct jitterc_command_line *cl = state->input;
  switch (key)
    {
      /* FIXME: within the case I could use state->arg_num to get the current
         non-option argument index, if needed. */
    case ARGP_KEY_INIT:
      /* Set sensible default values. */
      cl->verbose = false;
      cl->count_specialized_instructions_and_exit = false;
      cl->generate_frontend = false;
      cl->generate_line = true;
      cl->input_file = NULL;
      cl->flag_directory = JITTERC_DEFAULT_FLAG_DIRECTORY;
      cl->template_directory = JITTERC_DEFAULT_TEMPLATE_DIRECTORY;
      cl->output_directory = NULL;
      cl->max_fast_register_no_per_class = -1;
      cl->max_nonresidual_literal_no = -1;
      break;
    case 'v':
      cl->verbose = true;
      break;
    case jitterc_negative_option_no_verbose:
      cl->verbose = false;
      break;
    case 'c':
      cl->count_specialized_instructions_and_exit = true;
      break;
    case jitterc_long_only_option_dump_version:
      printf ("%s\n", JITTER_PACKAGE_VERSION);
      exit (EXIT_SUCCESS);
    case jitterc_negative_option_no_count_specialized_instructions:
      cl->count_specialized_instructions_and_exit = false;
      break;
    case 'f':
      cl->generate_frontend = true;
      break;
    case 'l': /* The default is true. */
      cl->generate_line = false;
      break;
    case jitterc_negative_option_generate_line:
      cl->generate_line = true;
      break;
    case jitterc_negative_option_no_frontend:
      cl->generate_frontend = false;
      break;
    case 'F':
      cl->flag_directory = arg;
      break;
    case 'T':
      cl->template_directory = arg;
      break;
    case 'o':
      cl->output_directory = arg;
      break;
    case 'r':
      cl->max_fast_register_no_per_class = jitter_string_to_natural (arg);
      if (cl->max_fast_register_no_per_class != -1
          && cl->max_fast_register_no_per_class < 0)
        argp_error (state,
                    "the fast register number limit must be a natural, "
                    "or -1 for no limit.");
      break;
    case 'n':
      {
        jitter_long_long conversion;
        if (jitter_string_to_long_long_inconvenient (arg, &conversion) != 0)
          argp_error (state,
                      "the maximum nonresidual number must be an integer.");
        if (conversion < 0 && conversion != -1)
          argp_error
             (state,
              "the maximum nonresidual number must be a natural or -1 .");
        cl->max_nonresidual_literal_no = conversion;
        break;
      }
    case ARGP_KEY_ARG:
      cl->input_file = arg;
      break;
    /* case ARGP_KEY_NO_ARGS: /\* If this case is omitted, the default is sensible. *\/ */
    /*   argp_error (state, "you have to supply the input file\n"); */
    /*   break; */

    /* case ARGP_KEY_SUCCESS: /\* If this case is omitted, the default is sensible. *\/ */
    /*   break; */

    case ARGP_KEY_END:
      if (state->arg_num != 1)
        argp_error (state, "you gave %i input files instead of one.",
                    (int)state->arg_num);
      if (cl->output_directory == NULL)
        argp_error (state, "you have to supply the output directory.");
      break;

    /* case ARGP_KEY_FINI: /\* If this case is omitted, the default is sensible. *\/ */
    /*   break; */

    default:
      return ARGP_ERR_UNKNOWN;
    }
  return 0;
}

int
main (int argc, char **argv)
{
  /* Parse our arguments; cl will contain the information provided in the
     command line. */
  struct jitterc_command_line cl;
  argp_parse (&argp, argc, argv,
              0,//ARGP_IN_ORDER,
              0, &cl);

  if (cl.verbose)
    printf ("input_file = %s\n"
            "output_directory = %s\n"
            "generate_frontend = %s\n"
            "verbose = %s\n"
            "generate_line = %s\n"
            "max_fast_register_no_per_class = %i\n"
            "max_nonresidual_literal_no = %i\n"
            "template directory = %s\n",
            cl.input_file,
            cl.output_directory,
            cl.generate_frontend ? "yes" : "no",
            cl.verbose ? "yes" : "no",
            cl.generate_line ? "yes" : "no",
            (int) cl.max_fast_register_no_per_class,
            (int) cl.max_nonresidual_literal_no,
            cl.template_directory);

  struct jitterc_vm *vm;
  if (! strcmp (cl.input_file, "-"))
    vm = jitterc_parse_file_star (stdin, cl.generate_line);
  else
    vm = jitterc_parse_file (cl.input_file, cl.generate_line);

  jitterc_specialize (vm,
                      cl.max_fast_register_no_per_class,
                      cl.max_nonresidual_literal_no);

  if (cl.count_specialized_instructions_and_exit)
    {
      printf ("%li\n", (long) gl_list_size (vm->specialized_instructions));
      exit (EXIT_SUCCESS);
    }

  if (cl.verbose)
    {
      //jitterc_print_specialized_instruction_forest  (vm->specialized_instruction_forest);
      printf ("Specialized instructions are %li.\n",
              (long) gl_list_size (vm->specialized_instructions));
      printf ("The maximal residual arity is %li.\n",
              (long) vm->max_residual_arity);

      printf ("Wrapped globals:\n  ");
      int i;
      size_t wrapped_global_no = gl_list_size (vm->wrapped_globals);
      for (i = 0; i < wrapped_global_no; i ++)
        {
          printf ("%s", (char*) gl_list_get_at (vm->wrapped_globals, i));
          if (i != wrapped_global_no - 1)
            printf (", ");
        }
      printf ("\n");

      printf ("Wrapped functions:\n  ");
      size_t wrapped_function_no = gl_list_size (vm->wrapped_functions);
      for (i = 0; i < wrapped_function_no; i ++)
        {
          printf ("%s", (char*) gl_list_get_at (vm->wrapped_functions, i));
          if (i != wrapped_function_no - 1)
            printf (", ");
        }
      printf ("\n");
    }

  jitterc_generate (vm, cl.generate_frontend,
                    cl.template_directory, cl.output_directory);

  if (cl.verbose)
    printf ("Still alive at the end.\n");
  exit (EXIT_SUCCESS);
}
