/********************************************************/
/*                                                      */
/*      ro_macr.c       macro and diversion routines    */
/*                      for ro                          */
/*                                                      */
/*      ro version 1.00                                 */
/*                                                      */
/*      Portions copyright (c) 1989 by Ted A. Campbell  */
/*              Bywater Software                        */
/*              P. O. Box 4023                          */
/*              Duke Station                            */
/*              Durham, NC  27706                       */
/*                                                      */
/*      Contains portions of ROFF4, Version 1.60        */
/*      (c) 1983, 4 by Ernest E. Bergmann               */
/*              Physics, Building #16                   */
/*              Lehigh University                       */
/*              Bethlehem, Pa. 18015                    */
/*                                                      */
/*      Contains portions of ROFF4, Version 1.61        */
/*      (c) 1985 by Konrad Kwok                         */
/*              20 3rd Street, Section M                */
/*              Fariview Park,                          */
/*              Hong Kong                               */
/*                                                      */
/*      ro and its predecessor ROFF4 are based on       */
/*      the ROFF text processor described in Kernigan   */
/*      and Plauger's now-classic text <Software Tools> */
/*                                                      */
/* Permission is hereby granted for all commercial and  */
/* non-commercial reproduction and distribution of this */
/* material provided this notice is included.           */
/*                                                      */
/********************************************************/

#include "ro.h"

/* a bug report from Usenet : the argument to strlen may be NULL,
 * giving a bus error on a SUN 3...
#define mystrlen(s) ((s) ? strlen(s) : 0)

/********************************************/
/* insert()  -- process .ds command         */
/* takes a command line in ro_curline and adds its
entry to the table */

insert()
        {
        static char name[ LSZ ];
        register int n;
        char c, *l;
        struct divfd *sptr;

         /*pass over command*/

         l = ro_curline;
         for ( c = *l; c != ' ' && c != '\n' && c != '\t'; l++ )
                {
                c = *l;
                }

         /*advance to first non-blank */

         for ( ; c == ' ' || c == '\t'; l++ )
                {
                c = *l;
                }
         if ( c == '\n' )
                {
                if ( ro_verbose == TRUE )
                        {
                        fprintf( stderr, ".ds:  no arguments \n" );
                        }
                return -1;
                }

        /* get the name of the macro into the buffer */

        n = 0;
        --l;
        do
                {
                name[ n ] = *l;
                ++l;
                ++n;
                }
        while( *l != ' ' && *l != '\t' && *l != '\n' );
        name[ n ] = '\0';               /* terminate name with \0 */

        if ( strlen( name ) > 2 )
                {
                name[ 2 ] = '\0';
                if ( ro_verbose == TRUE )
                        {
                        fprintf( stderr, ".ds:  string name over two characters, shortened to <%s>. \n",
                                name );
                        }
                }

         /*advance to first non-blank */

         c = *l;
         for ( ; c == ' ' || c == '\t'; l++ )
                {
                c = *l;
                }
         if ( c == '\n' )
                {
                if ( ro_verbose == TRUE )
                        {
                        fprintf( stderr, ".ds:  no string argument \n" );
                        }
                return -1;
                }

        /* now get the string itself into the tbuf buffer */

        if ( c == '\"' )
                {
                n = 0;
                do
                        {
                        tbuf[ n ] = *l;
                        ++l;
                        ++n;
                        }
                while( *l != c );
                tbuf[ n ] = '\0';               /* terminate name with \0 */
                }
        else
                {
                n = 0;
                do
                        {
                        tbuf[ n ] = *l;
                        ++l;
                        ++n;
                        }
                while( *l != ' ' && *l != '\t' && *l != '\n' );
                tbuf[ n ] = '\0';               /* terminate name with \0 */
                }

        /* see if it is already defined   */

        if( ( sptr = find2( name, slink )) != NULL )
                {
                if ( ro_verbose == TRUE )
                        {
                        fprintf( stderr, "%cWarning: <%s> was defined to be <%s>\n",
                                BELL, name, sptr->mstr );
                        fprintf( stderr, "...now it is defined to be <%s>\n", tbuf );
                        }
                }

        else
                {
                if ( ( sptr = (struct divfd *) malloc( (size_t) sizeof( struct divfd ))) == NULL )
                        {
                        fprintf( stderr, "Fatal error: cannot allocate memory for insertion structure.  \n" );
                        exit ( -1 );
                        }

                /* allocation succeeded, set up links */

                sptr->prev = slink;             /* save previous link   */
                slink = sptr;                   /* reset link           */
                strcpy( sptr->nm, name );
                sptr->mstr = NULL;
                }

        /* allocate memory for the new string if it is longer
           than the current string */

        if ( mystrlen( tbuf ) > mystrlen( sptr->mstr ) )
                {
                if ( ( sptr->mstr = malloc( (size_t) strlen( tbuf ) + 1 )) == NULL )
                        {
                        fprintf( stderr, "Fatal error:  failed to allocate memory for string. \n" );
                        exit( -1 );
                        }
                }

        /* copy the string to the new memory */

        strcpy( sptr->mstr, tbuf );

        }

