/*
 *	command.c - command input parser/processor for nroff text processor
 *
 *	adapted for atariST/TOS by Bill Rosenkranz 11/89
 *	net:	rosenkra@hall.cray.com
 *	CIS:	71460,17
 *	GENIE:	W.ROSENKRANZ
 *
 *	original author:
 *
 *	Stephen L. Browning
 *	5723 North Parker Avenue
 *	Indianapolis, Indiana 46220
 *
 *	history:
 *
 *	- Originally written in BDS C;
 *	- Adapted for standard C by W. N. Paul
 *	- Heavily hacked up to conform to "real" nroff by Bill Rosenkranz
 *      - Heavily modified by Devin Reade to avoid memory trashing bugs.
 *
 * $Id: command.c,v 1.2 1997/03/20 06:40:50 gdr Exp $
 */


#ifdef __ORCAC__
segment "command___";
#pragma noroot
#pragma optimize 79
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#ifdef __GNO__
#include <err.h>
#else
#include "unix/err.h"
#endif

#ifdef sparc
#include "unix/sunos.h"
#endif

#include "nroff.h"
#include "escape.h"
#include "macros.h"
#include "io.h"

#define iscond(x)	((x)=='>'||(x)=='<'||(x)=='=')
#define isoper(x)	((x)=='+'||(x)=='-'||(x)=='*'||(x)=='/'||(x)=='%')

static int comtyp (char *p, char *m);
static void read_if (void);
static void gettl (char *p, char *q, int *limit);
static int getval (char *p, char *p_argtyp);
static int getnumeric (char *p);
static int do_oper (int first, int oper, int second);

/*
 * comand
 *	main command processor
 */
