/*
**   cref - C cross referencer
**
**  This program reads its standard input or one or more files
**  and prints the file plus an alphabetic list of word references
**  by line number.
**
**  To run:
**      cref [-ppointer] [-q] [-lnnn] [-wnnn] [-hheading] [-tnnn] [-?] [file ...]
**
**  Options:
**      q    - don't print normal input file listing.
**      lnnn - set page length to n instead of the default 66.
**      wnnn - set page width to n instead of the default 132.
**      hccc - set page heading to 'ccc' rather than file names.
**      tnnn - set tab spaces to n instead if the default 8.
**      ?    - display this list.
**
**  Version 2.00 - Mike Edmonds - 5/81
**  Version 3.00 - Tony Preston - 12/97
**--------------------------------------------------------------------
**
**  VERSION 2 with comment bug fixed. May 1988
**
**  Amiga port by Joel Swank 9/87
**
**  Compiled under Manx:
**  cc cref.c
**  ln cref.o -lc
**
**  Changes besides porting:
**
**  + Deleted temporary file option because the Amiga sort command
**      cannot sort files larger than memory.
**  + Added -t option
**  + Added -? option
**  + Added Usage message and error msgs.
**  + Rewrote case statments that overflowed compiler table
**  + Fixed BUG that caused end of comment to be missed. star-star-slash
**    was missed.
**  VERSION 3   19 Oct 1997
**  Amiga update by Tony Preston
**  Cleaned up all the warnings for a 100% clean compile
**  Added a pointer option to have a list of files that are processed
**  to get around the limitations of the command line.  Now, you can
**  process a virtually unlimited number of files.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <ctype.h>
#include <signal.h>
#include <exec/types.h>
#include <stat.h>
#include <math.h>
#include <time.h>
#define MAXWORD     15      /* maximum chars in word */
#define MAXPAGLINES 9999    /* maximum posible lines in page */
#define MINPAGLINES 4       /* minimum posible lines in page */
#define MAXLINWIDTH 132     /* maximum posible chars in line */
#define MINLINWIDTH MAXWORD+12  /* minimum posible chars in line */
#define MAXTABSIZE  132     /* maximum posible chars in tab */
#define MINTABSIZE  1       /* minimum posible chars in tab */
#define igncase(c)  (isupper(c)? tolower(c) : (c))
#undef FALSE
#undef TRUE
#define FALSE   0
#define TRUE    1
#ifdef  DEBUG
#define debug(a,b)  fprintf(stderr,(a),(b))
#else
#define debug(a,b)
#endif
/*
*  global structures
*/
#define LINLIST struct  _linlist
#define WRDLIST struct  _wrdlist
struct  _linlist
  {
  /* line number list node */
  long     ln_no ;      /* line number */
  LINLIST *ln_next ;    /* next element pointer (or NULL) */

  };
struct  _wrdlist
  {
  /* word list node */
  char    *wd_wd ;      /* pointer to word */
  char    *wd_lwd ;     /* pointer to word (lower case) */
  LINLIST  wd_ln ;      /* first element of line list */
  WRDLIST *wd_low ;     /* lower child */
  WRDLIST *wd_hi ;      /* higher child */

  };
/*
*  options
*/
char    *Progname ;
int  Quiet = FALSE ;    /* don't print input file listing? (-q) */
int  Maxpaglines = 66 ; /* maximum lines in page (-l) */
int  Maxlinwidth = 132 ;    /* maximum chars in print line (-w) */
int  Tabsize    = 8 ;   /* default length of tab */
/*
*  global variables
*/
char     Crefhdr[MAXLINWIDTH+1] ;/* report header */
char    *Filename ;     /* name of current input file */
char     Date[30] ;     /* current or last file modify date */
long     Hiline = 1L ;      /* current (and max.) input file line number */
int      files=0 ;      /* count of source files */
int      fileln;        /* starting line number for this file */
WRDLIST *Wdtree = NULL ;    /* ptr to root node of binary word list */
/*
*  C language reserved keywords (in pseudo random order)
*/
char    *Ckeywords[] =
  {
  "char",
  "static",
  "break",
  "#define",
  "#if",
  "default",
  "#ifdef",
  "#ifndef",
  "register",
  "void",
  "if",
  "while",
  "#line",
  "union",
  "switch",
  "#else",
  "asm",
  "do",
  "#include",
  "#undef",
  "#endif",
  "long",
  "continue",
  "float",
  "short",
  "typedef",
  "for",
  "struct",
  "case",
  "else",
  "unsigned",
  "int",
  "extern",
  "auto",
  "goto",
  "entry",
  "return",
  "double",
  "sizeof",
  0

  };