/****************************************/

showit()                /* displays the list of entries in the string
                        substitution table pointed to by HEAD. */
        {
        struct divfd *pw;
        fprintf( stderr, "Strings defined:\n");
        dashes();
        pw = slink;
        while( pw != NULL )
                {
                fprintf( stderr, "%s:\t<%s> \n", pw->nm, pw->mstr );
                pw = pw->prev;
                }
        dashes();
        }

/****************************************/

putback( c )    /*cf K & P, p256*/
        char c;
        {
        if ( ++ro_binp >= BACKSIZE )
                {
                fprintf( stderr, "Fatal error:  too many characters pushed back\n");
                exit( -1 );
                }
        ro_backbuf[ ro_binp ] = c;
        }

/** add by Conrad Kwok. 9th Sept.,84 ****/

pbmac( s, sl )                  /* s = pointer to macro text    */
        char s[], sl[];         /* sl = current line            */
        {
        static char *parms[10], dupln[LSZ];
        register int i;

        strcpy( dupln, sl );            /* get a copy of the current line */
        setparm( dupln, parms );
        for ( i = strlen(s)-1; i >= 0; )
                {
                if ( isdigit( s[i] ))
                        {
                        if ( i > 0 && s[ i - 1 ] == '$' )
                                {
                                pbstr( parms[ s[i--] & 0x0f ]);
                                --i;
                                }
                        else
                                {
                                putback( s[i--] );
                                }
                        }
                else
                        {
                        putback( s[i--] );
                        }
                }
        }

setparm( sl, parms)
        char *sl, *parms[];
        {
        char a,c;
        int nuparm;
        for ( c = *sl; c != ' ' && c != '\n' && c != '\t'; )
                {
                c = *++sl;
                }

        while ( c == ' ' || c == '\t' )
                {
                c = *++sl;
                }

        for ( nuparm = 0; nuparm < 10; )
                {
                if (!(isalnum(c) || (c=='+') || (c=='-')))
                        {
                        sl++;
                        }
                else
                        {
                        c = ' ';
                        }
                parms[ nuparm++ ] = sl;

                for ( a = *sl; a != c && a != '\n' && a != '\0'; )
                        {
                        if ( c == ' ' && a == '\t' )
                                {
                                break;
                                }

                        else
                                {
                                a = *++sl;
                                }
                        }
                *sl = '\0';
                c = a;
                if ( a != '\0' && a != '\n')
                        {
                        while ( c == ' ' || c == '\t')
                                {
                                c = *++sl;
                                }
                        }
                }
        }

/****************************************/

pbstr( s )              /*put back string on input; cf K&P,p257*/
        char s[LSZ];
        {
        int i;
        for ( i = strlen(s); i > 0; )
                {
                putback( s[--i] );
                }
        }