void
comand (char *p) {
    register int	i;
    register int	ct;
    register int	val;
    register int   	indx;
    int     	spval;
    int		not_cond;
    char    	argtyp;
    char    	name[MAXLINE];
    char    	macexp[MXMLEN];
    int		tmp;
    char	       *pfs;
    char		fs[20];
    char		c;
    char	       *ps1;
    char	       *ps2;
    
    DEBUGGING(("comand: enter, p=|%s|",p));

    /*
     *   get command code
     */
    ct = comtyp (p, macexp);
    
    /*
     *   error?
     */
    if (ct == UNKNOWN) {
	warnx("unrecognized command %s", p);
	return; 	/* gdr: added in this return */
    }
    
    /*
     *   ignore comments
     */
    if (ct == COMMENT) {
	return;
    }
    
    /*
     *   do escape expansion on command line args from p into name
     */
    expesc (p, name, MAXLINE);
  
  
    /*
     *   get value of command
     */
#if 0
    val = getval (p, &argtyp);
#endif

  
    /*
     *   do the command
     */
    switch (ct) {
	/* set (&param, val, type, defval, minval, maxval) */
    case FC:
	/*
	 *   field delim/pad chars
	 *
	 *   .fc [delim] [pad]
	 */
	warnx(".fc not available");
	break;
	
    case TR:
	/*
	 *   translate
	 *
	 *   .tr ab...
	 */
	warnx(".tr not available");
	break;
    
    case AD:
	/*
	 *   adjust
	 *
	 *   .ad [mode]
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	
	switch (*p) {
	case 'l':
	    dc.adjval = ADJ_LEFT;
	    dc.juval  = YES;
	    break;
	case 'r':
	    dc.adjval = ADJ_RIGHT;
	    dc.juval  = YES;
	    break;
	case 'c':
	    dc.adjval = ADJ_CENTER;
	    dc.juval  = YES;
	    break;
	case 'b':
	case 'n':
	    dc.adjval = ADJ_BOTH;
	    dc.juval  = YES;
	    break;
	default:
	    break;
	}
	break;

    case AF:
	/*
	 *   assign format to number reg
	 *
	 *   .af R {1,a,A,i,I,0...1}
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	if (!isalpha (*p)) {
	    warn("invalid or missing number register name");
	} else {
	    /*
	     *   number register format is 1,a,A,i,I,0...1
	     *   default is 1. for 0001 format, store num dig
	     *   or'ed with 0x80, up to 8 digits.
	     */
	    indx = tolower (*p) - 'a';
	    p = skipwd (p);
	    p = skipbl (p);
	    if (*p == '1')	dc.nrfmt[indx] = '1';
	    else if (*p == 'a')	dc.nrfmt[indx] = 'a';
	    else if (*p == 'A')	dc.nrfmt[indx] = 'A';
	    else if (*p == 'i')	dc.nrfmt[indx] = 'i';
	    else if (*p == 'I')	dc.nrfmt[indx] = 'I';
	    else if (*p == '0') {
		for (i = 0; isdigit (p[i]); i++)
		  ;
		dc.nrfmt[indx] = (char) (i);
		if (dc.nrfmt[indx] <= 0)
		  dc.nrfmt[indx] = '1';
		else if (dc.nrfmt[indx] > 8)
		  {
		      dc.nrfmt[indx]  = 8;
		      dc.nrfmt[indx] |= 0x80;
		  }
		else
		  dc.nrfmt[indx] |= 0x80;
		
	    }
	    else
	      dc.nrfmt[indx] = '1';
	}
	break;
    case BD:
	/*
	 *   embolden font (IGNORED)
	 *
	 *   .bd [S] F N
	 */
	break;
    case BO:
	/*
	 *   bold face
	 *
	 *   .bo [N]
	 */
	val = getval (p, &argtyp);
	set (&dc.boval, val, argtyp, 1, 0, HUGE);
	dc.cuval = dc.ulval = 0;
	break;
    case BP:
	/*
	 *   begin page
	 *
	 *   .bp [+/-N]
	 */
	val = getval (p, &argtyp);
	if (pg.lineno > 0)
	  nroffSpace (HUGE);
	set (&pg.curpag, val, argtyp, pg.curpag + 1, -HUGE, HUGE);
	pg.newpag = pg.curpag;
	set_ireg ("%", pg.newpag, 0);
	break;
    case BR:
	/*
	 *   break (page)
	 *
	 *   .br
	 */
	robrk ();
	break;
    case BS:
	/*
	 *   backspc in output
	 *
	 *   .bs [N]
	 */
	val = getval (p, &argtyp);
	set (&dc.bsflg, val, argtyp, 1, 0, 1);
	break;
    case C2:
	/*
	 *   nobreak char
	 *
	 *   .c2 [c=']
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    dc.nobrchr = '\'';
	} else {
	    dc.nobrchr = argtyp;
	}
	break;
    case CC:
	/*
	 *   command character
	 *
	 *   .cc [c=.]
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    dc.cmdchr = '.';
	} else {
	    dc.cmdchr = argtyp;
	}
	break;
    case CE:
	/*
	 *   center
	 *
	 *   .ce [N]
	 */
	val = getval (p, &argtyp);
	robrk ();
	set (&dc.ceval, val, argtyp, 1, 0, HUGE);
	break;
    case CS:
	/*
	 *   constant space char (IGNORED)
	 *
	 *   .cs F N M
	 */
	break;
    case CU:
	/*
	 *   continuous underline
	 *
	 *   .cu [N]
	 */
	val = getval (p, &argtyp);
	set (&dc.cuval, val, argtyp, 1, 0, HUGE);
	dc.ulval = dc.boval = 0;
	break;
    case DE:
	/*
	 *   define macro
	 *
	 *   .de name [end]
	 */
	val = getval (p, &argtyp);
	ignoring = FALSE;
	defmac (p, sofile[dc.flevel]);
	break;
    case DS:
	/*
	 *   define string
	 *
	 *   .ds name string
	 */
	val = getval (p, &argtyp);
	defstr (p);
	break;
    case EC:
	/*
	 *   escape char
	 *
	 *   .ec [c=\]
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    dc.escchr = '\\';
	} else {
	    dc.escchr = argtyp;
	}
	dc.escon = YES;
	break;
    case EF:
	/*
	 *   even footer
	 *
	 *   .ef "a" "b" "c"
	 */
	val = getval (p, &argtyp);
	gettl (p, pg.efoot, &pg.eflim[0]);
	break;
    case EH:
	/*
	 *   even header
	 *
	 *   .eh "a" "b" "c"
	 */
	val = getval (p, &argtyp);
	gettl (p, pg.ehead, &pg.ehlim[0]);
	break;
    case EN:
	/*
	 *   end macro def (should not get one here...)
	 *
	 *   .en or ..
	 */
	warnx("missing .de command");
	break;
    case EO:
	/*
	 *   escape off
	 *
	 *   .eo
	 */
	dc.escon = NO;
	break;
    case EX:
	/*
	 *   exit
	 *
	 *   .ex
	 */
	if (sofile[0] != stdin) {
	    fclose (sofile[0]);
	}
	for (i = 1; i <= Nfiles; i++) {
	    if (sofile[i] != NULL_FPTR) {
		fclose (sofile[i]);
	    }
	}
	err_exit(0);
	break;
    case FI:
	/*
	 *   fill
	 *
	 *   .fi
	 */
	robrk ();
	dc.fill = YES;
	break;
    case FL:
	/*
	 *   flush NOW
	 *
	 *   .fl
	 */
	fflush (out_stream);
	break;
    case FO:
	/*
	 *   footer
	 *
	 *   .fo "a" "b" "c"
	 */
	val = getval (p, &argtyp);
	gettl (p, pg.efoot, &pg.eflim[0]);
	gettl (p, pg.ofoot, &pg.oflim[0]);
	break;
    case FT:
	/*
	 *   font change
	 *
	 *   .ft {R,I,B,S,P}
	 *
	 *   the way it's implemented here, it causes a break
	 *   rather than be environmental...
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	if (!isalpha (*p)) {
	    warnx("invalid or missing font name");
	} else {
	    pfs = &fs[0];
	    
	    fontchange (*p, pfs);
	    
	    robrk ();
	    fflush (out_stream);
	    fprintf (out_stream, "%s", pfs);
	    fflush (out_stream);
	}
	break;
    case TL:
    case HE:
	/*
	 *   header (both are currently identical. .he is -me)
	 *
	 *   .tl "a" "b" "c"
	 *   .he "a" "b" "c"
	 */
	val = getval (p, &argtyp);
	gettl (p, pg.ehead, &pg.ehlim[0]);
		gettl (p, pg.ohead, &pg.ohlim[0]);
	break;
    case IE:
	/*
	 *   if of if/else conditional
	 *
	 *   .ie condition anything
	 *   .el anything
	 *
	 *   .ie condition \{\
	 *   ...
	 *   ... \}
	 *   .el \{\
	 *   ...
	 *   ... \}
	 */
	warnx(".ie not available");
	break;
    case EL:
	/*
	 *   else of if/else conditional
	 *
	 *   .ie condition anything
	 *   .el anything
	 *
	 *   .ie condition \{\
	 *   ...
	 *   ... \}
	 *   .el \{\
	 *   ...
	 *   ... \}
	 */
	warnx(".el not available");
	break;
    case IF:
	/*
	 *   conditional
	 *
	 *   .if c command		[c=n(roff),t(roff),e(ven),o(dd)]
	 *   .if !c command
	 *   .if 's1's2' command	[s1 == s2]
	 *   .if !'s1's2' command	[s1 != s2]
	 *   .if N command		[N > 0]
	 *   .if !N command		[N <= 0]
	 *
	 *   .if cond \{\
	 *   command
	 *   ... \}
	 */
	p = skipwd (p);
	p = skipbl (p);
	not_cond = 0;
	if (*p == '!') {
	    p++;
	    not_cond = 1;
	}
	if (islower (*p) && isspace (*(p+1))) {
	    /*
	     *   single char: n=nroff,t=troff,e=evenpage,o=oddpage
	     */
	    c = *p;
	    switch (c) {
	    case 'n':		/* if nroff... (always T) */
		p = skipwd (p);
		p = skipbl (p);
		
		DEBUGGING(("comand: p=|%s|", p));
		if (*p != EOS && not_cond == 0) {
		    if (*p == '\\' && *(p+1) == '{') {
			read_if ();
		    } else {
			if (*p == dc.cmdchr) {
			    comand (p);
			} else {
			    if (*p == '\"') {
				p++;
			    }
			    if (*p == ' ') {
				robrk ();
			    }
			    text (p);
			}
		    }
		}
		break;
	    case 't':		/* if troff... (always F) */
		p = skipwd (p);
		p = skipbl (p);
		
		DEBUGGING(("comand: p=|%s|", p));
		
		if (*p != EOS && not_cond != 0) {
		    if (*p == '\\' && *(p+1) == '{') {
			read_if ();
		    } else {
			if (*p == dc.cmdchr) {
			    comand (p);
			} else {
			    if (*p == '\"') {
				p++;
			    }
			    if (*p == ' ') {
				robrk ();
			    }
			    text (p);
			}
		    }
		}
		break;
	    case 'e':		/* if even page... */
		p = skipwd (p);
		p = skipbl (p);
		
		DEBUGGING(("comand: p=|%s|", p));
		
		if (((pg.curpag % 2) == 0 && not_cond == 0)
		    ||  ((pg.curpag % 2) != 0 && not_cond != 0)) {
		    /* could be newpag, too */
		    if (*p == '\\' && *(p+1) == '{') {
			read_if ();
		    } else {
			if (*p == dc.cmdchr) {
			    comand (p);
			} else {
			    if (*p == '\"') {
				p++;
			    }
			    if (*p == ' ') {
				robrk ();
			    }
			    text (p);
			}
		    }
		}
		break;
	    case 'o':		/* if odd page... */
		p = skipwd (p);
		p = skipbl (p);
		
		DEBUGGING(("comand: p=|%s|", p));
		
		if (((pg.curpag % 2) == 1 && not_cond == 0)
		    ||  ((pg.curpag % 2) != 1 && not_cond != 0)) {
		    if (*p == '\\' && *(p+1) == '{') {
			read_if ();
		    } else {
			if (*p == dc.cmdchr) {
			    comand (p);
			} else {
			    if (*p == '\"') {
				p++;
			    }
			    if (*p == ' ') {
				robrk ();
			    }
			    text (p);
			}
		    }
		}
		break;
	    }
	} else if (*p == '\'' || *p == '/' || *p == '\"') {
	    /*
	     *   compare strings. we need to interpolate here
	     */
	    c = *p;
	    ps1 = ++p;
	    while (*p != EOS && *p != c) {
		p++;
	    }
	    *p = EOS;
	    ps2 = ++p;
	    while (*p != EOS && *p != c) {
		p++;
	    }
	    *p = EOS;
	    
	    DEBUGGING(("comand: strcmp (ps1=|%s|,ps2=|%s|)", ps1, ps2));

	    if ((!strcmp (ps1, ps2) && not_cond == 0)
		||  ( strcmp (ps1, ps2) && not_cond != 0)) {
		p++;
		p = skipbl (p);
		
		if (*p == '\\' && *(p+1) == '{') {
		    read_if ();
		} else {
		    if (*p == dc.cmdchr) {
			comand (p);
		    } else {
			if (*p == '\"') {
			    p++;
			}
			if (*p == ' ') {
			    robrk ();
			}
			text (p);
		    }
		}
	    }
	} else {
	    /*
	     *   number
	     */
	    
	    DEBUGGING(("comand: p=|%s|", p));
	    val = getnumeric (p);
	    if ((val >  0 && not_cond == 0)
		||  (val <= 0 && not_cond != 0)) {
		p = skipwd (p);
		p = skipbl (p);
		
		if (*p == '\\' && *(p+1) == '{') {
		    read_if ();
		} else {
		    if (*p == dc.cmdchr) {
			comand (p);
		    } else {
			if (*p == '\"') {
			    p++;
			}
			if (*p == ' ') {
			    robrk ();
			}
			text (p);
		    }
		}
	    }
	}
	break;
    case IG:
	/*
	 *   ignore input lines
	 *
	 *   .ig name
	 */
	val = getval (p, &argtyp);
	ignoring = TRUE;
	defmac (p, sofile[dc.flevel]);
	break;
    case IN:
	/*
	 *   indenting
	 *
	 *   .in [+/-N]
	 */
	val = getval (p, &argtyp);
	set (&dc.inval, val, argtyp, 0, 0, dc.rmval - 1);
	set_ireg (".i", dc.inval, 0);
	dc.tival = dc.inval;
	break;
    case JU:
	/*
	 *   justify
	 *
	 *   .ju
	 */
	dc.juval = YES;
	break;
    case LG:
	/*
	 *   ligature (IGNORED)
	 *
	 *   .lg [N]
	 */
	break;
    case LL:
	/*
	 *   line length
	 *
	 *   .ll [+/-N]
	 *   .rm [+/-N]
	 */
	val = getval (p, &argtyp);
	set (&dc.rmval, val, argtyp, PAGEWIDTH, dc.tival + 1, HUGE);
	set (&dc.llval, val, argtyp, PAGEWIDTH, dc.tival + 1, HUGE);
	set_ireg (".l", dc.llval, 0);
	break;
    case LS:
	/*
	 *   line spacing
	 *
	 *   .ls [+/-N=+1]
	 */
	val = getval (p, &argtyp);
	set (&dc.lsval, val, argtyp, 1, 1, HUGE);
	set_ireg (".v", dc.lsval, 0);
	break;
    case LT:
	/*
	 *   title length
	 *
	 *   .lt N
	 */
	val = getval (p, &argtyp);
	set (&dc.ltval, val, argtyp, PAGEWIDTH, 0, HUGE);
	pg.ehlim[RIGHT] = dc.ltval;
	pg.ohlim[RIGHT] = dc.ltval;
		break;
    case M1:
	/*
	 *   topmost margin
	 *
	 *   .m1 N
	 */
	val = getval (p, &argtyp);
	set (&pg.m1val, val, argtyp, 2, 0, HUGE);
	break;
    case M2:
	/*
	 *   second top margin
	 *
	 *   .m2 N
	 */
	val = getval (p, &argtyp);
	set (&pg.m2val, val, argtyp, 2, 0, HUGE);
	break;
    case M3:
	/*
	 *   1st bottom margin
	 *
	 *   .m3 N
	 */
	val = getval (p, &argtyp);
	set (&pg.m3val, val, argtyp, 2, 0, HUGE);
	pg.bottom = pg.plval - pg.m4val - pg.m3val;
	break;
    case M4:
	/*
	 *   bottom-most marg
	 *
	 *   .m4 N
	 */
	val = getval (p, &argtyp);
	set (&pg.m4val, val, argtyp, 2, 0, HUGE);
	pg.bottom = pg.plval - pg.m4val - pg.m3val;
	break;
    case MACRO:
	/*
	 *   macro expansion
	 *
	 *   (internal)
	 */
	maceval (p, macexp);
	break;
    case MC:
	/*
	 *   margin character (change bars)
	 *
	 *   .mc [c [N]]
	 *
	 *   right margin only, default 0.2i
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    mc_ing = FALSE;		/* turn off... */
	} else {
	    mc_ing   = TRUE;	/* turn on... */
	    mc_space = 2;		/* force these for now... */
	    mc_char  = argtyp;	/* single char only!!! */
	    
	    p = skipwd (p);
	    p = skipbl (p);
	    
	    val = getval (p, &argtyp);
	    set (&mc_space, val, argtyp, 2, 0, dc.llval);
	}
	break;
    case NA:
	/*
	 *   no adjust
	 *
	 *   .na
	 */
	dc.adjval = ADJ_OFF;
	dc.juval  = NO;
	break;
    case NE:
	/*
	 *   need n lines
	 *
	 *   .ne N
	 */
	val = getval (p, &argtyp);
	robrk ();
	if ((pg.bottom - pg.lineno + 1) < (val * dc.lsval)) {
	    nroffSpace (HUGE);
	}
	break;
    case NF:
	/*
	 *   no fill
	 *
	 *   .nf
	 */
	robrk ();
	dc.fill = NO;
	break;
    case NJ:
	/*
	 *   no justify
	 *
	 *   .nj
	 */
	dc.juval = NO;
	break;
    case NR:
	/*
	 *   set number reg
	 *
	 *   .nr R +/-N M
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	if (!isalpha (*p)) {
	    warn("invalid or missing number register name");
	} else {
	    /*
	     *   indx is the register, R, and val is the final
	     *   value (default = 0). getval does skipwd,skipbl
	     */
	    indx = tolower (*p) - 'a';
	    val = getval (p, &argtyp);
	    set (&dc.nr[indx], val, argtyp, 0, -INFINITE, INFINITE);
	    
	    /*
	     *   now get autoincrement M, if any (default = 1).
	     *   getval does skipwd,skipbl
	     */
	    p = skipwd (p);
	    p = skipbl (p);
	    val = getval (p, &argtyp);
	    set (&dc.nrauto[indx], val, '1', 1, -INFINITE, INFINITE);
	}
	break;
    case OF:
	/*
	 *   odd footer
	 *
	 *   .of "a" "b" "c"
	 */
	val = getval (p, &argtyp);
	gettl (p, pg.ofoot, &pg.oflim[0]);
	break;
    case OH:
	/*
	 *   odd header
	 *
	 *   .oh "a" "b" "c"
		 */
	val = getval (p, &argtyp);
	gettl (p, pg.ohead, &pg.ohlim[0]);
	break;
    case PC:
	/*
	 *   page number char
	 *
	 *   .pc [c=NULL]
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    dc.pgchr = EOS;
	} else {
	    dc.pgchr = argtyp;
	}
	break;
    case PL:
	/*
	 *   page length
	 *
	 *   .pl N
	 */
	val = getval (p, &argtyp);
	set (&pg.plval,
	     val,
	     argtyp,
	     PAGELEN,
	     pg.m1val + pg.m2val + pg.m3val + pg.m4val + 1,
	     HUGE);
	set_ireg (".p", pg.plval, 0);
	pg.bottom = pg.plval - pg.m3val - pg.m4val;
	break;
    case PM:
	/*
	 *   print macro names and sizes
	 *
	 *   .pm [t]
	 */
	val = getval (p, &argtyp);
	if (argtyp == '\r' || argtyp == '\n') {
	    printmac (0);
	} else if (argtyp == 't') {
	    printmac (1);
	} else if (argtyp == 'T') {
	    printmac (2);
	} else {
	    printmac (0);
	}
	break;
    case PN:
	/*
	 *   page number
	 *
	 *   .pn N
	 */
	val = getval (p, &argtyp);
	tmp = pg.curpag;
	set (&pg.curpag, val - 1, argtyp, tmp, -HUGE, HUGE);
	pg.newpag = pg.curpag + 1;
	set_ireg ("%", pg.newpag, 0);
	break;
    case PO:
	/*
	 *   page offset
	 *
	 *   .po N
	 */
	val = getval (p, &argtyp);
	set (&pg.offset, val, argtyp, 0, 0, HUGE);
	set_ireg (".o", pg.offset, 0);
	break;
    case PS:
	/*
	 *   point size (IGNORED)
	 *
	 *   .ps +/-N
	 */
	break;
    case RR:
	/*
	 *   unset number reg
	 *
	 *   .rr R
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	if (!isalpha (*p)) {
	    warnx("invalid or missing number register name");
	} else {
	    indx = tolower (*p) - 'a';
	    val = 0;
	    set (&dc.nr[indx], val, argtyp, 0, -HUGE, HUGE);
	}
	break;
    case SO:
	/*
	 *   source file
	 *
	 *   .so name
	 */
	val = getval (p, &argtyp);
	p = skipwd (p);
	p = skipbl (p);
	if (getwrd (p, name) == 0) {
	    break;
	}
	if (dc.flevel + 1 >= Nfiles) {
	    errx(-1, ".so commands nested too deeply");
	}
	if ((sofile[dc.flevel + 1] = fopen (name, "r")) == NULL_FPTR) {
	    errx(-1, "unable to open %s\n", name);
	}
	dc.flevel += 1;
	break;
    case SP:
	/*
	 *   space
	 *
	 *   .sp [N=1]
	 */
	val = getval (p, &argtyp);
	set (&spval, val, argtyp, 1, 0, HUGE);
	nroffSpace (spval);
	break;
    case SS:
	/*
	 *   space char size (IGNORED)
	 *
	 *   .ss N
	 */
	break;
    case TI:
	/*
	 *   temporary indent
	 *
	 *   .ti [+/-N]
	 */
	val = getval (p, &argtyp);
	robrk ();
	set (&dc.tival, val, argtyp, 0, 0, dc.rmval);
	break;
    case UL:
	/*
	 *   underline
	 *
	 *   .ul [N]
	 */
	val = getval (p, &argtyp);
	set (&dc.ulval, val, argtyp, 0, 1, HUGE);
	dc.cuval = dc.boval = 0;
	break;
    }
}


