/*
 * gccrules.c  -  Program to compile optimizer rules into C header file
 *
 * This program is based on an idea from Christopher W. Fraser.
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  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
 *  any later version.
 *
 *  This program 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: gccrules.c,v 1.10 2007/01/06 18:31:19 gkminix Exp $
 */


/* This will include all necessary system header files */
#include <common.h>
#include <nblib.h>



/* Define comment character */
#ifndef COMMENT
#define COMMENT	'#'
#endif



/*
 * Maximum length of input line
 */
#define MAXLINE		160



/*
 * Struct containing each string of an input file
 */
struct line_s {
	char          *text;
	struct line_s *prev;
	struct line_s *next;
	struct line_s *nextlist;
	unsigned       num;
};



/*
 * Struct containing one rule
 */
struct rule_s {
	struct line_s *old;
	struct line_s *new;
	struct rule_s *next;
	struct rule_s *nextlist;
	unsigned       num;
};

/*
 * Definition of rule sets
 */
struct ruleset_s {
	struct rule_s    *first;
	struct rule_s    *last;
	struct ruleset_s *nextlist;
	unsigned          num;
};



/*
 * Global variables
 */
static struct line_s *lines = NULL;		/* input lines */
static struct line_s *lastline = NULL;		/* last input line */
static struct rule_s *rules = NULL;		/* rules */
static struct rule_s *lastrule = NULL;		/* last rule */
static struct ruleset_s *rulesets = NULL;	/* rule sets */
static struct ruleset_s *lastset = NULL;	/* last rule set */
static unsigned linenum = 0;			/* number of text lines */
static unsigned rulenum = 0;			/* number of rule structures */
static unsigned setnum = 0;			/* number of rule sets */
static char *myprogname = NULL;			/* program name */



/*
 * Allocate memory and print error if none available
 */
__attribute__((malloc)) static voidstar mymalloc __F((size), size_t size)
{
  voidstar p;

  if ((p = malloc(size)) == NULL) {
	fprintf(stderr, "%s: not enough memory\n", myprogname);
	exit(1);
  }
  memzero(p, size);
  return(p);
}



/*
 * Allocate memory for a string
 */
static inline char *newstr __F((str), const char *str)
{
  char *ret;

  ret = (char *)mymalloc(strlen(str) + 2);
  strcpy(ret, str);
  return(ret);
}



/*
 * Read one line from input file and skip all blanks at the beginning.
 * Also delete any trailing comments and whitespaces.
 */
static char *readline __F((fp), FILE *fp)
{
  static char buf[MAXLINE];
  char *cp;

  /* Read line from input file */
  if (fgets(buf, MAXLINE-1, fp) == NULL)
	return(NULL);
  buf[MAXLINE-1] = '\0';

  /* Delete trailing newline */
  if ((cp = strchr(buf, '\n')) != NULL)
	*cp = '\0';

  /* Delete leading white spaces */
  for (cp = buf; *cp && isspace((int)(*cp)); cp++) ;
  if (cp != buf && *cp)
	strcpy(buf, cp);

  /* Delete any trailing comment, if the line does not start with a comment */
  if (buf[0] != COMMENT && (cp = strchr(buf, COMMENT)) != NULL)
	*cp = '\0';

  /* Delete trailing white space */
  for (cp = (buf + strlen(buf));
			(cp > buf) && (!*cp || isspace((int)(*cp))); cp--) ;
  if (*cp != '\0')
	*(cp + 1) = '\0';

  return(buf);
}



/*
 * Read a list of input lines. Terminate reading when the 'quit' character
 * has been found in the first column of the input line. All lines with the
 * comment character in the first position will be skipped for rules files.
 */
static struct line_s *readlist __F((fp, quit), FILE *fp AND char quit)
{
  struct line_s *lp;
  struct line_s *line_first = NULL;
  struct line_s *line_last = NULL;
  char *cp;

  while ((cp = readline(fp)) != NULL) {
	if (*cp == quit)
		break;
	if (*cp == COMMENT || *cp == '\0')
		continue;
	lp = (struct line_s *)mymalloc(sizeof(struct line_s));
	lp->text = newstr(cp);
	lp->prev = line_last;
	lp->next = NULL;
	lp->nextlist = NULL;
	lp->num = linenum++;
	if (line_first == NULL)
		line_first = lp;
	if (line_last != NULL) {
		line_last->next = lp;
		line_last->nextlist = lp;
	}
	line_last = lp;
  }

  if (line_first != NULL) {
	if (lines == NULL)
		lines = line_first;
	if (lastline != NULL)
		lastline->nextlist = line_first;
	lastline = line_last;
  }
  return(line_first);
}



/*
 * Read rules file
 */
static void readrules __F((filename, curset),
				const char *filename AND
				struct ruleset_s *curset)
{
  struct rule_s *rp;
  struct line_s *line_old, *line_new;
  FILE *fp;

  /* Open pattern file */
  if ((fp = fopen(filename, "r")) == NULL) {
	fprintf(stderr, "%s: can't open rules file %s\n", myprogname, filename);
	exit(1);
  }