ro_expand()
        {
        char c, d, name[ 36 ];
        register int n;
        struct divfd *sptr;

        c = ro_getch();
        switch( c )
                {
                case BACKSLASH:
                        return PASSBACK;
                        break;
                case '*':               /* expand defined string */
                        d = ro_getch();
                        if ( d == '(' ) /* two-letter name */
                                {
                                name[ 0 ] = ro_getch();
                                name[ 1 ] = ro_getch();
                                name[ 2 ] = '\0';
                                }
                        else            /* one-character name */
                                {
                                name[ 0 ] = d;
                                name[ 1 ] = '\0';
                                }
                        if ( ( sptr = find2( name, slink )) == NULL )
                                {
                                if ( ro_verbose == TRUE )
                                        {
                                        fprintf( stderr, "ro:  failed to find string name <%s> \n",
                                                name );
                                        }
                                }
                        else
                                {
                                n = strlen( sptr->mstr ) - 1;
                                while( n >= 0 )
                                        {
                                        putback( sptr->mstr[ n ] );
                                        --n;
                                        }
                                }
                        break;
                case 'n':               /* expand number register */
                        d = ro_getch();
                        if ( d == '(' ) /* two-letter name */
                                {
                                name[ 0 ] = ro_getch();
                                name[ 1 ] = ro_getch();
                                name[ 2 ] = '\0';
                                }
                        else            /* one-character name */
                                {
                                name[ 0 ] = d;
                                name[ 1 ] = '\0';
                                }
                        if ( ( sptr = find2( name, rlink )) == NULL )
                                {
                                if ( ro_verbose == TRUE )
                                        {
                                        fprintf( stderr, "ro:  failed to find register name <%s> \n",
                                                name );
                                        }
                                }
                        else
                                {
                                sprintf( tbuf, "%d", sptr->val );
                                n = strlen( tbuf ) - 1;
                                while( n >= 0 )
                                        {
                                        putback( tbuf[ n ] );
                                        --n;
                                        }
                                }
                        break;
                case 'u':               /* Half-line up */
                        putback( HALFUP );
                        putback( ESCAPE );
                        break;
                case 'd':               /* Half-line down */
                        putback( HALFDOWN );
                        putback( ESCAPE );
                        break;
                case 'f':               /* switch font */
                        d = ro_getch();
                        switch( d )
                                {
                                case 'R':
                                        putback( ROMAN );
                                        putback( ESCAPE );
                                        break;
                                case 'I':
                                        putback( ITALIC );
                                        putback( ESCAPE );
                                        break;
                                case 'B':
                                        putback( BOLD );
                                        putback( ESCAPE );
                                        break;
                                default:
                                        if ( ro_verbose == TRUE )
                                                {
                                                fprintf( stderr,
                                                        "ro:  unrecognized font name \"%c\" in input stream. \n",
                                                        d );
                                                }
                                }
                        break;
                default:
                        putback( c );
                        break;
                }
        return TRUE;
        }

/****************************************/
/* takes a .de and following lines and places
the information in the table;   no macro
definition nesting permitted */