/*
 * comtyp
 *	decodes nro command and returns its associated value.
 *	ptr "p" is incremented (and returned)
 */
static int 
comtyp (char *p, char *m) {
    register char	c1;
    register char	c2;
    char	       *s;
    char    	macnam[MNLEN];
    int result;
    
    /*
     *   quick check: if null, ignore
     */
    if (*p == EOS) {
	return (COMMENT);
    }
    
    /*
     *   skip past dot and any whitespace
     */
    p++;
    while (*p && (*p == ' ' || *p == '\t')) {
	p++;
    }
    if (*p == '\0') {
	return (COMMENT);
    }

    /* 
     *   First check to see if the command is a macro. If it is,
     *   truncate to two characters and return expansion in m
     *   (i.e. the text of the macro). Note that upper and lower
     *   case characters are handled differently.
     */
    getwrd (p, macnam);
    macnam[2] = EOS;
    if ((s = getmac (macnam)) != NULL_CPTR) {
	strcpy (m, s);
	return (MACRO);
    }
    c1 = *p++;
    c2 = *p;
    result = UNKNOWN;

    switch (c1) {
    case '\\':
	switch (c2) {
	case '"':  result = COMMENT; break;
	}
	break;
    case 'a':
	switch (c2) {
	case 'd':  result = AD;  break;
	case 'f':  result = AF;  break;
	}
	break;
    case 'b':
	switch (c2) {
	case 'd':  result = BD;  break;
	case 'o':  result = BO;  break;
	case 'p':  result = BP;  break;
	case 'r':  result = BR;  break;
	case 's':  result = BS;  break;
	}
	break;
    case 'c':
	switch (c2) {
	case '2':  result = C2;  break;
	case 'c':  result = CC;  break;
	case 'e':  result = CE;  break;
	case 's':  result = CS;  break;
	case 'u':  result = CU;  break;
	}
	break;
    case 'd':
	switch (c2) {
	case 'e':  result = DE;  break;
	case 's':  result = DS;  break;
	}
	break;
    case 'e':
	switch (c2) {
	case 'c':  result = EC;  break;
	case 'f':  result = EF;  break;
	case 'h':  result = EH;  break;
#if 0
	case 'l':  result = EL;  break;
#endif
	case 'n':  result = EN;  break;
	case 'o':  result = EO;  break;
	case 'x':  result = EX;  break;
	}
	break;
    case 'f':
	switch (c2) {
	case 'c':  result = FC;  break;
	case 'i':  result = FI;  break;
	case 'l':  result = FL;  break;
	case 'o':  result = FO;  break;
	case 't':  result = FT;  break;
	}
	break;
    case 'h':
	switch (c2) {
	case 'e':  result = HE;  break;
	}
	break;
    case 'i':
	switch (c2) {
	case 'f':  result = IF;  break;
#if 0
	case 'e':  result = IE;  break;
#endif
	case 'g':  result = IG;  break;
	case 'n':  result = IN;  break;
	}
	break;
    case 'j':
	switch (c2) {
	case 'u':  result = JU;  break;
	}
	break;
    case 'l':
	switch (c2) {
	case 'g':  result = LG;  break;
	case 'l':  result = LL;  break;
	case 's':  result = LS;  break;
	case 't':  result = LT;  break;
	}
	break;
    case 'm':
	switch (c2) {
	case '1':  result = M1;  break;
	case '2':  result = M2;  break;
	case '3':  result = M3;  break;
	case '4':  result = M4;  break;
	case 'c':  result = MC;  break;
	}
	break;
    case 'n':
	switch (c2) {
	case 'a':  result = NA;  break;
	case 'e':  result = NE;  break;
	case 'f':  result = NF;  break;
	case 'j':  result = NJ;  break;
	case 'r':  result = NR;  break;
	}
	break;
    case 'o':
	switch (c2) {
	case 'f':  result = OF;  break;
	case 'h':  result = OH;  break;
	}
	break;
    case 'p':
	switch (c2) {
	case 'c':  result = PC;  break;
	case 'l':  result = PL;  break;
	case 'm':  result = PM;  break;
	case 'n':  result = PN;  break;
	case 'o':  result = PO;  break;
	case 's':  result = PS;  break;
	}
	break;
    case 'r':
	switch (c2) {
	case 'm':  result = RM;  break;
	case 'r':  result = RR;  break;
	}
	break;
    case 's':
	switch (c2) {
	case 'o':  result = SO;  break;
	case 'p':  result = SP;  break;
	case 's':  result = SS;  break;
	}
	break;
    case 't':
	switch (c2) {
	case 'i':  result = TI;  break;
	case 'l':  result = TL;  break;
	case 'r':  result = TR;  break;
	}
	break;
    case 'u':
	switch (c2) {
	case 'l':  result = UL;  break;
	}
	break;
    case '.':
	result = EN;
	break;
    }
    return result;
}