void fatal( char *ptrn, char *data1, char *data2);
void error(char *ptrn, char *data1, char *data2);
void     strjoin(char *str1, int sep, char *str2, int max);
void     mkdate(long    atime);
void     ltoc(char *fld, long lval, long len);
void     printheads(void);
void     printword(char *word, int nos);
void     creftree(WRDLIST *wdp);           /* recursively print word tree nodes */
void     cref(WRDLIST *wdtree);
WRDLIST *addword(WRDLIST *wdp, char *word, char *lword, long lineno);
void     storword(char *word, long lineno);
void     listline(char *line);
void     listchr(int chr);
void     usage(void);
void     getcmd(int argc, char *argv[] );
char * getword(FILE *filep);
/*
*  main - Store C keywords.
*     Get program options and format heading lines.
*     Get words from input file (or files) and store in tree.
*     Retrieve and print in word sequence.
*/
char pfile[80];   /* allow for up to an 79 character filename */
int  p_flag=0;    /* no pointer file by default */
int main(int argc, char *argv[])
  {
  FILE  *ipf;
  register FILE   *filep ;
  char    *word ;
  struct   stat   stbuf ;
  register cnt ;
  files  = 0;
  Progname = *argv ;      /* get options */
  getcmd(argc, argv) ;
  /*
  store C keywords
  */
  for (cnt=0 ; Ckeywords[cnt] ; cnt++) storword(Ckeywords[cnt], 0L) ;
  listchr(-2);    /* clear output line */
  /*
  read and store files
  */
  for (cnt=1 ; cnt < argc ; cnt++)
    {
    if (*argv[cnt] != '-')
      {
      files++ ;
      fileln = Hiline;
      Filename = argv[cnt] ;
      if ((filep = fopen(Filename, "r")) == NULL)
        {
        fatal("can't open %s", Filename, NULL) ;

        };
      stat(Filename, &stbuf) ;
      mkdate((long)stbuf.st_mtime) ;
      while (word = getword(filep)) storword(word, Hiline);
      fclose(filep) ;
      printf("...Processing %15s Line %6d to %6d\n",Filename,fileln, Hiline-1);

      };

    };
  if ( p_flag  )
    {
    if( ( ipf = fopen(pfile,"r") ) == NULL )
      {
      fatal("can't open %$s, the pointer file", pfile, NULL);

      };
    Filename = pfile;
    while( fgets(pfile, sizeof(pfile), ipf) )
      {
      int i;
      files++;
      fileln = Hiline;
      i = strlen(pfile);
      pfile[i-1] = '\0';
      if ((filep = fopen(pfile, "r")) == NULL)
        {
        fatal("can't open %s", pfile, NULL) ;

        };
      stat(pfile, &stbuf) ;
      mkdate((long)stbuf.st_mtime) ;
      while (word = getword(filep)) storword(word, Hiline);
      fclose(filep) ;
      printf("...Processing %15s Line %6d to %6d\n",Filename,fileln, Hiline-1);

      };
    fclose(ipf);

    };
  if (!files)         /* no files - read stdin */
    {
    if (*Crefhdr)
    Filename = Crefhdr ;
    else
    Filename = "stdin" ;
    mkdate(time( (long *)0)) ;
    while (word = getword(stdin)) storword(word, Hiline) ;

    }
  /*
  print cross reference report
  */
  cref(Wdtree) ;
  exit(0) ;

  }
