/* Copyright 2007  Free Software Foundation, Inc.

   This file is part of the GNU opcodes library.

   This library 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, or (at your option)
   any later version.

   It 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 this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "getopt.h"
#include "libiberty.h"
#include "safe-ctype.h"

#include "i386-opc.h"

#include <libintl.h>
#define _(String) gettext (String)

static const char *program_name = NULL;
static int debug = 0;

static void
fail (const char *message, ...)
{
  va_list args;
  
  va_start (args, message);
  fprintf (stderr, _("%s: Error: "), program_name);
  vfprintf (stderr, message, args);
  va_end (args);
  xexit (1);
}

/* Remove leading white spaces.  */

static char *
remove_leading_whitespaces (char *str)
{
  while (ISSPACE (*str))
    str++;
  return str;
}

/* Remove trailing white spaces.  */

static void
remove_trailing_whitespaces (char *str)
{
  size_t last = strlen (str);

  if (last == 0)
    return;

  do
    {
      last--;
      if (ISSPACE (str [last]))
	str[last] = '\0';
      else
	break;
    }
  while (last != 0);
}

/* Find next field separated by '.' and terminate it. Return a
   pointer to the one after it.  */

static char *
next_field (char *str, char **next)
{
  char *p;

  p = remove_leading_whitespaces (str);
  for (str = p; *str != ',' && *str != '\0'; str++);

  *str = '\0';
  remove_trailing_whitespaces (p);

  *next = str + 1; 

  return p;
}

