/*  checks.c -- main source file for check register program                  */

/*  copyright (c) 1986 by Jim Woolley and WoolleyWare, San Jose, CA          */

/*  vers. 1.0, 12/85 thru 5/86
 *
 *  vers. 1.1, 07/86
 *  corrected startup() when line = 10
 *  updated *copyright
 *
 *  vers. 1.2, 07/86
 *  updated *copyright
 *  main(): initialize _Leadin, _Savech, _Funkey, _Newkey, and _Keymap
 *  startup(): read _Leadin, _Funkey, _Newkey, and _Keymap
 *  moved createrr(), writerr(), and baddisk() to checkc.c
 *  integrated CLEARS using #ifdef
 */

/*  this file contains:
 *      main()
 *      startup()
 *      getval( q)
 *      getentry()
 *      getinfo()
 *      getyesno( def)
 *      struct nlist *install( name, def)
 *      struct nlist *lookup( s)
 *      hash( s)
 *      char *strsave( s)
 *      compdate( e1, e2)
 *      datecomp( d1, d2)
 *      comppayee( e1, e2)
 *      compcateg( e1, e2)
 *      categcomp( e1, e2)
 *      compamount( e1, e2)
 *      compbbf( e1, e2)
 *      compabrev( p1, p2)
 *      isebbf( e)
 *      isibbf( i)
 *      char *index( s, c)
 *      char *skipspace( s)
 *      typcat( f, t)
 *      openerr()
 *      readerr()
 */

#include    "a:checks.h"

main( argc, argv)                       /*  check register program           */
int argc;
char *argv[];
{
    char *p, *copyright, *allrights;
    int i, j, length;

#ifdef  CLEARS
    copyright = "Check Register CLEARS Program, v.1.2 (c) 1986 by WoolleyWare";
#else
    copyright = "Check Register Program, v.1.2 (c) 1986 by WoolleyWare";
#endif          /*  copyright must be < (COLS - FNAMSIZE - 3) chars          */
    allrights = "All Rights Reserved";

    _Outdev = CONOUT;                   /*  direct putchar() to screen       */
    _Leadin = _Savech[ 0] = _Savech[ 1] = _Lastch = 0;
    for ( i = 0; i < 10; ++i)
        for ( j = 0; j < 2; ++j)
            _Funkey[ i][ j] = _Newkey[ i][ j] = 0;
    for ( i = 0; i < 32; ++i)
        _Keymap[ i] = i;                /*  no CTRL key translation          */
    Speed = 5;                          /*  initial Speed for ^QW and ^QZ    */
    Oldfield = -1;                      /*  initialize other globals         */
    Modified = Printing = Ctrlyundo = FALSE;
    strcpy( Title, copyright);
#ifdef  CLEARS
    Usechex = USECHEX;
    Ftoc[ CATFIELD] = 52;               /*  locate cursor for Field f        */
    Ftoc[ CLRFIELD] = 67;               /*  at column c                      */
#else
    Savrecno = -1;
    Today.yy = 0;
    for ( i = 0; i < HASHSIZE; ++i)
        Hashtab[ i] = 0;
    Ftoc[ MMFIELD] = 1;                 /*  Ftoc[ f] = c                     */
    Ftoc[ DDFIELD] = 4;                 /*  locates cursor for Field f       */
    Ftoc[ YYFIELD] = 7;                 /*  at column c, where c = 0         */
    Ftoc[ PAYFIELD] = 9;                /*  corresponds to left edge         */
    Ftoc[ CATFIELD] = 52;               /*  of screen                        */
    Ftoc[ AMTFIELD] = 61;
    Ftoc[ DEPFIELD] = 63;
    Ftoc[ CLRFIELD] = 67;
#endif

    if ( argc > 1)                      /*  get Filename root                */
        p = argv[ 1];
    else p = DEFNAM;
    length = strlen( p);
    if ( !index( p, ':'))               /*  if no drive designated           */
    {
        Filename[ 0] = 'A' + defdsk();
        Filename[ 1] = ':';
        i = 2;
    }
    else                                /*  drive was designated             */
    {
        if ( length == 2)               /*  if just d: without filename      */
        {
            strcpy( Filename, p);
            p = DEFNAM;                 /*  use default                      */
            i = 2;
        }
        else i = 0;
    }
    if ( length > ( FNAMSIZE - 5 - i))  /*  should be FALSE if p = DEFNAM    */
        *( p + FNAMSIZE - 5 - i) = '\0';
    strcpy(( Filename + i), p);
    if ( !( p = index( Filename, '.'))) /*  add dot if none                  */
        *( p = Filename + strlen( Filename)) = '.';
    *( p + 1) = '\0';                   /*  truncate after dot               */

    startup();
    getentry();                         /*  read entry data                  */
    disheading();                       /*  initialize display               */
    Recno = Maxentry + ( 1 - PAGE);     /*  initialize gobottom()            */
    First = Recno - PAGE;
    Last = First - 1;
    Field = 0;
    gobottom();
    getinfo();                          /*  read title, abrev, auto entries  */
    control();                          /*  never returns                    */
}

