/*--- EDIT # 0263	14-MAY-1986 15:50   SYS$SYSROOT:[RVT.UTILS]CBU.C;144 */
/*--- Previous edit 	24-APR-1986 13:20   SYS$SYSROOT:[RVT.UTILS]CBU.C;142 */
/* Indent a C program:
 * Usage: CBU file1 file2 ... filen
 *	    Indent a C program source file.
 *	    If no files specified, it uses stdin/stdout.
 *	    Otherwise a new (formatted) version of each file is made.
 *	    The default file type is ".C"
 *
 *	If any errors are found, the exit status is set to EX_ERR (error).
 *
 *	Ray Van Tassle
 *	Motorola
 *	1301 E. Algonquin Rd.
 *	Room 4135
 *	Schaumburg, Ill.
 *	(312)-576-6017
 *
*/

#include <stdio.h>
#define NEED_UTIL	/* Include private library routines */
char *fgetss();
char *fgets();
#ifdef decus
#define NEED_UTIL	/* Include private library routines */
#endif

#ifdef MSDOS
#define NEED_UTIL	/* Include private library routines */
#define NO_VERSIONS
#define fputss(b,fp) (fputs(b,fp),fputs("\n",fp))
#define streq(a,b) (!strcmp(a,b))
#endif

char *skip_qs();
extern FILE *fopen();
extern char *n_substr();
extern char *substr();
extern char *index();
extern char *n_index();

#ifdef EX_ERR		/**** If a task can return exit status to the OS */
extern int __exst;		/* Exit status value - set if error */
#else
int __exst;
#define EX_ERR 2
#endif

#define TRUE 1
#define FALSE 0
#define MAXLINE 200		/* The max size of a line */


int line_num = 0;		/* Input line number */
char buf[MAXLINE] = {NULL};	/* Input/line buffer */
char file_name[256] = "STDIN";	/* The file-name */

/******* We put out one line behind. This saves it. This line might
 * possibly be modified.
*/
int prev_ind = -1;		/* It's indent (-1 means no line) */
char prev_line[MAXLINE] = {NULL};
char split_line[MAXLINE] = {NULL}; /* Implied next line (prev line split) */

FILE *ifp = NULL;			/* The input file */
FILE *ofp = NULL;			/* The output file */

/*** Set leading tab stops for indenting *****/
#define TAB_STOP  256	/* This line goes last for never tab */
#define TAB_STOP  8	/* This line goes last for normal DEC tabs */



#define LVL_COL   3		/* # columns to indent per nesting level */
#define CONT_COL  5		/* # columns to indent a continued line */


#define L_BRACE   '{'		/* Left curly brace	*/
#define R_BRACE   '}'		/* Right curly brace	*/
#define SEMI	  ';'		/* Semi-colon		*/
#define SLASH	  '/'		/* Slash		*/
#define STAR	  '*'		/* Star			*/
#define COMMA	  ','		/* Comma		*/
#define L_PAREN   '('		/* Left paren		*/
#define R_PAREN   ')'		/* Right paren		*/
#define QUOTE	  '\"'		/* Quote mark		*/
#define APOSTR	  '\''		/* Apostrophe		*/
#define BACKSLASH '\\'		/* Back-slash		*/

#define STAT_IF 	1	/* Control statement types */
#define STAT_WHILE	2	/* Control statement types */
#define STAT_FOR	3	/* Control statement types */
#define STAT_ELSE	4	/* Control statement types */

int d_flag = 0; 		/* Debug flag */
char start_cmt[] = "/*";	/* Start comment string */
char stop_cmt[]  = "*/";	/* Stop comment string */