/*
*  getcmd - get arguments from command line & build page headings
*/
void getcmd(int argc, char *argv[] )
  {
  register cnt ;
  debug("GETCMD(%d", argc) ;
  debug(", %s)\n", argv[0]) ;
  *Crefhdr = '\0' ;
  /* get command options */
  for (cnt=1; cnt < argc; cnt++)
    {
    if (*argv[cnt] == '-')
      {
      switch(argv[cnt][1])
        {
        case 'q':
        Quiet = TRUE ;
        break ;
        case 'l':
        Maxpaglines = atoi(&argv[cnt][2]) ;
        if (Maxpaglines < MINPAGLINES
        || Maxpaglines > MAXPAGLINES)
        fatal("Bad -l value: %s", argv[cnt], NULL) ;
        break ;
        case 'p':
        strcpy( pfile, &argv[cnt][2]);
        p_flag = 1;
        break;
        case 'w':
        Maxlinwidth = atoi(&argv[cnt][2]) ;
        if (Maxlinwidth < MINLINWIDTH
        || Maxlinwidth > MAXLINWIDTH)
        fatal("Bad -w value: %s", argv[cnt], NULL) ;
        break ;
        case 't':
        Tabsize = atoi(&argv[cnt][2]) ;
        if (Tabsize < MINTABSIZE
        || Tabsize > MAXTABSIZE)
        fatal("Bad -t value: %s", argv[cnt], NULL) ;
        break ;
        case 'h':
        strncpy(Crefhdr, &argv[cnt][2], MAXLINWIDTH) ;
        Crefhdr[MAXLINWIDTH] = '\0' ;
        break ;
        case '?':                    /* help option */
        usage();
        printf(" Options:\n");
        printf(" q    - don't print normal input file listing\n");
        printf(" lnnn - set page length to n instead of the default 66.\n");
        printf(" wnnn - set page width to n instead of the default 132.\n");
        printf(" hccc - set page heading to 'ccc' rather than file names\n");
        printf(" tnnn - set tab spacing to n instead of the default 8\n");
        printf(" pfff - use fff, as a pointer file\n");
        printf(" ?    - display this list.\n");
        exit(0);
        default:
        usage();
        exit(0);

        }

      }

    }
  /* insert file names in hdr */
  if (!*Crefhdr)
  for (cnt=1; cnt < argc; cnt++)
  if (*argv[cnt] != '-')
  strjoin(Crefhdr, ' ', argv[cnt], MAXLINWIDTH) ;

  }
void usage()
  {
  printf("usage:cref [-q] [-lnnn] [-wnnn] [-hheading] [-tnnn] [-?] [file ...]\n");

  }
/*
*  getword - read, print and return next word from file
*/
char * getword(FILE *filep)
  {
  static   char   wordbuf[MAXWORD+1] ;
  register char   *wp = wordbuf ;
  register maxw = sizeof(wordbuf) ;
  register chr ;
  int  inword=0, lastchr=0 ;
  long     slineno ;
  #define _listchr(chr)   if (!Quiet) listchr(chr)
  #define _rtrnwrd(wp)   { ungetc(chr, filep) ; *(wp) = '\0' ;   return wordbuf ;  }
  while ((chr = getc(filep)) != EOF)
    {
    /* normal char - add to current word */
    if ((chr <= 'z' && chr >= 'a') ||
    (chr <= 'Z' && chr >= 'A') ||
    chr == '_' )
      {
      if (maxw-- <= 1)
      _rtrnwrd(wp) ;
      *wp++ = chr ;
      inword++ ;
      _listchr(chr) ;

      }
    else switch (chr)
      {
      /* digit - can't be 1st char in word */
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      if (inword)
        {
        if (maxw-- <= 1)
        _rtrnwrd(wp) ;
        *wp++ = chr ;

        }
      _listchr(chr) ;
      break ;
      /* '#' - must be 1st char in word */
      case '#':
      if (inword)
      _rtrnwrd(wp) ;
      *wp++ = chr ;
      inword++ ;
      _listchr(chr) ;
      break ;
      /* newline - end current word */
      case '\n':
      if (inword)
      _rtrnwrd(wp) ;
      _listchr(chr) ;
      Hiline++ ;
      break ;
      /* comments - print & bypass */
      case '/':
      if (inword)
      _rtrnwrd(wp) ;
      _listchr(chr) ;
      slineno = Hiline ;
      if ((chr = getc(filep)) == '*')
        {
        _listchr(chr) ;
        while (chr != EOF)
          {
          chr = getc(filep) ;
          _listchr(chr) ;
          if (chr == '\n')
          Hiline++ ;
          else if (chr == '*')
            {
            restar:  /* Fix for missing end of comment bug */
            chr = getc(filep) ; /* star-star-slash was missed */
            _listchr(chr) ;
            if (chr == '\n')
            Hiline++ ;
            else if (chr == '/')
            break ; ;
            if (chr == '*') goto restar; /* JHS 5/24/88 */

            }

          }
        if (chr == EOF)
        fatal("unterminated comment at %ld in %s", (char *)slineno, Filename);

        }
      else
      ungetc(chr, filep) ;
      break ;
      /* words in quotes - print & bypass */
      case '"':
      if (inword)
      _rtrnwrd(wp) ;
      _listchr(chr) ;
      slineno = Hiline ;
      if (lastchr != '\\')
        {
        do
          {
          if (chr == '\\' && lastchr == '\\')
          lastchr = '\0' ;
          else
          lastchr = chr ;
          if ((chr = getc(filep)) == EOF)
          fatal("unterminated quote at %ld in %s", (char *)slineno, Filename) ;
          _listchr(chr) ;
          if (chr == '\n')
          Hiline++ ;

          }
        while (chr != '"' || lastchr == '\\') ;

        }
      break ;
      /* letters in quotes - print & bypass */
      case '\'':
      if (inword)
      _rtrnwrd(wp) ;
      _listchr(chr) ;
      if (isprint(chr = getc(filep)))
        {
        _listchr(chr) ;
        if (chr == '\\')
          {
          if (!isprint(chr = getc(filep)))
          goto toofar ;
          _listchr(chr) ;

          }
        if ((chr = getc(filep)) != '\'')
        goto toofar ;
        _listchr(chr) ;

        }
      else
      toofar:
      ungetc(chr, filep) ;
      break ;
      default:
      if (inword)
      _rtrnwrd(wp) ;
      _listchr(chr) ;
      break ;

      }
    lastchr = chr ;

    }
  if (inword)
  _rtrnwrd(wp) ;
  _listchr(EOF) ;
  return NULL ;

  }