startup()                               /*  startup check register program   */
{
    char line, *p, *q, keymap;
    char s[ MAXLINE], f[ FNAMSIZE], buf[ BUFSIZ], *fgets();
    int i;

    strcpy( f, DEFNAM);
    typcat( f, SCRTYP);
    if ( fopen( f, buf) == ERROR)
        abort( "Cannot open ", f);      /*  never returns                    */
    for ( line = 6; line; --line)       /*  skip 6 lines                     */
        if ( !fgets( s, buf))
            readerr( f);                /*  never returns                    */
    for ( line = 0; line < 11; ++line)  /*  get cursor/screen controls       */
    {
        if ( !fgets(( q = s), buf))
            readerr( f);                /*  never returns                    */
        switch ( line)
        {
        case 0:
            p = Clead1;
            break;
        case 1:
            p = Clead2;
            break;
        case 2:
            p = Ctrail;
            break;
        case 3:
            Cb4flg = getval( &q);
            Linoff = getval( &q);
            Coloff = getval( &q);
            if ( Ascur = getval( &q))   /*  Ascur must be 0, 2, or 3         */
                Ascur = min( 3, max( 2, Ascur));
            continue;                   /*  next line                        */
        case 4:
            p = Eraeol;
            break;
        case 5:
            p = Lindel;
            break;
        case 6:
            p = Linins;
            break;
        case 7:
            p = Ivon;
            break;
        case 8:
            p = Ivoff;
            break;
        case 9:
            p = Trmini;
            break;
        case 10:
            Dloop = (( DLOOP/10)*max( 1, min( 1000, getval( &q))))/10;
            Inserton = getval( &q);
            _Leadin = getval( &q);
            keymap = getval( &q);
            continue;                   /*  next line                        */
        default:
            continue;                   /*  next line                        */
        }                               /*  note that 1st i may be negative  */
        for ( *p++ = i = getval( &q); i > 0; --i)
            *p++ = getval( &q);         /*  -1 < line < 3 or 3 < line < 10   */
    }
    if ( _Leadin)                       /*  if _Funkey and _Newmap present   */
    {
        for (( line = 2, p = _Funkey[ 0]); line; ( --line, p = _Newkey[ 0]))
        {                               /*  read _Funkey first, then _Newkey */
            if ( !fgets(( q = s), buf))
                readerr( f);
            for ( i = 20; i; --i)
                *p++ = getval( &q);
        }
    }
    if ( keymap)                        /*  if CTRL key map present          */
    {
        p = _Keymap;
        for ( line = 2; line; --line)
        {
            if ( !fgets(( q = s), buf))
                readerr( f);
            for ( i = 16; i; --i)       /*  read 16 per line                 */
                *p++ = getval( &q);
        }
        for ( i = 1; i < 32; ++i)       /*  except for map from NULL         */
            if ( !( _Keymap[ i]))       /*  do not allow map to NULL         */
                _Keymap[ i] = i;        /*  instead, map to self             */
    }
    if ( fgets( s, buf))                /*  if more lines                    */
    {
        clrscr();
        while (( i = getc( buf)) != CPMEOF && i != ERROR)
            putchar( i - 1);
        if ( getchar() == CTRLC)
            exit();                     /*  never returns                    */
    }
    fclose( buf);
    clrscr();
}

