/*
 *      Command processor for NRO text processor
 *
 *      Originally by Stephen L. Browning, 5723 North Parker Avenue
 *      Indianapolis, Indiana 46220
 *
 *      Transformed beyond immediate recognition, and
 *      adapted for Amiga by Olaf Seibert, KosmoSoft
 *
 *      Vossendijk 149-1 (study)   Beek 5 (home)
 *      5634 TN  Nijmegen          5815 CS  Merselo
 *      The Netherlands            The Netherlands
 *      Phone:
 *             (...-31)80561045     (...-31)4786205
 *          or 080-561045           04786-205
 *
 *      This program is NOT in the public domain. It may, however
 *      be distributed only at no charge, and this notice must be
 *      included unaltered.
 */

#include <stdio.h>
#include "nro.h"
#include "nroxtrn.h"

uchar *skipbl();
uchar *skipwd();

/*
 *      Communicating the current file through a global
 *      variable is a bit of a kludge. We could as well use
 *      it everywhere instead of passing it as a parameter
 *      all the time. But that would not be very `structured',
 *      whatever that may be.
 */

comand(word)
uchar *word;
{
        int ct, val;
        int spval;
        uchar argtyp, chr;
        uchar *macexp;

        val = getcmdwrd(word, infile);
        if (word[0] == env.c2chr) {
                env.dontbrk = TRUE;
                word++;
        } else if (word[0] == env.cmdchr) {
                word++;
        } else if (val > 0) {
                putbak(' ');
                pbstr(word);
                return;
        }

        ct = comtyp(word, &macexp);

        if (ct == UNKNOWN) {
                error("nro: unrecognized command %s\n", word);
                env.dontbrk = FALSE;
                return;
        }
        if (! (ct & NOARGS))
                val = getval(&argtyp, infile);  /* Eat more of line */

        switch (ct & ~NOARGS) {
        case BO: /* Bold face */
                set(&env.boval, val, argtyp, 1, -1, HUGE);
                if (env.boval)  env.reqmode |= FXBO;
                else            env.reqmode &= ~FXBO;
                if (dc.bsflg != BSAMIGA)
                        env.cuval = env.ulval = 0;
                break;
        case BP: /* Begin page */
                if (pg.lineno > 0) space(pg.plval);
                set(&pg.curpag, val, argtyp, pg.curpag+1, -HUGE, HUGE);
                pg.newpag = pg.curpag;
                break;
        case BR: /* Break */
                dobrk();
                break;
        case BS: /* Backspaces in output: 0=No, Amiga; 1=Yes; 2=Use CR */
                set(&dc.bsflg, val, argtyp, 1, 0, 2);
                break;
        case C2: /* No-break command character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.c2chr = C2CHAR;
                else { env.c2chr = chr; ct = 0; }
                break;
        case CC: /* Command character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.cmdchr = CMDCHAR;
                else { env.cmdchr = chr; ct = 0; }
                break;
        case CE: /* Center */
                dobrk();
                set(&env.ceval, val, argtyp, 1, -1, HUGE);
                break;
        case COMMENT:
                while (chr = ngetc(infile), isnteol(chr));
                break;
        case CU: /* Continuous underline */
                set(&env.cuval, val, argtyp, 1, -1, HUGE);
                if (env.cuval)  env.reqmode |= FXUL;
                else            env.reqmode &= ~FXUL;
                if (dc.bsflg != BSAMIGA)
                        env.ulval = env.boval = 0;
                break;
        case DE: /* Define macro */
                defmac(word, infile);
                break;
        case EF: /* Even footer */
                gettl(infile, pg.efoot, NULL, &pg.eflim[0], NULL);
                break;
        case EH: /* Even header */
                gettl(infile, pg.ehead, NULL, &pg.ehlim[0], NULL);
                break;
        case EL: /* Else */
                doel(infile);
                break;
        case EN: /* End macro definition */
                error("nro: missing .de command\n");
                break;
        case EV: /* Environment switch */
                if (isdigit(argtyp)) {  /* Supplied argument: push */
                        if (dc.envsp >= ENVSTACK-1) {
                                error("nro: cannot push environment.\n"); break;
                        }
                        spval = dc.envstack[dc.envsp];  /* Current environment */
                        dc.envstack[++dc.envsp] = val;  /* Save number on stack*/
                        storenv(spval);                                 /* Save current envir. */
                        loadenv(val);                                   /* Get new one         */
                } else {        /* Pop an environment */
                        if ((int) dc.envsp <= 0) {
                                error("*** nro: cannot pop environment.\n"); break;
                        }
                        spval = dc.envstack[dc.envsp];  /* Current environment */
                        val = dc.envstack[--dc.envsp];  /* Number of old env   */
                        if (argtyp == '-') freenv(spval);/* Dump current environ*/
                        else storenv(spval);                    /* ..or save it        */
                        loadenv(val);                                   /* Get old one back    */
                }
                break;
        case FI: /* Fill */
                dobrk();
                env.fill = YES;
                break;
        case FO: /* Footer */
                gettl(infile, pg.efoot, pg.ofoot, &pg.eflim[0], &pg.oflim[0]);
                break;
        case HE: /* Header */
                gettl(infile, pg.ehead, pg.ohead, &pg.ehlim[0], &pg.ohlim[0]);
                break;
        case IE:
        case IF:
                doieif(infile, ct & ~NOARGS);
                break;
        case IN: /* Indenting */
                dobrk();
                set(&env.inval, val, argtyp, 0, 0, env.tmval-1);
                env.tival = env.inval;
                break;
        case IT: /* Italic face */
                set(&env.itval, val, argtyp, 1, -1, HUGE);
                if (env.itval)  env.reqmode |= FXIT;
                else            env.reqmode &= ~FXIT;
                if (dc.bsflg != BSAMIGA)
                        error("nro: italics cannot be done by overstrike :-)\n");
                break;
        case JU: /* Justify */
                env.juval = YES;
                break;
        case LS: /* Line spacing */
                set(&env.lsval, val, argtyp, 1, 1, HUGE);
                break;
        case M1: /* Set topmost margin */
                set(&pg.m1val, val, argtyp, 2, 0, HUGE);
                break;
        case M2: /* Set second top margin */
                set(&pg.m2val, val, argtyp, 2, 0, HUGE);
                break;
        case M3: /* Set first bottom margin */
                set(&pg.m3val, val, argtyp, 2, 0, HUGE);
                pg.bottom = pg.plval - pg.m4val - pg.m3val;
                break;
        case M4: /* Set bottom-most margin */
                set(&pg.m4val, val, argtyp, 2, 0, HUGE);
                pg.bottom = pg.plval - pg.m4val - pg.m3val;
                break;
        case MACRO: /* Macro expansion */
                maceval(macexp, infile);
                break;
        case NE: /* Need n lines */
                /* dobrk(); */
                if ((pg.bottom-pg.lineno+1) < (val*env.lsval)) space(pg.plval);
                break;
        case NF: /* No fill */
                dobrk();
                env.fill = NO;
                break;
        case NJ: /* No justify */
                env.juval = NO;
                break;
        case NR: /* Set number register */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (!isalpha(chr)) {
                        error("nro: invalid or missing number register name\n");
                } else {
                        ct = tolower(chr) - 'a';
                        val = getval(&chr, infile);
                        set(&dc.nr[ct], val, chr, 0, -HUGE, HUGE);
                }
                ct = 0;
                break;
        case OF: /* Odd footer */
                gettl(infile, pg.ofoot, NULL, &pg.oflim[0], NULL);
                break;
        case OH: /* Odd header */
                gettl(infile, pg.ohead, NULL, &pg.ohlim[0], NULL);
                break;
        case PC: /* Page number character */
                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr)) env.pgchr = EOS;
                else { env.pgchr = chr; ct = 0; }
                break;
        case PL: /* Page length */
                set(&pg.plval, val, argtyp, PAGELEN,
                        pg.m1val+pg.m2val+pg.m3val+pg.m4val+1, HUGE);
                pg.bottom = pg.plval - pg.m3val - pg.m4val;
                break;
        case PN: /* Page numbering mode */
                set(&env.pnflg, val, argtyp, PNARABIC, PNARABIC, PNUROMAN);
                break;
        case PO: /* Page offset */
                set(&pg.offset, val, argtyp, 0, 0, HUGE);
                break;
        case RM: /* Right margin */
                set(&env.rmval, val, argtyp, PAGEWIDTH, env.tival+1, HUGE);
                env.tmval = env.rmval;
                break;
        case SO: /* Source file */
                if (getcmdwrd(word, infile) == EOF) break;
                skipeol(infile);
                ct = NOARGS;
                if (dc.flevel+1 >= NFILES) {
                        error("nro: .so commands nested too deeply\n");
                        break;
                }
                if ((sofile[dc.flevel+1] = fopen(word, "r")) == NULL) {
                        error("nro: unable to open %s\n", word);
                        break;
                }
                if (verbose > 2) error("nro: processing file '%s'\n", word);
                sopbb[dc.flevel] = mac.pbb;     /* Stack push back stacks */
                mac.pbb = mac.ppb + 1;
                dc.flevel++;
                infile = sofile[dc.flevel];
                break;
        case SP: /* Space */
                set(&spval, val, argtyp, 1, 0, HUGE);
                space(spval * env.lsval);
                break;
        case TA: /* Tab settings */
                tabs(infile);
                break;
        case TI: /* Temporary indent */
                dobrk();
                set(&env.tival, val, argtyp, 0, 0, env.tmval);
                break;
        case TM: /* Terminal Message */
                fflush(stdout);
                if (env.dontbrk == FALSE || pout != stdout) {
                        while (val = ngetc(infile), isnteol(val) && val != EOF)
                                putc(val, stderr);
                        putc('\n', stderr);
                        fflush(stderr);
                } else ct = 0; /* Skip rest of command line */
                break;
        case UL: /* Underline */
                set(&env.ulval, val, argtyp, -1, 1, HUGE);
                if (env.ulval)  env.reqmode |= FXUL;
                else            env.reqmode &= ~FXUL;
                if (dc.bsflg != BSAMIGA)
                        env.cuval = env.boval = 0;
                break;
        case UN: /* Undefine macro */
                undefmac(word, infile);
                break;
        }
        env.dontbrk = FALSE;

        if (argtyp != STRINGTYP && !(ct & NOARGS))      skipeol(infile);

        return;
}


