/* Utility to generate SubMakefile and makefile variable list.
   This utility reads stubs.cf and can generate SubMakefile
   This utility reads stubs.cf and stubs.list and can generate a variable list.
   Copyright 1997 Tristan Gingold
   Written December 1997 by Tristan Gingold

   This program 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 2 of the
   License, or (at your option) any later version.

   This library 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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   The author may be reached by US/French mail:
   Tristan Gingold 
   8 rue Parmentier
   F-91120 PALAISEAU
   FRANCE
 */

#include <stdio.h>
#include <strings.h>
#include <stdlib.h>
#include <assert.h>

#define ISSPACE(c) ((c) == ' ' || (c) == '\t')
#define SKIP_SPACES(s) do { while (ISSPACE(*s)) s++; } while (0)

struct gen_list;

struct gen_list
{
  struct gen_list *next;
  struct gen_list *parent;
  const char *name;
  unsigned int flags;

  union
  {
    /* For library.  */
    struct
    {
      struct gen_list *files;
    } library;

    /* For file.  */
    struct
    {
      struct gen_list *depends;
      struct gen_list *functions;
    } file;

    /* For functions.  */
    struct
    {
      struct gen_list *standards;
      char *cflag;
    } function;

    /* For standards.  */
  } info;
};

struct gen_list *libraries;

/* Set when the needed functions were dumped.  */
#define FL_DUMPED	(1 << 0)

/* Functions marked are to be handled.  */
#define FL_MARKED	(1 << 1)

/* For a function: set when the file need inconditionnaly this function.  */
/* For a file: add this file.  */
#define FL_REQUIRED	(1 << 10)

/* For a library: set if this is libc.  */
#define FL_LIBC		(1 << 11)

#define FL_COMPILER_CHECKERGCC (1 << 12)

/* ARGV[0].  */
char *progname;

/* More debug information.  */
int flag_debug;

/* Current line number and filename.  */
int lineno;
char *current_filename;

/* Filename for stub.cf.  */
char *stub_filename;

/* Search a function in the list.  */
struct gen_list *
search_function (const char *function, const char *filename)
{
  struct gen_list *libs;
  struct gen_list *files;
  struct gen_list *funcs;

  for (libs = libraries; libs; libs = libs->next)
    {
      for (files = libs->info.library.files; files; files = files->next)
	{
	  if (filename && strcmp (filename, files->name) != 0)
	    continue;
	  for (funcs = files->info.file.functions; funcs; funcs = funcs->next)
	    if (strcmp (funcs->name, function) == 0)
	      return funcs;
	  /* Used to debug.  */
	  if (flag_debug)
	    fprintf (stderr,
		     "%s:%d:info: "
		     "function %s not found in file %s, library %s\n",
		     current_filename, lineno,
		     function, files->name, libs->name);
	}
    }

  return NULL;
}

/* Disp the canonical filename for func.  */
void
disp_filename (struct gen_list *func)
{
  if (func->parent->parent->flags & FL_LIBC)
    printf ("c-%s-%s.o", func->parent->name, func->name);
  else
    printf ("%s-%s.o", func->parent->name, func->name);
}

/* Add a simple element.  */
static int out_len;
void
disp_list_element (struct gen_list *func)
{
  int len;

  if (func->flags & FL_DUMPED)
    {
      fprintf (stderr,
	       "%d:warning: function `%s' of `%s' was already inserted\n",
	       lineno, func->name, func->parent->name);
      return;
    }
  else
    func->flags |= FL_DUMPED;

  len = 6 + strlen (func->parent->name) + strlen (func->name);
  if (len + out_len > 78)
    {
      printf ("\\\n ");
      out_len = 1;
    }
  disp_filename (func);
  printf (" ");
  out_len += len;
}

void
disp_compiler (struct gen_list *l, int isfunc)
{
  printf ("\t");
  if (l->flags & FL_COMPILER_CHECKERGCC)
    printf ("$(CHECKERGCC) ");
  else
    printf ("$(CC) ");
  if (isfunc && l->info.function.cflag)
    printf ("%s ", l->info.function.cflag);
  printf ("$(STUB_CFLAGS) ");
}

