/*  CB3.C - C Beautifier - Jim Kyle (76703,762) - 05/06/86
 *      not mainstream rules, but I like 'em!!
 */

/* port to Apollo Domain by Jinfu Chen (72327,2434) - 03/07/87 */
/* port to Atari ST by Jinfu Chen                   - 03/08/87 */

/*#define CPM           / remove for MSDOS, include for CPM */
/*#define APOLL O       / remove for Apollo Domain version */
#define Megamax

/* ------------ *
 * header files *
 * ------------ */
#ifdef CPM
#include "libc.h"                 /* specifically, Aztec Cii V1.05c */
#include "ctype.h"
#else
#ifdef MSDOS
#include "stdio.h"                /* specifically, CI's C86 V1.33 */
#define ishex(x) isdigit(x) || ( toupper(x) < 'G' && toupper(x) >= 'A')
#else
#ifdef APOLLO             /* specifically, Domain C Rev 4.16 */
#include <ctype.h>
#include <stdio.h>
#define ishex(x) isdigit(x) || ( toupper(x) < 'G' && toupper(x) >= 'A')
#else
#ifdef Megamax             /* specifically, Megamax C Rev 1.1 */
#include <ctype.h>
#include <stdio.h>
#include <osbind.h>
#define ishex(x) isdigit(x) || ( toupper(x) < 'G' && toupper(x) >= 'A')
#endif

#endif

/* ------------------ *
 * symbolic constants *
 * ------------------ */
#define LINEW 78
#define CMNTCOL 40
#define MTAB 8
#define LIND 2
#define YES 1
#define NO 0

/* ---------------- *
 * global variables *
 * ---------------- */
char bfr [ 128 ];
FILE * inf, * otf;
int clvl = 0,
    plvl = 1,
    ilvl = 0,
    ppilv = 0,
    istk [ 64 ],
    istkp = 0,
    indf = 0,
    cflg = 0,
    dflg = 0,
    iflg = 0,
    tok = 0,
    oldtok = 0,
    mtabv = MTAB,
    lnwv = LINEW,
    lindv = LIND,
    ccv = CMNTCOL,
    nval = 0,
    ln = 1,
    col = 0;

main ( argc, argv ) int argc ;
char * argv [];
{ if ( argc == 1 || * argv [ 1 ] == '?' )/* help summary */
    { msg ( "\nCommand syntax is: CB [[<args>] [<file>]] <CR>\n" );
      msg ( "  where <args> begins with '-' and is followed by\n" );
      msg ( "\tCn - start comments at col n\n" );
      msg ( "\tIn - each indent is n spaces\n" );
      msg ( "\tNn - line number with increment n\n" );
      msg ( "\tO<file> - output to <file> (must be last)\n" );
      msg ( "\tTn - machine tabs are n cols apart\n" );
      msg ( "\tWn - line length is n cols\n" );
      msg ( "\tAny mix of args can follow the '-'; each one\n" );
      msg ( "\t    takes effect immediately.\n" );
      msg ( "  <file> is a full filespec; <CR> is RETURN or ENTER.\n" );
      msg ( "  The [ and ] indicate options; don't type them.\n\n" );
      msg ( "Default values of the settings are:\n" );
      msg ( "\tC40 I2 N0 T8 W78\n" );
      exit ( 0 );
    }
  otf = stdout;
  while ( -- argc )                     /* let parsearg() do most */
    parsearg ( *++ argv );
}

char * dmpdgt ( fn )                    /* skip over digits */
char * fn;
{ if ( isdigit ( * fn ))
    { while ( isdigit ( * fn ))
        ++ fn;
      -- fn;
    }
  return ( fn );
}