/********************************************************/
main(argc, argv)
int argc;
char **argv;
{
   int i;

#ifdef vms
argc = redir(argc, argv);
#endif

   if (argc > 1 && streq(argv[1], "-D")) {
      argc = 1;
      ++d_flag;
   }
   if (argc <= 1) {	/* No args, do stdin/stdout */
      ifp = stdin;
      ofp = stdout;
      indent();
   }
   if (argc > 1 && *argv[1] == '-')
      usage(argv[1][1]);
   for (i = 1; i < argc; ++i) { 	/* Do each arg file */
      strcpy(file_name, argv[i]);
      if (index(file_name, '.') == NULL)	/* Assume a C file */
	 strcat(file_name, ".C");
      if ((ifp = fopen(file_name, "r")) == NULL)
	 fprintf(stderr, "CBU - Can't open input file: %s\n", file_name);
      else {
#ifdef NO_VERSIONS
	 strcpy(index(file_name, '.'), ".CBU");
#endif
	 if ((ofp = fopen(file_name, "w")) == NULL)
	    fprintf(stderr, "CBU - Can't open output file: %s\n", file_name);
	 else {
	    indent();
	    fclose(ofp);
	 }
	 fclose(ifp);
      }
   }
}



/********************************************************/
/* This routine does all the work */
indent()
{
   register char *bp;	/* Statement pointer */
   char *ep;
   register int ind;	/* Current indent amount */
   register int temp;	/* Indent amount for next line only (extra) */
   char *left_brace;	/* Left curly brace on this line */
   char *right_brace;	/* Right curly brace on this line */

   line_num = temp = ind = 0;
   prev_ind = -1;
   *split_line = NULL;
   while (*split_line != NULL || fgetss(buf, sizeof(buf) - 2, ifp)) {
      ++line_num;
      bp = buf;
      if (*split_line != NULL) {	/* Do this line instead of reading */
	 strcpy(bp, split_line);
	 *split_line = NULL;
      }
      if (d_flag) {
	 printf("\n%d. %s\n", line_num, bp);
	 printf("Ind = %d      temp = %d\n", ind, temp);
      }
      /* Don't indent a comment that starts in column 1
       * or a pre-processor line.
      */
      if (strpfx(bp, start_cmt) || *bp == '#') {
	 put_line(0, bp);
	 temp = 0;
	 continue;
      }
      /* Strip leading whitespace (except form-feed) */
      while (*bp != NULL && iswhite(*bp) && *bp != '\f')
	 ++bp;

      /* Strip trailing whitespace (except form-feed) */
      ep = bp + strlen(bp) - 1;
      while (ep >= bp && iswhite(*ep) && *ep != '\f')
	 *(ep--) = NULL;

      if (*bp == NULL ||	/* This line is now empty */
	   prev_line[strlen(prev_line) - 1] == BACKSLASH ) {
	 put_line(0, bp);	/* or ends with back-slash */
	 continue;
      }
      fix_up(bp);		/* Fix up spaces */

      /* CASE and DEFAULT undent themselves only */
      if (strpfx(bp, "case ") || strpfx(bp, "case\t") ||
	   strpfx(bp, "default:")) {
	 if (ind < LVL_COL) {
	    fprintf(stderr, "%s: Line %d -- Nest level went negative.\n",
		 file_name, line_num);
	    __exst = EX_ERR;	/* Set error return status */
	 }
	 /* Also undent the last line if it was a comment  */
	 if (prev_ind > 0 && strpfx(prev_line, start_cmt))
	    prev_ind = max(0, prev_ind - LVL_COL);
	 put_line(temp + ind - LVL_COL, bp);
	 temp = 0;
	 continue;
      }

      left_brace = n_index(bp, L_BRACE);
      right_brace = n_index(bp, R_BRACE);
      /* Check for the close of a nest level */
      if (right_brace) {	/* split " } else" apart */
	 ep = right_brace + 1;
	 while (*ep != NULL && iswhite(*ep))
	    ++ep;
	 if (strpfx(ep, "else")) {
	    strcpy(split_line, ep);		/* Split after the "}"	*/
	    *(right_brace + 1) = NULL;
	    if (left_brace > right_brace)
	       left_brace = NULL;
	 }
	 if ( !left_brace || left_brace > right_brace) {
	    ind -= LVL_COL;		/* Undent all following lines */
	    if (ind < 0) {
	       fprintf(stderr, "%s: Line %d -- Nest level went negative.\n",
		    file_name, line_num);
	       __exst = EX_ERR; /* Set error return status */
	       ind = 0;
	    }
	    put_line(temp + ind, bp);
	    temp = 0;
	    continue;
	 }
      }

      put_line(temp + ind, bp); /* Put out the line */
      if (*bp == NULL) {	/* We spanned a multi-line comment */
	 if (left_brace && !right_brace)	/* We also should indent. */
	    ind += LVL_COL;
	 continue;
      }

      if ( !strpfx(bp, start_cmt)) /* Preserve temp indent over comment */
	 temp = 0;

      /* Check for the start of a nest level */
      if (left_brace && !right_brace) {
	 ind += LVL_COL;
	 temp = 0;
	 continue;
      }

      /* See if a normal statement is complete on this line. */
      /* See if a control statement has the statement on this line */
      if (strpfx(bp, "if ("))
	 temp = chk_kwl(bp, 3, STAT_IF);
      else if (strpfx(bp, "else if ("))
	 temp = chk_kwl(bp, 8, STAT_IF);
      else if (strpfx(bp, "while ("))
	 temp = chk_kwl(bp, 6, STAT_WHILE);
      else if (strpfx(bp, "for ("))
	 temp = chk_kwl(bp, 4, STAT_FOR);
      else if (strpfx(bp, "else")) {
	 if (bp[4] == NULL)
	    temp = LVL_COL;	/* "else" is all by itself */
	 else
	    if (!isalnum(bp[4]) && bp[4] != '_')
	    temp = chk_kwl(bp, 4, STAT_ELSE);
      }
      else
	 temp = chk_cont(bp);	/* Normal statement */
   }

   ind_line(0, "");		/* Force out the last line */
   if (ind != 0) {
      fprintf(stderr, "%s: Line %d -- Nest level is %d at EOF.\n",
	   file_name, line_num, ind / LVL_COL);
      __exst = EX_ERR;	/* Set error return status */
   }
}