void
create_makefile (void)
{
  struct gen_list *libs;
  struct gen_list *files;
  struct gen_list *funcs;
  struct gen_list *deps;

  printf ("# This file was automatically created by %s\n", progname);
  printf ("# Please, do not edit.\n\n");

  /* The variables.  */
  for (libs = libraries; libs; libs = libs->next)
    {
      if (!(libs->flags & FL_MARKED))
	continue;
      printf ("\n"
	      "OBJS_%s=", libs->name);
      out_len = strlen (libs->name) + 6;
      for (files = libs->info.library.files; files; files = files->next)
	if (files->flags & FL_REQUIRED)
	  printf ("%s.o ", files->name);
	else
	  for (funcs = files->info.file.functions; funcs; funcs = funcs->next)
	    if (funcs->flags & FL_MARKED)
	      disp_list_element (funcs);
      if (libs->flags & FL_LIBC)
	printf ("$(TARG_C_STUBS)");
      printf ("\n");
    }

  /* The all target.  */
  printf ("\n\n"
	  "all:");
  for (libs = libraries; libs; libs = libs->next)
    {
      if (!(libs->flags & FL_MARKED))
 	continue;
      printf (" ../libchkr_%s.a", libs->name);
    }
  printf ("\n\n");

  /* The targets.  */
  for (libs = libraries; libs; libs = libs->next)
    {
      if (!(libs->flags & FL_MARKED))
	continue;
      printf ("../libchkr_%s.a: $(OBJS_%s)\n"
	      "\t$(AR) rcv $@ $(OBJS_%s)\n\n",
	      libs->name, libs->name, libs->name);
    }

  for (libs = libraries; libs; libs = libs->next)
    {
      printf ("\n"
	      "# Library `%s'\n", libs->name);
      for (files = libs->info.library.files; files; files = files->next)
	{
	  printf ("\n"
		  "# file `%s'\n", files->name);
	  if (files->flags & FL_REQUIRED)
	    {
	      printf ("%s.o: $(srcdir)/%s ../checker_api.h\n",
		      files->name, files->name);
	      disp_compiler (files, 0);
	      printf ("-o $@ -c $<\n\n");
	    }
	  else
	    for (funcs = files->info.file.functions; funcs;
		 funcs = funcs->next)
	      if (funcs->flags & FL_MARKED)
		{
		  disp_filename (funcs);
		  printf (": $(srcdir)/stubs-%s ../checker_api.h \\\n"
			  "  ../available-stubs.h",
			  files->name);
		  for (deps = files->info.file.depends; deps;
		       deps = deps->next)
		    printf (" %s", deps->name);
		  printf ("\n");
		  disp_compiler (funcs, 1);
		  printf ("-DHAVE_%s -o $@ -c $<\n\n",
			  funcs->name);
		}
	}
    }
}
  

/* Add an element (and all other elements necessary) to the list.  */
void
mark_function (struct gen_list *func)
{
  struct gen_list *all_funcs;

  func->flags |= FL_MARKED;
  func->parent->parent->flags |= FL_MARKED;

  for (all_funcs = func->parent->info.file.functions; all_funcs;
       all_funcs = all_funcs->next)
    if (all_funcs->flags & FL_REQUIRED)
      all_funcs->flags |= FL_MARKED;
}