parsearg ( fn )                         /* process one arg */
char * fn;
{ if ( * fn == '-' )                    /* is an argument */
    while ( *++ fn )
      { switch ( toupper ( * fn ))
          {
          case 'C' :
            ccv = atoi ( ++ fn );       /* set comment column */
            fn = dmpdgt ( fn );
            break;

          case 'I' :
            lindv = atoi ( ++ fn );     /* set indent width */
            if ( lindv < 1 )
              lindv = 1;
            fn = dmpdgt ( fn );
            break;

          case 'N' :
            nval = atoi ( ++ fn );      /* set line number increment */
            fn = dmpdgt ( fn );
            break;

          case 'O' :
            fclose ( otf );             /* write to file */
            otf = fopen ( ++ fn, "w" );
            msg ( "Writing output to: " );
            msg ( fn );
            tocrt ( '\n' );
            lnwv = 32767;               /* never wrap to a file */
            return;

          case 'T' :
            mtabv = atoi ( ++ fn );     /* set mach tab spacing */
            if ( mtabv < 1 )
              mtabv = 1000;
            fn = dmpdgt ( fn );
            break;

          case 'W' :
            lnwv = atoi ( ++ fn );      /* set line width */
            fn = dmpdgt ( fn );
            if ( lnwv < 1 )
              lnwv = 32767;
            break;

          default :
            msg ( "Unknown arg: " );
            tocrt ( * fn );
            tocrt ( '\n' );
          }
      }
  else                                  /* treat as filename */
    {
      if ( nval )
        lnwv -= mtabv;
      dofil ( fn );
      if ( nval )
        lnwv += mtabv;
    }
}

dofil ( fn )                            /* process one file */
char * fn;
{ int loop ,
     svtok;
  if ( ! ( inf = fopen ( fn, "r" )))    /* open the file */
    { msg ( "can't open " );
      msg ( fn );
      tocrt ( '\n' );
      return;
    }
  col = clvl = oldtok = 0;              /* initialize globals */
  ln = 1;
  if ( nval )                           /* output line number */
    outf ( ln * nval );
  outb ( '/' );                         /* fake out C86 quirk */
  outs ( "*  " );
  outs ( fn );
  outs ( " as formatted by CB.C\t*" );
  outb ( '/' );
  nl ();
  for ( loop = 1; loop; oldtok = tok )  /* main loop */
    { tok = gtok ( inf );               /* get next token */
      if ( oldtok == ';'
            && tok != ' '
            && col )                    /* break after statement */
        outb ( '\n' );
      switch ( tok )                    /* special processing */
        {
        case 0 :                        /* newline or comment */
          if ( clvl && ! iflg )
            tok = oldtok;               /* ignore it outside condx */
          else
            if ( iflg || ! oldtok )
              { nl ();
                if ( iflg )
                  ind ( ilvl + 2 );
                tok = ' ';
              }
          continue;

        case ' ' :                      /* blank */
          tok = oldtok;
          continue;

        case EOF  :                     /* end of input */
          loop = 0;
          continue;

        case '{' :                      /* begin compound */
          if ( col )
            nl ();
          ind ( ilvl ++ );              /* push ilvl in */
          ++ clvl;
          outs ( bfr );
          tok = ' ';
          continue;

        case '}' :                      /* end compound */
          if ( col )
            nl ();
          if ( ilvl )
            -- ilvl;                    /* pop ilvl out for c.s. */
          ind ( ilvl );
          outs ( bfr );
          nl ();
          if ( iflg )
            iflg = 0;
          -- clvl;
          popilvl ();                   /* pop ilvl up if applicable */
          tok = ';';
          continue;

        case '(' :                      /* may be condition start */
          ++ plvl;
          if ( isalnum ( oldtok )
                || isop ( oldtok ))
            outb ( ' ' );
          outs ( bfr );
          tok = '(';
          continue;

        case ')' :                      /* may be condition end */
          -- plvl;
          if ( isalnum ( oldtok ))
            outb ( ' ' );
          outs ( bfr );
          tok = ')';
          if ( iflg && ( iflg == plvl ))
            { iflg = 0;                 /* end of condition */
              tok = ';';                /* so fake statement end */
            }
          continue;

        case ':' :                      /* may be end of label */
          outb ( ' ' );
          outs ( bfr );
          tok = ':';
          if ( cflg )                   /* case or default label */
            { tok = ';';                /* will force \n after comments */
              ++ ilvl;
              cflg = 0;
            }
          continue;

        case ';' :                      /* may be end of statement */
          ind ( ilvl );
          outs ( bfr );
          if ( iflg && ( plvl != iflg ))
            outb ( ' ' );               /* inside "for" condition */
          else                          /* not in a "for" */
            popilvl ();                 /* pop back up if ready */
          dflg = 0;
          continue;

        case ',' :                      /* list separator */
          outs ( bfr );
          if ( dflg )                   /* inside a declaration list */
            outb ( '\n' ), ind ( 2 );
          else                          /* elsewhere */
            outb ( ' ' );
          continue;

        case '#' :                      /* preprocessor flag */
/*        if ( col )                    /* shouldn't happen */
/*          outs ( bfr );       */
/*        else                          /* handle preprocessor line */
          dopp ( inf );
          continue;

        case 'A' :                      /* keyword or identifier */
          if ( indf )
            { nl ();
              tok = 'A';
              indf = 0;
            }

        case '0' :                      /* numeric string */

        case 'C' :                      /* char constant */

        case 'S' :                      /* string constant */
          svtok = tok;
          if (( tok == 'A' )
                && kwd ()
                && ( ! dflg )
                && ( col > ( ppilv + ilvl ) * lindv ))
            nl ();
          if ( oldtok && ( oldtok != ' ' ))
            outb ( ' ' );
          break;

        default :                       /* operators, etc. */
          if ( tok & 128 )
            { outb ( tok );             /* escaped chars ignored */
              tok = svtok;
              continue;
            }
          svtok = tok;
          if ( isop ( svtok ) && ! isop ( oldtok ))
            outb ( ' ' );
          break;
        }
      ind ( ilvl );
      if ( indf )                       /* push ilvl in */
        { istk [ ++ istkp ] = (( ilvl ++ ) << 8 ) | ( clvl & 255 );
          if ( iflg )
            indf = 0;
        }
      if ( isalnum ( oldtok )
            && ! ( isalnum ( svtok )
            || isop ( svtok )))
        outb ( ' ' );
      outs ( bfr );
      tok = svtok;
      if ( tok == oldtok )
        outb ( ' ' );
    }
  nl ();
  outb ( '/' );                         /* trailer comment */
  outs ( "* end of " );
  outs ( fn );
  outs ( " *" );
  outb ( '/' );
  nl ();
}