/*
 *      Decodes nro command and returns its associated
 *      value.
 */

comtyp(line, macdef)
uchar *line;
uchar **macdef;
{
        uchar c1, c2;

        /*
         *      First check to see if the command is a macro.
         *      If it is, return expansion in macdef.
         *      Note that upper and lower case characters are handled
         *      differently for macro names, but not for normal command names.
         */

        if ((*macdef = getmac(line)) != NULL) {
                return MACRO | NOARGS;
        }

        c1 = tolower(*line++);
        c2 = tolower(*line  );

        switch (c1) {
        case '"':
        case '*':
                return COMMENT | NOARGS;
        case 'b':
                if              (c2 == 'o') return BO;
                else if (c2 == 'p') return BP;
                else if (c2 == 'r') return BR;
                else if (c2 == 's') return BS;
                break;
        case 'c':
                if              (c2 == '2') return C2 | NOARGS;
                else if (c2 == 'c') return CC | NOARGS;
                else if (c2 == 'e') return CE;
                else if (c2 == 'u') return CU;
                break;
        case 'd':
                if      (c2 == 'e') return DE | NOARGS;
                break;
        case 'e':
                if              (c2 == 'f') return EF | NOARGS;
                else if (c2 == 'h') return EH | NOARGS;
                else if (c2 == 'l') return EL | NOARGS;
                else if (c2 == 'n') return EN;
                else if (c2 == 'v') return EV;
                break;
        case 'f':
                if              (c2 == 'i') return FI;
                else if (c2 == 'o') return FO | NOARGS;
                break;
        case 'h':
                if      (c2 == 'e') return HE | NOARGS;
                break;
        case 'i':
                if              (c2 == 'e') return IE | NOARGS;
                else if (c2 == 'f') return IF | NOARGS;
                else if (c2 == 'n') return IN;
                else if (c2 == 't') return IT;
                break;
        case 'j':
                if      (c2 == 'u') return JU;
                break;
        case 'l':
                if      (c2 == 's') return LS;
                break;
        case 'm':
                if              (c2 == '1') return M1;
                else if (c2 == '2') return M2;
                else if (c2 == '3') return M3;
                else if (c2 == '4') return M4;
                break;
        case 'n':
                if              (c2 == 'e') return NE;
                else if (c2 == 'f') return NF;
                else if (c2 == 'j') return NJ;
                else if (c2 == 'r') return NR | NOARGS;
                break;
        case 'o':
                if              (c2 == 'f') return OF | NOARGS;
                else if (c2 == 'h') return OH | NOARGS;
                break;
        case 'p':
                if              (c2 == 'c') return PC | NOARGS;
                else if (c2 == 'l') return PL;
                else if (c2 == 'n') return PN;
                else if (c2 == 'o') return PO;
                break;
        case 'r':
                if      (c2 == 'm') return RM;
                break;
        case 's':
                if              (c2 == 'o') return SO | NOARGS;
                else if (c2 == 'p') return SP;
                break;
        case 't':
                if      (c2 == 'a') return TA | NOARGS;
                else if (c2 == 'i') return TI;
                else if (c2 == 'm') return TM | NOARGS;
                break;
        case 'u':
                if              (c2 == 'l') return UL;
                else if (c2 == 'n') return UN | NOARGS;
        }
        return UNKNOWN;
}