/*  getval( q) returns next int value from string of decimal numbers separated
 *  by white space; string must be pointed to by *q; each decimal number in
 *  string may be headed by white space with optional minus sign followed by
 *  consecutive decimal digits; first non-digit terminates the scan; zero is
 *  returned if no legal value is found; *q will be updated to point to first
 *  white space char following the current decimal number; *q will not point
 *  beyond end of string
 *
 *  sample calling program segment:
 *
 *      char *p;                        string pointer
 *      p = " 1   -32 123   9";         point to typical space separated string
 *      printf( "%d", getval( &p));     pass pointer to string pointer
 */

getval( q)                              /*  return int value and update *q   */
char **q;                               /*  pointer to string pointer        */
{
    int i;

    i = atoi( *q = skipspace( *q));
    while ( !isspace( **q) && **q)
        ++( *q);
    return ( i);
}

getentry()                              /*  get check register entries       */
{
    char *p, buf[ BUFSIZ];
    int g, imax;

#ifdef  CLEARS
    typcat( Filename, CLRTYP);
    if ( fopen( Filename, buf) == ERROR)
        abort( "Cannot open ", Filename);
#else
    typcat( Filename, DATTYP);
    if ( fopen( Filename, buf) == ERROR)
    {
        openerr( Filename);
        Maxentry = -1;                  /*  initialize                       */
        Memory.dollar = Memory.cent = 0;
        return;
    }
#endif
    if (( g = getw( buf)) == ERROR)
        readerr( Filename);             /*  never returns                    */
    imax = RECSIZ*g;
    Maxentry = g - 1;
    Memory.dollar = getw( buf);         /*  assume no read error             */
    Memory.cent = getw( buf);
    p = Entry;
    while ( imax--)
    {
        if (( g = getc( buf)) == ERROR)
            readerr( Filename);         /*  never returns                    */
        *p++ = g;
    }
    fclose( buf);
    for ( g = 0; g <= Maxentry; ++g)
        newbalance( g);
}