/*
 * gettl
 *	get header or footer title
 */
static void 
gettl (char *p, char *q, int *limit) {
    /*
     *   skip forward a word...
     */
    p = skipwd (p);
    p = skipbl (p);
    
    /*
     *   copy and set limits
     */
    strcpy (q, p);
    limit[LEFT]  = dc.inval;
    limit[RIGHT] = dc.rmval;
}

/*
 * getval
 *	retrieves optional argument following command.
 *	returns positive integer value with sign (if any)
 *	saved in character addressed by p_argt.
 */
static int 
getval (char *p, char *p_argtyp) {
    p = skipwd (p);
    p = skipbl (p);
    *p_argtyp = *p;
    if ((*p == '+') || (*p == '-')) {
	++p;
    }
    return (ctod (p));
}

#define N_ADD			0
#define N_SUB			1
#define N_MUL			2
#define N_DIV			3
#define N_MOD			4
#define N_LT			5
#define N_GT			6
#define N_LE			7
#define N_GE			8
#define N_EQ			9
#define N_AND			10
#define N_OR			11


/*
 * getnumeric
 *
 *	retrieves numeric argument. will parse for number registers,
 *	constants, operations, and logical comparisons. no imbeded spaces!
 *	start at p (don't skip)
 */
static int
getnumeric (char *p) {
    char		name[10];
    int		val;
    int		thisval;
    int		autoinc;
    char		buf[256];
    char	       *pbuf;
    int		next_op;
    int		nreg;
    const char *percent = "%";
    char *scratch;
    
    val     = 0;
    next_op = N_ADD;
    while (*p != EOS && !isspace (*p)) {
	if (!strncmp (p, "\\n", 2)) {
	    DEBUGGING(("getnumeric: found number reg..."));
	    /*
	     *   number register
	     */
	    autoinc = 0;
	    p += 2;
	    if (*p == '+') {
		autoinc = 1;
		p++;
	    } else if (*p == '-') {
		autoinc = -1;
		p++;
	    }
	    if (isalpha (*p)) {
		/*
		 *   \nx form. find reg (a-z)
		 */
		nreg = tolower (*p) - 'a';
		p++;
		
		/*
		 *   was this \n+x or \n-x? if so, do the
		 *   auto incr
		 */
		if (autoinc > 0) {
		    dc.nr[nreg] += dc.nrauto[nreg];
		} else if (autoinc < 0) {
		    dc.nr[nreg] -= dc.nrauto[nreg];
		}
				
		val = do_oper (val, next_op, dc.nr[nreg]);
	    } else if (*p == '%') {
		/*
		 *   \n% form. find index into reg struct
		 */
		FINDREG(percent, nreg, scratch);
		p++;
		if (nreg < 0) {
		    errx(-1, "no register match");
		}
		
		/*
		 *   was this \n+% or \n-%? if so, do the
		 *   auto incr
		 */
		if (autoinc > 0) {
		    rg[nreg].rval += rg[nreg].rauto;
		} else if (autoinc < 0) {
		    rg[nreg].rval -= rg[nreg].rauto;
		}

		
		val = do_oper (val, next_op, rg[nreg].rval);
	    } else if (*p == '(') {
		/*
		 *   \n(xx form. find index into reg struct
		 */
		p++;
		name[0] = *p++;
		name[1] = *p++;
		if (name[1] == ' '  || name[1] == '\t'
		    ||  name[1] == '\n' || name[1] == '\r') {
		    name[1] = '\0';
		}
		name[2] = '\0';
		FINDREG(name, nreg, scratch);
		if (nreg < 0) {
		    errx(-1, "no register match");
		}
		
		/*
		 *   was this \n+(xx or \n-(xx? if so, do the
		 *   auto incr
		 */
		if (rg[nreg].rflag & RF_WRITE) {
		    if (autoinc > 0) {
			rg[nreg].rval += rg[nreg].rauto;
		    } else if (autoinc < 0) {
			rg[nreg].rval -= rg[nreg].rauto;
		    }
		}
		
		val = do_oper (val, next_op, rg[nreg].rval);
	    }
	} else if (isdigit (*p)) {
	    pbuf = buf;
	    while (1) {
		if ((*p == EOS || isspace (*p)) ||
		    (*p == '\\') ||
		    (iscond (*p)) ||
		    (isoper (*p))) {
		    break;
		}
		*pbuf++ = *p++;
	    }
	    *pbuf = EOS;
	    
	    DEBUGGING(("getnumeric: buf:|%s| next_op:%d val:%d",
		       buf, next_op, val));
		
	    thisval = ctod (buf);
	    val     = do_oper (val, next_op, thisval);
	    DEBUGGING(("getnumeric: thisval:%d val:%d", thisval, val));
	}
	
	/*
	 *   p should now be at the next thing, either a
	 *   space, a null, or an operator
	 */
	if (*p == EOS || isspace (*p)) {
	    break;
	}
	switch (*p) {
	case '+':
	    next_op = N_ADD;
	    p++;
	    break;
	case '-':
	    next_op = N_SUB;
	    p++;
	    break;
	case '*':
	    next_op = N_MUL;
	    p++;
	    break;
	case '/':
	    next_op = N_DIV;
	    p++;
	    break;
	case '%':
	    next_op = N_MOD;
	    p++;
	    break;
	case '&':
	    next_op = N_AND;
	    p++;
	    break;
	case ':':
	    next_op = N_OR;
	    p++;
			break;
	case '<':
	    p++;
	    if (*p == '=') {
		p++;
		next_op = N_LE;
	    } else {
		next_op = N_LT;
	    }
	    break;
	case '>':
	    p++;
	    if (*p == '=') {
		p++;
		next_op = N_GE;
	    } else {
		next_op = N_GT;
	    }
	    break;
	case '=':
	    p++;
	    if (*p == '=') {
		p++;
	    }
	    next_op = N_EQ;
	    break;
	}
    }
    return (val);
}