/*
 *      Retrieves optional argument following nro command.
 *      returns positive integer value with sign (if any)
 *      saved in character addressed by pargtyp.
 *      In case of a string, puts in a quote.
 *      It won't read the character it doesn't understand,
 *      i.e. semicolons and newlines, and garbage.
 */

getval(pargtyp, infile)
uchar *pargtyp;
register FILE *infile;
{
        register uchar chr, operator = '+';
        short digit;
        register int val, cumval;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        *pargtyp = chr;
        if (chr == '+' || chr == '-' || chr == '/' || chr == '*')
                chr = ngetc(infile);
        else if (chr == STRINGTYP)
                return 1;

        cumval = 0;

again:
        val = 0;

        if (chr == '(') {       /* Parenthesized subexpression */
                val = getval(&digit, infile); /* Dummy argtype pointer */
                chr = ngetc(infile);
                if (chr != ')')
                        error("nro: missing close parenthesis in expression\n");
                else
                        chr = ngetc(infile); /* Get next operator or anything */
                if (*pargtyp == '(') *pargtyp = '0';
        } else {                        /* Try to collect a number from the input */
                while ((digit = atod(chr)) >= 0) {
                        val = 10 * val + digit;
                        chr = ngetc(infile);
                }
        }

        /* Check if we need to evaluate an operator */
        if (operator) {
                switch (operator) {
                case '+': cumval += val; break;
                case '-': cumval -= val; break;
                case '*': cumval *= val; break;
                case '/':
                        if (val != 0) cumval /= val;
                        else error("nro: division by zero\n");
                        break;
                case '%':
                        if (val != 0) cumval %= val;
                        else error("nro: modulo by zero\n");
                        break;
                case '<': cumval = cumval < val;  break;
                case '=': cumval = cumval == val; break;
                case '>': cumval = cumval > val;  break;
                case '&': cumval = cumval & val; break;
                case '|': cumval = cumval | val; break;
                }
                operator = '\0';
        }

        /* See if there is more to come */
        switch (chr) {
        case '+': case '-': case '*': case '/': case '%':
        case '<': case '=': case '>':
        case '&': case '|':
                operator = chr;
                chr = ngetc(infile);
                goto again;
        default:
                putbak(chr);    /* Put back what we can't interpret */

                return cumval;
        }
}


