]> git.cameronkatri.com Git - mandoc.git/blob - roff.c
Fix a memory-offset bug that was hell tracking down.
[mandoc.git] / roff.c
1 /* $Id: roff.c,v 1.161 2011/07/27 14:58:28 kristaps Exp $ */
2 /*
3 * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "mandoc.h"
28 #include "libroff.h"
29 #include "libmandoc.h"
30
31 /* Maximum number of nested if-else conditionals. */
32 #define RSTACK_MAX 128
33
34 enum rofft {
35 ROFF_ad,
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_hy,
45 ROFF_ie,
46 ROFF_if,
47 ROFF_ig,
48 ROFF_it,
49 ROFF_ne,
50 ROFF_nh,
51 ROFF_nr,
52 ROFF_ns,
53 ROFF_ps,
54 ROFF_rm,
55 ROFF_so,
56 ROFF_ta,
57 ROFF_tr,
58 ROFF_TS,
59 ROFF_TE,
60 ROFF_T_,
61 ROFF_EQ,
62 ROFF_EN,
63 ROFF_cblock,
64 ROFF_ccond,
65 ROFF_USERDEF,
66 ROFF_MAX
67 };
68
69 enum roffrule {
70 ROFFRULE_ALLOW,
71 ROFFRULE_DENY
72 };
73
74 /*
75 * A single register entity. If "set" is zero, the value of the
76 * register should be the default one, which is per-register.
77 * Registers are assumed to be unsigned ints for now.
78 */
79 struct reg {
80 int set; /* whether set or not */
81 unsigned int u; /* unsigned integer */
82 };
83
84 struct roffstr {
85 char *key; /* key of symbol */
86 char *val; /* current value */
87 struct roffstr *next; /* next in list */
88 };
89
90 struct roff {
91 struct mparse *parse; /* parse point */
92 struct roffnode *last; /* leaf of stack */
93 enum roffrule rstack[RSTACK_MAX]; /* stack of !`ie' rules */
94 int rstackpos; /* position in rstack */
95 struct reg regs[REG__MAX];
96 struct roffstr *first_string; /* user-defined strings & macros */
97 const char *current_string; /* value of last called user macro */
98 struct tbl_node *first_tbl; /* first table parsed */
99 struct tbl_node *last_tbl; /* last table parsed */
100 struct tbl_node *tbl; /* current table being parsed */
101 struct eqn_node *last_eqn; /* last equation parsed */
102 struct eqn_node *first_eqn; /* first equation parsed */
103 struct eqn_node *eqn; /* current equation being parsed */
104 };
105
106 struct roffnode {
107 enum rofft tok; /* type of node */
108 struct roffnode *parent; /* up one in stack */
109 int line; /* parse line */
110 int col; /* parse col */
111 char *name; /* node name, e.g. macro name */
112 char *end; /* end-rules: custom token */
113 int endspan; /* end-rules: next-line or infty */
114 enum roffrule rule; /* current evaluation rule */
115 };
116
117 #define ROFF_ARGS struct roff *r, /* parse ctx */ \
118 enum rofft tok, /* tok of macro */ \
119 char **bufp, /* input buffer */ \
120 size_t *szp, /* size of input buffer */ \
121 int ln, /* parse line */ \
122 int ppos, /* original pos in buffer */ \
123 int pos, /* current pos in buffer */ \
124 int *offs /* reset offset of buffer data */
125
126 typedef enum rofferr (*roffproc)(ROFF_ARGS);
127
128 struct roffmac {
129 const char *name; /* macro name */
130 roffproc proc; /* process new macro */
131 roffproc text; /* process as child text of macro */
132 roffproc sub; /* process as child of macro */
133 int flags;
134 #define ROFFMAC_STRUCT (1 << 0) /* always interpret */
135 struct roffmac *next;
136 };
137
138 struct predef {
139 const char *name; /* predefined input name */
140 const char *str; /* replacement symbol */
141 };
142
143 #define PREDEF(__name, __str) \
144 { (__name), (__str) },
145
146 static enum rofft roffhash_find(const char *, size_t);
147 static void roffhash_init(void);
148 static void roffnode_cleanscope(struct roff *);
149 static void roffnode_pop(struct roff *);
150 static void roffnode_push(struct roff *, enum rofft,
151 const char *, int, int);
152 static enum rofferr roff_block(ROFF_ARGS);
153 static enum rofferr roff_block_text(ROFF_ARGS);
154 static enum rofferr roff_block_sub(ROFF_ARGS);
155 static enum rofferr roff_cblock(ROFF_ARGS);
156 static enum rofferr roff_ccond(ROFF_ARGS);
157 static enum rofferr roff_cond(ROFF_ARGS);
158 static enum rofferr roff_cond_text(ROFF_ARGS);
159 static enum rofferr roff_cond_sub(ROFF_ARGS);
160 static enum rofferr roff_ds(ROFF_ARGS);
161 static enum roffrule roff_evalcond(const char *, int *);
162 static void roff_free1(struct roff *);
163 static void roff_freestr(struct roff *);
164 static char *roff_getname(struct roff *, char **, int, int);
165 static const char *roff_getstrn(const struct roff *,
166 const char *, size_t);
167 static enum rofferr roff_line_ignore(ROFF_ARGS);
168 static enum rofferr roff_nr(ROFF_ARGS);
169 static void roff_openeqn(struct roff *, const char *,
170 int, int, const char *);
171 static enum rofft roff_parse(struct roff *, const char *, int *);
172 static enum rofferr roff_parsetext(char *);
173 static void roff_res(struct roff *,
174 char **, size_t *, int, int);
175 static enum rofferr roff_rm(ROFF_ARGS);
176 static void roff_setstr(struct roff *,
177 const char *, const char *, int);
178 static enum rofferr roff_so(ROFF_ARGS);
179 static enum rofferr roff_TE(ROFF_ARGS);
180 static enum rofferr roff_TS(ROFF_ARGS);
181 static enum rofferr roff_EQ(ROFF_ARGS);
182 static enum rofferr roff_EN(ROFF_ARGS);
183 static enum rofferr roff_T_(ROFF_ARGS);
184 static enum rofferr roff_userdef(ROFF_ARGS);
185
186 /* See roffhash_find() */
187
188 #define ASCII_HI 126
189 #define ASCII_LO 33
190 #define HASHWIDTH (ASCII_HI - ASCII_LO + 1)
191
192 static struct roffmac *hash[HASHWIDTH];
193
194 static struct roffmac roffs[ROFF_MAX] = {
195 { "ad", roff_line_ignore, NULL, NULL, 0, NULL },
196 { "am", roff_block, roff_block_text, roff_block_sub, 0, NULL },
197 { "ami", roff_block, roff_block_text, roff_block_sub, 0, NULL },
198 { "am1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
199 { "de", roff_block, roff_block_text, roff_block_sub, 0, NULL },
200 { "dei", roff_block, roff_block_text, roff_block_sub, 0, NULL },
201 { "de1", roff_block, roff_block_text, roff_block_sub, 0, NULL },
202 { "ds", roff_ds, NULL, NULL, 0, NULL },
203 { "el", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
204 { "hy", roff_line_ignore, NULL, NULL, 0, NULL },
205 { "ie", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
206 { "if", roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT, NULL },
207 { "ig", roff_block, roff_block_text, roff_block_sub, 0, NULL },
208 { "it", roff_line_ignore, NULL, NULL, 0, NULL },
209 { "ne", roff_line_ignore, NULL, NULL, 0, NULL },
210 { "nh", roff_line_ignore, NULL, NULL, 0, NULL },
211 { "nr", roff_nr, NULL, NULL, 0, NULL },
212 { "ns", roff_line_ignore, NULL, NULL, 0, NULL },
213 { "ps", roff_line_ignore, NULL, NULL, 0, NULL },
214 { "rm", roff_rm, NULL, NULL, 0, NULL },
215 { "so", roff_so, NULL, NULL, 0, NULL },
216 { "ta", roff_line_ignore, NULL, NULL, 0, NULL },
217 { "tr", roff_line_ignore, NULL, NULL, 0, NULL },
218 { "TS", roff_TS, NULL, NULL, 0, NULL },
219 { "TE", roff_TE, NULL, NULL, 0, NULL },
220 { "T&", roff_T_, NULL, NULL, 0, NULL },
221 { "EQ", roff_EQ, NULL, NULL, 0, NULL },
222 { "EN", roff_EN, NULL, NULL, 0, NULL },
223 { ".", roff_cblock, NULL, NULL, 0, NULL },
224 { "\\}", roff_ccond, NULL, NULL, 0, NULL },
225 { NULL, roff_userdef, NULL, NULL, 0, NULL },
226 };
227
228 /* Array of injected predefined strings. */
229 #define PREDEFS_MAX 38
230 static const struct predef predefs[PREDEFS_MAX] = {
231 #include "predefs.in"
232 };
233
234 /* See roffhash_find() */
235 #define ROFF_HASH(p) (p[0] - ASCII_LO)
236
237 static void
238 roffhash_init(void)
239 {
240 struct roffmac *n;
241 int buc, i;
242
243 for (i = 0; i < (int)ROFF_USERDEF; i++) {
244 assert(roffs[i].name[0] >= ASCII_LO);
245 assert(roffs[i].name[0] <= ASCII_HI);
246
247 buc = ROFF_HASH(roffs[i].name);
248
249 if (NULL != (n = hash[buc])) {
250 for ( ; n->next; n = n->next)
251 /* Do nothing. */ ;
252 n->next = &roffs[i];
253 } else
254 hash[buc] = &roffs[i];
255 }
256 }
257
258 /*
259 * Look up a roff token by its name. Returns ROFF_MAX if no macro by
260 * the nil-terminated string name could be found.
261 */
262 static enum rofft
263 roffhash_find(const char *p, size_t s)
264 {
265 int buc;
266 struct roffmac *n;
267
268 /*
269 * libroff has an extremely simple hashtable, for the time
270 * being, which simply keys on the first character, which must
271 * be printable, then walks a chain. It works well enough until
272 * optimised.
273 */
274
275 if (p[0] < ASCII_LO || p[0] > ASCII_HI)
276 return(ROFF_MAX);
277
278 buc = ROFF_HASH(p);
279
280 if (NULL == (n = hash[buc]))
281 return(ROFF_MAX);
282 for ( ; n; n = n->next)
283 if (0 == strncmp(n->name, p, s) && '\0' == n->name[(int)s])
284 return((enum rofft)(n - roffs));
285
286 return(ROFF_MAX);
287 }
288
289
290 /*
291 * Pop the current node off of the stack of roff instructions currently
292 * pending.
293 */
294 static void
295 roffnode_pop(struct roff *r)
296 {
297 struct roffnode *p;
298
299 assert(r->last);
300 p = r->last;
301
302 r->last = r->last->parent;
303 free(p->name);
304 free(p->end);
305 free(p);
306 }
307
308
309 /*
310 * Push a roff node onto the instruction stack. This must later be
311 * removed with roffnode_pop().
312 */
313 static void
314 roffnode_push(struct roff *r, enum rofft tok, const char *name,
315 int line, int col)
316 {
317 struct roffnode *p;
318
319 p = mandoc_calloc(1, sizeof(struct roffnode));
320 p->tok = tok;
321 if (name)
322 p->name = mandoc_strdup(name);
323 p->parent = r->last;
324 p->line = line;
325 p->col = col;
326 p->rule = p->parent ? p->parent->rule : ROFFRULE_DENY;
327
328 r->last = p;
329 }
330
331
332 static void
333 roff_free1(struct roff *r)
334 {
335 struct tbl_node *t;
336 struct eqn_node *e;
337
338 while (NULL != (t = r->first_tbl)) {
339 r->first_tbl = t->next;
340 tbl_free(t);
341 }
342
343 r->first_tbl = r->last_tbl = r->tbl = NULL;
344
345 while (NULL != (e = r->first_eqn)) {
346 r->first_eqn = e->next;
347 eqn_free(e);
348 }
349
350 r->first_eqn = r->last_eqn = r->eqn = NULL;
351
352 while (r->last)
353 roffnode_pop(r);
354
355 roff_freestr(r);
356 }
357
358
359 void
360 roff_reset(struct roff *r)
361 {
362 int i;
363
364 roff_free1(r);
365
366 memset(&r->regs, 0, sizeof(struct reg) * REG__MAX);
367
368 for (i = 0; i < PREDEFS_MAX; i++)
369 roff_setstr(r, predefs[i].name, predefs[i].str, 0);
370 }
371
372
373 void
374 roff_free(struct roff *r)
375 {
376
377 roff_free1(r);
378 free(r);
379 }
380
381
382 struct roff *
383 roff_alloc(struct mparse *parse)
384 {
385 struct roff *r;
386 int i;
387
388 r = mandoc_calloc(1, sizeof(struct roff));
389 r->parse = parse;
390 r->rstackpos = -1;
391
392 roffhash_init();
393
394 for (i = 0; i < PREDEFS_MAX; i++)
395 roff_setstr(r, predefs[i].name, predefs[i].str, 0);
396
397 return(r);
398 }
399
400 /*
401 * Pre-filter each and every line for reserved words (one beginning with
402 * `\*', e.g., `\*(ab'). These must be handled before the actual line
403 * is processed.
404 * This also checks the syntax of regular escapes.
405 */
406 static void
407 roff_res(struct roff *r, char **bufp, size_t *szp, int ln, int pos)
408 {
409 enum mandoc_esc esc;
410 const char *stesc; /* start of an escape sequence ('\\') */
411 const char *stnam; /* start of the name, after "[(*" */
412 const char *cp; /* end of the name, e.g. before ']' */
413 const char *res; /* the string to be substituted */
414 int i, maxl;
415 size_t nsz;
416 char *n;
417
418 again:
419 cp = *bufp + pos;
420 while (NULL != (cp = strchr(cp, '\\'))) {
421 stesc = cp++;
422
423 /*
424 * The second character must be an asterisk.
425 * If it isn't, skip it anyway: It is escaped,
426 * so it can't start another escape sequence.
427 */
428
429 if ('\0' == *cp)
430 return;
431
432 if ('*' != *cp) {
433 res = cp;
434 esc = mandoc_escape(&cp, NULL, NULL);
435 if (ESCAPE_ERROR != esc)
436 continue;
437 cp = res;
438 mandoc_msg
439 (MANDOCERR_BADESCAPE, r->parse,
440 ln, (int)(stesc - *bufp), NULL);
441 return;
442 }
443
444 cp++;
445
446 /*
447 * The third character decides the length
448 * of the name of the string.
449 * Save a pointer to the name.
450 */
451
452 switch (*cp) {
453 case ('\0'):
454 return;
455 case ('('):
456 cp++;
457 maxl = 2;
458 break;
459 case ('['):
460 cp++;
461 maxl = 0;
462 break;
463 default:
464 maxl = 1;
465 break;
466 }
467 stnam = cp;
468
469 /* Advance to the end of the name. */
470
471 for (i = 0; 0 == maxl || i < maxl; i++, cp++) {
472 if ('\0' == *cp) {
473 mandoc_msg
474 (MANDOCERR_BADESCAPE,
475 r->parse, ln,
476 (int)(stesc - *bufp), NULL);
477 return;
478 }
479 if (0 == maxl && ']' == *cp)
480 break;
481 }
482
483 /*
484 * Retrieve the replacement string; if it is
485 * undefined, resume searching for escapes.
486 */
487
488 res = roff_getstrn(r, stnam, (size_t)i);
489
490 if (NULL == res) {
491 mandoc_msg
492 (MANDOCERR_BADESCAPE, r->parse,
493 ln, (int)(stesc - *bufp), NULL);
494 res = "";
495 }
496
497 /* Replace the escape sequence by the string. */
498
499 pos = stesc - *bufp;
500
501 nsz = *szp + strlen(res) + 1;
502 n = mandoc_malloc(nsz);
503
504 strlcpy(n, *bufp, (size_t)(stesc - *bufp + 1));
505 strlcat(n, res, nsz);
506 strlcat(n, cp + (maxl ? 0 : 1), nsz);
507
508 free(*bufp);
509
510 *bufp = n;
511 *szp = nsz;
512 goto again;
513 }
514 }
515
516 /*
517 * Process text streams: convert all breakable hyphens into ASCII_HYPH.
518 */
519 static enum rofferr
520 roff_parsetext(char *p)
521 {
522 char l, r;
523 size_t sz;
524 const char *start;
525 enum mandoc_esc esc;
526
527 start = p;
528
529 while ('\0' != *p) {
530 sz = strcspn(p, "-\\");
531 p += sz;
532
533 if ('\0' == *p)
534 break;
535
536 if ('\\' == *p) {
537 /* Skip over escapes. */
538 p++;
539 esc = mandoc_escape
540 ((const char **)&p, NULL, NULL);
541 if (ESCAPE_ERROR == esc)
542 break;
543 continue;
544 } else if (p == start) {
545 p++;
546 continue;
547 }
548
549 l = *(p - 1);
550 r = *(p + 1);
551 if ('\\' != l &&
552 '\t' != r && '\t' != l &&
553 ' ' != r && ' ' != l &&
554 '-' != r && '-' != l &&
555 ! isdigit((unsigned char)l) &&
556 ! isdigit((unsigned char)r))
557 *p = ASCII_HYPH;
558 p++;
559 }
560
561 return(ROFF_CONT);
562 }
563
564 enum rofferr
565 roff_parseln(struct roff *r, int ln, char **bufp,
566 size_t *szp, int pos, int *offs)
567 {
568 enum rofft t;
569 enum rofferr e;
570 int ppos, ctl;
571
572 /*
573 * Run the reserved-word filter only if we have some reserved
574 * words to fill in.
575 */
576
577 roff_res(r, bufp, szp, ln, pos);
578
579 ppos = pos;
580 ctl = mandoc_getcontrol(*bufp, &pos);
581
582 /*
583 * First, if a scope is open and we're not a macro, pass the
584 * text through the macro's filter. If a scope isn't open and
585 * we're not a macro, just let it through.
586 * Finally, if there's an equation scope open, divert it into it
587 * no matter our state.
588 */
589
590 if (r->last && ! ctl) {
591 t = r->last->tok;
592 assert(roffs[t].text);
593 e = (*roffs[t].text)
594 (r, t, bufp, szp, ln, pos, pos, offs);
595 assert(ROFF_IGN == e || ROFF_CONT == e);
596 if (ROFF_CONT != e)
597 return(e);
598 if (r->eqn)
599 return(eqn_read(&r->eqn, ln, *bufp, pos, offs));
600 if (r->tbl)
601 return(tbl_read(r->tbl, ln, *bufp, pos));
602 return(roff_parsetext(*bufp + pos));
603 } else if ( ! ctl) {
604 if (r->eqn)
605 return(eqn_read(&r->eqn, ln, *bufp, pos, offs));
606 if (r->tbl)
607 return(tbl_read(r->tbl, ln, *bufp, pos));
608 return(roff_parsetext(*bufp + pos));
609 } else if (r->eqn)
610 return(eqn_read(&r->eqn, ln, *bufp, ppos, offs));
611
612 /*
613 * If a scope is open, go to the child handler for that macro,
614 * as it may want to preprocess before doing anything with it.
615 * Don't do so if an equation is open.
616 */
617
618 if (r->last) {
619 t = r->last->tok;
620 assert(roffs[t].sub);
621 return((*roffs[t].sub)
622 (r, t, bufp, szp,
623 ln, ppos, pos, offs));
624 }
625
626 /*
627 * Lastly, as we've no scope open, try to look up and execute
628 * the new macro. If no macro is found, simply return and let
629 * the compilers handle it.
630 */
631
632 if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos)))
633 return(ROFF_CONT);
634
635 assert(roffs[t].proc);
636 return((*roffs[t].proc)
637 (r, t, bufp, szp,
638 ln, ppos, pos, offs));
639 }
640
641
642 void
643 roff_endparse(struct roff *r)
644 {
645
646 if (r->last)
647 mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
648 r->last->line, r->last->col, NULL);
649
650 if (r->eqn) {
651 mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
652 r->eqn->eqn.ln, r->eqn->eqn.pos, NULL);
653 eqn_end(&r->eqn);
654 }
655
656 if (r->tbl) {
657 mandoc_msg(MANDOCERR_SCOPEEXIT, r->parse,
658 r->tbl->line, r->tbl->pos, NULL);
659 tbl_end(&r->tbl);
660 }
661 }
662
663 /*
664 * Parse a roff node's type from the input buffer. This must be in the
665 * form of ".foo xxx" in the usual way.
666 */
667 static enum rofft
668 roff_parse(struct roff *r, const char *buf, int *pos)
669 {
670 const char *mac;
671 size_t maclen;
672 enum rofft t;
673
674 if ('\0' == buf[*pos] || '"' == buf[*pos] ||
675 '\t' == buf[*pos] || ' ' == buf[*pos])
676 return(ROFF_MAX);
677
678 /*
679 * We stop the macro parse at an escape, tab, space, or nil.
680 * However, `\}' is also a valid macro, so make sure we don't
681 * clobber it by seeing the `\' as the end of token.
682 */
683
684 mac = buf + *pos;
685 maclen = strcspn(mac + 1, " \\\t\0") + 1;
686
687 t = (r->current_string = roff_getstrn(r, mac, maclen))
688 ? ROFF_USERDEF : roffhash_find(mac, maclen);
689
690 *pos += (int)maclen;
691
692 while (buf[*pos] && ' ' == buf[*pos])
693 (*pos)++;
694
695 return(t);
696 }
697
698 /* ARGSUSED */
699 static enum rofferr
700 roff_cblock(ROFF_ARGS)
701 {
702
703 /*
704 * A block-close `..' should only be invoked as a child of an
705 * ignore macro, otherwise raise a warning and just ignore it.
706 */
707
708 if (NULL == r->last) {
709 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
710 return(ROFF_IGN);
711 }
712
713 switch (r->last->tok) {
714 case (ROFF_am):
715 /* FALLTHROUGH */
716 case (ROFF_ami):
717 /* FALLTHROUGH */
718 case (ROFF_am1):
719 /* FALLTHROUGH */
720 case (ROFF_de):
721 /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
722 /* FALLTHROUGH */
723 case (ROFF_dei):
724 /* FALLTHROUGH */
725 case (ROFF_ig):
726 break;
727 default:
728 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
729 return(ROFF_IGN);
730 }
731
732 if ((*bufp)[pos])
733 mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
734
735 roffnode_pop(r);
736 roffnode_cleanscope(r);
737 return(ROFF_IGN);
738
739 }
740
741
742 static void
743 roffnode_cleanscope(struct roff *r)
744 {
745
746 while (r->last) {
747 if (--r->last->endspan < 0)
748 break;
749 roffnode_pop(r);
750 }
751 }
752
753
754 /* ARGSUSED */
755 static enum rofferr
756 roff_ccond(ROFF_ARGS)
757 {
758
759 if (NULL == r->last) {
760 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
761 return(ROFF_IGN);
762 }
763
764 switch (r->last->tok) {
765 case (ROFF_el):
766 /* FALLTHROUGH */
767 case (ROFF_ie):
768 /* FALLTHROUGH */
769 case (ROFF_if):
770 break;
771 default:
772 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
773 return(ROFF_IGN);
774 }
775
776 if (r->last->endspan > -1) {
777 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
778 return(ROFF_IGN);
779 }
780
781 if ((*bufp)[pos])
782 mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
783
784 roffnode_pop(r);
785 roffnode_cleanscope(r);
786 return(ROFF_IGN);
787 }
788
789
790 /* ARGSUSED */
791 static enum rofferr
792 roff_block(ROFF_ARGS)
793 {
794 int sv;
795 size_t sz;
796 char *name;
797
798 name = NULL;
799
800 if (ROFF_ig != tok) {
801 if ('\0' == (*bufp)[pos]) {
802 mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
803 return(ROFF_IGN);
804 }
805
806 /*
807 * Re-write `de1', since we don't really care about
808 * groff's strange compatibility mode, into `de'.
809 */
810
811 if (ROFF_de1 == tok)
812 tok = ROFF_de;
813 if (ROFF_de == tok)
814 name = *bufp + pos;
815 else
816 mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos,
817 roffs[tok].name);
818
819 while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
820 pos++;
821
822 while (isspace((unsigned char)(*bufp)[pos]))
823 (*bufp)[pos++] = '\0';
824 }
825
826 roffnode_push(r, tok, name, ln, ppos);
827
828 /*
829 * At the beginning of a `de' macro, clear the existing string
830 * with the same name, if there is one. New content will be
831 * added from roff_block_text() in multiline mode.
832 */
833
834 if (ROFF_de == tok)
835 roff_setstr(r, name, "", 0);
836
837 if ('\0' == (*bufp)[pos])
838 return(ROFF_IGN);
839
840 /* If present, process the custom end-of-line marker. */
841
842 sv = pos;
843 while ((*bufp)[pos] && ! isspace((unsigned char)(*bufp)[pos]))
844 pos++;
845
846 /*
847 * Note: groff does NOT like escape characters in the input.
848 * Instead of detecting this, we're just going to let it fly and
849 * to hell with it.
850 */
851
852 assert(pos > sv);
853 sz = (size_t)(pos - sv);
854
855 if (1 == sz && '.' == (*bufp)[sv])
856 return(ROFF_IGN);
857
858 r->last->end = mandoc_malloc(sz + 1);
859
860 memcpy(r->last->end, *bufp + sv, sz);
861 r->last->end[(int)sz] = '\0';
862
863 if ((*bufp)[pos])
864 mandoc_msg(MANDOCERR_ARGSLOST, r->parse, ln, pos, NULL);
865
866 return(ROFF_IGN);
867 }
868
869
870 /* ARGSUSED */
871 static enum rofferr
872 roff_block_sub(ROFF_ARGS)
873 {
874 enum rofft t;
875 int i, j;
876
877 /*
878 * First check whether a custom macro exists at this level. If
879 * it does, then check against it. This is some of groff's
880 * stranger behaviours. If we encountered a custom end-scope
881 * tag and that tag also happens to be a "real" macro, then we
882 * need to try interpreting it again as a real macro. If it's
883 * not, then return ignore. Else continue.
884 */
885
886 if (r->last->end) {
887 for (i = pos, j = 0; r->last->end[j]; j++, i++)
888 if ((*bufp)[i] != r->last->end[j])
889 break;
890
891 if ('\0' == r->last->end[j] &&
892 ('\0' == (*bufp)[i] ||
893 ' ' == (*bufp)[i] ||
894 '\t' == (*bufp)[i])) {
895 roffnode_pop(r);
896 roffnode_cleanscope(r);
897
898 while (' ' == (*bufp)[i] || '\t' == (*bufp)[i])
899 i++;
900
901 pos = i;
902 if (ROFF_MAX != roff_parse(r, *bufp, &pos))
903 return(ROFF_RERUN);
904 return(ROFF_IGN);
905 }
906 }
907
908 /*
909 * If we have no custom end-query or lookup failed, then try
910 * pulling it out of the hashtable.
911 */
912
913 t = roff_parse(r, *bufp, &pos);
914
915 /*
916 * Macros other than block-end are only significant
917 * in `de' blocks; elsewhere, simply throw them away.
918 */
919 if (ROFF_cblock != t) {
920 if (ROFF_de == tok)
921 roff_setstr(r, r->last->name, *bufp + ppos, 1);
922 return(ROFF_IGN);
923 }
924
925 assert(roffs[t].proc);
926 return((*roffs[t].proc)(r, t, bufp, szp,
927 ln, ppos, pos, offs));
928 }
929
930
931 /* ARGSUSED */
932 static enum rofferr
933 roff_block_text(ROFF_ARGS)
934 {
935
936 if (ROFF_de == tok)
937 roff_setstr(r, r->last->name, *bufp + pos, 1);
938
939 return(ROFF_IGN);
940 }
941
942
943 /* ARGSUSED */
944 static enum rofferr
945 roff_cond_sub(ROFF_ARGS)
946 {
947 enum rofft t;
948 enum roffrule rr;
949 char *ep;
950
951 rr = r->last->rule;
952 roffnode_cleanscope(r);
953
954 /*
955 * If the macro is unknown, first check if it contains a closing
956 * delimiter `\}'. If it does, close out our scope and return
957 * the currently-scoped rule (ignore or continue). Else, drop
958 * into the currently-scoped rule.
959 */
960
961 if (ROFF_MAX == (t = roff_parse(r, *bufp, &pos))) {
962 ep = &(*bufp)[pos];
963 for ( ; NULL != (ep = strchr(ep, '\\')); ep++) {
964 ep++;
965 if ('}' != *ep)
966 continue;
967
968 /*
969 * Make the \} go away.
970 * This is a little haphazard, as it's not quite
971 * clear how nroff does this.
972 * If we're at the end of line, then just chop
973 * off the \} and resize the buffer.
974 * If we aren't, then conver it to spaces.
975 */
976
977 if ('\0' == *(ep + 1)) {
978 *--ep = '\0';
979 *szp -= 2;
980 } else
981 *(ep - 1) = *ep = ' ';
982
983 roff_ccond(r, ROFF_ccond, bufp, szp,
984 ln, pos, pos + 2, offs);
985 break;
986 }
987 return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
988 }
989
990 /*
991 * A denied conditional must evaluate its children if and only
992 * if they're either structurally required (such as loops and
993 * conditionals) or a closing macro.
994 */
995
996 if (ROFFRULE_DENY == rr)
997 if ( ! (ROFFMAC_STRUCT & roffs[t].flags))
998 if (ROFF_ccond != t)
999 return(ROFF_IGN);
1000
1001 assert(roffs[t].proc);
1002 return((*roffs[t].proc)(r, t, bufp, szp,
1003 ln, ppos, pos, offs));
1004 }
1005
1006 /* ARGSUSED */
1007 static enum rofferr
1008 roff_cond_text(ROFF_ARGS)
1009 {
1010 char *ep;
1011 enum roffrule rr;
1012
1013 rr = r->last->rule;
1014 roffnode_cleanscope(r);
1015
1016 ep = &(*bufp)[pos];
1017 for ( ; NULL != (ep = strchr(ep, '\\')); ep++) {
1018 ep++;
1019 if ('}' != *ep)
1020 continue;
1021 *ep = '&';
1022 roff_ccond(r, ROFF_ccond, bufp, szp,
1023 ln, pos, pos + 2, offs);
1024 }
1025 return(ROFFRULE_DENY == rr ? ROFF_IGN : ROFF_CONT);
1026 }
1027
1028 static enum roffrule
1029 roff_evalcond(const char *v, int *pos)
1030 {
1031
1032 switch (v[*pos]) {
1033 case ('n'):
1034 (*pos)++;
1035 return(ROFFRULE_ALLOW);
1036 case ('e'):
1037 /* FALLTHROUGH */
1038 case ('o'):
1039 /* FALLTHROUGH */
1040 case ('t'):
1041 (*pos)++;
1042 return(ROFFRULE_DENY);
1043 default:
1044 break;
1045 }
1046
1047 while (v[*pos] && ' ' != v[*pos])
1048 (*pos)++;
1049 return(ROFFRULE_DENY);
1050 }
1051
1052 /* ARGSUSED */
1053 static enum rofferr
1054 roff_line_ignore(ROFF_ARGS)
1055 {
1056
1057 if (ROFF_it == tok)
1058 mandoc_msg(MANDOCERR_REQUEST, r->parse, ln, ppos, "it");
1059
1060 return(ROFF_IGN);
1061 }
1062
1063 /* ARGSUSED */
1064 static enum rofferr
1065 roff_cond(ROFF_ARGS)
1066 {
1067 int sv;
1068 enum roffrule rule;
1069
1070 /*
1071 * An `.el' has no conditional body: it will consume the value
1072 * of the current rstack entry set in prior `ie' calls or
1073 * defaults to DENY.
1074 *
1075 * If we're not an `el', however, then evaluate the conditional.
1076 */
1077
1078 rule = ROFF_el == tok ?
1079 (r->rstackpos < 0 ?
1080 ROFFRULE_DENY : r->rstack[r->rstackpos--]) :
1081 roff_evalcond(*bufp, &pos);
1082
1083 sv = pos;
1084 while (' ' == (*bufp)[pos])
1085 pos++;
1086
1087 /*
1088 * Roff is weird. If we have just white-space after the
1089 * conditional, it's considered the BODY and we exit without
1090 * really doing anything. Warn about this. It's probably
1091 * wrong.
1092 */
1093
1094 if ('\0' == (*bufp)[pos] && sv != pos) {
1095 mandoc_msg(MANDOCERR_NOARGS, r->parse, ln, ppos, NULL);
1096 return(ROFF_IGN);
1097 }
1098
1099 roffnode_push(r, tok, NULL, ln, ppos);
1100
1101 r->last->rule = rule;
1102
1103 /*
1104 * An if-else will put the NEGATION of the current evaluated
1105 * conditional into the stack of rules.
1106 */
1107
1108 if (ROFF_ie == tok) {
1109 if (r->rstackpos == RSTACK_MAX - 1) {
1110 mandoc_msg(MANDOCERR_MEM,
1111 r->parse, ln, ppos, NULL);
1112 return(ROFF_ERR);
1113 }
1114 r->rstack[++r->rstackpos] =
1115 ROFFRULE_DENY == r->last->rule ?
1116 ROFFRULE_ALLOW : ROFFRULE_DENY;
1117 }
1118
1119 /* If the parent has false as its rule, then so do we. */
1120
1121 if (r->last->parent && ROFFRULE_DENY == r->last->parent->rule)
1122 r->last->rule = ROFFRULE_DENY;
1123
1124 /*
1125 * Determine scope. If we're invoked with "\{" trailing the
1126 * conditional, then we're in a multiline scope. Else our scope
1127 * expires on the next line.
1128 */
1129
1130 r->last->endspan = 1;
1131
1132 if ('\\' == (*bufp)[pos] && '{' == (*bufp)[pos + 1]) {
1133 r->last->endspan = -1;
1134 pos += 2;
1135 }
1136
1137 /*
1138 * If there are no arguments on the line, the next-line scope is
1139 * assumed.
1140 */
1141
1142 if ('\0' == (*bufp)[pos])
1143 return(ROFF_IGN);
1144
1145 /* Otherwise re-run the roff parser after recalculating. */
1146
1147 *offs = pos;
1148 return(ROFF_RERUN);
1149 }
1150
1151
1152 /* ARGSUSED */
1153 static enum rofferr
1154 roff_ds(ROFF_ARGS)
1155 {
1156 char *name, *string;
1157
1158 /*
1159 * A symbol is named by the first word following the macro
1160 * invocation up to a space. Its value is anything after the
1161 * name's trailing whitespace and optional double-quote. Thus,
1162 *
1163 * [.ds foo "bar " ]
1164 *
1165 * will have `bar " ' as its value.
1166 */
1167
1168 string = *bufp + pos;
1169 name = roff_getname(r, &string, ln, pos);
1170 if ('\0' == *name)
1171 return(ROFF_IGN);
1172
1173 /* Read past initial double-quote. */
1174 if ('"' == *string)
1175 string++;
1176
1177 /* The rest is the value. */
1178 roff_setstr(r, name, string, 0);
1179 return(ROFF_IGN);
1180 }
1181
1182 int
1183 roff_regisset(const struct roff *r, enum regs reg)
1184 {
1185
1186 return(r->regs[(int)reg].set);
1187 }
1188
1189 unsigned int
1190 roff_regget(const struct roff *r, enum regs reg)
1191 {
1192
1193 return(r->regs[(int)reg].u);
1194 }
1195
1196 void
1197 roff_regunset(struct roff *r, enum regs reg)
1198 {
1199
1200 r->regs[(int)reg].set = 0;
1201 }
1202
1203 /* ARGSUSED */
1204 static enum rofferr
1205 roff_nr(ROFF_ARGS)
1206 {
1207 const char *key;
1208 char *val;
1209 int iv;
1210
1211 val = *bufp + pos;
1212 key = roff_getname(r, &val, ln, pos);
1213
1214 if (0 == strcmp(key, "nS")) {
1215 r->regs[(int)REG_nS].set = 1;
1216 if ((iv = mandoc_strntoi(val, strlen(val), 10)) >= 0)
1217 r->regs[(int)REG_nS].u = (unsigned)iv;
1218 else
1219 r->regs[(int)REG_nS].u = 0u;
1220 }
1221
1222 return(ROFF_IGN);
1223 }
1224
1225 /* ARGSUSED */
1226 static enum rofferr
1227 roff_rm(ROFF_ARGS)
1228 {
1229 const char *name;
1230 char *cp;
1231
1232 cp = *bufp + pos;
1233 while ('\0' != *cp) {
1234 name = roff_getname(r, &cp, ln, (int)(cp - *bufp));
1235 if ('\0' != *name)
1236 roff_setstr(r, name, NULL, 0);
1237 }
1238 return(ROFF_IGN);
1239 }
1240
1241 /* ARGSUSED */
1242 static enum rofferr
1243 roff_TE(ROFF_ARGS)
1244 {
1245
1246 if (NULL == r->tbl)
1247 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
1248 else
1249 tbl_end(&r->tbl);
1250
1251 return(ROFF_IGN);
1252 }
1253
1254 /* ARGSUSED */
1255 static enum rofferr
1256 roff_T_(ROFF_ARGS)
1257 {
1258
1259 if (NULL == r->tbl)
1260 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
1261 else
1262 tbl_restart(ppos, ln, r->tbl);
1263
1264 return(ROFF_IGN);
1265 }
1266
1267 #if 0
1268 static int
1269 roff_closeeqn(struct roff *r)
1270 {
1271
1272 return(r->eqn && ROFF_EQN == eqn_end(&r->eqn) ? 1 : 0);
1273 }
1274 #endif
1275
1276 static void
1277 roff_openeqn(struct roff *r, const char *name, int line,
1278 int offs, const char *buf)
1279 {
1280 struct eqn_node *e;
1281 int poff;
1282
1283 assert(NULL == r->eqn);
1284 e = eqn_alloc(name, offs, line, r->parse);
1285
1286 if (r->last_eqn)
1287 r->last_eqn->next = e;
1288 else
1289 r->first_eqn = r->last_eqn = e;
1290
1291 r->eqn = r->last_eqn = e;
1292
1293 if (buf) {
1294 poff = 0;
1295 eqn_read(&r->eqn, line, buf, offs, &poff);
1296 }
1297 }
1298
1299 /* ARGSUSED */
1300 static enum rofferr
1301 roff_EQ(ROFF_ARGS)
1302 {
1303
1304 roff_openeqn(r, *bufp + pos, ln, ppos, NULL);
1305 return(ROFF_IGN);
1306 }
1307
1308 /* ARGSUSED */
1309 static enum rofferr
1310 roff_EN(ROFF_ARGS)
1311 {
1312
1313 mandoc_msg(MANDOCERR_NOSCOPE, r->parse, ln, ppos, NULL);
1314 return(ROFF_IGN);
1315 }
1316
1317 /* ARGSUSED */
1318 static enum rofferr
1319 roff_TS(ROFF_ARGS)
1320 {
1321 struct tbl_node *t;
1322
1323 if (r->tbl) {
1324 mandoc_msg(MANDOCERR_SCOPEBROKEN, r->parse, ln, ppos, NULL);
1325 tbl_end(&r->tbl);
1326 }
1327
1328 t = tbl_alloc(ppos, ln, r->parse);
1329
1330 if (r->last_tbl)
1331 r->last_tbl->next = t;
1332 else
1333 r->first_tbl = r->last_tbl = t;
1334
1335 r->tbl = r->last_tbl = t;
1336 return(ROFF_IGN);
1337 }
1338
1339 /* ARGSUSED */
1340 static enum rofferr
1341 roff_so(ROFF_ARGS)
1342 {
1343 char *name;
1344
1345 mandoc_msg(MANDOCERR_SO, r->parse, ln, ppos, NULL);
1346
1347 /*
1348 * Handle `so'. Be EXTREMELY careful, as we shouldn't be
1349 * opening anything that's not in our cwd or anything beneath
1350 * it. Thus, explicitly disallow traversing up the file-system
1351 * or using absolute paths.
1352 */
1353
1354 name = *bufp + pos;
1355 if ('/' == *name || strstr(name, "../") || strstr(name, "/..")) {
1356 mandoc_msg(MANDOCERR_SOPATH, r->parse, ln, pos, NULL);
1357 return(ROFF_ERR);
1358 }
1359
1360 *offs = pos;
1361 return(ROFF_SO);
1362 }
1363
1364 /* ARGSUSED */
1365 static enum rofferr
1366 roff_userdef(ROFF_ARGS)
1367 {
1368 const char *arg[9];
1369 char *cp, *n1, *n2;
1370 int i;
1371
1372 /*
1373 * Collect pointers to macro argument strings
1374 * and null-terminate them.
1375 */
1376 cp = *bufp + pos;
1377 for (i = 0; i < 9; i++)
1378 arg[i] = '\0' == *cp ? "" :
1379 mandoc_getarg(r->parse, &cp, ln, &pos);
1380
1381 /*
1382 * Expand macro arguments.
1383 */
1384 *szp = 0;
1385 n1 = cp = mandoc_strdup(r->current_string);
1386 while (NULL != (cp = strstr(cp, "\\$"))) {
1387 i = cp[2] - '1';
1388 if (0 > i || 8 < i) {
1389 /* Not an argument invocation. */
1390 cp += 2;
1391 continue;
1392 }
1393
1394 *szp = strlen(n1) - 3 + strlen(arg[i]) + 1;
1395 n2 = mandoc_malloc(*szp);
1396
1397 strlcpy(n2, n1, (size_t)(cp - n1 + 1));
1398 strlcat(n2, arg[i], *szp);
1399 strlcat(n2, cp + 3, *szp);
1400
1401 cp = n2 + (cp - n1);
1402 free(n1);
1403 n1 = n2;
1404 }
1405
1406 /*
1407 * Replace the macro invocation
1408 * by the expanded macro.
1409 */
1410 free(*bufp);
1411 *bufp = n1;
1412 if (0 == *szp)
1413 *szp = strlen(*bufp) + 1;
1414
1415 return(*szp > 1 && '\n' == (*bufp)[(int)*szp - 2] ?
1416 ROFF_REPARSE : ROFF_APPEND);
1417 }
1418
1419 static char *
1420 roff_getname(struct roff *r, char **cpp, int ln, int pos)
1421 {
1422 char *name, *cp;
1423
1424 name = *cpp;
1425 if ('\0' == *name)
1426 return(name);
1427
1428 /* Read until end of name. */
1429 for (cp = name; '\0' != *cp && ' ' != *cp; cp++) {
1430 if ('\\' != *cp)
1431 continue;
1432 cp++;
1433 if ('\\' == *cp)
1434 continue;
1435 mandoc_msg(MANDOCERR_NAMESC, r->parse, ln, pos, NULL);
1436 *cp = '\0';
1437 name = cp;
1438 }
1439
1440 /* Nil-terminate name. */
1441 if ('\0' != *cp)
1442 *(cp++) = '\0';
1443
1444 /* Read past spaces. */
1445 while (' ' == *cp)
1446 cp++;
1447
1448 *cpp = cp;
1449 return(name);
1450 }
1451
1452 /*
1453 * Store *string into the user-defined string called *name.
1454 * In multiline mode, append to an existing entry and append '\n';
1455 * else replace the existing entry, if there is one.
1456 * To clear an existing entry, call with (*r, *name, NULL, 0).
1457 */
1458 static void
1459 roff_setstr(struct roff *r, const char *name, const char *string,
1460 int multiline)
1461 {
1462 struct roffstr *n;
1463 char *c;
1464 size_t oldch, newch;
1465
1466 /* Search for an existing string with the same name. */
1467 n = r->first_string;
1468 while (n && strcmp(name, n->key))
1469 n = n->next;
1470
1471 if (NULL == n) {
1472 /* Create a new string table entry. */
1473 n = mandoc_malloc(sizeof(struct roffstr));
1474 n->key = mandoc_strdup(name);
1475 n->val = NULL;
1476 n->next = r->first_string;
1477 r->first_string = n;
1478 } else if (0 == multiline) {
1479 /* In multiline mode, append; else replace. */
1480 free(n->val);
1481 n->val = NULL;
1482 }
1483
1484 if (NULL == string)
1485 return;
1486
1487 /*
1488 * One additional byte for the '\n' in multiline mode,
1489 * and one for the terminating '\0'.
1490 */
1491 newch = strlen(string) + (multiline ? 2u : 1u);
1492 if (NULL == n->val) {
1493 n->val = mandoc_malloc(newch);
1494 *n->val = '\0';
1495 oldch = 0;
1496 } else {
1497 oldch = strlen(n->val);
1498 n->val = mandoc_realloc(n->val, oldch + newch);
1499 }
1500
1501 /* Skip existing content in the destination buffer. */
1502 c = n->val + (int)oldch;
1503
1504 /* Append new content to the destination buffer. */
1505 while (*string) {
1506 /*
1507 * Rudimentary roff copy mode:
1508 * Handle escaped backslashes.
1509 */
1510 if ('\\' == *string && '\\' == *(string + 1))
1511 string++;
1512 *c++ = *string++;
1513 }
1514
1515 /* Append terminating bytes. */
1516 if (multiline)
1517 *c++ = '\n';
1518 *c = '\0';
1519 }
1520
1521 static const char *
1522 roff_getstrn(const struct roff *r, const char *name, size_t len)
1523 {
1524 const struct roffstr *n;
1525
1526 for (n = r->first_string; n; n = n->next)
1527 if (0 == strncmp(name, n->key, len) &&
1528 '\0' == n->key[(int)len])
1529 return(n->val);
1530
1531 return(NULL);
1532 }
1533
1534 static void
1535 roff_freestr(struct roff *r)
1536 {
1537 struct roffstr *n, *nn;
1538
1539 for (n = r->first_string; n; n = nn) {
1540 free(n->key);
1541 free(n->val);
1542 nn = n->next;
1543 free(n);
1544 }
1545
1546 r->first_string = NULL;
1547 }
1548
1549 const struct tbl_span *
1550 roff_span(const struct roff *r)
1551 {
1552
1553 return(r->tbl ? tbl_span(r->tbl) : NULL);
1554 }
1555
1556 const struct eqn *
1557 roff_eqn(const struct roff *r)
1558 {
1559
1560 return(r->last_eqn ? &r->last_eqn->eqn : NULL);
1561 }
1562
1563 char
1564 roff_eqndelim(const struct roff *r)
1565 {
1566
1567 return('\0');
1568 }