/*
 * do_oper
 */

static int 
do_oper (int first, int oper, int second) {
    int	val;
    
    DEBUGGING(("do_oper: first:%d op:%d second:%d", first, oper, second));
    switch (oper) {
    case N_ADD:
	val = first + second;
	break;
    case N_SUB:
	val = first - second;
	break;
    case N_MUL:
	val = first * second;
	break;
    case N_DIV:
	val = first / second;
	break;
    case N_MOD:
	val = first % second;
	break;
    case N_LT:
	val = ((first < second) ? 1 : 0);
	break;
    case N_GT:
	val = ((first > second) ? 1 : 0);
	break;
    case N_LE:
	val = ((first <= second) ? 1 : 0);
	break;
    case N_GE:
	val = ((first >= second) ? 1 : 0);
	break;
    case N_EQ:
	val = ((first == second) ? 1 : 0);
	break;
    case N_AND:
	val = ((first && second) ? 1 : 0);
	break;
    case N_OR:
	val = ((first || second) ? 1 : 0);
	break;
    }
    return (val);
}


/*
 * set
 *	set parameter and check range. this is for basically all commands
 *	which take interger args
 *
 *	no param (i.e. \r or \n) means reset default
 *	+ means param += val (increment)
 *	- means param -= val (decrement)
 *	anything else makes an assignment within the defined numerical limits
 *
 *	examples:
 *
 *	.nr a 14	set register 'a' to 14
 *	.nr a +1	increment register 'a' by 1
 *	.nr a		reset register 'a' to default value (0)
 */