/*
 *      Convert string to decimal.
 *      processes only positive values.
 */

ctod(p)
uchar *p;
{
        int val, d;

        val = 0;
        while (*p != EOS) {
                d = atod(*p++);
                if (d == -1) return val;
                val = 10 * val + d;
        }
        return val;
}


/*
 *      Convert ascii character to decimal.
 */

atod(c)
uchar c;
{
        return ((c < '0') || (c > '9')) ? -1 : c-'0';
}


/*
 *      Get non-blank word from the input file.
 *      Returns the number of spaces skipped before the word,
 *      or EOF on end of file.
 *      It won't read past a newline or semicolon,
 *      but will skip the first space following the word.
 */

getcmdwrd(to, infile)
register uchar *to;
register FILE *infile;
{
        register short chr;
        int skipped = 0;
        short length = 0;


        chr = ngetc(infile);
        if (chr == EOF) {
                *to = EOS;
                return EOF;
        }

        /* Skip spaces */

        while (isspace(chr)) {
                chr = ngetc(infile);
                skipped++;
        }

        while (isntspace(chr) && isnteol(chr) && chr != EOF &&
                        chr != MORETXT && length < MAXWORD-3) {
                *to++ = chr;
                length++;
                chr = ngetc(infile);
        }

        if (iseol(chr) || chr == MORETXT) putbak(chr);

        *to = EOS;
        if (length >= MAXWORD-3) error("nro: command word buffer overflow\n");

        return skipped;
}


