/*
 * gccopt.c  -  Peephole-optimize GCC compiler output
 *
 * This program is based on an idea from Christopher W. Fraser.
 *
 * Copyright (C) 1995-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: gccopt.c,v 1.11 2007/01/06 18:31:19 gkminix Exp $
 */


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


#define MAXLINE		1024
#define HASHSIZE	107
#define DEFBACKUP	0

#define VARNUM		38
#define VARSTART	10
#define VAREND		11
#define VARGLOBAL	12
#ifndef VARCHAR
#define VARCHAR		'?'
#endif

#ifndef COMMENT
#define COMMENT		'#'
#endif
#ifndef ASMSTART
#define ASMSTART	"#APP"
#endif
#ifndef ASMEND
#define ASMEND		"#NO_APP"
#endif


/* Struct containing each string of the input file */
struct iline_s {
	char           *text;
	struct iline_s *prev;
	struct iline_s *next;
	struct rule_s  *rule;
	int            comment_flg;
};

/* Hash table to store strings in a space saving way */
struct hash_s {
	char          *text;
	struct hash_s *next;
};



/*
 * Global variables
 */
static struct iline_s *inlines = NULL;	/* list of strings in input file */
static struct hash_s *htab[HASHSIZE];	/* string hash table */
static int hash_init = 0;		/* flag if hash table initialized */
static int current_set = 0;		/* current rule set */
static int no_asm = 0;			/* flag if not optimizing in ASM */
static char *vars[VARNUM];		/* variable table */
static char *myprogname;		/* 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);
}



/*
 * Insert a string into the hash table. If the string is already in there
 * just return the pointer to that string.
 */
static char *install __F((str, slen), const char *str AND size_t slen)
{
  struct hash_s *hp;
  char *chkstr;
  char *cp;
  int hashval;

  /* Clear the hashing table if not already done */
  if (!hash_init) {
	for (hashval = 0; hashval < HASHSIZE; hashval++)
		htab[hashval] = NULL;
	hash_init++;
  }

  /* Get check string */
  chkstr = (char *)mymalloc(slen + 1);
  strncpy(chkstr, str, slen);
  chkstr[slen] = '\0';

  /* Determine hashing value of string */
  hashval = 0;
  for (cp = chkstr; *cp; cp++)
	hashval += *cp;
  hashval %= HASHSIZE;

  /* Check if the string is already in the hashing table */
  for (hp = htab[hashval]; hp != NULL; hp = hp->next)
	if (!strcmp(chkstr, hp->text)) {
		free(chkstr);
		return(hp->text);
	}

  /* String is not in hash table, so create a new entry */
  hp = (struct hash_s *)mymalloc(sizeof(struct hash_s));
  hp->text = chkstr;
  hp->next = htab[hashval];
  htab[hashval] = hp;
  return(hp->text);
}



/*
 * 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, *cp1, *cp2;
  int instring;

  /* 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';

  /*
   * Remove all blanks following a comma, and convert all other blanks
   * into tabs
   */
  cp = buf;
  instring = FALSE;
  while (*cp) {
	if (*cp == '"')
		instring = !instring;
	else if (!instring) {
		if (isspace((int)(*cp)))
			*cp = '\t';
		else if (*cp == ',') {
			cp1 = cp;
			cp2 = ++cp1;
			while (isspace((int)(*cp2)))
				cp2++;
			while (*cp2)
				*cp1++ = *cp2++;
			*cp1 = '\0';
		}
	}
	cp++;
  }
  return(buf);
}



/*
 * Read input file
 */
static void readinfile __F((filename), const char *filename)
{
  struct iline_s *lp;
  struct iline_s *first_line = NULL;
  struct iline_s *last_line = NULL;
  char *cp;
  FILE *fp;

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

  /* Read all lines from the input file */
  while ((cp = readline(fp)) != NULL) {
	if (*cp == '\0')
		continue;
	lp = (struct iline_s *)mymalloc(sizeof(struct iline_s));
	lp->text = install(cp, strlen(cp));
	lp->prev = last_line;
	lp->rule = NULL;
	lp->next = NULL;
	lp->comment_flg = (*cp == COMMENT);
	if (first_line == NULL)
		first_line = lp;
	if (last_line != NULL)
		last_line->next = lp;
	last_line = lp;
  }
  inlines = first_line;

  /* Close input file */
  if (fp != stdin)
	  (void)fclose(fp);
}