poll ()                                 /* break or pause */
#ifdef CPM
  { int c ;
    if (( c = bdos ( 6, 255 )) == 3 )     /* check for BREAK */
      exit ( 7 );
    if ( c )                              /* check for PAUSE */
      { msg ( "<<PAUSED>>" );
        while ( bdos ( 6, 255 ) != ' ' )
          ;                               /* wait for SPACE */
        msg ( "\b\b\b\b\b\b\b\b\b\b" );
      }
  }
#else
  {                                       /* empty for MSDOS */
  }
#endif

dopp ( fd )                             /* handle preprocessor lines */
FILE * fd;
{ int asmflg;
  tok = gtok ( fd );
  if ( tok == 'A' )                     /* keyword at start of line */
    { if ( ! strcmp ( bfr, "asm" ))
        { if ( col )
            nl ();
          asmflg = 0;
/*        ind ( 0 );    */
          outb ( '#' );
          outs ( bfr );
          do                            /* just echo until #endasm */
            {
              tok = getc ( fd );
              outb ( tok );
              switch ( tok )
                { case '#':
    if (asmflg == 0)
                      ++asmflg;
                    else
                      asmflg = 0;
                    break;
                  case 'e':
                    if (asmflg == 1)
                      ++asmflg;
    else
                      asmflg = 0;
    break;
                  case 'n':
                    if (asmflg == 2)
                      ++asmflg;
                    else
                      asmflg = 0;
                    break;
                  case 'd':
                    if (asmflg == 3)
                      ++asmflg;
                    else
                      asmflg = 0;
                    break;
                  case 'a':
                    if (asmflg == 4)
                      ++asmflg;
    else
                      asmflg = 0;
                    break;
                  case 's':
                    if (asmflg == 5)
                      ++asmflg;
                    else
                      asmflg = 0;
                    break;
                  case 'm':
                    if (asmflg == 6)
                      ++asmflg;
                    else
      asmflg = 0;
                    break;
                  default:
                    asmflg = 0;
                }
            }
          while ( asmflg != 7 )
            ;
        }
      else
        if ( bfr [ 0 ] == 'i' && bfr [ 1 ] == 'f' )
          { ppkw ( 0 );
            ppilv ++ ;
          }
      else
        if ( ! strcmp ( bfr, "else" ))
          ppkw ( - 1 );
      else
        if ( ! strcmp ( bfr, "endif" ))
          { -- ppilv;
            ppkw ( 0 );
          }
      else                              /* all other keywords */
        ppkw ( 0 );
    }
  else                                  /* no keyword after the '#' */
    ppkw ( 0 );
  while ( tok = gtok ( fd ))            /* process rest of line here */
    outs ( bfr );                       /* output without change */
  nl ();
  tok = 0;                              /* tell main scanner it was a newline */
}

