mirror of
https://github.com/GnoConsortium/gno.git
synced 2024-11-19 11:30:51 +00:00
1675 lines
29 KiB
C
1675 lines
29 KiB
C
/*
|
|
* 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 (¶m, 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;
|
|
}
|
|
}
|
|
}
|
|
}
|