xref: /openbsd-src/usr.bin/mandoc/mdoc.c (revision e053e0fdfba331a8d17a51c5dc98690ed97b4287)
1*e053e0fdSschwarze /* $OpenBSD: mdoc.c,v 1.164 2020/04/06 09:55:49 schwarze Exp $ */
2f73abda9Skristaps /*
3*e053e0fdSschwarze  * Copyright (c) 2010, 2012-2018, 2020 Ingo Schwarze <schwarze@openbsd.org>
42791bd1cSschwarze  * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
5f73abda9Skristaps  *
6f73abda9Skristaps  * Permission to use, copy, modify, and distribute this software for any
7a6464425Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8a6464425Sschwarze  * copyright notice and this permission notice appear in all copies.
9f73abda9Skristaps  *
102a238f45Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11a6464425Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
122a238f45Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13a6464425Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14a6464425Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15a6464425Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16a6464425Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*e053e0fdSschwarze  *
18*e053e0fdSschwarze  * Top level and utility functions of the mdoc(7) parser for mandoc(1).
19f73abda9Skristaps  */
20e81991a1Sschwarze #include <sys/types.h>
21e81991a1Sschwarze 
22f73abda9Skristaps #include <assert.h>
236cd76589Sschwarze #include <ctype.h>
24f73abda9Skristaps #include <stdarg.h>
25f73abda9Skristaps #include <stdio.h>
26f73abda9Skristaps #include <stdlib.h>
27f73abda9Skristaps #include <string.h>
284368544dSschwarze #include <time.h>
29f73abda9Skristaps 
304f4f7972Sschwarze #include "mandoc_aux.h"
31d1982c71Sschwarze #include "mandoc.h"
32d1982c71Sschwarze #include "roff.h"
33d1982c71Sschwarze #include "mdoc.h"
34a66b65d0Sschwarze #include "libmandoc.h"
35fa2127f9Sschwarze #include "roff_int.h"
36d1982c71Sschwarze #include "libmdoc.h"
37f73abda9Skristaps 
38f73abda9Skristaps const	char *const __mdoc_argnames[MDOC_ARG_MAX] = {
39f73abda9Skristaps 	"split",		"nosplit",		"ragged",
40f73abda9Skristaps 	"unfilled",		"literal",		"file",
41f73abda9Skristaps 	"offset",		"bullet",		"dash",
42f73abda9Skristaps 	"hyphen",		"item",			"enum",
43f73abda9Skristaps 	"tag",			"diag",			"hang",
44f73abda9Skristaps 	"ohang",		"inset",		"column",
45f73abda9Skristaps 	"width",		"compact",		"std",
46f73abda9Skristaps 	"filled",		"words",		"emphasis",
474175bdabSschwarze 	"symbolic",		"nested",		"centered"
48f73abda9Skristaps };
49f73abda9Skristaps const	char * const *mdoc_argnames = __mdoc_argnames;
50f73abda9Skristaps 
51ede1b9d0Sschwarze static	int		  mdoc_ptext(struct roff_man *, int, char *, int);
52ede1b9d0Sschwarze static	int		  mdoc_pmacro(struct roff_man *, int, char *, int);
53bd6ebe84Sschwarze 
5449aff9f8Sschwarze 
55f73abda9Skristaps /*
56f73abda9Skristaps  * Main parse routine.  Parses a single line -- really just hands off to
57c46ca0cbSschwarze  * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()).
58f73abda9Skristaps  */
59f73abda9Skristaps int
mdoc_parseln(struct roff_man * mdoc,int ln,char * buf,int offs)60ede1b9d0Sschwarze mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs)
61f73abda9Skristaps {
62f73abda9Skristaps 
63d1982c71Sschwarze 	if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line)
647ead8a4eSschwarze 		mdoc->flags |= MDOC_NEWLINE;
65e52bf3bcSschwarze 
66e52bf3bcSschwarze 	/*
67e52bf3bcSschwarze 	 * Let the roff nS register switch SYNOPSIS mode early,
68e52bf3bcSschwarze 	 * such that the parser knows at all times
69e52bf3bcSschwarze 	 * whether this mode is on or off.
70e52bf3bcSschwarze 	 * Note that this mode is also switched by the Sh macro.
71e52bf3bcSschwarze 	 */
7222881299Sschwarze 	if (roff_getreg(mdoc->roff, "nS"))
737ead8a4eSschwarze 		mdoc->flags |= MDOC_SYNOPSIS;
74e52bf3bcSschwarze 	else
757ead8a4eSschwarze 		mdoc->flags &= ~MDOC_SYNOPSIS;
76e52bf3bcSschwarze 
77526e306bSschwarze 	return roff_getcontrol(mdoc->roff, buf, &offs) ?
787ead8a4eSschwarze 	    mdoc_pmacro(mdoc, ln, buf, offs) :
79526e306bSschwarze 	    mdoc_ptext(mdoc, ln, buf, offs);
80f73abda9Skristaps }
81f73abda9Skristaps 
8207f8b160Sschwarze void
mdoc_tail_alloc(struct roff_man * mdoc,int line,int pos,enum roff_tok tok)8314a309e3Sschwarze mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok)
84f73abda9Skristaps {
853a0d07afSschwarze 	struct roff_node *p;
86f73abda9Skristaps 
87fa2127f9Sschwarze 	p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok);
88fa2127f9Sschwarze 	roff_node_append(mdoc, p);
89ede1b9d0Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
90f73abda9Skristaps }
91f73abda9Skristaps 
923a0d07afSschwarze struct roff_node *
mdoc_endbody_alloc(struct roff_man * mdoc,int line,int pos,enum roff_tok tok,struct roff_node * body)9314a309e3Sschwarze mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos,
9414a309e3Sschwarze     enum roff_tok tok, struct roff_node *body)
9532dadbacSschwarze {
963a0d07afSschwarze 	struct roff_node *p;
9732dadbacSschwarze 
98c4b0939cSschwarze 	body->flags |= NODE_ENDED;
99c4b0939cSschwarze 	body->parent->flags |= NODE_ENDED;
100fa2127f9Sschwarze 	p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok);
101ae2efdd8Sschwarze 	p->body = body;
102606f1493Sschwarze 	p->norm = body->norm;
10301290091Sschwarze 	p->end = ENDBODY_SPACE;
104fa2127f9Sschwarze 	roff_node_append(mdoc, p);
105ede1b9d0Sschwarze 	mdoc->next = ROFF_NEXT_SIBLING;
106526e306bSschwarze 	return p;
10732dadbacSschwarze }
10832dadbacSschwarze 
1093a0d07afSschwarze struct roff_node *
mdoc_block_alloc(struct roff_man * mdoc,int line,int pos,enum roff_tok tok,struct mdoc_arg * args)110ede1b9d0Sschwarze mdoc_block_alloc(struct roff_man *mdoc, int line, int pos,
11114a309e3Sschwarze     enum roff_tok tok, struct mdoc_arg *args)
112f73abda9Skristaps {
1133a0d07afSschwarze 	struct roff_node *p;
114f73abda9Skristaps 
115fa2127f9Sschwarze 	p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok);
11616054616Sschwarze 	p->args = args;
11716054616Sschwarze 	if (p->args)
118f73abda9Skristaps 		(args->refcnt)++;
1198c62fbf5Sschwarze 
1208c62fbf5Sschwarze 	switch (tok) {
12149aff9f8Sschwarze 	case MDOC_Bd:
12249aff9f8Sschwarze 	case MDOC_Bf:
12349aff9f8Sschwarze 	case MDOC_Bl:
124551cd4a8Sschwarze 	case MDOC_En:
12549aff9f8Sschwarze 	case MDOC_Rs:
1268c62fbf5Sschwarze 		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
1278c62fbf5Sschwarze 		break;
1288c62fbf5Sschwarze 	default:
1298c62fbf5Sschwarze 		break;
1308c62fbf5Sschwarze 	}
131fa2127f9Sschwarze 	roff_node_append(mdoc, p);
132ede1b9d0Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
133526e306bSschwarze 	return p;
134f73abda9Skristaps }
135f73abda9Skristaps 
136f4086bd4Sschwarze void
mdoc_elem_alloc(struct roff_man * mdoc,int line,int pos,enum roff_tok tok,struct mdoc_arg * args)137ede1b9d0Sschwarze mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos,
13814a309e3Sschwarze      enum roff_tok tok, struct mdoc_arg *args)
139f73abda9Skristaps {
1403a0d07afSschwarze 	struct roff_node *p;
141f73abda9Skristaps 
142fa2127f9Sschwarze 	p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok);
14316054616Sschwarze 	p->args = args;
14416054616Sschwarze 	if (p->args)
145f73abda9Skristaps 		(args->refcnt)++;
1468c62fbf5Sschwarze 
1478c62fbf5Sschwarze 	switch (tok) {
14849aff9f8Sschwarze 	case MDOC_An:
1498c62fbf5Sschwarze 		p->norm = mandoc_calloc(1, sizeof(union mdoc_data));
1508c62fbf5Sschwarze 		break;
1518c62fbf5Sschwarze 	default:
1528c62fbf5Sschwarze 		break;
1538c62fbf5Sschwarze 	}
154fa2127f9Sschwarze 	roff_node_append(mdoc, p);
155ede1b9d0Sschwarze 	mdoc->next = ROFF_NEXT_CHILD;
156d96468c6Sschwarze }
157d96468c6Sschwarze 
158f73abda9Skristaps /*
159f73abda9Skristaps  * Parse free-form text, that is, a line that does not begin with the
160f73abda9Skristaps  * control character.
161f73abda9Skristaps  */
162f73abda9Skristaps static int
mdoc_ptext(struct roff_man * mdoc,int line,char * buf,int offs)163ede1b9d0Sschwarze mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs)
164f73abda9Skristaps {
1653a0d07afSschwarze 	struct roff_node *n;
1663361af39Sschwarze 	const char	 *cp, *sp;
167b6e2fe00Sschwarze 	char		 *c, *ws, *end;
168f73abda9Skristaps 
1697ead8a4eSschwarze 	n = mdoc->last;
1706093755cSschwarze 
1716093755cSschwarze 	/*
172ad7fa6e5Sschwarze 	 * If a column list contains plain text, assume an implicit item
173ad7fa6e5Sschwarze 	 * macro.  This can happen one or more times at the beginning
174ad7fa6e5Sschwarze 	 * of such a list, intermixed with non-It mdoc macros and with
175ad7fa6e5Sschwarze 	 * nodes generated on the roff level, for example by tbl.
1766093755cSschwarze 	 */
1776093755cSschwarze 
178ad7fa6e5Sschwarze 	if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
179ad7fa6e5Sschwarze 	     n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
180ad7fa6e5Sschwarze 	    (n->parent != NULL && n->parent->tok == MDOC_Bl &&
181ad7fa6e5Sschwarze 	     n->parent->norm->Bl.type == LIST_column)) {
1827ead8a4eSschwarze 		mdoc->flags |= MDOC_FREECOL;
18316fe0cfcSschwarze 		(*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It,
18416fe0cfcSschwarze 		    line, offs, &offs, buf);
185526e306bSschwarze 		return 1;
1866093755cSschwarze 	}
1876093755cSschwarze 
188b6e2fe00Sschwarze 	/*
189b6e2fe00Sschwarze 	 * Search for the beginning of unescaped trailing whitespace (ws)
190b6e2fe00Sschwarze 	 * and for the first character not to be output (end).
191b6e2fe00Sschwarze 	 */
1926093755cSschwarze 
1936093755cSschwarze 	/* FIXME: replace with strcspn(). */
194b6e2fe00Sschwarze 	ws = NULL;
19562d9ccdbSschwarze 	for (c = end = buf + offs; *c; c++) {
196b6e2fe00Sschwarze 		switch (*c) {
197b6e2fe00Sschwarze 		case ' ':
198b6e2fe00Sschwarze 			if (NULL == ws)
199b6e2fe00Sschwarze 				ws = c;
200b6e2fe00Sschwarze 			continue;
201b6e2fe00Sschwarze 		case '\t':
202b6e2fe00Sschwarze 			/*
203b6e2fe00Sschwarze 			 * Always warn about trailing tabs,
204b6e2fe00Sschwarze 			 * even outside literal context,
205b6e2fe00Sschwarze 			 * where they should be put on the next line.
206b6e2fe00Sschwarze 			 */
207b6e2fe00Sschwarze 			if (NULL == ws)
208b6e2fe00Sschwarze 				ws = c;
209b6e2fe00Sschwarze 			/*
210b6e2fe00Sschwarze 			 * Strip trailing tabs in literal context only;
211b6e2fe00Sschwarze 			 * outside, they affect the next line.
212b6e2fe00Sschwarze 			 */
213b8223a52Sschwarze 			if (mdoc->flags & ROFF_NOFILL)
214b6e2fe00Sschwarze 				continue;
215b6e2fe00Sschwarze 			break;
216b6e2fe00Sschwarze 		case '\\':
217b6e2fe00Sschwarze 			/* Skip the escaped character, too, if any. */
218b6e2fe00Sschwarze 			if (c[1])
219b6e2fe00Sschwarze 				c++;
220b6e2fe00Sschwarze 			/* FALLTHROUGH */
221b6e2fe00Sschwarze 		default:
222b6e2fe00Sschwarze 			ws = NULL;
223b6e2fe00Sschwarze 			break;
224b6e2fe00Sschwarze 		}
225b6e2fe00Sschwarze 		end = c + 1;
226b6e2fe00Sschwarze 	}
227b6e2fe00Sschwarze 	*end = '\0';
228d96468c6Sschwarze 
229b6e2fe00Sschwarze 	if (ws)
230a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SPACE_EOL, line, (int)(ws - buf), NULL);
231d96468c6Sschwarze 
2323361af39Sschwarze 	/*
2333361af39Sschwarze 	 * Blank lines are allowed in no-fill mode
2343361af39Sschwarze 	 * and cancel preceding \c,
2353361af39Sschwarze 	 * but add a single vertical space elsewhere.
2363361af39Sschwarze 	 */
2373361af39Sschwarze 
238b8223a52Sschwarze 	if (buf[offs] == '\0' && (mdoc->flags & ROFF_NOFILL) == 0) {
2393361af39Sschwarze 		switch (mdoc->last->type) {
2403361af39Sschwarze 		case ROFFT_TEXT:
2413361af39Sschwarze 			sp = mdoc->last->string;
2423361af39Sschwarze 			cp = end = strchr(sp, '\0') - 2;
2433361af39Sschwarze 			if (cp < sp || cp[0] != '\\' || cp[1] != 'c')
2443361af39Sschwarze 				break;
2453361af39Sschwarze 			while (cp > sp && cp[-1] == '\\')
2463361af39Sschwarze 				cp--;
2473361af39Sschwarze 			if ((end - cp) % 2)
2483361af39Sschwarze 				break;
2493361af39Sschwarze 			*end = '\0';
2503361af39Sschwarze 			return 1;
2513361af39Sschwarze 		default:
2523361af39Sschwarze 			break;
2533361af39Sschwarze 		}
254a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_FI_BLANK, line, (int)(c - buf), NULL);
2556561cb23Sschwarze 		roff_elem_alloc(mdoc, line, offs, ROFF_sp);
256c4b0939cSschwarze 		mdoc->last->flags |= NODE_VALID | NODE_ENDED;
257ede1b9d0Sschwarze 		mdoc->next = ROFF_NEXT_SIBLING;
258526e306bSschwarze 		return 1;
2591ef47510Sschwarze 	}
260f73abda9Skristaps 
26169c34eaaSschwarze 	roff_word_alloc(mdoc, line, offs, buf+offs);
262f73abda9Skristaps 
263b8223a52Sschwarze 	if (mdoc->flags & ROFF_NOFILL)
264526e306bSschwarze 		return 1;
265b6e2fe00Sschwarze 
2668f1c9461Sschwarze 	/*
267bc49dbe1Sschwarze 	 * End-of-sentence check.  If the last character is an unescaped
268bc49dbe1Sschwarze 	 * EOS character, then flag the node as being the end of a
269bc49dbe1Sschwarze 	 * sentence.  The front-end will know how to interpret this.
2708f1c9461Sschwarze 	 */
2718f1c9461Sschwarze 
272b6e2fe00Sschwarze 	assert(buf < end);
273bc49dbe1Sschwarze 
274f29433e9Sschwarze 	if (mandoc_eos(buf+offs, (size_t)(end-buf-offs)))
275c4b0939cSschwarze 		mdoc->last->flags |= NODE_EOS;
276df457681Sschwarze 
277df457681Sschwarze 	for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) {
278df457681Sschwarze 		if (c - buf < offs + 2)
279df457681Sschwarze 			continue;
280f7444fabSschwarze 		if (end - c < 3)
281df457681Sschwarze 			break;
282af7b5bc7Sschwarze 		if (c[1] != ' ' ||
2835e4965a1Sschwarze 		    isalnum((unsigned char)c[-2]) == 0 ||
2845e4965a1Sschwarze 		    isalnum((unsigned char)c[-1]) == 0 ||
285af7b5bc7Sschwarze 		    (c[-2] == 'n' && c[-1] == 'c') ||
286af7b5bc7Sschwarze 		    (c[-2] == 'v' && c[-1] == 's'))
287af7b5bc7Sschwarze 			continue;
288af7b5bc7Sschwarze 		c += 2;
289af7b5bc7Sschwarze 		if (*c == ' ')
290af7b5bc7Sschwarze 			c++;
291af7b5bc7Sschwarze 		if (*c == ' ')
292af7b5bc7Sschwarze 			c++;
293af7b5bc7Sschwarze 		if (isupper((unsigned char)(*c)))
294a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_EOS, line, (int)(c - buf), NULL);
295df457681Sschwarze 	}
296df457681Sschwarze 
297526e306bSschwarze 	return 1;
298f73abda9Skristaps }
299f73abda9Skristaps 
300f73abda9Skristaps /*
301f73abda9Skristaps  * Parse a macro line, that is, a line beginning with the control
302f73abda9Skristaps  * character.
303f73abda9Skristaps  */
304769ee804Sschwarze static int
mdoc_pmacro(struct roff_man * mdoc,int ln,char * buf,int offs)305ede1b9d0Sschwarze mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs)
306f73abda9Skristaps {
3073a0d07afSschwarze 	struct roff_node *n;
3085bb18d70Sschwarze 	const char	 *cp;
3096050a3daSschwarze 	size_t		  sz;
31014a309e3Sschwarze 	enum roff_tok	  tok;
3116050a3daSschwarze 	int		  sv;
3126050a3daSschwarze 
3136050a3daSschwarze 	/* Determine the line macro. */
314f73abda9Skristaps 
315a35fc07aSschwarze 	sv = offs;
3166050a3daSschwarze 	tok = TOKEN_NONE;
3176050a3daSschwarze 	for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++)
3186050a3daSschwarze 		offs++;
3196050a3daSschwarze 	if (sz == 2 || sz == 3)
3206050a3daSschwarze 		tok = roffhash_find(mdoc->mdocmac, buf + sv, sz);
3212d6f95d3Sschwarze 	if (tok == TOKEN_NONE) {
322a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1);
323526e306bSschwarze 		return 1;
324f73abda9Skristaps 	}
325f73abda9Skristaps 
3265bb18d70Sschwarze 	/* Skip a leading escape sequence or tab. */
32716730bf7Sschwarze 
3285bb18d70Sschwarze 	switch (buf[offs]) {
3295bb18d70Sschwarze 	case '\\':
3305bb18d70Sschwarze 		cp = buf + offs + 1;
3315bb18d70Sschwarze 		mandoc_escape(&cp, NULL, NULL);
3325bb18d70Sschwarze 		offs = cp - buf;
3335bb18d70Sschwarze 		break;
3345bb18d70Sschwarze 	case '\t':
335a35fc07aSschwarze 		offs++;
3365bb18d70Sschwarze 		break;
3375bb18d70Sschwarze 	default:
3385bb18d70Sschwarze 		break;
3395bb18d70Sschwarze 	}
34016730bf7Sschwarze 
34116730bf7Sschwarze 	/* Jump to the next non-whitespace word. */
342f73abda9Skristaps 
3439b423113Sschwarze 	while (buf[offs] == ' ')
344a35fc07aSschwarze 		offs++;
345f73abda9Skristaps 
346a4c002ecSschwarze 	/*
347a4c002ecSschwarze 	 * Trailing whitespace.  Note that tabs are allowed to be passed
348a4c002ecSschwarze 	 * into the parser as "text", so we only warn about spaces here.
349a4c002ecSschwarze 	 */
3508521b0bcSschwarze 
351a35fc07aSschwarze 	if ('\0' == buf[offs] && ' ' == buf[offs - 1])
352a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL);
3538521b0bcSschwarze 
3545df9f685Sschwarze 	/*
355*e053e0fdSschwarze 	 * If an initial or transparent macro or a list invocation,
356*e053e0fdSschwarze 	 * divert directly into macro processing.
3575df9f685Sschwarze 	 */
3586093755cSschwarze 
359ad7fa6e5Sschwarze 	n = mdoc->last;
360*e053e0fdSschwarze 	if (n == NULL || tok == MDOC_It || tok == MDOC_El ||
361*e053e0fdSschwarze 	    roff_tok_transparent(tok)) {
36216fe0cfcSschwarze 		(*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf);
363526e306bSschwarze 		return 1;
36407f8b160Sschwarze 	}
3656093755cSschwarze 
3666093755cSschwarze 	/*
367ad7fa6e5Sschwarze 	 * If a column list contains a non-It macro, assume an implicit
368ad7fa6e5Sschwarze 	 * item macro.  This can happen one or more times at the
369ad7fa6e5Sschwarze 	 * beginning of such a list, intermixed with text lines and
370ad7fa6e5Sschwarze 	 * with nodes generated on the roff level, for example by tbl.
3716093755cSschwarze 	 */
3726093755cSschwarze 
373ad7fa6e5Sschwarze 	if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY &&
374ad7fa6e5Sschwarze 	     n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) ||
375ad7fa6e5Sschwarze 	    (n->parent != NULL && n->parent->tok == MDOC_Bl &&
376ad7fa6e5Sschwarze 	     n->parent->norm->Bl.type == LIST_column)) {
3777ead8a4eSschwarze 		mdoc->flags |= MDOC_FREECOL;
37816fe0cfcSschwarze 		(*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, ln, sv, &sv, buf);
379526e306bSschwarze 		return 1;
3806093755cSschwarze 	}
3816093755cSschwarze 
3826093755cSschwarze 	/* Normal processing of a macro. */
3836093755cSschwarze 
38416fe0cfcSschwarze 	(*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf);
385f73abda9Skristaps 
386f1da507fSschwarze 	/* In quick mode (for mandocdb), abort after the NAME section. */
387f1da507fSschwarze 
388f1da507fSschwarze 	if (mdoc->quick && MDOC_Sh == tok &&
389f1da507fSschwarze 	    SEC_NAME != mdoc->last->sec)
390526e306bSschwarze 		return 2;
391f1da507fSschwarze 
392526e306bSschwarze 	return 1;
393f73abda9Skristaps }
3945df9f685Sschwarze 
395a35fc07aSschwarze enum mdelim
mdoc_isdelim(const char * p)396a35fc07aSschwarze mdoc_isdelim(const char *p)
397a35fc07aSschwarze {
3985df9f685Sschwarze 
399a35fc07aSschwarze 	if ('\0' == p[0])
400526e306bSschwarze 		return DELIM_NONE;
401a35fc07aSschwarze 
402a35fc07aSschwarze 	if ('\0' == p[1])
403a35fc07aSschwarze 		switch (p[0]) {
40449aff9f8Sschwarze 		case '(':
40549aff9f8Sschwarze 		case '[':
406526e306bSschwarze 			return DELIM_OPEN;
40749aff9f8Sschwarze 		case '|':
408526e306bSschwarze 			return DELIM_MIDDLE;
40949aff9f8Sschwarze 		case '.':
41049aff9f8Sschwarze 		case ',':
41149aff9f8Sschwarze 		case ';':
41249aff9f8Sschwarze 		case ':':
41349aff9f8Sschwarze 		case '?':
41449aff9f8Sschwarze 		case '!':
41549aff9f8Sschwarze 		case ')':
41649aff9f8Sschwarze 		case ']':
417526e306bSschwarze 			return DELIM_CLOSE;
418a35fc07aSschwarze 		default:
419526e306bSschwarze 			return DELIM_NONE;
420a35fc07aSschwarze 		}
421a35fc07aSschwarze 
422a35fc07aSschwarze 	if ('\\' != p[0])
423526e306bSschwarze 		return DELIM_NONE;
424a35fc07aSschwarze 
425a35fc07aSschwarze 	if (0 == strcmp(p + 1, "."))
426526e306bSschwarze 		return DELIM_CLOSE;
4276374af4dSschwarze 	if (0 == strcmp(p + 1, "fR|\\fP"))
428526e306bSschwarze 		return DELIM_MIDDLE;
429a35fc07aSschwarze 
430526e306bSschwarze 	return DELIM_NONE;
431a35fc07aSschwarze }
432