ppkw ( n )                              /* indent preprocessor line */
int n ;
{ if ( col )                            /* force newline if needed */
    nl ();
/*  ind ( n );                          /* only to preprocessor level */
  outb ( '#' );                         /* output the '#' */
  outs ( bfr );                         /* output the keyword */
}

gtok ( fd )                             /* input scanner */
FILE * fd;
{ int c ;
  char * p;
  poll ();                              /* check for pause or break */
  p = bfr;
  * p ++ = c = esc ( fd );              /* save char in buffer */
  * p = 0;
  if ( c == '\n' )                      /* newline */
    return ( 0 );
  if ( isspace ( c ) || c == '\r' )     /* whitespace */
    return ( ' ' );
  if ( c < ' ' )                        /* any other control char */
    return ( EOF );
  if ( c == '/' )                       /* comment? */
    { if (( c = esc ( fd )) != '*' )
        { ungetc ( c, fd );             /* nope */
          c = '/';
        }
      else                              /* yep, echo it all */
        {
          if ( col )
            ind ( ccv / lindv );
          outb ( '/' );
          outb ( '*' );
          while ( 1 )
            { while (( c = esc ( fd )) != '*' )
                outb ( c );
              outb ( '*' );
              if (( c = esc ( fd )) == '/' )
{ outb ( '/' );
                  return ( oldtok != ';' ? 0 : ' ' );
                }
              else
                outb ( c );
            }
        }
    }
  if ( isalpha ( c ) || c == '_' )      /* keyword or identifier */
    { while ( c = esc ( fd ), isalnum ( c )
            || c == '_' )
        * p ++ = c;
      * p = 0;
      ungetc ( c, fd );
      return ( 'A' );
    }
  if ( isdigit ( c ))                   /* numeric constant */
    { if ( c == '0' )                   /* octal or hex */
        { c = esc ( fd );               /* check flag char */
          if ( toupper ( c ) != 'X' )   /* then was not hex */
            ungetc ( c, fd );           /* put it back */
          else
            { *
                p ++ = c;               /* save hex flag */
              while ( c = esc ( fd ), ishex ( c ))
                * p ++ = c;
              * p = 0;
              ungetc ( c, fd );
              return ( '0' );           /* and get out */
            }
        }
      while ( c = esc ( fd ), isdigit ( c ))
        * p ++ = c;
      * p = 0;
      ungetc ( c, fd );
      return ( '0' );
    }
  if ( c == '\"' )                      /* string constant */
    { while ( c = esc ( fd ), c != '\"' )
        * p ++ = c;
      * p ++ = c;
      * p = 0;
      return ( 'S' );
    }
  if ( c == '\'' )                      /* char constant */
    { while ( c = esc ( fd ), c != '\'' )
        * p ++ = c;
      * p ++ = c;
      * p = 0;
      return ( 'C' );
    }
  * p = 0;                              /* anything else */
  return ( c );
}

outs ( s )                              /* output string */
char * s;
{ if ( col + strlen ( s ) > lnwv )
    outb ( 128 + '\n' );
  while ( * s )
    outb ( * s ++ );
}

outb ( c )                              /* output byte */
char c ;
{ if ( c & 128 )                        /* ESC sequence */
    { outb ( '\\' );
      c &= 127;
    }
  switch ( c )                          /* special treatment */
    {
    case '\t' :                         /* machine tab */
      col = nxtb ( col );
      tok = ' ';
      out ( c );
      c = 0;
      break;

    case '\n' :                         /* newline */
      ln += 1;
      out ( c );
      if ( nval )                       /* line number */
        outf ( ln * nval );

    case '\r' :                         /* CR */
      c = col = 0;
      break;

    case ' ' :                          /* blank */
      tok = ' ';

    default :                           /* anything else */
      if ( c >= ' ' )                   /* printable */
        { if ( ++ col > lnwv )          /* check for wrap */
            { col = 0;
              outb ( 128 + '\n' );      /* force cont line */
            }
        }
      else                              /* control, skip it */
        c = 0;
      break;
    }
  if ( c )
    out ( c );                          /* send char out */
}