/*
*  listchr - list the input files one character at a time
*/
static  Listpage = 0 ;
static  Listpline = MAXPAGLINES ;
void listchr(int chr)
  {
  static  char     linebuf[MAXLINWIDTH*2], *lineptr=linebuf ;
  static  lastchr=0, linecnt=0 ;
  int remain;
  if (chr == -2)          /* clear line buffer */
    {
    setmem(linebuf,Maxlinwidth,' ');
    return;

    }
  if (chr == EOF)         /* EOF - print final line */
    {
    *lineptr = '\0' ;
    listline(linebuf) ;
    Listpage = 0 ;
    Listpline = MAXPAGLINES ;
    lineptr = linebuf ;
    linecnt = 0 ;
    return ;

    }
  if (lineptr == linebuf)     /* new line - format line number */
    {
    ltoc(linebuf, Hiline, 6) ;
    lineptr = linebuf+6 ;
    *lineptr++ = ' ' ;
    *lineptr++ = ' ' ;
    linecnt = 8 ;

    }
  #define _lineoflo(ctr, newctr) if ((ctr) >= Maxlinwidth) { *lineptr = '\0' ; listline(linebuf) ; lineptr = &linebuf[8] ; linecnt = (newctr) ; }
  switch (chr)
    {
    /* newline - print last line */
    case '\n':
    if (lastchr != '\f')
      {
      *lineptr = '\0' ;
      listline(linebuf) ;

      }
    lineptr = linebuf ;
    linecnt = 0 ;
    break ;
    /* formfeed - print line and end page */
    case '\f':
    if (linecnt != 8)
      {
      *lineptr = '\0' ;
      listline(linebuf) ;

      }
    Listpline = MAXPAGLINES ;
    lineptr = linebuf ;
    linecnt = 0 ;
    break ;
    /* tab - skip to next tab stop */
    case '\t':
    linecnt += Tabsize ;
    remain =  linecnt % Tabsize ;
    linecnt -= remain;
    _lineoflo(linecnt, 8) ;
    lineptr += Tabsize ;
    lineptr -= remain;
    break ;
    /* backspace - print, but don't count */
    case '\b':
    *lineptr++ = chr ;
    break ;
    /* ctl-char - print as "^x" */
    case 001: case 002: case 003:
    case 004: case 005: case 006: case 007:
    case 013:
    case 015: case 016: case 017:
    case 020: case 021: case 022: case 023:
    case 024: case 025: case 026: case 027:
    case 030: case 031: case 032: case 033:
    case 034: case 035: case 036: case 037:
    _lineoflo(linecnt+=2, 10) ;
    *lineptr++ = '^' ;
    *lineptr++ = ('A'-1) + chr ;
    break ;
    default:
    if (isprint(chr))
      {
      _lineoflo(++linecnt, 9) ;
      *lineptr++ = chr ;

      }
    else        /* non-ascii chars - print as "\nnn" */
      {
      _lineoflo(linecnt+=4, 12) ;
      *lineptr++ = '\\' ;
      *lineptr++ = '0' + ((chr & 0300) >> 6) ;
      *lineptr++ = '0' + ((chr & 070) >> 3) ;
      *lineptr++ = '0' + (chr & 07) ;

      }
    break ;

    }
  lastchr = chr ;

  }