void 
set (int *param, int val, char type, int defval, int minval, int maxval) {
    switch (type) {
    case '\r': 
    case '\n': 
	*param = defval;
	break;
    case '+': 
	*param += val;
	break;
    case '-': 
	*param -= val;
	break;
    default: 
	*param = val;
	break;
    }
    *param = MIN(*param, maxval);
    *param = MAX(*param, minval);
}

/*
 * set_ireg
 *	set internal register "name" to val. ret 0 if ok, else -1 if reg not
 *	found or 1 if read only
 *
 *      0=internal, 1=user set
 */
int 
set_ireg (const char *name, int val, int opt) {
    register int	nreg;
    char *p;
    
    FINDREG(name, nreg, p);
    if (nreg < 0) {
	return (-1);
    }

    if ((rg[nreg].rflag & RF_WRITE) || (opt == 0)) {
	rg[nreg].rval = val;
	
	return (0);
    }
    
    return (1);
}


/*
 * read_if
 *	read input while in if statement. stop when a line starts with \}
 */
static void 
read_if (void) {
    char	ibuf[MAXLINE];
    char   *pp;
    
    while (getlin (ibuf, sofile[dc.flevel]) != EOF) {
	DEBUGGING(("read_if: ibuf=|%s|", ibuf));

	pp = skipbl (ibuf);
	if (*pp == '\\' && *(pp+1) == '}') {
	    return;
	}
	
	/*
	 *   if line is a command or text
	 */
	if (ibuf[0] == dc.cmdchr) {
	    comand (ibuf);
	} else {
	    /*
	     *   this is a text line. first see if
	     *   first char is space. if it is, break
	     *   line.
	     */
	    if (ibuf[0] == ' ') {
		robrk ();
	    }
	    text (ibuf);
	}
	
	pp = ibuf;
	while (*pp != EOS) {
	    if (*pp == '\\' && *(pp+1) == '}') {
		return;
	    }
	}
    }
}