/*
 *      Skip the rest of the current input line
 */

skipeol(infile)
FILE *infile;
{
        int chr;

        while ((chr = ngetc(infile)) != '\n' && chr != MORETXT &&
                chr != EOF);
}


/*
 *      Process an IF or IE request
 */

doieif(infile, request)
FILE *infile;
int request;
{
        uchar *strp;
        short negation;
        int chr, val;
        uchar delim;
        uchar string[MAXWORD];

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');
        if (chr == '!') {
                negation = TRUE;
                chr = ngetc(infile);
        } else
                negation = FALSE;

        switch (chr) {
        case 'e':       /* Even page number */
                val = (pg.curpag % 2) == 0;
                break;
        case 'o':       /* Odd page number */
                val = (pg.curpag % 2) != 0;
                break;
        case 'n':       /* Nro(ff) is formatter */
                val = TRUE; break;
        case 't':       /* Troff certainly not */
                val = FALSE; break;
        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
        case '+': case '-': case '(':
                /* Must be an expression */
                putbak(chr);
                chr = getval(&string[0], infile);
                val = 0;
                set(&val, chr, string[0], 0, -HUGE, HUGE);
                val = val > 0;
                break;
        default:        /* String comparison */
                delim  = chr;
                strp   = string;        /* Collect first string */
                while ((chr = ngetc(infile)) != delim &&
                                strp < string + sizeof(string) - 2)
                        *strp++ = chr;
                *strp = EOS;
                val = TRUE;

                strp = string;  /* Compare with second string */
                while ((chr = ngetc(infile)) != delim) {
                        if (*strp++ != chr) val = FALSE;
                }
                if (*strp != EOS) val = FALSE;
                break;
        }
        if (negation) val = !val;
        if (request == IE) env.lastie = val;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        if (val) {     /* Condition is true. Don't skip any text */
                if (chr != BEGIF) putbak(chr);
        } else { /* Need to skip some text, maybe even very much */
                if (chr == BEGIF)          /* Skip until end of line */
                        dc.iflvl = 1;
                while (isnteol(chr)) chr = ngetc(infile);
        }
}


/*
 *      Process an EL request
 */

doel(infile)
FILE *infile;
{
        int chr, val;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        /* Toggle last remembered condition */
        val = env.lastie = !env.lastie;

        if (val) {     /* Condition is true. Don't skip any text */
                if (chr != BEGIF) putbak(chr);
        } else { /* Need to skip some text, maybe even very much */
                if (chr == BEGIF)          /* Skip until end of line */
                        dc.iflvl = 1;
                while (isnteol(chr)) chr = ngetc(infile);
        }
}


/*
 *      Process a TA request
 */

tabs(infile)
FILE *infile;
{
        int chr, val, i, j;

        while ((chr = ngetc(infile)) == ' ' || chr == '\t');

        if (iseol(chr) || chr == EOF) { /* No arguments */
                for (i=0; i<MAXTAB; i++)
                        env.tabstop[i] = NOTAB;
                return;
        }

        i = env.inval;

        for (;;) {      /* Collect the values */
                putbak(chr);
                val = getval((uchar *)&chr, infile);

                if (*(uchar *)&chr == '+') {    /* Saves a temporary uchar :-) */
                        val += i;
                }

                /* Find where to insert the tab */
                i=0;
                while (i<MAXTAB && env.tabstop[i] < val) i++;

                if (i < MAXTAB && env.tabstop[i] != val)  {
                        /* Insert the tab */
                        for (j=MAXTAB-1; j > i; j--)
                                env.tabstop[j] = env.tabstop[j-1];

                        env.tabstop[i] = val;
                }
                i = val;

                while ((chr = ngetc(infile)) == ' ' || chr == '\t');
                if (iseol(chr) || chr == MORETXT || chr == EOF) break;
        }
}



/*
 *      Loadenv - get an environment
 */