static void
process_i386_opcodes (void)
{
  FILE *fp = fopen ("i386-opc.tbl", "r");
  char buf[2048];
  unsigned int i;
  char *str, *p, *last;
  char *name, *operands, *base_opcode, *extension_opcode;
  char *cpu_flags, *opcode_modifier, *operand_types [MAX_OPERANDS];

  if (fp == NULL)
    fail (_("can't find i386-opc.tbl for reading\n"));

  printf ("\n/* i386 opcode table.  */\n\n");
  printf ("const template i386_optab[] =\n{\n");

  while (!feof (fp))
    {
      if (fgets (buf, sizeof (buf), fp) == NULL)
	break;

      p = remove_leading_whitespaces (buf);

      /* Skip comments.  */
      str = strstr (p, "//");
      if (str != NULL)
	str[0] = '\0';

      /* Remove trailing white spaces.  */
      remove_trailing_whitespaces (p);

      switch (p[0])
	{
	case '#':
	  printf ("%s\n", p);
	case '\0':
	  continue;
	  break;
	default:
	  break;
	}

      last = p + strlen (p);

      /* Find name.  */
      name = next_field (p, &str);

      if (str >= last)
	abort ();

      /* Find number of operands.  */
      operands = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find base_opcode.  */
      base_opcode = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find extension_opcode.  */
      extension_opcode = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find cpu_flags.  */
      cpu_flags = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find opcode_modifier.  */
      opcode_modifier = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Remove the first {.  */
      str = remove_leading_whitespaces (str);
      if (*str != '{')
	abort ();
      str = remove_leading_whitespaces (str + 1);

      i = strlen (str);

      /* There are at least "X}".  */
      if (i < 2)
	abort ();

      /* Remove trailing white spaces and }. */
      do
	{
	  i--;
	  if (ISSPACE (str[i]) || str[i] == '}')
	    str[i] = '\0';
	  else
	    break;
	}
      while (i != 0);

      last = str + i;

      /* Find operand_types.  */
      for (i = 0; i < ARRAY_SIZE (operand_types); i++)
	{
	  if (str >= last)
	    {
	      operand_types [i] = NULL;
	      break;
	    }

	  operand_types [i] = next_field (str, &str);
	  if (*operand_types[i] == '0')
	    {
	      if (i != 0)
		operand_types[i] = NULL;
	      break;
	    }
	}

      printf ("  { \"%s\", %s, %s, %s, %s,\n",
	      name, operands, base_opcode, extension_opcode,
	      cpu_flags);

      printf ("    %s,\n", opcode_modifier);

      printf ("    { ");

      for (i = 0; i < ARRAY_SIZE (operand_types); i++)
	{
	  if (operand_types[i] == NULL
	      || *operand_types[i] == '0')
	    {
	      if (i == 0)
		printf ("0");
	      break;
	    }

	  if (i != 0)
	    printf (",\n      ");

	  printf ("%s", operand_types[i]);
	}
      printf (" } },\n");
    }

  printf ("  { NULL, 0, 0, 0, 0, 0, { 0 } }\n");
  printf ("};\n");
}

static void
process_i386_registers (void)
{
  FILE *fp = fopen ("i386-reg.tbl", "r");
  char buf[2048];
  char *str, *p, *last;
  char *reg_name, *reg_type, *reg_flags, *reg_num;

  if (fp == NULL)
    fail (_("can't find i386-reg.tbl for reading\n"));

  printf ("\n/* i386 register table.  */\n\n");
  printf ("const reg_entry i386_regtab[] =\n{\n");

  while (!feof (fp))
    {
      if (fgets (buf, sizeof (buf), fp) == NULL)
	break;

      p = remove_leading_whitespaces (buf);

      /* Skip comments.  */
      str = strstr (p, "//");
      if (str != NULL)
	str[0] = '\0';

      /* Remove trailing white spaces.  */
      remove_trailing_whitespaces (p);

      switch (p[0])
	{
	case '#':
	  printf ("%s\n", p);
	case '\0':
	  continue;
	  break;
	default:
	  break;
	}

      last = p + strlen (p);

      /* Find reg_name.  */
      reg_name = next_field (p, &str);

      if (str >= last)
	abort ();

      /* Find reg_type.  */
      reg_type = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find reg_flags.  */
      reg_flags = next_field (str, &str);

      if (str >= last)
	abort ();

      /* Find reg_num.  */
      reg_num = next_field (str, &str);

      printf ("  { \"%s\", %s, %s, %s },\n",
	      reg_name, reg_type, reg_flags, reg_num);
    }

  printf ("};\n");

  printf ("\nconst unsigned int i386_regtab_size = ARRAY_SIZE (i386_regtab);\n");
}

/* Program options.  */
#define OPTION_SRCDIR	200

struct option long_options[] = 
{
  {"srcdir",  required_argument, NULL, OPTION_SRCDIR},
  {"debug",   no_argument,       NULL, 'd'},
  {"version", no_argument,       NULL, 'V'},
  {"help",    no_argument,       NULL, 'h'},
  {0,         no_argument,       NULL, 0}
};

static void
print_version (void)
{
  printf ("%s: version 1.0\n", program_name);
  xexit (0);
}

static void
usage (FILE * stream, int status)
{
  fprintf (stream, "Usage: %s [-V | --version] [-d | --debug] [--srcdir=dirname] [--help]\n",
	   program_name);
  xexit (status);
}

int
main (int argc, char **argv)
{
  extern int chdir (char *);
  char *srcdir = NULL;
  int c;
  
  program_name = *argv;
  xmalloc_set_program_name (program_name);

  while ((c = getopt_long (argc, argv, "vVdh", long_options, 0)) != EOF)
    switch (c)
      {
      case OPTION_SRCDIR:
	srcdir = optarg;
	break;
      case 'V':
      case 'v':
	print_version ();
	break;
      case 'd':
	debug = 1;
	break;
      case 'h':
      case '?':
	usage (stderr, 0);
      default:
      case 0:
	break;
      }

  if (optind != argc)
    usage (stdout, 1);

  if (srcdir != NULL) 
    if (chdir (srcdir) != 0)
      fail (_("unable to change directory to \"%s\", errno = %s\n"),
	    srcdir, strerror (errno));

  printf ("/* This file is automatically generated by i386-gen.  Do not edit!  */\n");
  printf ("/* Copyright 2007  Free Software Foundation, Inc.\n\
\n\
   This file is part of the GNU opcodes library.\n\
\n\
   This library is free software; you can redistribute it and/or modify\n\
   it under the terms of the GNU General Public License as published by\n\
   the Free Software Foundation; either version 3, or (at your option)\n\
   any later version.\n\
\n\
   It is distributed in the hope that it will be useful, but WITHOUT\n\
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n\
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public\n\
   License for more details.\n\
\n\
   You should have received a copy of the GNU General Public License\n\
   along with this program; if not, write to the Free Software\n\
   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,\n\
   MA 02110-1301, USA.  */\n");

  process_i386_opcodes ();
  process_i386_registers ();

  exit (0);
}
