Home | History | Annotate | Line # | Download | only in dist
roff.c revision 1.1.1.1
      1 /*	$Vendor-Id: roff.c,v 1.85 2010/05/29 19:41:47 kristaps Exp $ */
      2 /*
      3  * Copyright (c) 2010 Kristaps Dzonsons <kristaps (at) bsd.lv>
      4  *
      5  * Permission to use, copy, modify, and distribute this software for any
      6  * purpose with or without fee is hereby granted, provided that the above
      7  * copyright notice and this permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     16  */
     17 #ifdef HAVE_CONFIG_H
     18 #include "config.h"
     19 #endif
     20 
     21 #include <assert.h>
     22 #include <ctype.h>
     23 #include <stdlib.h>
     24 #include <string.h>
     25 #include <stdio.h>
     26 
     27 #include "mandoc.h"
     28 #include "roff.h"
     29 
     30 #define	RSTACK_MAX	128
     31 
     32 #define	ROFF_CTL(c) \
     33 	('.' == (c) || '\'' == (c))
     34 
     35 enum	rofft {
     36 	ROFF_am,
     37 	ROFF_ami,
     38 	ROFF_am1,
     39 	ROFF_de,
     40 	ROFF_dei,
     41 	ROFF_de1,
     42 	ROFF_ds,
     43 	ROFF_el,
     44 	ROFF_ie,
     45 	ROFF_if,
     46 	ROFF_ig,
     47 	ROFF_rm,
     48 	ROFF_tr,
     49 	ROFF_cblock,
     50 	ROFF_ccond,
     51 	ROFF_MAX
     52 };
     53 
     54 enum	roffrule {
     55 	ROFFRULE_ALLOW,
     56 	ROFFRULE_DENY
     57 };
     58 
     59 struct	roff {
     60 	struct roffnode	*last; /* leaf of stack */
     61 	mandocmsg	 msg; /* err/warn/fatal messages */
     62 	void		*data; /* privdata for messages */
     63 	enum roffrule	 rstack[RSTACK_MAX]; /* stack of !`ie' rules */
     64 	int		 rstackpos; /* position in rstack */
     65 };
     66 
     67 struct	roffnode {
     68 	enum rofft	 tok; /* type of node */
     69 	struct roffnode	*parent; /* up one in stack */
     70 	int		 line; /* parse line */
     71 	int		 col; /* parse col */
     72 	char		*end; /* end-rules: custom token */
     73 	int		 endspan; /* end-rules: next-line or infty */
     74 	enum roffrule	 rule; /* current evaluation rule */
     75 };
     76 
     77 #define	ROFF_ARGS	 struct roff *r, /* parse ctx */ \
     78 			 enum rofft tok, /* tok of macro */ \
     79 		 	 char **bufp, /* input buffer */ \
     80 			 size_t *szp, /* size of input buffer */ \
     81 			 int ln, /* parse line */ \
     82 			 int ppos, /* original pos in buffer */ \
     83 			 int pos, /* current pos in buffer */ \
     84 			 int *offs /* reset offset of buffer data */
     85 
     86 typedef	enum rofferr (*roffproc)(ROFF_ARGS);
     87 
     88 struct	roffmac {
     89 	const char	*name; /* macro name */
     90 	roffproc	 proc; /* process new macro */
     91 	roffproc	 text; /* process as child text of macro */
     92 	roffproc	 sub; /* process as child of macro */
     93 	int		 flags;
     94 #define	ROFFMAC_STRUCT	(1 << 0) /* always interpret */
     95 	struct roffmac	*next;
     96 };
     97 
     98 static	enum rofferr	 roff_block(ROFF_ARGS);
     99 static	enum rofferr	 roff_block_text(ROFF_ARGS);
    100 static	enum rofferr	 roff_block_sub(ROFF_ARGS);
    101 static	enum rofferr	 roff_cblock(ROFF_ARGS);
    102 static	enum rofferr	 roff_ccond(ROFF_ARGS);
    103 static	enum rofferr	 roff_cond(ROFF_ARGS);
    104 static	enum rofferr	 roff_cond_text(ROFF_ARGS);
    105 static	enum rofferr	 roff_cond_sub(ROFF_ARGS);
    106 static	enum rofferr	 roff_line(ROFF_ARGS);
    107 
    108 /* See roff_hash_find() */
    109 
    110 #define	ASCII_HI	 126
    111 #define	ASCII_LO	 33
    112 #define	HASHWIDTH	(ASCII_HI - ASCII_LO + 1)
    113 
    114 static	struct roffmac	*hash[HASHWIDTH];
    115 
    116 static	struct roffmac	 roffs[ROFF_MAX] = {
    117 	{ "am", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    118 	{ "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    119 	{ "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    120 	{ "de", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    121 	{ "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    122 	{ "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    123 	{ "ds", roff_line, NULL, NULL, 0, NULL },
    124 	{ "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
    125 	{ "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
    126 	{ "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
    127 	{ "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
    128 	{ "rm", roff_line, NULL, NULL, 0, NULL },
    129 	{ "tr", roff_line, NULL, NULL, 0, NULL },
    130 	{ ".", roff_cblock, NULL, NULL, 0, NULL },
    131 	{ "\\}", roff_ccond, NULL, NULL, 0, NULL },
    132 };
    133 
    134 static	void		 roff_free1(struct roff *);
    135 static	enum rofft	 roff_hash_find(const char *);
    136 static	void		 roff_hash_init(void);
    137 static	void		 roffnode_cleanscope(struct roff *);
    138 static	int		 roffnode_push(struct roff *,
    139 				enum rofft, int, int);
    140 static	void		 roffnode_pop(struct roff *);
    141 static	enum rofft	 roff_parse(const char *, int *);
    142 
    143 /* See roff_hash_find() */
    144 #define	ROFF_HASH(p)	(p[0] - ASCII_LO)
    145 
    146 static void
    147 roff_hash_init(void)
    148 {
    149 	struct roffmac	 *n;
    150 	int		  buc, i;
    151 
    152 	for (i = 0; i < (int)ROFF_MAX; i++) {
    153 		assert(roffs[i].name[0] >= ASCII_LO);
    154 		assert(roffs[i].name[0] <= ASCII_HI);
    155 
    156 		buc = ROFF_HASH(roffs[i].name);
    157 
    158 		if (NULL != (n = hash[buc])) {
    159 			for ( ; n->next; n = n->next)
    160 				/* Do nothing. */ ;
    161 			n->next = &roffs[i];
    162 		} else
    163 			hash[buc] = &roffs[i];
    164 	}
    165 }
    166 
    167 
    168 /*
    169  * Look up a roff token by its name.  Returns ROFF_MAX if no macro by
    170  * the nil-terminated string name could be found.
    171  */
    172 static enum rofft
    173 roff_hash_find(const char *p)
    174 {
    175 	int		 buc;
    176 	struct roffmac	*n;
    177 
    178 	/*
    179 	 * libroff has an extremely simple hashtable, for the time
    180 	 * being, which simply keys on the first character, which must
    181 	 * be printable, then walks a chain.  It works well enough until
    182 	 * optimised.
    183 	 */
    184 
    185 	if (p[0] < ASCII_LO || p[0] > ASCII_HI)
    186 		return(ROFF_MAX);
    187 
    188 	buc = ROFF_HASH(p);
    189 
    190 	if (NULL == (n = hash[buc]))
    191 		return(ROFF_MAX);
    192 	for ( ; n; n = n->next)
    193 		if (0 == strcmp(n->name, p))
    194 			return((enum rofft)(n - roffs));
    195 
    196 	return(ROFF_MAX);
    197 }
    198 
    199 
    200 /*
    201  * Pop the current node off of the stack of roff instructions currently
    202  * pending.
    203  */
    204 static void
    205 roffnode_pop(struct roff *r)
    206 {
    207 	struct roffnode	*p;
    208 
    209 	assert(r->last);
    210 	p = r->last;
    211 
    212 	if (ROFF_el == p->tok)
    213 		if (r->rstackpos > -1)
    214 			r->rstackpos--;
    215 
    216 	r->last = r->last->parent;
    217 	if (p->end)
    218 		free(p->end);
    219 	free(p);
    220 }
    221 
    222 
    223 /*
    224  * Push a roff node onto the instruction stack.  This must later be
    225  * removed with roffnode_pop().
    226  */
    227 static int
    228 roffnode_push(struct roff *r, enum rofft tok, int line, int col)
    229 {
    230 	struct roffnode	*p;
    231 
    232 	if (NULL == (p = calloc(1, sizeof(struct roffnode)))) {
    233 		(*r->msg)(MANDOCERR_MEM, r->data, line, col, NULL);
    234 		return(0);
    235 	}
    236 
    237 	p->tok = tok;
    238 	p->parent = r->last;
    239 	p->line = line;
    240 	p->col = col;
    241 	p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY;
    242 
    243 	r->last = p;
    244 	return(1);
    245 }
    246 
    247 
    248 static void
    249 roff_free1(struct roff *r)
    250 {
    251 
    252 	while (r->last)
    253 		roffnode_pop(r);
    254 }
    255 
    256 
    257 void
    258 roff_reset(struct roff *r)
    259 {
    260 
    261 	roff_free1(r);
    262 }
    263 
    264 
    265 void
    266 roff_free(struct roff *r)
    267 {
    268 
    269 	roff_free1(r);
    270 	free(r);
    271 }
    272 
    273 
    274 struct roff *
    275 roff_alloc(const mandocmsg msg, void *data)
    276 {
    277 	struct roff	*r;
    278 
    279 	if (NULL == (r = calloc(1, sizeof(struct roff)))) {
    280 		(*msg)(MANDOCERR_MEM, data, 0, 0, NULL);
    281 		return(0);
    282 	}
    283 
    284 	r->msg = msg;
    285 	r->data = data;
    286 	r->rstackpos = -1;
    287 
    288 	roff_hash_init();
    289 	return(r);
    290 }
    291 
    292 
    293 enum rofferr
    294 roff_parseln(struct roff *r, int ln,
    295 		char **bufp, size_t *szp, int pos, int *offs)
    296 {
    297 	enum rofft	 t;
    298 	int		 ppos;
    299 
    300 	/*
    301 	 * First, if a scope is open and we're not a macro, pass the
    302 	 * text through the macro's filter.  If a scope isn't open and
    303 	 * we're not a macro, just let it through.
    304 	 */
    305 
    306 	if (r->last && ! ROFF_CTL((*bufp)[pos])) {
    307 		t = r->last->tok;
    308 		assert(roffs[t].text);
    309 		return((*roffs[t].text)
    310 				(r, t, bufp, szp, ln, pos, pos, offs));
    311 	} else if ( ! ROFF_CTL((*bufp)[pos]))
    312 		return(ROFF_CONT);
    313 
    314 	/*
    315 	 * If a scope is open, go to the child handler for that macro,
    316 	 * as it may want to preprocess before doing anything with it.
    317 	 */
    318 
    319 	if (r->last) {
    320 		t = r->last->tok;
    321 		assert(roffs[t].sub);
    322 		return((*roffs[t].sub)
    323 				(r, t, bufp, szp, ln, pos, pos, offs));
    324 	}
    325 
    326 	/*
    327 	 * Lastly, as we've no scope open, try to look up and execute
    328 	 * the new macro.  If no macro is found, simply return and let
    329 	 * the compilers handle it.
    330 	 */
    331 
    332 	ppos = pos;
    333 	if (ROFF_MAX == (t = roff_parse(*bufp, &pos)))
    334 		return(ROFF_CONT);
    335 
    336 	assert(roffs[t].proc);
    337 	return((*roffs[t].proc)
    338 			(r, t, bufp, szp, ln, ppos, pos, offs));
    339 }
    340 
    341 
    342 int
    343 roff_endparse(struct roff *r)
    344 {
    345 
    346 	if (NULL == r->last)
    347 		return(1);
    348 	return((*r->msg)(MANDOCERR_SCOPEEXIT, r->data, r->last->line,
    349 				r->last->col, NULL));
    350 }
    351 
    352 
    353 /*
    354  * Parse a roff node's type from the input buffer.  This must be in the
    355  * form of ".foo xxx" in the usual way.
    356  */
    357 static enum rofft
    358 roff_parse(const char *buf, int *pos)
    359 {
    360 	int		 j;
    361 	char		 mac[5];
    362 	enum rofft	 t;
    363 
    364 	assert(ROFF_CTL(buf[*pos]));
    365 	(*pos)++;
    366 
    367 	while (buf[*pos] && (' ' == buf[*pos] || '\t' == buf[*pos]))
    368 		(*pos)++;
    369 
    370 	if ('\0' == buf[*pos])
    371 		return(ROFF_MAX);
    372 
    373 	for (j = 0; j < 4; j++, (*pos)++)
    374 		if ('\0' == (mac[j] = buf[*pos]))
    375 			break;
    376 		else if (' ' == buf[*pos] || (j && '\\' == buf[*pos]))
    377 			break;
    378 
    379 	if (j == 4 || j < 1)
    380 		return(ROFF_MAX);
    381 
    382 	mac[j] = '\0';
    383 
    384 	if (ROFF_MAX == (t = roff_hash_find(mac)))
    385 		return(t);
    386 
    387 	while (buf[*pos] && ' ' == buf[*pos])
    388 		(*pos)++;
    389 
    390 	return(t);
    391 }
    392 
    393 
    394 /* ARGSUSED */
    395 static enum rofferr
    396 roff_cblock(ROFF_ARGS)
    397 {
    398 
    399 	/*
    400 	 * A block-close `..' should only be invoked as a child of an
    401 	 * ignore macro, otherwise raise a warning and just ignore it.
    402 	 */
    403 
    404 	if (NULL == r->last) {
    405 		if ( ! (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL))
    406 			return(ROFF_ERR);
    407 		return(ROFF_IGN);
    408 	}
    409 
    410 	switch (r->last->tok) {
    411 	case (ROFF_am):
    412 		/* FALLTHROUGH */
    413 	case (ROFF_ami):
    414 		/* FALLTHROUGH */
    415 	case (ROFF_am1):
    416 		/* FALLTHROUGH */
    417 	case (ROFF_de):
    418 		/* FALLTHROUGH */
    419 	case (ROFF_dei):
    420 		/* FALLTHROUGH */
    421 	case (ROFF_de1):
    422 		/* FALLTHROUGH */
    423 	case (ROFF_ig):
    424 		break;
    425 	default:
    426 		if ( ! (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL))
    427 			return(ROFF_ERR);
    428 		return(ROFF_IGN);
    429 	}
    430 
    431 	if ((*bufp)[pos])
    432 		if ( ! (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL))
    433 			return(ROFF_ERR);
    434 
    435 	roffnode_pop(r);
    436 	roffnode_cleanscope(r);
    437 	return(ROFF_IGN);
    438 
    439 }
    440 
    441 
    442 static void
    443 roffnode_cleanscope(struct roff *r)
    444 {
    445 
    446 	while (r->last) {
    447 		if (--r->last->endspan < 0)
    448 			break;
    449 		roffnode_pop(r);
    450 	}
    451 }
    452 
    453 
    454 /* ARGSUSED */
    455 static enum rofferr
    456 roff_ccond(ROFF_ARGS)
    457 {
    458 
    459 	if (NULL == r->last) {
    460 		if ( ! (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL))
    461 			return(ROFF_ERR);
    462 		return(ROFF_IGN);
    463 	}
    464 
    465 	switch (r->last->tok) {
    466 	case (ROFF_el):
    467 		/* FALLTHROUGH */
    468 	case (ROFF_ie):
    469 		/* FALLTHROUGH */
    470 	case (ROFF_if):
    471 		break;
    472 	default:
    473 		if ( ! (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL))
    474 			return(ROFF_ERR);
    475 		return(ROFF_IGN);
    476 	}
    477 
    478 	if (r->last->endspan > -1) {
    479 		if ( ! (*r->msg)(MANDOCERR_NOSCOPE, r->data, ln, ppos, NULL))
    480 			return(ROFF_ERR);
    481 		return(ROFF_IGN);
    482 	}
    483 
    484 	if ((*bufp)[pos])
    485 		if ( ! (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL))
    486 			return(ROFF_ERR);
    487 
    488 	roffnode_pop(r);
    489 	roffnode_cleanscope(r);
    490 	return(ROFF_IGN);
    491 }
    492 
    493 
    494 /* ARGSUSED */
    495 static enum rofferr
    496 roff_block(ROFF_ARGS)
    497 {
    498 	int		sv;
    499 	size_t		sz;
    500 
    501 	if (ROFF_ig != tok && '\0' == (*bufp)[pos]) {
    502 		if ( ! (*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL))
    503 			return(ROFF_ERR);
    504 		return(ROFF_IGN);
    505 	} else if (ROFF_ig != tok) {
    506 		while ((*bufp)[pos] && ' ' != (*bufp)[pos])
    507 			pos++;
    508 		while (' ' == (*bufp)[pos])
    509 			pos++;
    510 	}
    511 
    512 	if ( ! roffnode_push(r, tok, ln, ppos))
    513 		return(ROFF_ERR);
    514 
    515 	if ('\0' == (*bufp)[pos])
    516 		return(ROFF_IGN);
    517 
    518 	sv = pos;
    519 	while ((*bufp)[pos] && ' ' != (*bufp)[pos] &&
    520 			'\t' != (*bufp)[pos])
    521 		pos++;
    522 
    523 	/*
    524 	 * Note: groff does NOT like escape characters in the input.
    525 	 * Instead of detecting this, we're just going to let it fly and
    526 	 * to hell with it.
    527 	 */
    528 
    529 	assert(pos > sv);
    530 	sz = (size_t)(pos - sv);
    531 
    532 	if (1 == sz && '.' == (*bufp)[sv])
    533 		return(ROFF_IGN);
    534 
    535 	r->last->end = malloc(sz + 1);
    536 
    537 	if (NULL == r->last->end) {
    538 		(*r->msg)(MANDOCERR_MEM, r->data, ln, pos, NULL);
    539 		return(ROFF_ERR);
    540 	}
    541 
    542 	memcpy(r->last->end, *bufp + sv, sz);
    543 	r->last->end[(int)sz] = '\0';
    544 
    545 	if ((*bufp)[pos])
    546 		if ( ! (*r->msg)(MANDOCERR_ARGSLOST, r->data, ln, pos, NULL))
    547 			return(ROFF_ERR);
    548 
    549 	return(ROFF_IGN);
    550 }
    551 
    552 
    553 /* ARGSUSED */
    554 static enum rofferr
    555 roff_block_sub(ROFF_ARGS)
    556 {
    557 	enum rofft	t;
    558 	int		i, j;
    559 
    560 	/*
    561 	 * First check whether a custom macro exists at this level.  If
    562 	 * it does, then check against it.  This is some of groff's
    563 	 * stranger behaviours.  If we encountered a custom end-scope
    564 	 * tag and that tag also happens to be a "real" macro, then we
    565 	 * need to try interpreting it again as a real macro.  If it's
    566 	 * not, then return ignore.  Else continue.
    567 	 */
    568 
    569 	if (r->last->end) {
    570 		i = pos + 1;
    571 		while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
    572 			i++;
    573 
    574 		for (j = 0; r->last->end[j]; j++, i++)
    575 			if ((*bufp)[i] != r->last->end[j])
    576 				break;
    577 
    578 		if ('\0' == r->last->end[j] &&
    579 				('\0' == (*bufp)[i] ||
    580 				 ' ' == (*bufp)[i] ||
    581 				 '\t' == (*bufp)[i])) {
    582 			roffnode_pop(r);
    583 			roffnode_cleanscope(r);
    584 
    585 			if (ROFF_MAX != roff_parse(*bufp, &pos))
    586 				return(ROFF_RERUN);
    587 			return(ROFF_IGN);
    588 		}
    589 	}
    590 
    591 	/*
    592 	 * If we have no custom end-query or lookup failed, then try
    593 	 * pulling it out of the hashtable.
    594 	 */
    595 
    596 	ppos = pos;
    597 	t = roff_parse(*bufp, &pos);
    598 
    599 	/* If we're not a comment-end, then throw it away. */
    600 	if (ROFF_cblock != t)
    601 		return(ROFF_IGN);
    602 
    603 	assert(roffs[t].proc);
    604 	return((*roffs[t].proc)(r, t, bufp,
    605 			szp, ln, ppos, pos, offs));
    606 }
    607 
    608 
    609 /* ARGSUSED */
    610 static enum rofferr
    611 roff_block_text(ROFF_ARGS)
    612 {
    613 
    614 	return(ROFF_IGN);
    615 }
    616 
    617 
    618 /* ARGSUSED */
    619 static enum rofferr
    620 roff_cond_sub(ROFF_ARGS)
    621 {
    622 	enum rofft	 t;
    623 	enum roffrule	 rr;
    624 
    625 	ppos = pos;
    626 	rr = r->last->rule;
    627 
    628 	roff_cond_text(r, tok, bufp, szp, ln, ppos, pos, offs);
    629 
    630 	if (ROFF_MAX == (t = roff_parse(*bufp, &pos)))
    631 		return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
    632 
    633 	/*
    634 	 * A denied conditional must evaluate its children if and only
    635 	 * if they're either structurally required (such as loops and
    636 	 * conditionals) or a closing macro.
    637 	 */
    638 	if (ROFFRULE_DENY == rr)
    639 		if ( ! (ROFFMAC_STRUCT & roffs[t].flags))
    640 			if (ROFF_ccond != t)
    641 				return(ROFF_IGN);
    642 
    643 	assert(roffs[t].proc);
    644 	return((*roffs[t].proc)
    645 			(r, t, bufp, szp, ln, ppos, pos, offs));
    646 }
    647 
    648 
    649 /* ARGSUSED */
    650 static enum rofferr
    651 roff_cond_text(ROFF_ARGS)
    652 {
    653 	char		*ep, *st;
    654 	enum roffrule	 rr;
    655 
    656 	rr = r->last->rule;
    657 
    658 	/*
    659 	 * We display the value of the text if out current evaluation
    660 	 * scope permits us to do so.
    661 	 */
    662 
    663 	st = &(*bufp)[pos];
    664 	if (NULL == (ep = strstr(st, "\\}"))) {
    665 		roffnode_cleanscope(r);
    666 		return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
    667 	}
    668 
    669 	if (ep > st && '\\' != *(ep - 1)) {
    670 		ep = '\0';
    671 		roffnode_pop(r);
    672 	}
    673 
    674 	roffnode_cleanscope(r);
    675 	return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
    676 }
    677 
    678 
    679 /* ARGSUSED */
    680 static enum rofferr
    681 roff_cond(ROFF_ARGS)
    682 {
    683 	int		 cpos;  /* position of the condition */
    684 	int		 sv;
    685 
    686 	/* Stack overflow! */
    687 
    688 	if (ROFF_ie == tok && r->rstackpos == RSTACK_MAX - 1) {
    689 		(*r->msg)(MANDOCERR_MEM, r->data, ln, ppos, NULL);
    690 		return(ROFF_ERR);
    691 	}
    692 
    693 	cpos = pos;
    694 
    695 	if (ROFF_if == tok || ROFF_ie == tok) {
    696 		/*
    697 		 * Read ahead past the conditional.  FIXME: this does
    698 		 * not work, as conditionals don't end on whitespace,
    699 		 * but are parsed according to a formal grammar.  It's
    700 		 * good enough for now, however.
    701 		 */
    702 		while ((*bufp)[pos] && ' ' != (*bufp)[pos])
    703 			pos++;
    704 	}
    705 
    706 	sv = pos;
    707 	while (' ' == (*bufp)[pos])
    708 		pos++;
    709 
    710 	/*
    711 	 * Roff is weird.  If we have just white-space after the
    712 	 * conditional, it's considered the BODY and we exit without
    713 	 * really doing anything.  Warn about this.  It's probably
    714 	 * wrong.
    715 	 */
    716 	if ('\0' == (*bufp)[pos] && sv != pos) {
    717 		if ( ! (*r->msg)(MANDOCERR_NOARGS, r->data, ln, ppos, NULL))
    718 			return(ROFF_ERR);
    719 		return(ROFF_IGN);
    720 	}
    721 
    722 	if ( ! roffnode_push(r, tok, ln, ppos))
    723 		return(ROFF_ERR);
    724 
    725 	/* XXX: Implement more conditionals. */
    726 
    727 	if (ROFF_if == tok || ROFF_ie == tok)
    728 		r->last->rule = 'n' == (*bufp)[cpos] ?
    729 		    ROFFRULE_ALLOW : ROFFRULE_DENY;
    730 	else if (ROFF_el == tok) {
    731 		/*
    732 		 * An `.el' will get the value of the current rstack
    733 		 * entry set in prior `ie' calls or defaults to DENY.
    734 	 	 */
    735 		if (r->rstackpos < 0)
    736 			r->last->rule = ROFFRULE_DENY;
    737 		else
    738 			r->last->rule = r->rstack[r->rstackpos];
    739 	}
    740 	if (ROFF_ie == tok) {
    741 		/*
    742 		 * An if-else will put the NEGATION of the current
    743 		 * evaluated conditional into the stack.
    744 		 */
    745 		r->rstackpos++;
    746 		if (ROFFRULE_DENY == r->last->rule)
    747 			r->rstack[r->rstackpos] = ROFFRULE_ALLOW;
    748 		else
    749 			r->rstack[r->rstackpos] = ROFFRULE_DENY;
    750 	}
    751 	if (r->last->parent && ROFFRULE_DENY == r->last->parent->rule)
    752 		r->last->rule = ROFFRULE_DENY;
    753 
    754 	r->last->endspan = 1;
    755 
    756 	if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
    757 		r->last->endspan = -1;
    758 		pos += 2;
    759 	}
    760 
    761 	/*
    762 	 * If there are no arguments on the line, the next-line scope is
    763 	 * assumed.
    764 	 */
    765 
    766 	if ('\0' == (*bufp)[pos])
    767 		return(ROFF_IGN);
    768 
    769 	/* Otherwise re-run the roff parser after recalculating. */
    770 
    771 	*offs = pos;
    772 	return(ROFF_RERUN);
    773 }
    774 
    775 
    776 /* ARGSUSED */
    777 static enum rofferr
    778 roff_line(ROFF_ARGS)
    779 {
    780 
    781 	return(ROFF_IGN);
    782 }
    783