getinfo()                               /*  get title, abrev, and auto trans */
{
#ifdef  CLEARS
    char *p, new, next;
    char *key[ 3], buf[ BUFSIZ], s[ MAXLINE], *fgets();
    int i;
    struct record *e;
#else
    char *p, *q, *amsg, c, new, next, def, savmodified;
    char *key[ 3], buf[ BUFSIZ], s[ MAXLINE], *fgets();
    char adate, adeposit, acategory;
    int i, delta, count, adollar, acent, compdate();
    struct record *e;
    struct calendar maxdate;

    amsg = "Installing abreviations ... ";
#endif

    key[ 0] = "TITLE";                  /*  initialize                       */
    key[ 1] = "ABREV";
    key[ 2] = "AUTOM";
    typcat( Filename, INFTYP);
    if ( fopen( Filename, buf) == ERROR)
    {
        openerr( Filename);
        return;
    }
    next = 0;
    while ( fgets( s, buf))             /*  read one line at a time          */
    {
        s[ strlen( s) - 1] = '\0';      /*  truncate Abc\n\0 at \n           */
        if ( p = index( s, '|'))        /*  skip comment starting with |     */
            *p = '\0';
        if ( s[ 0] == '\0')
            continue;
        for ( new = 3; new; --new)      /*  look for keyword                 */
        {
            p = key[ new - 1];
            for ( i = 0; i < 5;  ++i)
                if ( toupper( s[ i]) != *p++)
                    break;
            if ( i == 5)
            {
#ifdef  CLEARS
                next = new;
#else
                if (( next = new) == 2)
                    prompt( amsg);
#endif
                break;                  /*  break for loop on new            */
            }
        }
        if ( new)                       /*  if keyword found                 */
            continue;                   /*      get next line from file      */
        switch ( next)
        {
#ifndef CLEARS  /*  CLEARS only recognizes TITLE  */
        case 2:                         /*  keyword was ABREV                */
            if ( strlen( p = s) < 3)
            {
                prompt( "Invalid abreviation ");
                goto error;
            }
            while ( p < ( s + 3))       /*  make abreviation upper case      */
            {
                *p = toupper( *p);
                ++p;
            }
            *p++ = '\0';                /*  end abreviation                  */
            p = skipspace( p);          /*  locate full text                 */
            if ( strlen( p) > ( PAYEESIZE - 1))
                *( p + PAYEESIZE - 1) = '\0';
            if ( !install( s, p))
            {
                prompt( "Could not install abreviation ");
                goto error;
            }
            break;                      /*  break switch on next             */
error:      puts( s);                   /*  complete error message           */
            waitesc();
            prompt( amsg);
            break;                      /*  break switch on next             */
        case 3:                         /*  keyword was AUTOM                */
            if ( Maxentry == ( ENTRYSIZE - 1))
            {
                prompt( "Number of entries is maximum allowed");
                waitesc();
                next = 0;
                break;                  /*  break switch on next             */
            }
            if ( !Today.yy)             /*  initialize maxdate once          */
                maxdate.mm = maxdate.dd = maxdate.yy = 0;
            ++Maxentry;                 /*  create temporary entry           */
            ++Last;
            for ( i = 0; i < Maxentry; ++i)
                datemax( &maxdate, &( Entry[ i].date));
            cursorput( Recno, 0);       /*  Recno and Maxentry are same      */
            putdate( &maxdate);
            e = &Entry[ Recno];         /*  setup to use eddate              */
            e->date.mm = maxdate.mm;
            e->date.dd = maxdate.dd;
            e->date.yy = maxdate.yy;
            count = Ftoc[ YYFIELD] + 4;
            savmodified = Modified;
            FOREVER
            {
                if ( Today.yy)
                    goto query;
                cursorput( Recno, count);
                puts( p = "<<<  Enter today's date");
                Field = MMFIELD;
                while ( Field < ( YYFIELD + 1))
                {
                    putquery();
                    putcursor( Recno, Field);
                    c = eddate( getchar());
                    if ( c == ESC)
                        goto skipit;
                    switch ( c)
                    {
                        case 0:
                            break;
                        case CTRLD:  case '\r':  case CTRLF:  case '\t':
                            goright( c);
                            break;
                        case CTRLS:  case '\b':  case CTRLA:
                            if ( Field > MMFIELD)
                            {
                                goleft( c);
                                break;
                            }           /*  else fall thru                   */
                        default:
                            putchar( BEL);
                            break;
                    }                   /*  end of switch on c               */
                }                       /*  end of while loop on Field       */
query:          cursorput( Recno, count);
                clreol( count);
                prompt( "Do you wish to revise today's date (Y/N)? ");
                if ( getyesno( NO))
                    Today.yy = 0;
                else break;             /*  break FOREVER loop               */
            }                           /*  end of FOREVER loop              */
skipit:     Today.mm = e->date.mm;
            Today.dd = e->date.dd;
            Today.yy = e->date.yy;
            if ( Recno > 0)             /*  delta = last date to cur month   */
            {                           /*  limit 1 year; use 32 days/month  */
                delta = ( min( 1, ( Today.yy - maxdate.yy))*12
                        + ( Today.mm - maxdate.mm - 1))*32 + ( 32 - maxdate.dd);
            }
            else delta = 0;

/*  graphical representation of various automatic entry parameters:
 *
 *  when month of most recent entry (last) < current month (today):
 *
 *      | delta |        Today.dd        |
 *      |       |  adate |               |
 *  ----|-------|--------|---------------|-----------|---
 *     last   32|0      auto           today       32|
 *  last month  |           current month            |
 *
 *  when month of most recent entry (last) = current month (today):
 *
 *              |        Today.dd        | 
 *              | -delta | delta+Today.dd|
 *  ------------|--------|-----|---------|-----------|---
 *            32|0      last  auto     today       32|
 *  last month  |           current month            |
 */
            cursorput( Recno, 0);       /*  clear today's date               */
            clreol( 0);
            --Maxentry;                 /*  delete temporary entry           */
            --Last;
            Field = 0;
            Modified = savmodified;
            if (( delta + Today.dd) <= 0)
            {
                next = 0;
                break;                  /*  break switch on next             */
            }
            next = 4;                   /*  prepare for next line from file  */
        case 4:                         /*  interpret automatic transaction  */
            adate = atoi( p = skipspace( s));
            acategory = DEFCAT;
            if ( q = index( p, ' '))
                acategory = *( p = skipspace( q));
            if ( acategory == '-')
                acategory = DEFCAT;
            else acategory = toupper( acategory);
            adollar = acent = 0;
            if ( q = index( p, ' '))
            {
                q = skipspace( q);      /*  start of amount                  */
                while ( *q == '-')
                    ++q;                /*  ignore minus signs               */
                if ( p = index( q, '.'))
                    acent = atoi( p + 1);
                else p = index( q, ' ');
                if (( p - q) > 2)
                {
                    p -= 2;
                    acent += 100*atoi( p);
                    *p = '|';           /*  mark end of adollar              */
                    adollar = atoi( q);
                }
                else acent += 100*atoi( q);
                p = q;
            }
            adeposit = FALSE;
            if ( q = index( p, ' '))
                adeposit = ( toupper( *( p = skipspace( q))) == 'D');
            if ( q = index( p, ' '))
                p = skipspace( q);      /*  start of payee                   */
            if ( strlen( p) > ( PAYEESIZE - 1))
                *( p + PAYEESIZE - 1) = '\0';
            count = max( 0, ( delta + adate - 1)/32);
            if ( Today.dd >= adate && -delta < adate)
                ++count;                /*  count is number of entries       */
            while ( count--)            /*  skip if count is zero            */
            {
                savmodified = Modified;
                if ( !insert())         /*  if cannot insert Recno           */
                {
                    next = 0;
                    break;              /*  break while loop on count        */
                }
                e = &Entry[ Recno];
                i = Today.mm - count;
                if ( Today.dd < adate)
                    --i;
                e->date.yy = Today.yy;
                while ( i <= 0)
                {
                    i += 12;
                    --( e->date.yy);
                }
                e->date.mm = i;
                e->date.dd = adate;
                strcpy( e->payee, p);
                e->category = acategory;
                e->amount.dollar = adollar;
                e->amount.cent = acent;
                e->deposit = adeposit;
                newbalance( Recno);
                putrecord( Recno);
                def = NO;               /*  default                          */
                c = ESC;
                while ( c == ESC)
                {
                    prompt( "Do you accept this automatic entry (Y/N)? ");
                    def = !def;         /*  reverse                          */
                    putchar( def ? 'Y' : 'N');
                    putcursor( Recno, ( Field = 0));
                    c = getchar();
                }
                c = toupper( c);
                if ( c == 'N' || ( !def && c != 'Y'))
                {
                    delete();           /*  delete Recno                     */
                    Modified = savmodified;
                    Ctrlyundo = FALSE;
                }
                else godown( CTRLX);
            }                           /*  end of while loop on count       */
            break;                      /*  break switch on next             */
#endif  /*  not CLEARS  */
        case 1:                         /*  keyword was TITLE                */
            s[ COLS - FNAMSIZE - 3] = '\0';
            strcpy( Title, s);          /*  s truncated at max Title length  */
            cursorto( 0, 0);
            putscr( Ivon);
            puttitle();
            putscr( Ivoff);
            break;                      /*  break switch on next             */
        default:
            break;                      /*  break switch on next             */
        }                               /*  end of switch on next            */
    }                                   /*  end of while loop on fgets       */
    fclose( buf);
}

