1*c1c95addSBrooks Davis /* $Id: man.c,v 1.189 2022/08/16 23:01:09 schwarze Exp $ */
261d06d6bSBaptiste Daroussin /*
3*c1c95addSBrooks Davis * Copyright (c) 2013-2015,2017-2019,2022 Ingo Schwarze <schwarze@openbsd.org>
461d06d6bSBaptiste Daroussin * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
561d06d6bSBaptiste Daroussin * Copyright (c) 2011 Joerg Sonnenberger <joerg@netbsd.org>
661d06d6bSBaptiste Daroussin *
761d06d6bSBaptiste Daroussin * Permission to use, copy, modify, and distribute this software for any
861d06d6bSBaptiste Daroussin * purpose with or without fee is hereby granted, provided that the above
961d06d6bSBaptiste Daroussin * copyright notice and this permission notice appear in all copies.
1061d06d6bSBaptiste Daroussin *
1161d06d6bSBaptiste Daroussin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1261d06d6bSBaptiste Daroussin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1361d06d6bSBaptiste Daroussin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1461d06d6bSBaptiste Daroussin * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1561d06d6bSBaptiste Daroussin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1661d06d6bSBaptiste Daroussin * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1761d06d6bSBaptiste Daroussin * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1861d06d6bSBaptiste Daroussin */
1961d06d6bSBaptiste Daroussin #include "config.h"
2061d06d6bSBaptiste Daroussin
2161d06d6bSBaptiste Daroussin #include <sys/types.h>
2261d06d6bSBaptiste Daroussin
2361d06d6bSBaptiste Daroussin #include <assert.h>
2461d06d6bSBaptiste Daroussin #include <ctype.h>
2561d06d6bSBaptiste Daroussin #include <stdarg.h>
2661d06d6bSBaptiste Daroussin #include <stdlib.h>
2761d06d6bSBaptiste Daroussin #include <stdio.h>
2861d06d6bSBaptiste Daroussin #include <string.h>
2961d06d6bSBaptiste Daroussin
3061d06d6bSBaptiste Daroussin #include "mandoc_aux.h"
3161d06d6bSBaptiste Daroussin #include "mandoc.h"
3261d06d6bSBaptiste Daroussin #include "roff.h"
3361d06d6bSBaptiste Daroussin #include "man.h"
3461d06d6bSBaptiste Daroussin #include "libmandoc.h"
3561d06d6bSBaptiste Daroussin #include "roff_int.h"
3661d06d6bSBaptiste Daroussin #include "libman.h"
3761d06d6bSBaptiste Daroussin
387295610fSBaptiste Daroussin static char *man_hasc(char *);
3961d06d6bSBaptiste Daroussin static int man_ptext(struct roff_man *, int, char *, int);
4061d06d6bSBaptiste Daroussin static int man_pmacro(struct roff_man *, int, char *, int);
4161d06d6bSBaptiste Daroussin
4261d06d6bSBaptiste Daroussin
4361d06d6bSBaptiste Daroussin int
man_parseln(struct roff_man * man,int ln,char * buf,int offs)4461d06d6bSBaptiste Daroussin man_parseln(struct roff_man *man, int ln, char *buf, int offs)
4561d06d6bSBaptiste Daroussin {
4661d06d6bSBaptiste Daroussin
4761d06d6bSBaptiste Daroussin if (man->last->type != ROFFT_EQN || ln > man->last->line)
4861d06d6bSBaptiste Daroussin man->flags |= MAN_NEWLINE;
4961d06d6bSBaptiste Daroussin
5061d06d6bSBaptiste Daroussin return roff_getcontrol(man->roff, buf, &offs) ?
5161d06d6bSBaptiste Daroussin man_pmacro(man, ln, buf, offs) :
5261d06d6bSBaptiste Daroussin man_ptext(man, ln, buf, offs);
5361d06d6bSBaptiste Daroussin }
5461d06d6bSBaptiste Daroussin
557295610fSBaptiste Daroussin /*
567295610fSBaptiste Daroussin * If the string ends with \c, return a pointer to the backslash.
577295610fSBaptiste Daroussin * Otherwise, return NULL.
587295610fSBaptiste Daroussin */
597295610fSBaptiste Daroussin static char *
man_hasc(char * start)607295610fSBaptiste Daroussin man_hasc(char *start)
6161d06d6bSBaptiste Daroussin {
627295610fSBaptiste Daroussin char *cp, *ep;
637295610fSBaptiste Daroussin
647295610fSBaptiste Daroussin ep = strchr(start, '\0') - 2;
657295610fSBaptiste Daroussin if (ep < start || ep[0] != '\\' || ep[1] != 'c')
667295610fSBaptiste Daroussin return NULL;
677295610fSBaptiste Daroussin for (cp = ep; cp > start; cp--)
687295610fSBaptiste Daroussin if (cp[-1] != '\\')
697295610fSBaptiste Daroussin break;
707295610fSBaptiste Daroussin return (ep - cp) % 2 ? NULL : ep;
717295610fSBaptiste Daroussin }
727295610fSBaptiste Daroussin
73*c1c95addSBrooks Davis /*
74*c1c95addSBrooks Davis * Rewind all open next-line scopes.
75*c1c95addSBrooks Davis */
767295610fSBaptiste Daroussin void
man_descope(struct roff_man * man,int line,int offs,char * start)777295610fSBaptiste Daroussin man_descope(struct roff_man *man, int line, int offs, char *start)
787295610fSBaptiste Daroussin {
79*c1c95addSBrooks Davis /* First close out all next-line element scopes, if any. */
8061d06d6bSBaptiste Daroussin
8161d06d6bSBaptiste Daroussin if (man->flags & MAN_ELINE) {
827295610fSBaptiste Daroussin while (man->last->parent->type != ROFFT_ROOT &&
837295610fSBaptiste Daroussin man_macro(man->last->parent->tok)->flags & MAN_ESCOPED)
8461d06d6bSBaptiste Daroussin man_unscope(man, man->last->parent);
857295610fSBaptiste Daroussin man->flags &= ~MAN_ELINE;
8661d06d6bSBaptiste Daroussin }
87*c1c95addSBrooks Davis
88*c1c95addSBrooks Davis /* Trailing \c keeps next-line block scope open. */
89*c1c95addSBrooks Davis
90*c1c95addSBrooks Davis if (start != NULL && man_hasc(start) != NULL)
91*c1c95addSBrooks Davis return;
92*c1c95addSBrooks Davis
93*c1c95addSBrooks Davis /* Close out the next-line block scope, if there is one. */
94*c1c95addSBrooks Davis
9561d06d6bSBaptiste Daroussin if ( ! (man->flags & MAN_BLINE))
9661d06d6bSBaptiste Daroussin return;
9761d06d6bSBaptiste Daroussin man_unscope(man, man->last->parent);
9861d06d6bSBaptiste Daroussin roff_body_alloc(man, line, offs, man->last->tok);
997295610fSBaptiste Daroussin man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
10061d06d6bSBaptiste Daroussin }
10161d06d6bSBaptiste Daroussin
10261d06d6bSBaptiste Daroussin static int
man_ptext(struct roff_man * man,int line,char * buf,int offs)10361d06d6bSBaptiste Daroussin man_ptext(struct roff_man *man, int line, char *buf, int offs)
10461d06d6bSBaptiste Daroussin {
10561d06d6bSBaptiste Daroussin int i;
10661d06d6bSBaptiste Daroussin char *ep;
10761d06d6bSBaptiste Daroussin
1087295610fSBaptiste Daroussin /* In no-fill mode, whitespace is preserved on text lines. */
10961d06d6bSBaptiste Daroussin
1107295610fSBaptiste Daroussin if (man->flags & ROFF_NOFILL) {
11161d06d6bSBaptiste Daroussin roff_word_alloc(man, line, offs, buf + offs);
1127295610fSBaptiste Daroussin man_descope(man, line, offs, buf + offs);
11361d06d6bSBaptiste Daroussin return 1;
11461d06d6bSBaptiste Daroussin }
11561d06d6bSBaptiste Daroussin
11661d06d6bSBaptiste Daroussin for (i = offs; buf[i] == ' '; i++)
11761d06d6bSBaptiste Daroussin /* Skip leading whitespace. */ ;
11861d06d6bSBaptiste Daroussin
11961d06d6bSBaptiste Daroussin /*
12061d06d6bSBaptiste Daroussin * Blank lines are ignored in next line scope
12161d06d6bSBaptiste Daroussin * and right after headings and cancel preceding \c,
12261d06d6bSBaptiste Daroussin * but add a single vertical space elsewhere.
12361d06d6bSBaptiste Daroussin */
12461d06d6bSBaptiste Daroussin
12561d06d6bSBaptiste Daroussin if (buf[i] == '\0') {
12661d06d6bSBaptiste Daroussin if (man->flags & (MAN_ELINE | MAN_BLINE)) {
1277295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_BLK_BLANK, line, 0, NULL);
12861d06d6bSBaptiste Daroussin return 1;
12961d06d6bSBaptiste Daroussin }
13061d06d6bSBaptiste Daroussin if (man->last->tok == MAN_SH || man->last->tok == MAN_SS)
13161d06d6bSBaptiste Daroussin return 1;
1327295610fSBaptiste Daroussin if (man->last->type == ROFFT_TEXT &&
1337295610fSBaptiste Daroussin ((ep = man_hasc(man->last->string)) != NULL)) {
13461d06d6bSBaptiste Daroussin *ep = '\0';
13561d06d6bSBaptiste Daroussin return 1;
13661d06d6bSBaptiste Daroussin }
13761d06d6bSBaptiste Daroussin roff_elem_alloc(man, line, offs, ROFF_sp);
13861d06d6bSBaptiste Daroussin man->next = ROFF_NEXT_SIBLING;
13961d06d6bSBaptiste Daroussin return 1;
14061d06d6bSBaptiste Daroussin }
14161d06d6bSBaptiste Daroussin
14261d06d6bSBaptiste Daroussin /*
14361d06d6bSBaptiste Daroussin * Warn if the last un-escaped character is whitespace. Then
14461d06d6bSBaptiste Daroussin * strip away the remaining spaces (tabs stay!).
14561d06d6bSBaptiste Daroussin */
14661d06d6bSBaptiste Daroussin
14761d06d6bSBaptiste Daroussin i = (int)strlen(buf);
14861d06d6bSBaptiste Daroussin assert(i);
14961d06d6bSBaptiste Daroussin
15061d06d6bSBaptiste Daroussin if (' ' == buf[i - 1] || '\t' == buf[i - 1]) {
15161d06d6bSBaptiste Daroussin if (i > 1 && '\\' != buf[i - 2])
1527295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_SPACE_EOL, line, i - 1, NULL);
15361d06d6bSBaptiste Daroussin
15461d06d6bSBaptiste Daroussin for (--i; i && ' ' == buf[i]; i--)
15561d06d6bSBaptiste Daroussin /* Spin back to non-space. */ ;
15661d06d6bSBaptiste Daroussin
15761d06d6bSBaptiste Daroussin /* Jump ahead of escaped whitespace. */
15861d06d6bSBaptiste Daroussin i += '\\' == buf[i] ? 2 : 1;
15961d06d6bSBaptiste Daroussin
16061d06d6bSBaptiste Daroussin buf[i] = '\0';
16161d06d6bSBaptiste Daroussin }
16261d06d6bSBaptiste Daroussin roff_word_alloc(man, line, offs, buf + offs);
16361d06d6bSBaptiste Daroussin
16461d06d6bSBaptiste Daroussin /*
16561d06d6bSBaptiste Daroussin * End-of-sentence check. If the last character is an unescaped
16661d06d6bSBaptiste Daroussin * EOS character, then flag the node as being the end of a
16761d06d6bSBaptiste Daroussin * sentence. The front-end will know how to interpret this.
16861d06d6bSBaptiste Daroussin */
16961d06d6bSBaptiste Daroussin
17061d06d6bSBaptiste Daroussin assert(i);
17161d06d6bSBaptiste Daroussin if (mandoc_eos(buf, (size_t)i))
17261d06d6bSBaptiste Daroussin man->last->flags |= NODE_EOS;
17361d06d6bSBaptiste Daroussin
1747295610fSBaptiste Daroussin man_descope(man, line, offs, buf + offs);
17561d06d6bSBaptiste Daroussin return 1;
17661d06d6bSBaptiste Daroussin }
17761d06d6bSBaptiste Daroussin
17861d06d6bSBaptiste Daroussin static int
man_pmacro(struct roff_man * man,int ln,char * buf,int offs)17961d06d6bSBaptiste Daroussin man_pmacro(struct roff_man *man, int ln, char *buf, int offs)
18061d06d6bSBaptiste Daroussin {
18161d06d6bSBaptiste Daroussin struct roff_node *n;
18261d06d6bSBaptiste Daroussin const char *cp;
18361d06d6bSBaptiste Daroussin size_t sz;
18461d06d6bSBaptiste Daroussin enum roff_tok tok;
18561d06d6bSBaptiste Daroussin int ppos;
18661d06d6bSBaptiste Daroussin int bline;
18761d06d6bSBaptiste Daroussin
18861d06d6bSBaptiste Daroussin /* Determine the line macro. */
18961d06d6bSBaptiste Daroussin
19061d06d6bSBaptiste Daroussin ppos = offs;
19161d06d6bSBaptiste Daroussin tok = TOKEN_NONE;
19261d06d6bSBaptiste Daroussin for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++)
19361d06d6bSBaptiste Daroussin offs++;
19461d06d6bSBaptiste Daroussin if (sz > 0 && sz < 4)
19561d06d6bSBaptiste Daroussin tok = roffhash_find(man->manmac, buf + ppos, sz);
19661d06d6bSBaptiste Daroussin if (tok == TOKEN_NONE) {
1977295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_MACRO, ln, ppos, "%s", buf + ppos - 1);
19861d06d6bSBaptiste Daroussin return 1;
19961d06d6bSBaptiste Daroussin }
20061d06d6bSBaptiste Daroussin
20161d06d6bSBaptiste Daroussin /* Skip a leading escape sequence or tab. */
20261d06d6bSBaptiste Daroussin
20361d06d6bSBaptiste Daroussin switch (buf[offs]) {
20461d06d6bSBaptiste Daroussin case '\\':
20561d06d6bSBaptiste Daroussin cp = buf + offs + 1;
20661d06d6bSBaptiste Daroussin mandoc_escape(&cp, NULL, NULL);
20761d06d6bSBaptiste Daroussin offs = cp - buf;
20861d06d6bSBaptiste Daroussin break;
20961d06d6bSBaptiste Daroussin case '\t':
21061d06d6bSBaptiste Daroussin offs++;
21161d06d6bSBaptiste Daroussin break;
21261d06d6bSBaptiste Daroussin default:
21361d06d6bSBaptiste Daroussin break;
21461d06d6bSBaptiste Daroussin }
21561d06d6bSBaptiste Daroussin
21661d06d6bSBaptiste Daroussin /* Jump to the next non-whitespace word. */
21761d06d6bSBaptiste Daroussin
21861d06d6bSBaptiste Daroussin while (buf[offs] == ' ')
21961d06d6bSBaptiste Daroussin offs++;
22061d06d6bSBaptiste Daroussin
22161d06d6bSBaptiste Daroussin /*
22261d06d6bSBaptiste Daroussin * Trailing whitespace. Note that tabs are allowed to be passed
22361d06d6bSBaptiste Daroussin * into the parser as "text", so we only warn about spaces here.
22461d06d6bSBaptiste Daroussin */
22561d06d6bSBaptiste Daroussin
22661d06d6bSBaptiste Daroussin if (buf[offs] == '\0' && buf[offs - 1] == ' ')
2277295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL);
22861d06d6bSBaptiste Daroussin
22961d06d6bSBaptiste Daroussin /*
23061d06d6bSBaptiste Daroussin * Some macros break next-line scopes; otherwise, remember
23161d06d6bSBaptiste Daroussin * whether we are in next-line scope for a block head.
23261d06d6bSBaptiste Daroussin */
23361d06d6bSBaptiste Daroussin
23461d06d6bSBaptiste Daroussin man_breakscope(man, tok);
23561d06d6bSBaptiste Daroussin bline = man->flags & MAN_BLINE;
23661d06d6bSBaptiste Daroussin
23761d06d6bSBaptiste Daroussin /*
23861d06d6bSBaptiste Daroussin * If the line in next-line scope ends with \c, keep the
23961d06d6bSBaptiste Daroussin * next-line scope open for the subsequent input line.
24061d06d6bSBaptiste Daroussin * That is not at all portable, only groff >= 1.22.4
24161d06d6bSBaptiste Daroussin * does it, but *if* this weird idiom occurs in a manual
24261d06d6bSBaptiste Daroussin * page, that's very likely what the author intended.
24361d06d6bSBaptiste Daroussin */
24461d06d6bSBaptiste Daroussin
2457295610fSBaptiste Daroussin if (bline && man_hasc(buf + offs))
24661d06d6bSBaptiste Daroussin bline = 0;
24761d06d6bSBaptiste Daroussin
24861d06d6bSBaptiste Daroussin /* Call to handler... */
24961d06d6bSBaptiste Daroussin
2507295610fSBaptiste Daroussin (*man_macro(tok)->fp)(man, tok, ln, ppos, &offs, buf);
25161d06d6bSBaptiste Daroussin
25261d06d6bSBaptiste Daroussin /* In quick mode (for mandocdb), abort after the NAME section. */
25361d06d6bSBaptiste Daroussin
25461d06d6bSBaptiste Daroussin if (man->quick && tok == MAN_SH) {
25561d06d6bSBaptiste Daroussin n = man->last;
25661d06d6bSBaptiste Daroussin if (n->type == ROFFT_BODY &&
25761d06d6bSBaptiste Daroussin strcmp(n->prev->child->string, "NAME"))
25861d06d6bSBaptiste Daroussin return 2;
25961d06d6bSBaptiste Daroussin }
26061d06d6bSBaptiste Daroussin
26161d06d6bSBaptiste Daroussin /*
26261d06d6bSBaptiste Daroussin * If we are in a next-line scope for a block head,
26361d06d6bSBaptiste Daroussin * close it out now and switch to the body,
26461d06d6bSBaptiste Daroussin * unless the next-line scope is allowed to continue.
26561d06d6bSBaptiste Daroussin */
26661d06d6bSBaptiste Daroussin
2677295610fSBaptiste Daroussin if (bline == 0 ||
2687295610fSBaptiste Daroussin (man->flags & MAN_BLINE) == 0 ||
2697295610fSBaptiste Daroussin man->flags & MAN_ELINE ||
2707295610fSBaptiste Daroussin man_macro(tok)->flags & MAN_NSCOPED)
27161d06d6bSBaptiste Daroussin return 1;
27261d06d6bSBaptiste Daroussin
27361d06d6bSBaptiste Daroussin man_unscope(man, man->last->parent);
27461d06d6bSBaptiste Daroussin roff_body_alloc(man, ln, ppos, man->last->tok);
2757295610fSBaptiste Daroussin man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
27661d06d6bSBaptiste Daroussin return 1;
27761d06d6bSBaptiste Daroussin }
27861d06d6bSBaptiste Daroussin
279*c1c95addSBrooks Davis /*
280*c1c95addSBrooks Davis * Rewind open next-line scopes
281*c1c95addSBrooks Davis * unless the tok request or macro is allowed inside them.
282*c1c95addSBrooks Davis */
28361d06d6bSBaptiste Daroussin void
man_breakscope(struct roff_man * man,int tok)28461d06d6bSBaptiste Daroussin man_breakscope(struct roff_man *man, int tok)
28561d06d6bSBaptiste Daroussin {
28661d06d6bSBaptiste Daroussin struct roff_node *n;
28761d06d6bSBaptiste Daroussin
28861d06d6bSBaptiste Daroussin /*
28961d06d6bSBaptiste Daroussin * An element next line scope is open,
29061d06d6bSBaptiste Daroussin * and the new macro is not allowed inside elements.
29161d06d6bSBaptiste Daroussin * Delete the element that is being broken.
29261d06d6bSBaptiste Daroussin */
29361d06d6bSBaptiste Daroussin
29461d06d6bSBaptiste Daroussin if (man->flags & MAN_ELINE && (tok < MAN_TH ||
2957295610fSBaptiste Daroussin (man_macro(tok)->flags & MAN_NSCOPED) == 0)) {
29661d06d6bSBaptiste Daroussin n = man->last;
29761d06d6bSBaptiste Daroussin if (n->type == ROFFT_TEXT)
29861d06d6bSBaptiste Daroussin n = n->parent;
29961d06d6bSBaptiste Daroussin if (n->tok < MAN_TH ||
3007295610fSBaptiste Daroussin (man_macro(n->tok)->flags & (MAN_NSCOPED | MAN_ESCOPED))
3017295610fSBaptiste Daroussin == MAN_NSCOPED)
30261d06d6bSBaptiste Daroussin n = n->parent;
303*c1c95addSBrooks Davis for (;;) {
3047295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
3057295610fSBaptiste Daroussin "%s breaks %s", roff_name[tok], roff_name[n->tok]);
306*c1c95addSBrooks Davis if (n->parent->type != ROFFT_ELEM ||
307*c1c95addSBrooks Davis (man_macro(n->parent->tok)->flags &
308*c1c95addSBrooks Davis MAN_ESCOPED) == 0)
309*c1c95addSBrooks Davis break;
310*c1c95addSBrooks Davis n = n->parent;
311*c1c95addSBrooks Davis }
31261d06d6bSBaptiste Daroussin roff_node_delete(man, n);
31361d06d6bSBaptiste Daroussin man->flags &= ~MAN_ELINE;
31461d06d6bSBaptiste Daroussin }
31561d06d6bSBaptiste Daroussin
31661d06d6bSBaptiste Daroussin /*
31761d06d6bSBaptiste Daroussin * Weird special case:
31861d06d6bSBaptiste Daroussin * Switching fill mode closes section headers.
31961d06d6bSBaptiste Daroussin */
32061d06d6bSBaptiste Daroussin
32161d06d6bSBaptiste Daroussin if (man->flags & MAN_BLINE &&
3227295610fSBaptiste Daroussin (tok == ROFF_nf || tok == ROFF_fi) &&
32361d06d6bSBaptiste Daroussin (man->last->tok == MAN_SH || man->last->tok == MAN_SS)) {
32461d06d6bSBaptiste Daroussin n = man->last;
32561d06d6bSBaptiste Daroussin man_unscope(man, n);
32661d06d6bSBaptiste Daroussin roff_body_alloc(man, n->line, n->pos, n->tok);
3277295610fSBaptiste Daroussin man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
32861d06d6bSBaptiste Daroussin }
32961d06d6bSBaptiste Daroussin
33061d06d6bSBaptiste Daroussin /*
33161d06d6bSBaptiste Daroussin * A block header next line scope is open,
33261d06d6bSBaptiste Daroussin * and the new macro is not allowed inside block headers.
33361d06d6bSBaptiste Daroussin * Delete the block that is being broken.
33461d06d6bSBaptiste Daroussin */
33561d06d6bSBaptiste Daroussin
3367295610fSBaptiste Daroussin if (man->flags & MAN_BLINE && tok != ROFF_nf && tok != ROFF_fi &&
3377295610fSBaptiste Daroussin (tok < MAN_TH || man_macro(tok)->flags & MAN_XSCOPE)) {
33861d06d6bSBaptiste Daroussin n = man->last;
33961d06d6bSBaptiste Daroussin if (n->type == ROFFT_TEXT)
34061d06d6bSBaptiste Daroussin n = n->parent;
34161d06d6bSBaptiste Daroussin if (n->tok < MAN_TH ||
3427295610fSBaptiste Daroussin (man_macro(n->tok)->flags & MAN_XSCOPE) == 0)
34361d06d6bSBaptiste Daroussin n = n->parent;
34461d06d6bSBaptiste Daroussin
34561d06d6bSBaptiste Daroussin assert(n->type == ROFFT_HEAD);
34661d06d6bSBaptiste Daroussin n = n->parent;
34761d06d6bSBaptiste Daroussin assert(n->type == ROFFT_BLOCK);
3487295610fSBaptiste Daroussin assert(man_macro(n->tok)->flags & MAN_BSCOPED);
34961d06d6bSBaptiste Daroussin
3507295610fSBaptiste Daroussin mandoc_msg(MANDOCERR_BLK_LINE, n->line, n->pos,
3517295610fSBaptiste Daroussin "%s breaks %s", roff_name[tok], roff_name[n->tok]);
35261d06d6bSBaptiste Daroussin
35361d06d6bSBaptiste Daroussin roff_node_delete(man, n);
3547295610fSBaptiste Daroussin man->flags &= ~(MAN_BLINE | ROFF_NONOFILL);
35561d06d6bSBaptiste Daroussin }
35661d06d6bSBaptiste Daroussin }
357