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