getyesno( def)                          /*  return YES or NO response        */
char def;                               /*  default response                 */
{
    char c;

    def = !def;                         /*  reverse default                  */
    c = ESC;
    while ( c == ESC)
    {
        def = !def;                     /*  reverse default                  */
        putchar( def ? 'Y' : 'N');      /*  display default                  */
        putchar( '\b');
        c = getchar();
    }
    c = toupper( c);
    if (( def && c != 'N') || ( !def && c != 'Y'))
        return ( def);
    def = !def;                         /*  reverse default                  */
    putchar( def ? 'Y' : 'N');          /*  display response                 */
    return ( def);
}

#ifndef CLEARS  /*  CLEARS does not use abreviations  */

struct nlist *install( name, def)       /*  install in Hashtab               */
char *name;                             /*  abrev                            */
char *def;                              /*  fullname                         */
{                                       /*  ref. K & R, p. 136               */
    int hashval;
    struct nlist *np;

    if ( !( np = lookup( name)))        /*  if not found                     */
    {
        if ( !( np = alloc( sizeof( *np))))
            return ( 0);
        if ( !( np->abrev = strsave( name)))
            return ( 0);
        hashval = hash( np->abrev);
        np->next = Hashtab[ hashval];   /*  initialized to zero              */
        Hashtab[ hashval] = np;
    }
    else if ( np->fullname)
        free( np->fullname);
    if ( !( np->fullname = strsave( def)))
        return ( 0);
    return ( np);
}