loadenv(number)
int number;
{
        struct environ *envp;
        short freeit = env.dontbrk;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp) {     /* It't there. Copy it. */
                env = *envp;
                if (freeit) {           /* We may free the saved image of it, */
                        freenv(number); /* if we are tight on memory.         */
                }
        } else {        /* It isn't. Just use default values. */
                initenv();
        }

        return OK;
}

/*
 *      Storenv - save an environment
 */

storenv(number)
int number;
{
        struct environ *envp;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp == NULL) {     /* Never saw this guy before */
                if ((envp = (struct environ *)malloc(sizeof(*envp))) == NULL) {
                        error("*** nro: cannot allocate environment #%d\n", number);
                        return ERR;
                } else if (verbose > 2)
                        error("nro: allocated environment #%d\n", number);
                environ[number] = envp;
        }

        *envp = env;

        return OK;
}


/*
 *      Freenv - throw an environment away
 */

freenv(number)
int number;
{
        struct environ *envp;

        if (number < 0 || number >= NUMENV) return ERR;

        envp = environ[number];

        if (envp) {
                free(envp);
                environ[number] = NULL;
                if (verbose > 2) error("nro: freed environment #%d\n", number);
        }

        return OK;
}


/*
 *      End current filled line
 */

dobrk()
{
        if (!env.dontbrk) {             /* Breaks may be disabled by using .'xx */
                if (env.outp > 0) {
#ifdef CPM
                        env.outbuf[env.outp++] = '\r';
                        env.outbuf[env.outp++] = '\n';
                        env.outbuf[env.outp  ] = EOS;
#else  CPM
                        env.outbuf[env.outp++] = '\n';
                        env.outbuf[env.outp  ] = EOS;
#endif CPM
                        if (env.ceval != 0)     /* Centering */
                                center(env.outbuf);
                        put(env.outbuf);
                }
                env.outp = 0;
                env.outw = 0;
                env.outwds = 0;
        }
}



/*
 *      Define a macro
 */

defmac(word, infile)
uchar *word;
FILE *infile;
{
        uchar line[MAXLINE];

        getcmdwrd(word, infile);
        skipeol(infile);

        if (word[0] == EOS && verbose)
                error("nro: appending to macro definition\n");

        while (getlin(line, infile) != EOF) {
                if (line[0] == env.cmdchr && line[1] == 'e' && line[2] == 'n')
                        break;
                if (putmac(word, line) == ERR) {
                        error("*** nro: macro definition table full\n");
                }
                word[0] = EOS;
        }
}


/*
 *      Put macro definition into table.
 *      If name == "", concatenate this definition to the previous one.
 */

putmac(name, def)
uchar *name;
uchar *def;
{
        int lenofname, lenofdef;

        lenofname = strlen(name);
        lenofdef = strlen(def);

        if ((lenofname && mac.lastp >= mac.mxmdef) ||
                (mac.emb + lenofname + lenofdef + 2 > &mac.mb[mac.macbuf])) {
                return ERR;
        }
        if (lenofname) {
                ++mac.lastp;
                mac.mnames[mac.lastp] = mac.emb;
                strcpy(mac.emb, name);
                mac.emb += lenofname + 1;
        } else
                mac.emb--;

        strcpy(mac.emb, def);
        mac.emb += lenofdef + 1;
        return OK;
}



/*
 *      Get macro definition from table
 */

uchar *getmac(name)
uchar *name;
{
        register int i;
        register uchar *name1, *mname;


        for (i = mac.lastp; i > 0; --i) {       /*V1.5*/
                name1 = name;
                mname = mac.mnames[i];
                while (*(name1++) == *(mname++)) {
                        if (name1[-1] == EOS)   return mname;
                }
        }
        return NULL;
}


/*
 *      Delete macro definition from table
 */

undefmac(word, infile)
uchar *word;
FILE *infile;
{
        register int i;
        register uchar *name, *mname;

        getcmdwrd(word, infile);
        skipeol(infile);

        for (i = mac.lastp; i >= 0; --i) {
                name = word;
                mname = mac.mnames[i];
                while (*(name++) == *(mname++)) {
                        if (name[-1] == EOS) {
                                mac.lastp = i-1;
                                mac.emb = mac.mnames[i];
                                return OK;
                        }
                }
        }
        return ERR;
}



/*
 *      Evaluate macro expansion for re-evaluation
 */