/* Create a list for a variable.  */
void
read_list (FILE *fin)
{
  char buffer[2048];
  int len;
  struct gen_list *libs;
  struct gen_list *files;
  struct gen_list *funcs;
  int error;
  char *funcname;
  char *s;

  lineno = 0;
  current_filename = "<stdin>";

  error = 0;
  while (fgets (buffer, 2048, stdin) != NULL)
    {
      /* This is a new line.  */
      lineno++;

      s = buffer;
      SKIP_SPACES(s);

      /* Eliminate null lines.  */
      if (s[0] == '#' || s[0] == '\n')
	continue;

      /* Check for end of line, and remove it.  */
      len = strlen (s);
      if (s[len - 1] != '\n')
	{
	  fprintf (stderr, "%s:%d: no new line at end of line\n",
		   current_filename, lineno);
	  exit (1);
	}
      while (len > 0 && ISSPACE(s[len - 1]))
	len--;
      s[len - 1] = 0;

      /* Mark a whole library.  */
      if (strncmp (s, "library", 7) == 0 && ISSPACE(s[7]))
	  {
	    s += 7;
	    SKIP_SPACES(s);
	    for (libs = libraries; libs; libs = libs->next)
	      if (strcmp (s, libs->name) == 0)
		{
		  libs->flags |= FL_MARKED;
		  for (files = libs->info.library.files;
		       files; files = files->next)
		    {
		      files->flags |= FL_MARKED;
		      for (funcs = files->info.file.functions; funcs;
			   funcs = funcs->next)
			funcs->flags |= FL_MARKED;
		    }
		}
	  }
      /* If the line begins with a '+', then the line contains a filename and
	 all stubs of the filename are inserted.  */
      else if (s[0] == '+')
	{
	  int found = 0;
	  
	  for (libs = libraries; libs; libs = libs->next)
	    {
	      for (files = libs->info.library.files;
		   files; files = files->next)
		{
		  if (strcmp (files->name, s + 1) == 0)
		    {
		      found = 1;
		      for (funcs = files->info.file.functions; funcs;
			   funcs = funcs->next)
			mark_function (funcs);
		    }
		}
	    }
	  if (!found)
	    {
	      fprintf (stderr,
		       "%s:%d: unknown filename `%s'\n",
		       current_filename, lineno, s + 1);
	      error = 1;
	    }
	}
      else
	{
	  /* Otherwise, this is a simple stub.  */
	  funcname = strchr (s, ':');
	  if (funcname)
	    {
	      /* The simple stub is a filename:funcname.  */
	      *funcname++ = 0;
	      funcs = search_function (funcname, s);
	    }
	  else
	    {
	      /* The simple stubs is just a funcname.  */
	      funcs = search_function (s, NULL);
#if 0
	      if (f && l->flags & LIST_MULTIPLY)
		{
		  fprintf (stderr, 
			   "function `%s' is defined in multiple file, and\n"
			   "no filename was specified\n", s);
		  error = 1;
		  continue;
		}
#endif
	    }
	  
	  if (funcs == NULL)
	    {
	      if (funcname)
		fprintf (stderr,
			 "%s:%d: unknown function `%s' for file `%s'\n",
			 current_filename, lineno,
			 funcname, s);
	      else
		fprintf (stderr, "%s:%d unknown function `%s'\n",
			 current_filename, lineno,
			 s);
	      error = 1;
	      continue;
	    }
	  if (funcs->flags & FL_DUMPED)
	    {
	      fprintf (stderr,
		       "%d:warning: function `%s' of `%s' already dumped\n",
		       lineno, funcs->name, funcs->parent->name);
	      continue;
	    }
	  mark_function (funcs);
	}
    }
  putchar ('\n');
  if (error)
    exit (1);
}

void
usage (void)
{
  fprintf (stderr,
	   "%s [-i file] [-d] [--makefile | --list]\n", progname);
  exit (1);
}


char *
get_token (char *init_str)
{
  static char *str;
  static char sep;
  static int paren_level;
  char *s;

  if (init_str)
    {
      str = init_str;
      paren_level = 0;
    }
  else
    *str = sep;

  /* Skip blanks.  */
  while (*str == ' ' || *str == '\n' || *str == '\t')
    str++;
  
  if (*str == ',' || (*str == '(' && paren_level == 0)
      || (*str == ')' && paren_level == 1) || *str == ':')
    {
      if (*str == '(')
	paren_level = 1;
      else if (*str == ')')
	paren_level = 0;
      str++;
      sep = *str;
      *str = 0;
      return str - 1;
    }
  if (!*str)
    return NULL;
  s = str;
  do
    {
      while (*str && strchr (" \n\t(),:", *str) == NULL)
	str++;
      if (*str == '(' && paren_level > 0)
	{
	  paren_level++;
	  continue;
	}
      if (*str == ')' && paren_level > 1)
	{
	  paren_level--;
	  continue;
	}
    }
  while (0);
  sep = *str;
  *str = 0;
  return s;
}

struct gen_list *
add_to_list (struct gen_list **alist,
	     struct gen_list *parent, const char *name)
{
  struct gen_list *res;

  res = malloc (sizeof (struct gen_list));
  if (!res)
    abort ();
  memset (res, 0, sizeof (struct gen_list));
  res->name = name;
  res->next = *alist;
  res->parent = parent;
  *alist = res;
  return res;
}