/********************************************************/
/* This is a normal statement. See if it is continued onto
 * the next line.
*/
int chk_cont(line)
register char *line;
{
   register char c, c1;
   int p_level; 		/* Paren level. Zero == balanced. */

   c = line[strlen(line) - 1];	/* Get last char on line */
   if (c == R_BRACE || c == SEMI)
      return(0);			/* Yup, it is complete */

   /* Find the last significant char before EOL or comment */
   c = SEMI;			/* First, salt the mine */
   p_level = 0; 		/* We start off balanced */
   while (!strpfx(line, start_cmt) && (c1 = *line++)) {
      /* Skip over a quoted string */
      if (c1 == QUOTE || c1 == APOSTR) {
	 c = QUOTE;		/* In case line ends here */
	 if ((line = skip_qs(line, c1)) == NULL)
	    return (0); 	/* No end of the quoted string--forget it */
	 continue;
      }
      if (!iswhite(c1))
	 c = c1;
      if (c1 == L_PAREN)
	 ++p_level;
      if (c1 == R_PAREN)
	 --p_level;
   }
   if (c == R_BRACE || c == SEMI)
      return(0);			/* Yup, it is complete */
   if (c == R_PAREN && p_level == 0)
      return(0);			/* Yup, it is complete */
   return(CONT_COL);			/* Nope, it is continued */
}