/* print a completed line from the input file */
void listline(char *line)
  {
  if (*line)
    {
    if (++Listpline >= (Maxpaglines-8))
      {
      if (files >1 || Listpage) putchar('\f') ;
      printf("\n%s %s  Page %d\n\n",
      Date, Filename, ++Listpage) ;
      Listpline = 0 ;

      }
    puts(line) ;
    listchr(-2);    /* clear line buffer */

    }

  }
/*
*  storword - store word and line # in binary word tree or word file
*/
void storword(char *word, long lineno)
  {
  char     lword[MAXWORD+1] ;
  register char   *cp1, *cp2 ;
  WRDLIST *addword() ;
  /* convert word to lower case */
  for (cp1=word, cp2=lword ; *cp2++ = igncase(*cp1) ; cp1++)
  ;
  /* store words and lineno */
  Wdtree = addword(Wdtree, word, lword, lineno) ;

  }
/*
*  addword - add word and line# to in-core word list
*/
WRDLIST *addword(WRDLIST *wdp, char *word, char *lword, long lineno)
  {
  int  comp ;
  /* insert new word into list */
  if (wdp == NULL)
    {
    register wordlen = strlen(word) + 1 ;
    wdp = (WRDLIST *)malloc((wordlen * 2) + sizeof(WRDLIST)) ;
    if (wdp == NULL)
    goto nomemory ;
    wdp->wd_wd  = (char *)wdp + sizeof(WRDLIST) ;
    wdp->wd_lwd = wdp->wd_wd + wordlen ;
    strcpy(wdp->wd_wd,  word) ;
    strcpy(wdp->wd_lwd, lword) ;
    wdp->wd_hi = wdp->wd_low = NULL ;
    wdp->wd_ln.ln_no = lineno ;
    wdp->wd_ln.ln_next = NULL ;

    }
  /* word matched in list? */
  else if (((comp = strcmp(lword, wdp->wd_lwd)) == 0)
  && ((comp = strcmp(word,  wdp->wd_wd))  == 0))
    {
    register LINLIST *lnp, **lnpp ;
    if (wdp->wd_ln.ln_no)
      {
      /* add line# to linked list */
      lnp = &wdp->wd_ln ;
      do
        {
        if (lineno == lnp->ln_no)
        return wdp ;
        lnpp = &lnp->ln_next ;

        }
      while ((lnp = *lnpp) != NULL) ;
      *lnpp = (LINLIST *)malloc(sizeof(LINLIST)) ;
      if ((lnp = *lnpp) == NULL)
      goto nomemory ;
      lnp->ln_no = lineno ;
      lnp->ln_next = NULL ;

      }

    }
  else if (comp < 0)      /* search for word in children */
  wdp->wd_low = addword(wdp->wd_low, word, lword, lineno) ;
  else
  wdp->wd_hi = addword(wdp->wd_hi, word, lword, lineno) ;
  return wdp ;
  /* not enough memory - convert to -b */
  nomemory:
  fatal("not enough memory for in-core word list", NULL, NULL) ;

  }
/*
*  cref - print cross reference report from internal word list
*/
#define MAXLNOS 2000        /* maximum line nos. for a word */
long    Linenos[MAXLNOS] ;  /* list of line numbers for a word */
void cref(WRDLIST *wdtree)
  {
  creftree(wdtree) ;

  }