struct gen_list *
parse_list (struct gen_list *parent, char **p)
{
  char *s = *p;
  struct gen_list *res = NULL;

  assert (*s == '(');
  while (1)
    {
      s = get_token (NULL);
      if (*s == ')')
	return res;
      if (*s == ',')
	{
	  s = get_token (NULL);
	  if (*s == ',')
	    {
	      fprintf (stderr, "duplicate use of ','\n");
	      do 
		s = get_token (NULL); 
	      while (*s == ',');
	    }
	}
      add_to_list (&res, parent, strdup (s));
    }
}

	  
void
read_config (FILE *fin)
{
  char buffer[2048];
  struct gen_list *current_library;
  struct gen_list *current_file;
  struct gen_list *current_function;
  int error;
  char *s;

  lineno = 0;
  current_filename = stub_filename;
  error = 0;
  current_file = NULL;
  current_library = NULL;

  /* Read each line.  */
  while (fgets (buffer, 2048, fin) != NULL)
    {
      lineno++;

      /* Remove comments.  */
      s = strchr (buffer, '#');
      if (s)
	{
	  s[0] = '\n';
	  s[1] = 0;
	}

#if 0
      /* Check for newline.  */
      len = strlen (buffer);
      if (buffer[len-1] != '\n')
	{
	  fprintf (stderr, "%s:%d: no newline at end of line\n",
		   current_filename, lineno);
	  error++;
	}
      else
	{
	  buffer[len - 1] = 0;
	  len--;
	}
      /* Remove tailing blanks.  */
      while (len > 0 && ISSPACE(buffer[len -1]))
	len--;
      buffer[len] = 0;
      if (!buffer[0])
	continue;
#endif

      /* Now parse lines.  */
      if (buffer[0] != '\t')
	{
	  s = get_token (buffer);

	  if (!s)
	    continue;

	  /* This is a library.  */
	  if (strcmp (s, "library") != 0)
	    {
	      fprintf (stderr, "%s:%d: `library' keyword expected\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    }
	  s = get_token (NULL);
	  if (!s)
	    {
	      fprintf (stderr, "%s:%d: no library name\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    } 
	  current_library = add_to_list (&libraries, NULL, strdup(s));
	  if (s[0] == 'c' && !s[1])
	    current_library->flags |= FL_LIBC;
	  s = get_token (NULL);
	  if (*s != ':')
	    {
	      fprintf (stderr, "%s:%d: `:' must be at end of line\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    }
	  if (get_token(NULL) != NULL)
	    {
	      fprintf (stderr, "%s:%d: garbage after `:'\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    }
	}
      else if (buffer[1] != '\t')
	{
	  size_t len;

	  s = get_token (buffer + 1);

	  /* Check for empty line.  */
	  if (!s)
	    continue;

	  /* Must be a file.  */
	  if (strcmp (s, "file") == 0)
	    s = get_token (NULL);
	  if (!current_library)
	    {
	      fprintf (stderr, "%s:%d: file without current library\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    }
	  len = strlen (s);
#if 0
	  if (s[len-1] != 'c' || s[len-2] != '.')
	    {
	      fprintf (stderr, "%s:%d: filename `%s' does not end with '.c'\n",
		       current_filename, lineno, s);
	      error++;
	      continue;
	    }
	  /* Remove `.c' extension.  */
	  s[len - 2] = 0;
#endif
	  current_file = add_to_list (&current_library->info.library.files,
				      current_library,
				      strdup (s));
	  
	  while (1)
	    {
	      s = get_token (NULL);
	      if (!s)
		{
		  fprintf (stderr, "%s:%d: ':' expected at end of line\n",
			   current_filename, lineno);
		  error++;
		  break;
		}

	      if (strcmp(s, "required") == 0)
		current_file->flags |= FL_REQUIRED;
	      else if (*s == '(')
		current_file->info.file.depends
		  = parse_list (current_file, &s);
	      else if (*s == ':')
		{
		  if (get_token (NULL))
		    {
		      fprintf (stderr, "%s:%d: garbage after `:'\n",
			       current_filename, lineno);
		      error++;
		    }
		  break;
		}
	      else
		{
		  fprintf (stderr, "%s:%d: unknown keyword `%s'\n",
			   current_filename, lineno, s);
		  error++;
		}
	    }
	  
	}
      else
	{
	  /* A function.  */
	  if (!current_file)
	    {
	      fprintf (stderr, "%s:%d: function without current file\n",
		       current_filename, lineno);
	      error++;
	      continue;
	    }
	  s = get_token (buffer + 2);
	  current_function = add_to_list(&current_file->info.file.functions,
					 current_file,
					 strdup (s));
	  while (1)
	    {
	      s = get_token (NULL);
	      if (!s)
		break;
	      if (strcmp (s, "required") == 0)
		current_function->flags |= FL_REQUIRED;
	      else if (strncmp (s, "compiler=", 9) == 0)
		{
		  s += 9;
		  if (strcmp (s, "gcc") == 0)
		    ;
		  else if (strcmp (s, "checkergcc") == 0)
		    current_function->flags |= FL_COMPILER_CHECKERGCC;
		  else
		    {
		      fprintf (stderr,
			       "%s:%d: unknown compiler `%s'\n",
			       current_filename, lineno, s);
		      error++;
		    }
		}
	      else if (strncmp (s, "cflag=", 6) == 0)
		{
		  s += 6;
		  current_function->info.function.cflag = strdup (s);
		}
	      else if (*s == '(')
		current_function->info.function.standards =
		  parse_list (current_function, &s);
	      else if (*s == ':')
		{
		  if (get_token (NULL))
		    {
		      fprintf (stderr, "%s:%d: garbage after `:'\n",
			       current_filename, lineno);
		      error++;
		    }
		  break;
		}
	      else
		{
		  fprintf (stderr,
			   "%s:%d: unknown keyword `%s'\n",
			   current_filename, lineno, s);
		  error++;
		  break;
		}
	    }
	}
    }

  if (error)
    exit (1);
}

void
disp_flags (unsigned int flags)
{
  if (flags & FL_REQUIRED)
    printf (" required");
  if (flags & FL_MARKED)
    printf (" marked");
}

void
disp_base (void)
{
  struct gen_list *libs;
  struct gen_list *files;
  struct gen_list *funcs;

  for (libs = libraries; libs; libs = libs->next)
    {
      printf ("library %s:\n", libs->name);
      for (files = libs->info.library.files; files; files = files->next)
	{
	  printf ("\tfile %s", files->name);
	  disp_flags (files->flags);
	  printf (":\n");
	  for (funcs = files->info.file.functions; funcs; funcs = funcs->next)
	    {
	      printf ("\t\t%s", funcs->name);
	      disp_flags (funcs->flags);
	      printf ("\n");
	    }
	}
      printf ("\n");
    }
}

int
main (int argc, char *argv[])
{
  int i;
  FILE *stub_file;
  enum {do_makefile, do_list, do_out} action;

  progname = argv[0];
  stub_file = stdin;
  stub_filename = "<stdin>";
  action = do_makefile;

  for (i = 1; i < argc; i++)
    {
      if (strcmp (argv[i], "-i") == 0)
	{
	  if (i + 1 == argc)
	    usage ();
	  if (stub_file != stdin)
	    {
	      fprintf (stderr, "multiple `-i' option\n");
	      exit (1);
	    }
	  stub_filename = argv[++i];
	  stub_file = fopen (argv[i], "r");
	  if (stub_file == NULL)
	    {
	      fprintf (stderr, "Can't open `%s': ", stub_filename);
	      perror (NULL);
	      exit (1);
	    }
	}
      else if (strcmp (argv[i], "-d") == 0)
	{
	  flag_debug++;
	}
      else if (strcmp (argv[i], "--makefile") == 0)
	{
	  action = do_makefile;
	}
      else if (strcmp (argv[i], "--list") == 0)
	{
	  action = do_list;
	}
      else if (strcmp (argv[i], "--out") == 0)
	{
	  action = do_out;
	}
      else
	usage ();
    }
  read_config (stub_file);
  switch (action)
    {
    case do_makefile:
    case do_list:
      read_list (stdin);
      create_makefile ();
      if (flag_debug)
	disp_base ();
      break;
    case do_out:
      disp_base ();
      break;
    }
  return 0;
}