/********************************************************/
int chk_kwl(line, n, type)
register char *line;
int n;			/* Offset into "line" to start scanning at */
int type;		/* Control statement type--STAT_???  */
{
   char *start;
   register char c;	/* Last non-WS char outside parens */
   register char c1;	/* Current char */
   int p_level; 	/* Paren level. Zero == balanced. */
   int saw_lp;		/* If we ever saw a left-paren */
   char *lp;		/* Addr after 1st balanced paren string */

   saw_lp = p_level = 0;		/* We start off balanced */
   start = line;
   line += n;
   c = NULL;
   lp = NULL;
   while (!strpfx(line, start_cmt) && (c1 = *line++)) {
      /* Skip over a quoted string */
      if (c1 == QUOTE || c1 == APOSTR) {
	 if ((line = skip_qs(line, c1)) == NULL)
	    return (0); 	/* No end of the quoted string--forget it */
	 continue;
      }
      if (c1 == L_PAREN) {
	 ++saw_lp;
	 ++p_level;
      }
      else if (c1 == R_PAREN) {
	 if (--p_level == 0) {
	    c = c1 = NULL;	/* Final close paren */
	    if (lp == NULL)
	       lp = line;
	 }
      }
      if (p_level == 0) {
	 if ( !iswhite(c1))	/* Save last non-WS after parens */
	    c = c1;
      }
   }

   if (p_level != 0)
      return(CONT_COL); /* condition is continued */

/* The condition (expression) is complete. See if the statement is
 * also on this line, and if it is itself complete.
*/
   if (type == STAT_ELSE) {
      if (c == NULL)
	 return(LVL_COL);		/* "else" all by itself */
      else
	 lp = start + n;			/* "else" + "statement" */
   }

   if (c == NULL)		/* No statement here, it's on next line */
      return(LVL_COL);
   if (d_flag)
      printf("Statement here:%s\n", lp);
   strcpy(split_line, lp);		/* Split the line here */
   p_level = lp - start;
   prev_line[p_level] = NULL;
   return(LVL_COL);			/* It's now on the next line! */
}


/********************************************************/
/* A line is ready to be put out, with indentation. Do so,
 * and pass by all lines in a comment (if any).
*/
put_line(ind, line)
int ind;		/* Number of columns to indent */
register char *line;	/* The line to write out */
{
   register char *com;	/* Pointer to comment start */
   register char *ecom; /* Pointer to comment end */

   ind_line(ind, line); /* Write out the line, indented. */
   if ((com = n_substr(line, start_cmt)) != NULL) { /* Comment starts here */
      if ((ecom = substr(com + 2, stop_cmt)) == NULL) {
	 /* This comment continues onto more lines */
	 do_ml_cmt(col_num(line, com, ind));
	 *line = NULL;	/* Erase the line--it's been handled */
      }
      else {		/* The comment ends on this line. */
	 /* Make sure another comment hasn't started */
	 com = n_substr(com + 2, start_cmt);
	 if (com != NULL && com < ecom) {
	    fprintf(stderr, "%s: Line %d -- Nested comment.\n",
		 file_name, line_num);
	    __exst = EX_ERR;	/* Set error return status */
	 }
      }
   }
}



/*******************************************************/
/** This puts out the previous line (if any), and saves this line. */
ind_line(ind, line)
int ind;		/* Number of columns to indent */
register char *line;	/* The line to write out (no leading whitespace) */
{
   register int i;

   if (ind < 0)
      ind = 0;
   if (prev_ind >= 0) { /* Put out the (saved) previous line. */
      i = prev_ind / TAB_STOP;	/* Number of tabs */
      while (i--)
	 putc('\t', ofp);
      i = prev_ind % TAB_STOP;	/* Number of blanks */
      while (i--)
	 putc(' ', ofp);
      fputss(prev_line, ofp);	/* Then the line itself */
   }
   prev_ind = ind;		/* Save this (new) line. */
   strcpy(prev_line, line);
}