  /* Read every line of the rules file */
  while (!feof(fp)) {
	line_old = readlist(fp, '=');
	line_new = readlist(fp, '\0');
	if (line_old == NULL)
		break;
	rp = (struct rule_s *)mymalloc(sizeof(struct rule_s));
	rp->old = line_old;
	rp->new = line_new;
	rp->next = NULL;
	rp->nextlist = NULL;
	rp->num = rulenum++;
	if (curset->first == NULL)
		curset->first = rp;
	if (curset->last != NULL) {
		curset->last->next = rp;
		curset->last->nextlist = rp;
	}
	curset->last = rp;
  }
  if (curset->first == NULL) {
	fprintf(stderr, "%s: empty rules file %s\n", myprogname, filename);
	exit(1);
  }
  if (rules == NULL)
	rules = curset->first;
  if (lastrule != NULL)
	lastrule->nextlist = curset->first;
  lastrule = curset->last;

  /* Close rules file */
  (void)fclose(fp);
}



/*
 * Generate one pointer definition for a line_s structure
 */
static inline char *genline __F((lp), const struct line_s *lp)
{
  static char buf[20];

  if (lp != NULL)
	sprintf(buf, "&linelist[%d]", lp->num);
  else
	sprintf(buf, "NULL");
  return(buf);
}



/*
 * Generate one pointer definition for a rule_s structure
 */
static inline char *genrule __F((rp), const struct rule_s *rp)
{
  static char buf[20];

  if (rp != NULL)
	sprintf(buf, "&rulelist[%d]", rp->num);
  else
	sprintf(buf, "NULL");
  return(buf);
}



/*
 * Write out all rule sets into destination file
 */
static void writesets __F((filename), const char *filename)
{
  struct ruleset_s *sp;
  struct rule_s *rp;
  struct line_s *lp;
  FILE *fp;

  /* Open output file */
  fp = stdout;
  if (filename != NULL && (fp = fopen(filename, "w")) == NULL) {
	fprintf(stderr, "%s: can't open output file %s\n", myprogname, filename);
	exit(1);
  }

  /* Write file header */
  fprintf(fp, "/* This file has been generated automatically by gccrules */\n");
  fprintf(fp, "/* DO NOT EDIT! */\n\n");

  /* Write array with all input lines */
  fprintf(fp, "/* Struct containing each string of the rules files */\n");
  fprintf(fp, "static struct rline_s {\n"
			"\tchar           *text;\n"
			"\tstruct rline_s *prev;\n"
			"\tstruct rline_s *next;\n"
			"} linelist[%d] = {\n", linenum);
  for (lp = lines; lp != NULL; lp = lp->nextlist) {
	fprintf(fp, "\t{\"%s\", ", lp->text);
	fprintf(fp, "%s, ", genline(lp->prev));
	fprintf(fp, "%s", genline(lp->next));
	fprintf(fp, "}%s\n", (lp->nextlist == NULL ? "" : ","));
  }
  fprintf(fp, "};\n\n");

  /* Write array with all rules */
  fprintf(fp, "/* Struct containing one rule */\n");
  fprintf(fp, "static struct rule_s {\n"
			"\tstruct rline_s *old;\n"
			"\tstruct rline_s *new;\n"
			"\tstruct rule_s  *next;\n"
			"} rulelist[%d] = {\n", rulenum);
  for (rp = rules; rp != NULL; rp = rp->nextlist) {
	fprintf(fp, "\t{%s, ", genline(rp->old));
	fprintf(fp, "%s, ", genline(rp->new));
	fprintf(fp, "%s", genrule(rp->next));
	fprintf(fp, "}%s\n", (rp->nextlist == NULL ? "" : ","));
  }
  fprintf(fp, "};\n\n");

  /* Write array with pointers to first rule in each rule set */
  fprintf(fp, "static struct rule_s *setlist[%d] = {\n", setnum);
  for (sp = rulesets; sp != NULL; sp = sp->nextlist)
	fprintf(fp, "\t%s%s\n",
			genrule(sp->first),
			(sp->nextlist == NULL ? "" : ","));
  fprintf(fp, "};\n\n");

  /* Write the number of rule sets */
  fprintf(fp, "static int rulesets = %d;\n\n", setnum);

  /* Finally close output file */
  if (fp != stdout)
	  (void)fclose(fp);
}



/*
 * Print usage
 */
static void usage __F_NOARGS
{
  fprintf(stderr, "usage: %s [-o<out-file>] <rules-file>...\n", myprogname);
  exit(1);
}



/*
 * Main program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  struct ruleset_s *tmp;
  char *outfile = NULL;
  int i;

  /* Get program name */
  if ((myprogname = strrchr(argv[0], '/')) == NULL)
	myprogname = argv[0];
  else
	myprogname++;

  /* Get options from command line */
  for (i = 1; i < argc; i++)
	if (!strncmp(argv[i], "-o", 2))
		outfile = &(argv[i][2]);
	else if (argv[i][0] == '-')
		usage();
	else
		break;

  /* Have to have enough parameters for rule file names */
  if ((argc - i) < 1)
	usage();

  /* Read all rules files into memory and finally write a C header file */
  for ( ; i < argc; i++) {
	tmp = (struct ruleset_s *)mymalloc(sizeof(struct ruleset_s));
	tmp->first = NULL;
	tmp->last = NULL;
	tmp->nextlist = NULL;
	tmp->num = setnum++;
	if (rulesets == NULL)
		rulesets = tmp;
	if (lastset != NULL)
		lastset->nextlist = tmp;
	lastset = tmp;
	readrules(argv[i], tmp);
  }
  writesets(outfile);

  return(0);
}