void creftree(WRDLIST *wdp)           /* recursively print word tree nodes */
  {
  register LINLIST *lnp ;
  register nos ;
  if (wdp != NULL)
    {
    creftree(wdp->wd_low) ; /* print lower children */
    nos = 0 ;
    if (Linenos[0] = wdp->wd_ln.ln_no)
      {
      lnp = &wdp->wd_ln ;
      while ((lnp = lnp->ln_next) != NULL)
      if (nos < (MAXLNOS-2))
      Linenos[++nos] = lnp->ln_no ;
      printword(wdp->wd_wd, nos) ;

      }
    creftree(wdp->wd_hi) ;  /* print higher children */

    }

  }
/*
*  printword - print a word and all its line number references
*/
void printword(char *word, int nos)
  {
  static  firstime=TRUE, linecnt, maxlnos, lnosize ;
  register cnt ;
  if (firstime)
    {
    firstime = FALSE ;
    linecnt = Maxpaglines ;
    for (lnosize=1 ; Hiline ; lnosize++)
    Hiline /= 10L ;
    maxlnos = (Maxlinwidth - (MAXWORD+7)) / lnosize ;

    }
  if (linecnt >= (Maxpaglines - 8))
    {
    printheads() ;
    linecnt = 5 ;

    }
  printf("%-15s%5d  ", word, ++nos) ;
  Linenos[nos] = 0 ;
  for (nos=0, cnt=0 ; Linenos[nos] ; nos++)
    {
    if (++cnt > maxlnos)
      {
      cnt = 1 ;
      if (linecnt++ >= (Maxpaglines - 2))
        {
        printheads() ;
        linecnt = 5 ;
        printf("%-15s(cont) ", word);

        }
      else
      printf("\n%22s", " ") ;

      }
    printf("%*ld", lnosize, Linenos[nos]) ;

    }
  putchar('\n') ;
  linecnt++ ;

  }
/*
*  printheads - print page headings
*/
void printheads()
  {
  static  page=0 ;
  long    time() ;
  if (!page)
  mkdate(time( (long *)0)) ;
  putchar('\f') ;
  printf("\nCREF  Version 3.00 %s %.*s  Page %d\n\n",
  Date, (Maxlinwidth-36), Crefhdr, ++page) ;
  printf("word             refs    line numbers\n\n") ;

  }
/*
*  ltoc - store ASCII equivalent of long value in given field
*/
void ltoc(char *fld, long lval, long len)
  {
  fld += len ;
  while (len-->0)
  if (lval)
    {
    *--fld = '0' + (lval%10L) ;
    lval /= 10L ;

    }
  else
  *--fld = ' ' ;

  }
/*
*  mkdate - build time/date for use in heading lines
*/
void mkdate(long    atime)
  {
  long    mtime ;
  char    *cp;
  debug("MKDATE(%ld)\n", atime) ;
  mtime = atime ;
  cp = ctime(&mtime) ;
  *(cp+24) = ' ' ;        /* clear newline */
  strcpy(cp+16, cp+19) ;      /* shift over seconds */
  strcpy(Date, cp+4) ;

  }
/*
*  strjoin - join "str1" to "str2" (separated by "sep")
*  Truncate if necessary to "max" chars.
*/
void strjoin(char *str1, int sep, char *str2, int max)
  {
  if (*str2)
    {
    if (*str1)
      {
      while (*str1++)
      if (--max <= 0)
      goto oflo ;
      max--, str1-- ;
      *str1++ = sep ;

      }
    while (*str1++ = *str2++)
    if (--max <= 0)
    goto oflo ;

    }
  return ;
  oflo:
  *--str1 = '\0' ;
  return ;

  }
/*
*  error - print standard error msg
*/
void error(char *ptrn, char *data1, char *data2)
  {
  fprintf(stderr, "%s: ", Progname) ;
  fprintf(stderr, ptrn, data1, data2) ;
  putc('\n', stderr) ;

  }
/*
*  fatal - print standard error msg and halt process
*/
void fatal( char *ptrn, char *data1, char *data2)
  {
  error(ptrn, data1, data2) ;
  exit(1);

  }