/*******************************************************/
/* Skip to the end of a multi-line comment */
do_ml_cmt(col_nbr)
int col_nbr;		/* Column number where comment starts */
{
   int start;		/* Starting line number of comment */
   register char *ecom; /* pointer to end-comment */
   register char *com;	/* pointer to start-comment */
   register char *bp;

   start = line_num;
   while (fgetss(buf, sizeof (buf) - 2, ifp)) {
      ++line_num;
      bp = buf;
      /* Strip leading whitespace */
      while (*bp != NULL && iswhite(*bp))
	 ++bp;
      if (isalnum(*bp) || (*bp == STAR && bp[1] != SLASH))
	 ind_line(col_nbr + 1, bp);	/* Write it out indented */
      else
	 ind_line(col_nbr, bp); 	/* Write it out */
      com = n_substr(buf, start_cmt);
      if ((ecom = substr(buf, stop_cmt)) != NULL) {
	 /* We hit the end of the comment */
	 /* Make sure another comment hasn't started */
	 if (com != NULL && com < ecom) {
	    fprintf(stderr, "%s: Line %d -- Nested comment. Begins on %d.\n",
		 file_name, line_num, start);
	    __exst = EX_ERR;	/* Set error return status */
	 }
	 return;
      }
      if (com != NULL) {
	 fprintf(stderr, "%s: Line %d -- Nested comment. Begins on %d.\n",
	      file_name, line_num, start);
	 __exst = EX_ERR;	/* Set error return status */
	 return;
      }
   }
   fprintf(stderr, "%s: Line %d -- EOF in comment.\n",
	file_name, start);
   __exst = EX_ERR;	/* Set error return status */
}




/********************************************************/
/* Fix up lack of a space in certain spots. */
fix_up(line)
char *line;		/* Start of the statement */
{
   register char *s1;

   /* Fix up keyword followed by paren, without an intervening blank */
   s1 = line;
   if (strpfx(s1, "if("))
      r_shift(s1 + 2);
   else if (strpfx(s1, "else if("))
      r_shift(s1 + 7);
   else if (strpfx(s1, "while("))
      r_shift(s1 + 5);
   else if (strpfx(s1, "for("))
      r_shift(s1 + 3);
   else if (strpfx(s1, "switch("))
      r_shift(s1 + 6);

   /* Fix up comma without a following space. Add the space. */
   s1 = line;
   while ((s1 = n_index(s1, COMMA)) != NULL) {
      ++s1;
      if ( !iswhite(*s1))
	 r_shift(s1);
   }

   /* Fix up "){" without a space. Add the space. */
   s1 = line;
   while ((s1 = n_substr(s1, "){")) != NULL) {
      ++s1;
      r_shift(s1);
   }
}



/*************************************************/
/* Right shift a line. Insert a blank. */
r_shift(l)
char *l;
{
   static char t[MAXLINE];

   if (*l != NULL) {
      strcpy(t, l);
      *l++ = ' ';
      strcpy(l, t);
   }
}



/*************************************************/
/* Returns column number of P in string S (zero relative).	*/
int col_num(s, p, cnum)
register char *s;	/* Start of string */
register char *p;	/* Ptr of char to find col num of */
register int cnum;		/* Col # to start from */
{
   while (s < p) {
      if (*s++ != '\t')
	 ++cnum;			/* bump col num */
      else
	 cnum = (cnum & ~07) + 8;	/* Go to next tab stop */
   }
   return (cnum);
}



/*************************************************/
/* Skip to the end of a quoted string (bypass backslash before close).
 * Input:   Addr of char after the opening quote.
 *	   The char which closes the quoted string.
 *
 * Returns: Addr of char after the end-of-string, or
 *	   NULL if the close quote isn't on this line.
*/
char *skip_qs(line, c1)
register char *line;
char c1;		/* The matching close quote char */
{
   do {
      if ((line = index(line, c1)) == NULL)
	 return (NULL); /* No end of the quoted string--forget it */

      if (*(line - 1) != BACKSLASH)	/* This is the end! */
	 break;
      if (*(line - 2) == BACKSLASH)	/* Two backslashes is same as none! */
	 break;
      ++line;	/* Keep looking for the true end-of-string */
   } while (TRUE);
   return (line + 1);
}