/*
 * Check if a character is within the allowable range of variable specifiers
 */
__attribute__((const)) static inline int isvar __F((c), char c)
{
  return((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'));
}



/*
 * Determine number of variable according to its specifier
 */
__attribute__((const)) static inline unsigned getvarnum __F((c), char c)
{
  return((c >= '0' && c <= '9') ? c - '0' :
         (c >= 'A' && c <= 'Z') ? c - 'A'  + VARGLOBAL : 0);
}




#define NO_OP  0
#define ADD_OP 1
#define SUB_OP 2
#define MUL_OP 3
#define DIV_OP 4
#define SHL_OP 5
#define SHR_OP 6
#define LOG_OP 7

static const int log_tab[32] = {
	0, 0,						/* 0 - 1 */
	1, 1,						/* 2 - 3 */
	2, 2, 2, 2,					/* 4 - 7 */
	3, 3, 3, 3, 3, 3, 3, 3,				/* 8 - 15 */
	4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4	/* 16 - 31 */
};


/*
 * Apply operation to current numeric value
 */
__attribute__((pure)) static long doretval __F((retval, num, sign, op),
				long retval AND
				long num AND
				int sign AND
				int op)
{
  switch (op) {
	case NO_OP:	retval = num * sign;
			break;
	case ADD_OP:	retval += num * sign;
			break;
	case SUB_OP:	retval -= num * sign;
			break;
	case MUL_OP:	retval *= num * sign;
			break;
	case DIV_OP:	retval /= num * sign;
			break;
	case SHL_OP:	retval <<= num;
			break;
	case SHR_OP:	retval >>= num;
			break;
	case LOG_OP:	if (retval < 0 || retval > 31 || num != 2)
				retval = 0;
			else
				retval = log_tab[retval];
			break;
  }
  return(retval);
}


/*
 * Eval an expression into an integer number
 */
static long eval __F((str, len), char *str AND size_t len)
{
  size_t i;
  char *cp, c;
  unsigned varnum;
  unsigned state = 0;
  long retval = 0;
  long num = 0;
  int sign = 1;
  int op = NO_OP;

  /* Scan through whole string and decode it */
  for (cp = str, i = 0; *cp && i < len; cp++, i++) {
	c = toupper(*cp);
	if (c == '-' && (state == 0 || state == 5)) {
		state = 1;
		sign = -1;
	} else if (c == '+' && (state == 0 || state == 5)) {
		state = 1;
		sign = 1;
	} else if (c == VARCHAR && isvar(*(cp + 1)) &&
	           (state < 2 || state == 5)) {
		state = 4;
		varnum = getvarnum(*(cp + 1));
		if (vars[varnum] == NULL || i >= len)
			return(0);
		num = eval(vars[varnum], strlen(vars[varnum]));
		retval = doretval(retval, num, sign, op);
		num = 0;
		sign = 1;
		op = NO_OP;
		cp++; i++;
	} else if ((c >= '0' && c <= '9') &&
	           (state <= 3 || state == 5)) {
		state = 3;
		num = num * 10 + (c - '0');
	} else if (c == ' ' && (state == 3 || state == 4 || state == 5)) {
		if (state == 3) {
			retval = doretval(retval, num, sign, op);
			num = 0;
			sign = 1;
			op = NO_OP;
			state = 4;
		}
	} else if (strchr("+-*/<>&", c) != NULL && (state == 3 || state == 4)) {
		if (state == 3)  {
			retval = doretval(retval, num, sign, op);
			num = 0;
			sign = 1;
			op = NO_OP;
		}
		state = 5;
		switch (c) {
			case '+':	op = ADD_OP;
					break;
			case '-':	op = SUB_OP;
					break;
			case '*':	op = MUL_OP;
					break;
			case '/':	op = DIV_OP;
					break;
			case '<':	op = SHL_OP;
					break;
			case '>':	op = SHR_OP;
					break;
			case '&':	op = LOG_OP;
					break;
		}
	} else
		return(0);
  }

  /* Check if the string has been terminated correctly */
  if (state != 3 && state != 4)
	return(0);
  if (state == 3)
	retval = doretval(retval, num, sign, op);
  return(retval);
}



/*
 * Compare an infile string with a pattern string. If there is any variable
 * defined, it will be inserted into the variable list from the pattern
 * string.
 */
static int match __F((ins, pat, substr), char *ins AND char *pat AND int substr)
{
  size_t len;
  char *cp, *oldpat;
  char *oldvars[VARNUM];
  unsigned varnum;
  long val, equval, minval, maxval;
  int hasequ, hasmin, hasmax;
  int i;

  /* Save variable pointers */
  for (varnum = 0; varnum < VARNUM; varnum++)
	oldvars[varnum] = vars[varnum];

  /* Handle substring match */
  len = 0;
  if (substr && pat[0] == VARCHAR && pat[1] == 's') {
	pat += 2;
	for (cp = ins; *ins && !match(ins, pat, FALSE); ins++) ;
	if (!*ins)
		goto nomatch;
	len = (size_t)(ins - cp);
	if (vars[VARSTART] == NULL)
		vars[VARSTART] = install(cp, len);
	return(TRUE);
  }

  /* Handle remaining part of input string */
  while (*ins && *pat) {
  	if (pat[0] != VARCHAR) {
		if (*pat++ != *ins++)
			goto nomatch;
	} else if (pat[1] == VARCHAR) {
		/* '??' actually means '?' */
		if (*ins++ != VARCHAR)
			goto nomatch;
		pat += 2;
	} else if (pat[1] == 'e') {
		break;
	} else if (pat[1] == '*' || isvar(pat[1])) {
		/* Copy variable text into vars array */
		pat += 2;
		for (cp = ins; *ins && !match(ins, pat, FALSE); ins++) ;
		len = (size_t)(ins - cp);
		if (pat[-1] == '*')
			continue;
		varnum = getvarnum(pat[-1]);
		if (vars[varnum] == NULL)
			vars[varnum] = install(cp, len);
		else if (strlen(vars[varnum]) != len ||
		           strncmp(vars[varnum], cp, len))
			goto nomatch;
		/* Check if match() has done all further work for us */
		if (*ins || (!*ins && !*pat))
			return(TRUE);
	} else if (pat[1] == '=' && isvar(pat[2])) {
		/* Copy variable text into vars array unconditionally */
		pat += 3;
		for (cp = ins; *ins && !match(ins, pat, FALSE); ins++) ;
		len = (size_t)(ins - cp);
		varnum = getvarnum(pat[-1]);
		vars[varnum] = install(cp, len);
		/* Check if match() has done all further work for us */
		if (*ins || (!*ins && !*pat))
			return(TRUE);
	} else if (pat[1] == '[') {
		/* Copy only specific variable text into vars array */
		if ((cp = strchr(pat + 2, ']')) == NULL ||
		    (*(cp + 1) != '*' && !isvar(*(cp + 1)))) {
			if (*ins != '[')
				goto nomatch;
			pat += 2;
			continue;
		}
		oldpat = pat + 1;
		pat = cp + 2;
		/* Seperate allowable patterns and compare them with ins */
		while (*oldpat && *oldpat != ']') {
			oldpat++;
			len = strcspn(oldpat, "|]");
			if (!strncmp(ins, oldpat, len))
				break;
			oldpat += len;
		}
		if (!*oldpat || *oldpat == ']')
			goto nomatch;
		ins += len;
		if (!match(ins, pat, FALSE))
			goto nomatch;
		/* Install new string into variable table */
		if (*(cp + 1) == '*')
			continue;
		varnum = getvarnum(*(cp + 1));
		if (vars[varnum] == NULL)
			vars[varnum] = install(oldpat, len);
		else if (strlen(vars[varnum]) != len ||
		           strncmp(vars[varnum], oldpat, len))
			goto nomatch;
		/* We can return here because match() has done the rest */
		return(TRUE);
	} else if (pat[1] == '!') {
		/* Match only if the pattern string is not found */
		if (pat[2] != '[' || (cp = strchr(pat + 3, ']')) == NULL ||
		    (*(cp + 1) != '*' && !isvar(*(cp + 1)))) {
			if (*ins != '!')
				goto nomatch;
			pat += 3;
			continue;
		}
		oldpat = pat + 2;
		pat = cp + 2;
		/* Seperate allowable patterns and compare them with ins */
		while (*oldpat && *oldpat != ']') {
			oldpat++;
			len = strcspn(oldpat, "|]");
			if (!strncmp(ins, oldpat, len))
				goto nomatch;
			oldpat += len;
		}
		/* Copy variable text into vars array */
		for (cp = ins; *ins && !match(ins, pat, FALSE); ins++) ;
		len = (size_t)(ins - cp);
		if (pat[-1] == '*')
			continue;
		varnum = getvarnum(pat[-1]);
		if (vars[varnum] == NULL)
			vars[varnum] = install(cp, len);
		else if (strlen(vars[varnum]) != len ||
		           strncmp(vars[varnum], cp, len))
			goto nomatch;
		/* Check if match() has done all further work for us */
		if (*ins || (!*ins && !*pat))
			return(TRUE);
	} else if (pat[1] == '(') {
		/* Match ins with expression */
		hasequ = hasmin = hasmax = FALSE;
		equval = minval = maxval = 0;
		if ((cp = strchr(pat + 2, ')')) == NULL ||
		    (*(cp + 1) != '*' && !isvar(*(cp + 1)))) {
			if (*ins != '(')
				goto nomatch;
			pat += 2;
			continue;
		}
		oldpat = pat + 1;
		pat = cp + 2;
		/* Seperate min/max/equal values */
		i = 0;
		while (*oldpat && *oldpat != ')') {
			oldpat++;
			len = strcspn(oldpat, "|)");
			if (len > 0)
				switch (i) {
					case 0:
						equval = eval(oldpat, len);
						hasequ = TRUE;
						break;
					case 1:
						minval = eval(oldpat, len);
						hasmin = TRUE;
						break;
					case 2:
						maxval = eval(oldpat, len);
						hasmax = TRUE;
						break;
					default:
						break;
				}
			i++;
			oldpat += len;
		}
		/* Check if the value matches */
		for (cp = ins; *ins && !match(ins, pat, FALSE); ins++) ;
		len = (size_t)(ins - cp);
		val = eval(cp, len);
		if ((hasequ && (val != equval)) ||
		    (hasmin && (val < minval)) ||
		    (hasmax && (val > maxval)))
			goto nomatch;
		/* Install new string into variable table */
		if (pat[-1] == '*')
			continue;
		varnum = getvarnum(*(pat - 1));
		if (vars[varnum] == NULL)
			vars[varnum] = install(cp, len);
		else if (strlen(vars[varnum]) != len ||
		           strncmp(vars[varnum], cp, len))
			goto nomatch;
		/* Check if match() has done all further work for us */
		if (*ins || (!*ins && !*pat))
			return(TRUE);
	} else /* Bad ? format cannot match */
		goto nomatch;
  }

 /* Handle end of substring match */
  if (pat[0] == VARCHAR && pat[1] == 'e') {
	pat += 2;
	for (cp = ins; *ins; ins++) ;
	if (vars[VAREND] == NULL)
		vars[VAREND] = install(cp, (ins - cp));
  }
  if (*ins != *pat)
	goto nomatch;
  return(TRUE);

nomatch:
  /* Restore variables in case of no-match */
  for (varnum = 0; varnum < VARNUM; varnum++)
	vars[varnum] = oldvars[varnum];
  return(FALSE);
}



/*
 * Substitute variables in a string
 */
static char *subst __F((pat), char *pat)
{
  char buf[MAXLINE];
  char *cp, *cp1, *cp2, *varptr;
  long num;
  size_t i = 0;
  size_t j, pos;

  /* Substitute the start string */
  if (vars[VARSTART] != NULL) {
	if ((i = strlen(vars[VARSTART])) >= MAXLINE - 1) {
		fprintf(stderr, "%s: line too long\n", myprogname);
		exit(1);
	}
	strncpy(buf, vars[VARSTART], i);
  }

  /* Now copy the string into the buffer while substituting variables */
  while (*pat)
	if (pat[0] == VARCHAR && isvar(pat[1])) {
		/* Substitute with value of variable */
		cp = vars[getvarnum(pat[1])];
		while (cp != NULL && *cp) {
			buf[i++] = *cp++;
			if (i >= MAXLINE - 1) {
				fprintf(stderr, "%s: line too long\n", myprogname);
				exit(1);
			}
		}
		pat += 2;
	} else if (pat[0] == VARCHAR && pat[1] == '(') {
		/* Substitute with expression */
		cp = pat + 2;
		if ((pat = strchr(cp, ')')) == NULL || (pat - cp) <= 0)
			num = 0;
		else
			num = eval(cp, (size_t)(pat - cp));
		if (i >= MAXLINE - 20) {
			fprintf(stderr, "%s: line too long\n", myprogname);
			exit(1);
		}
		i += sprintf(&buf[i], "%s%lu",
				num < 0 ? "-" : "", labs(num));
		pat++;
	} else if (pat[0] == VARCHAR && pat[1] == '=') {
		/* Set a variable */
		/* First determine the variable name and value */
		cp = pat + 2;
		cp1 = varptr = NULL;
		if (*cp == '[') {
			cp1 = ++cp;
			while (*cp && *cp != ']')
				cp++;
		}
		if (cp1 == NULL || cp[0] != ']' || !isvar(cp[1])) {
			buf[i++] = *pat++;
			if (i >= MAXLINE - 1) {
				fprintf(stderr, "%s: line too long\n", myprogname);
				exit(1);
			}
			continue;
		}
		pat = cp + 2;
		/* Now we can set the variable to its new value */
		vars[getvarnum(cp[1])] = install(cp1, cp - cp1);
	} else if (pat[0] == VARCHAR && pat[1] == '$') {
		/* Substitute with converted variable */
		/* First seperate all parts of the pattern string */
		cp = pat + 2;
		cp1 = cp2 = varptr = NULL;
		if (*cp == '[') {
			cp1 = ++cp;
			while (*cp && *cp != ']')
				cp++;
			if (cp[0] == ']' && cp[1] == '[') {
				cp += 2;
				cp2 = cp;
				while (*cp && *cp != ']')
					cp++;
				if (cp[0] == ']' && isvar(cp[1]))
					 varptr = vars[getvarnum(cp[1])];
			}
		}
		if (cp1 == NULL || cp2 == NULL || varptr == NULL) {
			buf[i++] = *pat++;
			if (i >= MAXLINE - 1) {
				fprintf(stderr, "%s: line too long\n", myprogname);
				exit(1);
			}
			continue;
		}
		pat = cp + 2;
		/* Now scan through the first string to find variable value */
		cp1--;
		pos = 0;
		while (*cp1 != ']') {
			cp1++;
			j = strcspn(cp1, "|]");
			if (strlen(varptr) == j && !strncmp(cp1, varptr, j))
				break;
			pos++;
			cp1 += j;
		}
		if (*cp1 == ']')
			continue;
		/* Scan through the second string to find the conversion */
		cp2--;
		while (*cp2 != ']' && pos > 0) {
			cp2++;
			j = strcspn(cp2, "|]");
			pos--;
			cp2 += j;
		}
		if (*cp2 == ']' || pos != 0)
			continue;
		/* Insert conversion string into destination */
		cp2++;
		while (*cp2 != '|' && *cp2 != ']') {
			buf[i++] = *cp2++;
			if (i >= MAXLINE - 1) {
				fprintf(stderr, "%s: line too long\n", myprogname);
				exit(1);
			}
		}
	} else {
		buf[i++] = *pat++;
		if (i >= MAXLINE - 1) {
			fprintf(stderr, "%s: line too long\n", myprogname);
			exit(1);
		}
	}

  /* Finally copy the end variable into the buffer */
  if (vars[VAREND] != NULL) {
	j = i;
	if ((i += strlen(vars[VAREND])) >= MAXLINE - 1) {
		fprintf(stderr, "%s: line too long\n", myprogname);
		exit(1);
	}
	strncpy(&buf[j], vars[VAREND], (i - j));
  }
  buf[i] = '\0';
  return(install(buf, i));
}



/*
 * Optimize one line of the input file
 */
static struct iline_s *optline __F((cur), struct iline_s *cur)
{
  struct rule_s *rp;
  struct rline_s *pat;
  struct iline_s *ins, *lp1, *lp2;
  struct iline_s *retcur;
  unsigned i;

  for (rp = setlist[current_set]; rp != NULL; rp = rp->next) {
	/* Check if this input line has been generated by this rule already */
	if (cur->rule == rp)
		continue;

	/* Clear per-rule variable array */
	for (i = 0; i < VARGLOBAL; i++)
		vars[i] = NULL;

	/* Scan through pattern texts and match them against the input file */
	ins = cur;
	pat = rp->old;
	while (ins != NULL &&
	       pat != NULL &&
	       (ins->comment_flg || match(ins->text, pat->text, TRUE))) {
		if (!ins->comment_flg)
			pat = pat->next;
		ins = ins->next;
	}

	/* Current pattern matched input line, so replace input with new */
	if (pat == NULL) {
		/* Clear all lines in the source file for this pattern */
		lp1 = cur;
		cur = retcur = cur->prev;
		while (lp1 != NULL && lp1 != ins) {
			lp2 = lp1;
			lp1 = lp1->next;
			if (lp2->comment_flg) {
				/* Keep comment lines */
				cur = lp2;
			} else {
				cur->next = lp1;
				if (lp1 != NULL)
					lp1->prev = cur;
				free(lp2);
			}
		}
		/* Insert new lines into list */
		pat = rp->new;
		lp1 = cur;
		lp2 = NULL;
		while (pat != NULL) {
			lp2 = (struct iline_s *)mymalloc(sizeof(struct iline_s));
			lp2->text = subst(pat->text);
			lp2->next = NULL;
			lp2->prev = lp1;
			lp2->rule = rp;
			lp2->comment_flg = 0;
			if (lp1 != NULL)
				lp1->next = lp2;
			else
				inlines = lp2;
			lp1 = lp2;
			pat = pat->next;
		}
		if (ins != NULL)
			ins->prev = lp2;
		if (lp2 != NULL)
			lp2->next = ins;
		else if (lp1 != NULL)
			lp1->next = NULL;
		else
			inlines = NULL;
		return(retcur);
	}
  }
  return(cur->next);
}



/*
 * Actually optimize all strings in the input file
 */
static void optimize __F((backup), int backup)
{
  struct iline_s *cur, *lp;
  int i;
  int in_asm = 0;

  /* Clear global variable array */
  for (i = VARGLOBAL; i < VARNUM; i++)
	vars[i] = NULL;

  /* Scan through all lines in the input file */
  cur = inlines;
  while (cur != NULL) {
	if (!in_asm && no_asm && !strcmp(cur->text, ASMSTART)) {
		lp = cur->next;
		in_asm = TRUE;
	} else if (cur->comment_flg || in_asm) {
		lp = cur->next;
		if (!strcmp(cur->text, ASMEND))
			in_asm = FALSE;
	} else if ((lp = optline(cur)) != NULL && lp != cur->next) {
		for (i = 0; i < backup && lp != NULL; i++)
			lp = lp->prev;
		if (lp == NULL)
			lp = inlines;
	}
	cur = lp;
  }
}



/*
 * Write out into destination file
 */
static void writeoutf __F((filename, headstr),
				const char *filename AND
				const char *headstr)
{
  FILE *fp;
  struct iline_s *lp;
  size_t len;

  fp = stdout;
  if (filename != NULL && (fp = fopen(filename, "w")) == NULL) {
	fprintf(stderr, "%s: can't open output file %s\n", myprogname, filename);
	exit(1);
  }
  if (headstr != NULL) {
	fprintf(fp, headstr);
	fprintf(fp, "\n");
  }
  for (lp = inlines; lp != NULL; lp = lp->next) {
	len = strlen(lp->text);
	if (lp->text[0] == COMMENT || lp->text[len - 1] == ':')
		fprintf(fp, "%s\n", lp->text);
	else
		fprintf(fp, "\t%s\n", lp->text);
  }
  if (fp != stdout)
	  (void)fclose(fp);
}



/*
 * Print usage
 */
static void usage __F_NOARGS
{
  fprintf(stderr, "usage: %s [-f<src-file>] [-o<out-file>] [-b<backup-num>] [-h<head-str>]\n", myprogname);
  exit(1);
}



/*
 * Main program
 */
int main __F((argc, argv), int argc AND char **argv)
{
  char *srcfile = NULL;
  char *outfile = NULL;
  char *headstr = NULL;
  int backup = DEFBACKUP;
  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], "-b", 2))
		backup = atoi(&(argv[i][3]));
	else if (!strncmp(argv[i], "-f", 2))
		srcfile = &(argv[i][2]);
	else if (!strncmp(argv[i], "-o", 2))
		outfile = &(argv[i][2]);
	else if (!strncmp(argv[i], "-h", 2))
		headstr = &(argv[i][2]);
	else if (!strncmp(argv[i], "-a", 2))
		no_asm++;
	else
		usage();

  /* Read source file and optimze it with every ruleset */
  readinfile(srcfile);
  for (current_set = 0 ; current_set < rulesets; current_set++)
	optimize(backup);
  writeoutf(outfile, headstr);

  return(0);
}