out ( c )                               /*byte output*/
char c ;
{ poll ();
  fprintf ( otf, "%c", c );
}

outf ( n )                              /*line number output*/
int n ;
{ fprintf ( otf, "%d\t", n );
}

msg ( s )                               /* output string to CRT */
char * s;
{ poll ();
  while ( * s )
    tocrt ( * s ++ );
}

tocrt ( c )                             /* output char to CRT */
char c ;
#ifdef CPM
  { if ( c == '\n' )
      bdos ( 6, 13 );
    bdos ( 6, c & 255 );                  /* use BDOS for CPM */
  }
#else
  { fputc ( c, stderr );                  /* use DOS for MSDOS */
  }
#endif

nl ()                                   /* newline */
{ outb ( '\n' );
  tok = 0;
}

ind ( n )                               /* do indenting */
int n ;
{ int svtok ;
  svtok = tok;                          /* save "tok" past the indent */
  n += ppilv;                           /* include preprocessor indent */
  n *= lindv;
  while ( nxtb ( col ) <= n )           /* machine tab if possible */
    outb ( '\t' );
  while ( col < n )                     /* then fill with spaces */
    outb ( ' ' );
  tok = svtok;                          /* restore "tok" */
}

nxtb ( n )                              /* return next tabstop */
int n ;
{ return ( n + ( mtabv - n % mtabv ));
}

int inchr = '\n';                       /* last input character */

esc ( fd )                              /* handle ESC chars */
FILE * fd;
{ if ( inchr == '\n' )                  /* if at start of line */
    { inchr = getc ( fd );
      if ( isdigit ( inchr ))
        { while ( inchr = getc ( fd ), isdigit ( inchr ))
            ;                           /* smash line numbers */
          while ( isspace ( inchr ))
            inchr = getc ( fd );        /* and trailing whitespace */
        }
    }
  else
    inchr = getc ( fd );                /* get next input char */
  if ( inchr != '\\' )
    return ( inchr );                   /* normal char */
  return ( inchr = ( getc ( fd ) | 128 ));/* ESC, flag next */
}

popilvl ()                              /* pop ilvl out */
{ while ( clvl == ( istk [ istkp ] & 255 ))
    { ilvl = 255 & ( istk [ istkp -- ] >> 8 );
      if ( istkp < 0 )
        { istkp = 0;
          return;
        }
    }
}

kwd ()                                  /* check for keyword */
{ if ( ! ( strcmp ( bfr, "break" ))
        || ! ( strcmp ( bfr, "continue" ))
        || ! ( strcmp ( bfr, "return" )))
    return ( YES );                     /* ordinary keywords */
  if ( ! ( strcmp ( bfr, "case" ))
        || ! ( strcmp ( bfr, "default" )))
    { outb ( '\n' );
      ++ cflg;
      -- ilvl;
      return ( YES );                   /* only inside switch() */
    }
  if ( ! ( strcmp ( bfr, "char" ))
        || ! ( strcmp ( bfr, "double" ))
        || ! ( strcmp ( bfr, "extern" ))
        || ! ( strcmp ( bfr, "float" ))
        || ! ( strcmp ( bfr, "int" ))
        || ! ( strcmp ( bfr, "static" )))
    { ++ dflg;
      return ( YES );                   /* declarations */
    }
  if ( ! ( strcmp ( bfr, "do" ))
        || ! ( strcmp ( bfr, "else" )))
    { ++ indf;
      return ( YES );                   /* indent but no parens */
    }
  if ( ! ( strcmp ( bfr, "for" ))
        || ! ( strcmp ( bfr, "if" ))
        || ! ( strcmp ( bfr, "switch" ))
        || ! ( strcmp ( bfr, "while" )))
    { iflg = plvl;
      ++ indf;
      return ( YES );                   /* indent with parens */
    }
  return ( NO );                        /* not a keyword */
}

isop ( t )                              /* return YES if t is operator */
int t ;
{ if (( ! oldtok ) || oldtok == ' ' )
    return ( NO );                      /* force NO if first nonblank */
  return ( index ( "+-/!*^?~=<>&|", t ) ? YES : NO );
}

/* end of CB3.C */