struct nlist *lookup( s)                /*  look for s in Hashtab            */
char *s;
{                                       /*  ref. K & R, p. 135               */
    struct nlist *np;

    for ( np = Hashtab[ hash( s)]; np; np = np->next)
        if ( !strcmp( s, np->abrev))
            return ( np);               /*  found                            */
    return ( 0);                        /*  not found                        */
}

hash( s)                                /*  determine hash value for s       */
char *s;
{                                       /*  ref. K & R, p. 135               */
    int hashval;

    hashval = 0;
    while ( *s)
        hashval += *s++;
    return ( hashval%HASHSIZE);
}

char *strsave( s)                       /*  save s somewhere                 */
char *s;
{                                       /*  ref. K & R, p. 103               */
    char *p;

    if ( p = alloc( strlen( s) + 1))
        strcpy( p, s);
    return ( p);
}

compabrev( p1, p2)                      /*  return  1 if abrev at p1 > p2    */
struct nlist **p1, **p2;                /*  return -1 if abrev at p1 < p2    */
{                                       /*  else, return 0 (not possible)    */
    return ( strcmp(( *p1)->abrev, ( *p2)->abrev));
}

#endif  /*  not CLEARS  */

compdate( e1, e2)                       /*  return  1 if date of e1 > e2     */
struct record *e1, *e2;                 /*  return -1 if date of e1 < e2     */
{                                       /*  else, return strcmp on payee     */
    int test;

    if ( test = compbbf( e1, e2))       /*  sort BBF entries to top          */
        return ( test);
    if ( test = datecomp( &( e1->date), &( e2->date)))
        return ( test);
    return ( strcmp( e1->payee, e2->payee));
}

datecomp( d1, d2)                       /*  return  1 if calendar d1 > d2    */
struct calendar *d1, *d2;               /*  return -1 if calendar d1 < d2    */
{                                       /*  else, return 0                   */
    if ( d1->yy > d2->yy)
        return ( 1);
    if ( d1->yy < d2->yy)
        return ( -1);
    if ( d1->mm > d2->mm)
        return ( 1);
    if ( d1->mm < d2->mm)
        return ( -1);
    if ( d1->dd > d2->dd)
        return ( 1);
    if ( d1->dd < d2->dd)
        return ( -1);
    return ( 0);
}