/*************************************************/
/* Similar to index, but will ignore the char if it
 * is in a quoted string.
*/
char *n_index(line, c)
register char *line;
register char c;
{
   register char c1;

   while (c1 = *line++) {
      if (c1 == c)
	 return (line - 1);		/* We found it. */

      /* Skip over a quoted string */
      if (c1 == QUOTE || c1 == APOSTR) {
	 if ((line = skip_qs(line, c1)) == NULL)
	    return (NULL);	/* No end of the quoted string--forget it */
      }
   }
   return (NULL);
}


/********************************************************/
/* similar to substr, but find the string only outside a quoted string */
char *n_substr(t, s)
register char *t;	/* target string to examine */
register char *s;	/* string to look for */
{

   while (t = n_index(t, *s)) { /* Look for the leading char */
      if (strpfx(t, s)) 	/* Now see if the rest match */
	 return (t);		/* Yup, success */
      ++t;			/* Nope, try again from next position */
   }
   return (NULL);
}


/*************************************************/
usage(c)
char c;
{
   puts("Usage: CBU file1 file2 ... filen");
   puts("         Indent a C program source file.");
   puts("         If no files specified, it uses stdin/stdout.");
   puts("         Otherwise a new (formatted) version of each file is made.");
   puts("         The default input file type is \".C\".");
   puts("         The default output file type is:");
   puts("               .C (on RSX)   or   .CBU (on PC)");
   if (c == '?') {
      puts("\n         Formatting is essentially K & R. Put the opening");
      puts("         brace on the same line as the if/while/etc. Put the");
      puts("         closing brace on a line by itself. Multi-line comments");
      puts("         should start with a star on each line; the last line");
      puts("         should be only \*\/. Comments starting in col 1 will");
      puts("         not be indented. Comments starting in any other column");
      puts("         will be indented along with the code.");
   }
   exit();
}

#ifdef NEED_UTIL
/************************ Not in the standard DECUS library *****/
char *fgetss(b, m, fp)
char *b;
int m;
FILE *fp;
{
   char *x;

   x = fgets(b, m, fp);
   b[strlen(b)-1] = 0;
   return(x);
}

#ifndef max
max(x, y)
int x, y;
{
   return	(((x) < (y)) ? (y) : (x));
}
#endif

#ifndef iswhite
iswhite(c)
char c;
{
   return ((c) <= ' ' || 0177 <= (c));
}
#endif

#ifndef isalnum
isalnum(c)
char c;
{
   return (isalpha(c) || isdigit(c));
}
#endif

char *substr(t, s)
register char *t;	/* target string to examine */
char *s;		/* string to look for */
{
   register int len_s;	/* length of s */
   register int len_t;	/* remaining length of target */

   len_t = strlen(t);
   len_s = strlen(s);
   for (; len_t >= len_s; len_t--, t++) {
      if (strneq(t, s, len_s))
	 return (t);
   }
   return (NULL);
}

/*********************************************************/
char *index(s1, c)
register char *s1;
register char c;
{
   while (*s1 && *s1 != c)
      s1++;
   if (*s1)
      return (s1);
   else
      return (NULL);
}

#ifndef strcat
#ifndef MSDOS
#ifdef qwer
strcat(a, b)
char *a, *b;
{
   concat(a, a, b, NULL);
}
#endif
#endif
#endif

#ifndef strneq
strneq(a, b, n)
char *a, *b;
int n;
{
   return( !strncmp(a, b, n));
}
#endif

strncmp(s1, s2, n)
register char *s1, *s2;
register int n;
{
   while (--n && *s1 == *s2)
      s1++, s2++;
   return (*s1 - *s2);
}


/**********************************************/
int strpfx(a, b)
register char *a, *b;
{
   while (*a++ == *b) { /* compare until not equal */
      if (*b++ == NULL)
	 return(TRUE);	/* They are equal up to the end of B */
   }
   if (*b == NULL)
      return(TRUE);	/* They are equal up to the end of B */
   return(FALSE);
}

#endif