maceval(macdef, infile)
register uchar *macdef;
FILE *infile;
{
        uchar *argp[10];
        int i;
        uchar c;
        uchar line[MAXLINE];
        register uchar *linep = line;

        *linep++ = EOS;
        getlin(linep, infile);
        /*
         *       Initialize argp array to substitute empty string
         *       string for any undefined argument
         */
        for (i=0; i<10; ++i) argp[i] = line;

        for (i=0; i<10; ++i) {
                linep = skipbl(linep);
                if (iseol(*linep) || *linep == MORETXT || *linep == EOS) break;
                if (*linep == '\'' || *linep == '"') {
                        c = *linep++;
                        argp[i] = linep;
                        while (*linep != c && isnteol(*linep) && *linep != EOS)
                                linep++;
                        *linep++ = EOS;
                } else {
                        argp[i] = linep;
                        linep = skipwd(linep);
                        *linep++ = EOS;
                }
        }

        /* First push back any remaining text or commands */

        if (*linep == MORETXT) pbstr(linep+1);

        /* Now push back macro body */

        for (i=strlen(macdef)-1; i>=0; --i) {
                /* Need we substitute an argument? */
                if (i > 0 && macdef[i-1] == '$') {
                        if (!isdigit(macdef[i]) || macdef[i-2] == '$') {
                                /* Re-evaluate escape characters from macro body */
                                putbak(macdef[i] | NOGUARD);
                        } else {
                                /* but not of any arguments */
                                pbstr(argp[macdef[i]-'0']);
                                --i;
                        }
                } else {
                        putbak(macdef[i] | NOGUARD);
                }
        }
}


/*
 *      Push back string into input stream for re-evaluation
 */

pbstr(p)
register uchar p[];
{
        register int i;

        for (i=strlen(p)-1; i>=0; --i) {
                putbak(p[i]);
        }
}



/*
 *      Push character back into input stream
 */

putbak(c)
int c;
{
        if (mac.ppb < mac.pbb) {
                mac.ppb = mac.pbb;
                *mac.ppb = c;
        } else {
                if (mac.ppb >= mac.pbbend-2) {
                        error("*** nro: push back buffer overflow\n");
                }
                *++mac.ppb = c;
        }

        /* Avoid evaluating escaped escape characters twice */
        if (c == ESCCHAR)
                *++mac.ppb = ESCCHAR;
}



/*
 *      Get header or footer title
 */

gettl(infile, line1, line2, limit1, limit2)
FILE *infile;
uchar *line1, *line2;
int limit1[], limit2[];
{
        uchar c;

        while (c = ngetc(infile), isspace(c));

        line1[0] = c;
        if (isnteol(c)) getlin(line1+1, infile);
        limit1[LEFT] = env.inval;
        limit1[RIGHT] = env.rmval;
        if (line2) {
                strcpy(line2, line1);
                limit2[LEFT] = limit1[LEFT];
                limit2[RIGHT] = limit1[RIGHT];
        }
}



/*
 *      Set parameter and check range
 */

set(param, val, type, defval, minval, maxval)
int *param;
int val;
uchar type;
int defval, minval, maxval;
{
        switch(type) {
        case '+':
                *param += val; break;
        case '-':
                *param -= val; break;
        case '*':
                *param *= val; break;
        case '/':
                if (val != 0) *param /= val;
                else error ("nro: division by zero modification\n");
                break;
        case '%':
                if (val != 0) *param %= val;
                else error ("nro: modulo by zero modification\n");
                break;
        default:
                *param = isdigit(type)? val : defval;
                break;
        }
        if (*param < minval)
                *param = minval;
        else if (*param > maxval)
                *param = maxval;
}



/*
 *      Skip blanks and tabs in character buffer.
 *      return pointer to first non-space char.
 */

uchar *skipbl(p)
uchar *p;
{
        while (isspace(*p)) ++p;
        return p;
}


/*
 *      Skip over word and punctuation
 */

uchar *skipwd(p)
uchar *p;
{
        while (isntspace(*p) && isnteol(*p) && *p != EOS)
                ++p;
        return p;
}



/*
 *      Space vertically n lines
 */

space(n)
int n;
{
        dobrk();
        if (pg.lineno > pg.bottom) return;
        if (pg.lineno == 0) phead();
        skip(min(n, pg.bottom+1-pg.lineno));
        pg.lineno += n;
        if (pg.lineno > pg.bottom) pfoot();
}