comppayee( e1, e2)                      /*  return  1 if payee of e1 > e2    */
struct record *e1, *e2;                 /*  return -1 if payee of e1 < e2    */
{                                       /*  else, return compdate( e1, e2)   */
    int test;

    if ( test = compbbf( e1, e2))       /*  sort BBF entries to top          */
        return ( test);
    if ( test = strcmp( e1->payee, e2->payee))
        return ( test);
    return ( compdate( e1, e2));
}

compcateg( e1, e2)                      /*  return  1 if category of e1 > e2 */
struct record *e1, *e2;                 /*  return -1 if category of e1 < e2 */
{                                       /*  else, return compdate( e1, e2)   */
    int test;

    if ( test = compbbf( e1, e2))       /*  sort BBF entries to top          */
        return ( test);
    return ( categcomp( e1, e2));
}

categcomp( e1, e2)                      /*  return  1 if category of e1 > e2 */
struct record *e1, *e2;                 /*  return -1 if category of e1 < e2 */
{                                       /*  else, return compdate( e1, e2)   */
    if ( e1->category > e2->category)
        return ( 1);
    if ( e1->category < e2->category)
        return ( -1);
    return ( compdate( e1, e2));
}

compamount( e1, e2)                     /*  return  1 if amount of e1 > e2   */
struct record *e1, *e2;                 /*  return -1 if amount of e1 < e2   */
{                                       /*  else, return compdate( e1, e2)   */
    int test;
    struct money *m1, *m2;

    if ( test = compbbf( e1, e2))       /*  sort BBF entries to top          */
        return ( test);
    m1 = &( e1->amount);
    m2 = &( e2->amount);
    if ( m1->dollar > m2->dollar)
        return ( 1);
    if ( m1->dollar < m2->dollar)
        return ( -1);
    if ( m1->cent > m2->cent)
        return ( 1);
    if ( m1->cent < m2->cent)
        return ( -1);
    return ( compdate( e1, e2));
}

compbbf( e1, e2)                        /*  return  1 if e1 not BBF, e2 is   */
struct record *e1, *e2;                 /*  return -1 if e1 is BBF, e2 not   */
{                                       /*  return  1 if both BBF, e1 > e2   */
                                        /*  return -1 if both BBF, e1 < e2   */
                                        /*  else, return 0                   */
    if ( isebbf( e1))                    
    {
        if ( isebbf( e2))               /*  note two BBF entries cannot      */
            return ( categcomp( e1, e2));   /*  have the same category       */
        return ( -1);
    }
    if ( isebbf( e2))
        return ( 1);
    return ( 0);
}

isebbf( e)                              /*  return TRUE if e is a BBF entry  */
struct record *e;                       /*  else, return FALSE               */
{
    return ( e->category & 0x80);
}

isibbf( i)                              /*  return TRUE if Entry[ i] is BBF  */
{                                       /*  else, return FALSE               */
    return ( isebbf( &Entry[ i]));
}

char *index( s, c)                      /*  point to char c in string s      */
char *s, c;
{
    while ( *s && ( *s != c))
        ++s;
    return ( *s == c ? s : NULL);
}

char *skipspace( s)                     /*  point to next non-space in s     */
char *s;
{
    while ( *s && isspace( *s))
        ++s;
    return ( s);
}

typcat( f, t)                           /*  add filetype t to filename f     */
char *f, *t;
{
    strcpy(( index( f, '.') + 1), t);   /*  filename f MUST have a dot       */
}

openerr( f)                             /*  display file open error          */
char *f;
{
    prompt( "Could not open ");
    puts( f);
    waitkey();                          /*  may never return                 */
}

readerr( f)                             /*  display file read error          */
char *f;
{
    abort( "Error reading ", f);        /*  never returns                    */
}
