/*
* Copyright (c) 1980, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1980, 1993\
The Regents of the University of California. All rights reserved.");
#endif /* not lint */
/*
* checknr: check an nroff/troff input file for matching macro calls.
* we also attempt to match size and font changes, but only the embedded
* kind. These must end in \s0 and \fP resp. Maybe more sophistication
* later but for now think of these restrictions as contributions to
* structured typesetting.
*/
#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXSTK 100 /* Stack size */
#define MAXBR 100 /* Max number of bracket pairs known */
#define MAXCMDS 500 /* Max number of commands known */
/*
* The stack on which we remember what we've seen so far.
*/
static struct stkstr {
int opno; /* number of opening bracket */
int pl; /* '+', '-', ' ' for \s, 1 for \f, 0 for .ft */
int parm; /* parm to size, font, etc */
int lno; /* line number the thing came in in */
} stk[MAXSTK];
static int stktop;
static int lineno; /* current line number in input file */
static const char *cfilename; /* name of current file */
static int nfiles; /* number of files to process */
static int fflag; /* -f: ignore \f */
static int sflag; /* -s: ignore \s */
static int ncmds; /* size of knowncmds */
static int slot; /* slot in knowncmds found by binsrch */
static void
prop(int i)
{
if (stk[i].pl == 0)
printf(".%s", br[stk[i].opno].opbr);
else switch(stk[i].opno) {
case SZ:
printf("\\s%c%d", stk[i].pl, stk[i].parm);
break;
case FT:
printf("\\f%c", stk[i].parm);
break;
default:
printf("Bug: stk[%d].opno = %d = .%s, .%s",
i, stk[i].opno, br[stk[i].opno].opbr,
br[stk[i].opno].clbr);
}
}
static void
chkcmd(const char *mac)
{
int i;
/*
* Check to see if it matches top of stack.
*/
if (stktop >= 0 && eq(mac, br[stk[stktop].opno].clbr))
stktop--; /* OK. Pop & forget */
else {
/* No. Maybe it's an opener */
for (i=0; br[i].opbr; i++) {
if (eq(mac, br[i].opbr)) {
/* Found. Push it. */
stktop++;
stk[stktop].opno = i;
stk[stktop].pl = 0;
stk[stktop].parm = 0;
stk[stktop].lno = lineno;
break;
}
/*
* Maybe it's an unmatched closer.
* NOTE: this depends on the fact
* that none of the closers can be
* openers too.
*/
if (eq(mac, br[i].clbr)) {
nomatch(mac);
break;
}
}
}
}
static void
nomatch(const char *mac)
{
int i, j;
/*
* Look for a match further down on stack
* If we find one, it suggests that the stuff in
* between is supposed to match itself.
*/
for (j=stktop; j>=0; j--)
if (eq(mac,br[stk[j].opno].clbr)) {
/* Found. Make a good diagnostic. */
if (j == stktop-2) {
/*
* Check for special case \fx..\fR and don't
* complain.
*/
if (stk[j+1].opno==FT && stk[j+1].parm!='R'
&& stk[j+2].opno==FT && stk[j+2].parm=='R') {
stktop = j -1;
return;
}
/*
* We have two unmatched frobs. Chances are
* they were intended to match, so we mention
* them together.
*/
pe(stk[j+1].lno);
prop(j+1);
printf(" does not match %d: ", stk[j+2].lno);
prop(j+2);
printf("\n");
} else for (i=j+1; i <= stktop; i++) {
complain(i);
}
stktop = j-1;
return;
}
/* Didn't find one. Throw this away. */
pe(lineno);
printf("Unmatched .%s\n", mac);
}
/* eq: are two strings equal? */
static int
eq(const char *s1, const char *s2)
{
return strcmp(s1, s2) == 0;
}
/* print the first part of an error message, given the line number */
static void
pe(int pelineno)
{
if (nfiles > 1)
printf("%s: ", cfilename);
printf("%d: ", pelineno);
}
static void
checkknown(const char *mac)
{
if (eq(mac, "."))
return;
if (binsrch(mac) >= 0)
return;
if (mac[0] == '\\' && mac[1] == '"') /* comments */
return;
/*
* We have a .de xx line in "line". Add xx to the list of known commands.
*/
static void
addcmd(char *line)
{
char *mac;
/* grab the macro being defined */
mac = line+4;
while (isspace((unsigned char)*mac))
mac++;
if (*mac == 0) {
pe(lineno);
printf("illegal define: %s\n", line);
return;
}
mac[2] = 0;
if (isspace((unsigned char)mac[1]) || mac[1] == '\\')
mac[1] = 0;
if (ncmds >= MAXCMDS) {
printf("Only %d known commands allowed\n", MAXCMDS);
exit(1);
}
addmac(mac);
}
/*
* Add mac to the list. We should really have some kind of tree
* structure here but this is a quick-and-dirty job and I just don't
* have time to mess with it. (I wonder if this will come back to haunt
* me someday?) Anyway, I claim that .de is fairly rare in user
* nroff programs, and the register loop below is pretty fast.
*/
static void
addmac(const char *mac)
{
const char **src, **dest, **loc;
if (binsrch(mac) >= 0){ /* it's OK to redefine something */
#ifdef DEBUG
printf("binsrch(%s) -> already in table\n", mac);
#endif /* DEBUG */
return;
}
/* binsrch sets slot as a side effect */
#ifdef DEBUG
printf("binsrch(%s) -> %d\n", mac, slot);
#endif
loc = &knowncmds[slot];
src = &knowncmds[ncmds-1];
dest = src+1;
while (dest > loc)
*dest-- = *src--;
if ((*loc = strdup(mac)) == NULL)
err(1, "strdup");
ncmds++;
#ifdef DEBUG
printf("after: %s %s %s %s %s, %d cmds\n", knowncmds[slot-2],
knowncmds[slot-1], knowncmds[slot], knowncmds[slot+1],
knowncmds[slot+2], ncmds);
#endif
}
/*
* Do a binary search in knowncmds for mac.
* If found, return the index. If not, return -1.
*/
static int
binsrch(const char *mac)
{
const char *p; /* pointer to current cmd in list */
int d; /* difference if any */
int mid; /* mid point in binary search */
int top, bot; /* boundaries of bin search, inclusive */
top = ncmds-1;
bot = 0;
while (top >= bot) {
mid = (top+bot)/2;
p = knowncmds[mid];
d = p[0] - mac[0];
if (d == 0)
d = p[1] - mac[1];
if (d == 0)
return mid;
if (d < 0)
bot = mid + 1;
else
top = mid - 1;
}
slot = bot; /* place it would have gone */
return -1;
}