xref: /freebsd/contrib/mandoc/man.c (revision c1c95add8c80843ba15d784f95c361d795b1f593)
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