minsert()
        {
        register int n;
        static char c, *src, *dst;
        int keepon;
        struct divfd *sptr;

        if DEBUG
                {
                fprintf( stderr, "DEBUG:  enter minsert() \n" );
                }

        /* pass over command and following white space */

        for ( src = ro_curline, c = *src; (c != ' ' ) && (c!='\n') && (c!='\t'); src++ )
                {
                c = *src;
                }
        for ( ; (c == ' ') || (c == '\t'); src++ )
                {
                c = *src;
                }

        /* Check to see if there is a name for the macro */

        if ( ( c == '\n' ) || ( c == '\0' ))
                {
                if ( ro_verbose )
                        {
                        fprintf( stderr, "ERROR:  .de:  no name given. \n");
                        }
                return -1;
                }

        /* Name detected, proceed to store the name */

        else
                {
                if DEBUG
                        {
                        fprintf( stderr, "DEBUG:  macro name found; prepare to allocate memory. \n" );
                        }

                if ( ( sptr = (struct divfd *) malloc( (size_t) sizeof( struct divfd ))) == NULL )
                        {
                        fprintf( stderr, "FATAL ERROR: cannot allocate memory for macro definition structure.  \n" );
                        exit ( -1 );
                        }

                if DEBUG
                        {
                        fprintf( stderr, "DEBUG:  memory allocated for macro.\n" );
                        }

                sptr->prev = mlink;             /* save previous link   */
                mlink = sptr;                   /* set mlink to this    */
                n = 0;
                while( class( c ) == BLACK )    /* Store each character */
                        {                       /* of the macro name    */
                        sptr->nm[ n++ ] = c;
                        c = *( src++ );
                        }
                sptr->nm[ n ] = '\0';

                if DEBUG
                        {
                        fprintf( stderr, "DEBUG:  new macro name is <%s> \n",
                                sptr->nm );
                        }

                }

        /* Now read from instream until end of macro or EOF */

        dst = tbuf;
        keepon = 1;
        while( keepon == 1 )
                {
                ro_gets( ro_curline );
                src = ro_curline;

                /* Check for end of macro */

                if (( ro_curline[0] == COMMAND) && ( comtyp( ro_curline ) == EM ))
                        {
                        keepon = 0;
                        }
                else                    /* Not end; transfer to buffer  */
                        {
                        transfer( &src, &dst, (char) '\0' );
                        *(dst - 1) = '\n';      /* put a LF at the end  */
                        *dst = 0;               /* and a temp. delimiter */
                        }

                if DEBUG
                        {
                        fprintf( stderr, "DEBUG:  minsert() current line <%s> \n",
                                tbuf );
                        }

                }
        *( dst ) = '\0';                /* Terminate with \0            */

        if DEBUG
                {
                fprintf( stderr, "DEBUG:  text of new macro is <%s> \n", tbuf );
                }

        /* allocate memory for this macro string and transfer the string to
                the new location */

        if ( ( sptr->mstr = malloc( (size_t) strlen( tbuf ) + 1 )) == NULL )
                {
                fprintf( stderr, "Fatal error:  failed to allocate memory for macro string. \n" );
                exit( -1 );
                }
        strcpy( sptr->mstr, tbuf );

        }

/****************************************/

showm() /*lists macro definitions*/
        {
        struct divfd *pw;
        char *pc;
        fprintf( stderr, "MACROS DEFINED:\n");
        pw = mlink;
        while ( pw != NULL )
                {
                fprintf( stderr, "\t.%s\t<%s>\n", pw->nm, pw->mstr );
                pw = pw->prev;
                }
        dashes();
        }

/****************************************/

char *macq( line )              /*looks up name to see if it is a macro
                                definition.     If it is, returns the
                                corresponding string, else returns
                                FALSE.
                                */
        char *line;
        {
        char c, *pc, wb[ LSZ ];
        struct divfd *pstr;

        pc = wb;
        while ( class( c = *( ++line )) == BLACK )
                {
                *( pc++ ) = c;
                }
        *pc='\0';

        pstr = find2( wb, mlink );
        if ( pstr == NULL )
                {
                return NULL;
                }
        else
                {
                return ( pstr->mstr );
                }
        }

/****************************************/

struct divfd *
find2( s, link)         /* finds or doesn't find s in table
                            of substitutions pointed to by link */
        char *s;
        struct divfd *link;
        {
        struct divfd *l;
        char *pc;

        l = link;
        while ( l != NULL )
                {
                if ( !strcmp( s, l->nm ) )
                        {
                        return( l );    /* return structure pointer     */
                        }
                l = l->prev;
                }

        return( NULL );                         /* failed               */
        }

/****************************************/

