xref: /openbsd-src/usr.bin/mandoc/roff.c (revision 7c5f3f4e47718536f6e33b60be8d0c8c0b793813)
1*7c5f3f4eSschwarze /* $OpenBSD: roff.c,v 1.276 2025/01/06 18:48:13 schwarze Exp $ */
2f673a3c7Sschwarze /*
3f4432377Sschwarze  * Copyright (c) 2010-2015, 2017-2025 Ingo Schwarze <schwarze@openbsd.org>
46a6803e4Sschwarze  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
5f673a3c7Sschwarze  *
6f673a3c7Sschwarze  * Permission to use, copy, modify, and distribute this software for any
7f673a3c7Sschwarze  * purpose with or without fee is hereby granted, provided that the above
8f673a3c7Sschwarze  * copyright notice and this permission notice appear in all copies.
9f673a3c7Sschwarze  *
10e53bb406Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11f673a3c7Sschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12e53bb406Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13f673a3c7Sschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14f673a3c7Sschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15f673a3c7Sschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16f673a3c7Sschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
176a6803e4Sschwarze  *
186a6803e4Sschwarze  * Implementation of the roff(7) parser for mandoc(1).
19f673a3c7Sschwarze  */
20f0d7487dSschwarze #include <sys/types.h>
21f0d7487dSschwarze 
22f673a3c7Sschwarze #include <assert.h>
23b31af00dSschwarze #include <ctype.h>
2495bbf73aSschwarze #include <limits.h>
256050a3daSschwarze #include <stddef.h>
266050a3daSschwarze #include <stdint.h>
27a91e242cSschwarze #include <stdio.h>
28f673a3c7Sschwarze #include <stdlib.h>
29f673a3c7Sschwarze #include <string.h>
30f673a3c7Sschwarze 
314f4f7972Sschwarze #include "mandoc_aux.h"
326050a3daSschwarze #include "mandoc_ohash.h"
33fae2491eSschwarze #include "mandoc.h"
34405987fcSschwarze #include "roff.h"
3599acaf1eSschwarze #include "mandoc_parse.h"
36769ee804Sschwarze #include "libmandoc.h"
37fa2127f9Sschwarze #include "roff_int.h"
38fb382a01Sschwarze #include "tbl_parse.h"
39abe31b56Sschwarze #include "eqn_parse.h"
40f673a3c7Sschwarze 
411631389bSschwarze /* Maximum number of string expansions per line, to break infinite loops. */
421631389bSschwarze #define	EXPAND_LIMIT	1000
431631389bSschwarze 
4407530f0aSschwarze /* Types of definitions of macros and strings. */
4507530f0aSschwarze #define	ROFFDEF_USER	(1 << 1)  /* User-defined. */
4607530f0aSschwarze #define	ROFFDEF_PRE	(1 << 2)  /* Predefined. */
4707530f0aSschwarze #define	ROFFDEF_REN	(1 << 3)  /* Renamed standard macro. */
4807530f0aSschwarze #define	ROFFDEF_STD	(1 << 4)  /* mdoc(7) or man(7) macro. */
4907530f0aSschwarze #define	ROFFDEF_ANY	(ROFFDEF_USER | ROFFDEF_PRE | \
5007530f0aSschwarze 			 ROFFDEF_REN | ROFFDEF_STD)
510cdab9e9Sschwarze #define	ROFFDEF_UNDEF	(1 << 5)  /* Completely undefined. */
5207530f0aSschwarze 
53fa2127f9Sschwarze /* --- data types --------------------------------------------------------- */
54fa2127f9Sschwarze 
55f8618d99Sschwarze /*
5604e980cbSschwarze  * An incredibly-simple string buffer.
5704e980cbSschwarze  */
58769ee804Sschwarze struct	roffstr {
5904e980cbSschwarze 	char		*p; /* nil-terminated buffer */
6004e980cbSschwarze 	size_t		 sz; /* saved strlen(p) */
6104e980cbSschwarze };
6204e980cbSschwarze 
6304e980cbSschwarze /*
6404e980cbSschwarze  * A key-value roffstr pair as part of a singly-linked list.
6504e980cbSschwarze  */
6604e980cbSschwarze struct	roffkv {
6704e980cbSschwarze 	struct roffstr	 key;
6804e980cbSschwarze 	struct roffstr	 val;
6904e980cbSschwarze 	struct roffkv	*next; /* next in list */
70769ee804Sschwarze };
71769ee804Sschwarze 
7222881299Sschwarze /*
7322881299Sschwarze  * A single number register as part of a singly-linked list.
7422881299Sschwarze  */
7522881299Sschwarze struct	roffreg {
7622881299Sschwarze 	struct roffstr	 key;
77c542e817Sschwarze 	int		 val;
78dcd9eb04Sschwarze 	int		 step;
7922881299Sschwarze 	struct roffreg	*next;
8022881299Sschwarze };
8122881299Sschwarze 
826050a3daSschwarze /*
836050a3daSschwarze  * Association of request and macro names with token IDs.
846050a3daSschwarze  */
856050a3daSschwarze struct	roffreq {
866050a3daSschwarze 	enum roff_tok	 tok;
876050a3daSschwarze 	char		 name[];
886050a3daSschwarze };
896050a3daSschwarze 
903dc5225dSschwarze /*
913dc5225dSschwarze  * A macro processing context.
923dc5225dSschwarze  * More than one is needed when macro calls are nested.
933dc5225dSschwarze  */
943dc5225dSschwarze struct	mctx {
953dc5225dSschwarze 	char		**argv;
963dc5225dSschwarze 	int		 argc;
973dc5225dSschwarze 	int		 argsz;
983dc5225dSschwarze };
993dc5225dSschwarze 
100f673a3c7Sschwarze struct	roff {
10129478532Sschwarze 	struct roff_man	*man; /* mdoc or man parser */
102f673a3c7Sschwarze 	struct roffnode	*last; /* leaf of stack */
1033dc5225dSschwarze 	struct mctx	*mstack; /* stack of macro contexts */
1044bc98c72Sschwarze 	int		*rstack; /* stack of inverted `ie' values */
1056050a3daSschwarze 	struct ohash	*reqtab; /* request lookup table */
10622881299Sschwarze 	struct roffreg	*regtab; /* number registers */
10704e980cbSschwarze 	struct roffkv	*strtab; /* user-defined strings & macros */
1087e291f10Sschwarze 	struct roffkv	*rentab; /* renamed strings & macros */
10904e980cbSschwarze 	struct roffkv	*xmbtab; /* multi-byte trans table (`tr') */
11004e980cbSschwarze 	struct roffstr	*xtab; /* single-byte trans table (`tr') */
111e53bb406Sschwarze 	const char	*current_string; /* value of last called user macro */
1122791bd1cSschwarze 	struct tbl_node	*first_tbl; /* first table parsed */
1132791bd1cSschwarze 	struct tbl_node	*last_tbl; /* last table parsed */
1142791bd1cSschwarze 	struct tbl_node	*tbl; /* current table being parsed */
115bf9acac6Sschwarze 	struct eqn_node	*last_eqn; /* equation parser */
116bf9acac6Sschwarze 	struct eqn_node	*eqn; /* active equation parser */
11757ca9bd5Sschwarze 	int		 eqn_inline; /* current equation is inline */
1184bc98c72Sschwarze 	int		 options; /* parse options */
1193dc5225dSschwarze 	int		 mstacksz; /* current size of mstack */
1203dc5225dSschwarze 	int		 mstackpos; /* position in mstack */
1214bc98c72Sschwarze 	int		 rstacksz; /* current size limit of rstack */
1224bc98c72Sschwarze 	int		 rstackpos; /* position in rstack */
123f0d7487dSschwarze 	int		 format; /* current file in mdoc or man format */
1244bc98c72Sschwarze 	char		 control; /* control character */
125be477484Sschwarze 	char		 escape; /* escape character */
126f673a3c7Sschwarze };
127f673a3c7Sschwarze 
1287dd0ff50Sschwarze /*
1297dd0ff50Sschwarze  * A macro definition, condition, or ignored block.
1307dd0ff50Sschwarze  */
131f673a3c7Sschwarze struct	roffnode {
13214a309e3Sschwarze 	enum roff_tok	 tok; /* type of node */
133f673a3c7Sschwarze 	struct roffnode	*parent; /* up one in stack */
134f673a3c7Sschwarze 	int		 line; /* parse line */
135f673a3c7Sschwarze 	int		 col; /* parse col */
136e53bb406Sschwarze 	char		*name; /* node name, e.g. macro name */
1377dd0ff50Sschwarze 	char		*end; /* custom end macro of the block */
1387dd0ff50Sschwarze 	int		 endspan; /* scope to: 1=eol 2=next line -1=\} */
1397dd0ff50Sschwarze 	int		 rule; /* content is: 1=evaluated 0=skipped */
140f673a3c7Sschwarze };
141f673a3c7Sschwarze 
142f673a3c7Sschwarze #define	ROFF_ARGS	 struct roff *r, /* parse ctx */ \
14314a309e3Sschwarze 			 enum roff_tok tok, /* tok of macro */ \
14428687875Sschwarze 			 struct buf *buf, /* input buffer */ \
145f673a3c7Sschwarze 			 int ln, /* parse line */ \
14662d9ccdbSschwarze 			 int ppos, /* original pos in buffer */ \
14762d9ccdbSschwarze 			 int pos, /* current pos in buffer */ \
14862d9ccdbSschwarze 			 int *offs /* reset offset of buffer data */
149f673a3c7Sschwarze 
150b7f92c5fSschwarze typedef	int (*roffproc)(ROFF_ARGS);
151f673a3c7Sschwarze 
152f673a3c7Sschwarze struct	roffmac {
15362d9ccdbSschwarze 	roffproc	 proc; /* process new macro */
15462d9ccdbSschwarze 	roffproc	 text; /* process as child text of macro */
15562d9ccdbSschwarze 	roffproc	 sub; /* process as child of macro */
15662d9ccdbSschwarze 	int		 flags;
15762d9ccdbSschwarze #define	ROFFMAC_STRUCT	(1 << 0) /* always interpret */
158f673a3c7Sschwarze };
159f673a3c7Sschwarze 
160a5e11edeSschwarze struct	predef {
161a5e11edeSschwarze 	const char	*name; /* predefined input name */
162a5e11edeSschwarze 	const char	*str; /* replacement symbol */
163a5e11edeSschwarze };
164a5e11edeSschwarze 
165a5e11edeSschwarze #define	PREDEF(__name, __str) \
166a5e11edeSschwarze 	{ (__name), (__str) },
167a5e11edeSschwarze 
168fa2127f9Sschwarze /* --- function prototypes ------------------------------------------------ */
169fa2127f9Sschwarze 
170b7f92c5fSschwarze static	int		 roffnode_cleanscope(struct roff *);
171b7f92c5fSschwarze static	int		 roffnode_pop(struct roff *);
17214a309e3Sschwarze static	void		 roffnode_push(struct roff *, enum roff_tok,
17304e980cbSschwarze 				const char *, int, int);
174fb382a01Sschwarze static	void		 roff_addtbl(struct roff_man *, int, struct tbl_node *);
175b7f92c5fSschwarze static	int		 roff_als(ROFF_ARGS);
176b7f92c5fSschwarze static	int		 roff_block(ROFF_ARGS);
177b7f92c5fSschwarze static	int		 roff_block_text(ROFF_ARGS);
178b7f92c5fSschwarze static	int		 roff_block_sub(ROFF_ARGS);
1797dd0ff50Sschwarze static	int		 roff_break(ROFF_ARGS);
180b7f92c5fSschwarze static	int		 roff_cblock(ROFF_ARGS);
181b7f92c5fSschwarze static	int		 roff_cc(ROFF_ARGS);
182b7f92c5fSschwarze static	int		 roff_ccond(struct roff *, int, int);
18329fcab3eSschwarze static	int		 roff_char(ROFF_ARGS);
184b7f92c5fSschwarze static	int		 roff_cond(ROFF_ARGS);
185fd9d9245Sschwarze static	int		 roff_cond_checkend(ROFF_ARGS);
186b7f92c5fSschwarze static	int		 roff_cond_text(ROFF_ARGS);
187b7f92c5fSschwarze static	int		 roff_cond_sub(ROFF_ARGS);
188b7f92c5fSschwarze static	int		 roff_ds(ROFF_ARGS);
189b7f92c5fSschwarze static	int		 roff_ec(ROFF_ARGS);
190b7f92c5fSschwarze static	int		 roff_eo(ROFF_ARGS);
191b7f92c5fSschwarze static	int		 roff_eqndelim(struct roff *, struct buf *, int);
1926a6803e4Sschwarze static	int		 roff_evalcond(struct roff *, int, char *, int *);
193f4432377Sschwarze static	int		 roff_evalpar(int, const char *, int *, int *,
194f4432377Sschwarze 				char, int);
1951271a177Sschwarze static	int		 roff_evalstrcond(const char *, int *);
196e94357f9Sschwarze static	int		 roff_expand(struct roff *, struct buf *,
197e94357f9Sschwarze 				int, int, char);
198cd14d642Sschwarze static	void		 roff_expand_patch(struct buf *, int,
199cd14d642Sschwarze 				const char *, int);
20004e980cbSschwarze static	void		 roff_free1(struct roff *);
20122881299Sschwarze static	void		 roff_freereg(struct roffreg *);
20204e980cbSschwarze static	void		 roff_freestr(struct roffkv *);
2033b5bd91eSschwarze static	size_t		 roff_getname(char **, int, int);
204f4432377Sschwarze static	int		 roff_getnum(const char *, int *, int *, char, int);
205541e3395Sschwarze static	int		 roff_getop(const char *, int *, char *);
206dcd9eb04Sschwarze static	int		 roff_getregn(struct roff *,
207dcd9eb04Sschwarze 				const char *, size_t, char);
20845ff8f42Sschwarze static	int		 roff_getregro(const struct roff *,
20945ff8f42Sschwarze 				const char *name);
2100cdab9e9Sschwarze static	const char	*roff_getstrn(struct roff *,
21107530f0aSschwarze 				const char *, size_t, int *);
21224ab75f2Sschwarze static	int		 roff_hasregn(const struct roff *,
21324ab75f2Sschwarze 				const char *, size_t);
214b7f92c5fSschwarze static	int		 roff_insec(ROFF_ARGS);
215b7f92c5fSschwarze static	int		 roff_it(ROFF_ARGS);
216b7f92c5fSschwarze static	int		 roff_line_ignore(ROFF_ARGS);
217405987fcSschwarze static	void		 roff_man_alloc1(struct roff_man *);
218405987fcSschwarze static	void		 roff_man_free1(struct roff_man *);
219b7f92c5fSschwarze static	int		 roff_manyarg(ROFF_ARGS);
220edb0312fSschwarze static	int		 roff_mc(ROFF_ARGS);
2210438bfdfSschwarze static	int		 roff_noarg(ROFF_ARGS);
222b7f92c5fSschwarze static	int		 roff_nop(ROFF_ARGS);
223b7f92c5fSschwarze static	int		 roff_nr(ROFF_ARGS);
224b7f92c5fSschwarze static	int		 roff_onearg(ROFF_ARGS);
22514a309e3Sschwarze static	enum roff_tok	 roff_parse(struct roff *, char *, int *,
2267963e2c1Sschwarze 				int, int);
227928431b4Sschwarze static	int		 roff_parse_comment(struct roff *, struct buf *,
228928431b4Sschwarze 				int, int, char);
229b7f92c5fSschwarze static	int		 roff_parsetext(struct roff *, struct buf *,
230e13b4195Sschwarze 				int, int *);
231b7f92c5fSschwarze static	int		 roff_renamed(ROFF_ARGS);
232f79258f3Sschwarze static	int		 roff_req_or_macro(ROFF_ARGS);
233b7f92c5fSschwarze static	int		 roff_return(ROFF_ARGS);
234b7f92c5fSschwarze static	int		 roff_rm(ROFF_ARGS);
235b7f92c5fSschwarze static	int		 roff_rn(ROFF_ARGS);
236b7f92c5fSschwarze static	int		 roff_rr(ROFF_ARGS);
2379f6d70bcSschwarze static	void		 roff_setregn(struct roff *, const char *,
238dcd9eb04Sschwarze 				size_t, int, char, int);
239769ee804Sschwarze static	void		 roff_setstr(struct roff *,
240e53bb406Sschwarze 				const char *, const char *, int);
24104e980cbSschwarze static	void		 roff_setstrn(struct roffkv **, const char *,
24204e980cbSschwarze 				size_t, const char *, size_t, int);
243b7f92c5fSschwarze static	int		 roff_shift(ROFF_ARGS);
244b7f92c5fSschwarze static	int		 roff_so(ROFF_ARGS);
245b7f92c5fSschwarze static	int		 roff_tr(ROFF_ARGS);
246b7f92c5fSschwarze static	int		 roff_Dd(ROFF_ARGS);
247b7f92c5fSschwarze static	int		 roff_TE(ROFF_ARGS);
248b7f92c5fSschwarze static	int		 roff_TS(ROFF_ARGS);
249b7f92c5fSschwarze static	int		 roff_EQ(ROFF_ARGS);
250b7f92c5fSschwarze static	int		 roff_EN(ROFF_ARGS);
251b7f92c5fSschwarze static	int		 roff_T_(ROFF_ARGS);
252b7f92c5fSschwarze static	int		 roff_unsupp(ROFF_ARGS);
253b7f92c5fSschwarze static	int		 roff_userdef(ROFF_ARGS);
254f673a3c7Sschwarze 
255fa2127f9Sschwarze /* --- constant data ------------------------------------------------------ */
256fa2127f9Sschwarze 
25714a309e3Sschwarze const char *__roff_name[MAN_MAX + 1] = {
2580438bfdfSschwarze 	"br",		"ce",		"fi",		"ft",
2590438bfdfSschwarze 	"ll",		"mc",		"nf",
2600438bfdfSschwarze 	"po",		"rj",		"sp",
261af1e8f15Sschwarze 	"ta",		"ti",		NULL,
26214a309e3Sschwarze 	"ab",		"ad",		"af",		"aln",
26314a309e3Sschwarze 	"als",		"am",		"am1",		"ami",
26414a309e3Sschwarze 	"ami1",		"as",		"as1",		"asciify",
26514a309e3Sschwarze 	"backtrace",	"bd",		"bleedat",	"blm",
26614a309e3Sschwarze         "box",		"boxa",		"bp",		"BP",
26714a309e3Sschwarze 	"break",	"breakchar",	"brnl",		"brp",
268e13b4195Sschwarze 	"brpnl",	"c2",		"cc",
26914a309e3Sschwarze 	"cf",		"cflags",	"ch",		"char",
27014a309e3Sschwarze 	"chop",		"class",	"close",	"CL",
27114a309e3Sschwarze 	"color",	"composite",	"continue",	"cp",
27214a309e3Sschwarze 	"cropat",	"cs",		"cu",		"da",
27314a309e3Sschwarze 	"dch",		"Dd",		"de",		"de1",
27414a309e3Sschwarze 	"defcolor",	"dei",		"dei1",		"device",
27514a309e3Sschwarze 	"devicem",	"di",		"do",		"ds",
27614a309e3Sschwarze 	"ds1",		"dwh",		"dt",		"ec",
27714a309e3Sschwarze 	"ecr",		"ecs",		"el",		"em",
27814a309e3Sschwarze 	"EN",		"eo",		"EP",		"EQ",
27914a309e3Sschwarze 	"errprint",	"ev",		"evc",		"ex",
28014a309e3Sschwarze 	"fallback",	"fam",		"fc",		"fchar",
28114a309e3Sschwarze 	"fcolor",	"fdeferlig",	"feature",	"fkern",
28214a309e3Sschwarze 	"fl",		"flig",		"fp",		"fps",
28314a309e3Sschwarze 	"fschar",	"fspacewidth",	"fspecial",	"ftr",
28414a309e3Sschwarze 	"fzoom",	"gcolor",	"hc",		"hcode",
28514a309e3Sschwarze 	"hidechar",	"hla",		"hlm",		"hpf",
28614a309e3Sschwarze 	"hpfa",		"hpfcode",	"hw",		"hy",
28714a309e3Sschwarze 	"hylang",	"hylen",	"hym",		"hypp",
28814a309e3Sschwarze 	"hys",		"ie",		"if",		"ig",
28914a309e3Sschwarze 	"index",	"it",		"itc",		"IX",
29014a309e3Sschwarze 	"kern",		"kernafter",	"kernbefore",	"kernpair",
29114a309e3Sschwarze 	"lc",		"lc_ctype",	"lds",		"length",
29214a309e3Sschwarze 	"letadj",	"lf",		"lg",		"lhang",
29314a309e3Sschwarze 	"linetabs",	"lnr",		"lnrf",		"lpfx",
29424f1eaadSschwarze 	"ls",		"lsm",		"lt",
29514a309e3Sschwarze 	"mediasize",	"minss",	"mk",		"mso",
29614a309e3Sschwarze 	"na",		"ne",		"nh",		"nhychar",
29714a309e3Sschwarze 	"nm",		"nn",		"nop",		"nr",
29814a309e3Sschwarze 	"nrf",		"nroff",	"ns",		"nx",
29914a309e3Sschwarze 	"open",		"opena",	"os",		"output",
30014a309e3Sschwarze 	"padj",		"papersize",	"pc",		"pev",
30114a309e3Sschwarze 	"pi",		"PI",		"pl",		"pm",
302af1e8f15Sschwarze 	"pn",		"pnr",		"ps",
30314a309e3Sschwarze 	"psbb",		"pshape",	"pso",		"ptr",
30414a309e3Sschwarze 	"pvs",		"rchar",	"rd",		"recursionlimit",
3056de096f4Sschwarze 	"return",	"rfschar",	"rhang",
30614a309e3Sschwarze 	"rm",		"rn",		"rnn",		"rr",
30714a309e3Sschwarze 	"rs",		"rt",		"schar",	"sentchar",
30814a309e3Sschwarze 	"shc",		"shift",	"sizes",	"so",
30914a309e3Sschwarze 	"spacewidth",	"special",	"spreadwarn",	"ss",
31014a309e3Sschwarze 	"sty",		"substring",	"sv",		"sy",
311f7242c43Sschwarze 	"T&",		"tc",		"TE",
31211d70615Sschwarze 	"TH",		"tkf",		"tl",
31314a309e3Sschwarze 	"tm",		"tm1",		"tmc",		"tr",
31414a309e3Sschwarze 	"track",	"transchar",	"trf",		"trimat",
31514a309e3Sschwarze 	"trin",		"trnt",		"troff",	"TS",
31614a309e3Sschwarze 	"uf",		"ul",		"unformat",	"unwatch",
31714a309e3Sschwarze 	"unwatchn",	"vpt",		"vs",		"warn",
31814a309e3Sschwarze 	"warnscale",	"watch",	"watchlength",	"watchn",
31914a309e3Sschwarze 	"wh",		"while",	"write",	"writec",
32014a309e3Sschwarze 	"writem",	"xflag",	".",		NULL,
3217e291f10Sschwarze 	NULL,		"text",
32214a309e3Sschwarze 	"Dd",		"Dt",		"Os",		"Sh",
32314a309e3Sschwarze 	"Ss",		"Pp",		"D1",		"Dl",
32414a309e3Sschwarze 	"Bd",		"Ed",		"Bl",		"El",
32514a309e3Sschwarze 	"It",		"Ad",		"An",		"Ap",
32614a309e3Sschwarze 	"Ar",		"Cd",		"Cm",		"Dv",
32714a309e3Sschwarze 	"Er",		"Ev",		"Ex",		"Fa",
32814a309e3Sschwarze 	"Fd",		"Fl",		"Fn",		"Ft",
32914a309e3Sschwarze 	"Ic",		"In",		"Li",		"Nd",
33014a309e3Sschwarze 	"Nm",		"Op",		"Ot",		"Pa",
33114a309e3Sschwarze 	"Rv",		"St",		"Va",		"Vt",
33214a309e3Sschwarze 	"Xr",		"%A",		"%B",		"%D",
33314a309e3Sschwarze 	"%I",		"%J",		"%N",		"%O",
33414a309e3Sschwarze 	"%P",		"%R",		"%T",		"%V",
33514a309e3Sschwarze 	"Ac",		"Ao",		"Aq",		"At",
33614a309e3Sschwarze 	"Bc",		"Bf",		"Bo",		"Bq",
33714a309e3Sschwarze 	"Bsx",		"Bx",		"Db",		"Dc",
33814a309e3Sschwarze 	"Do",		"Dq",		"Ec",		"Ef",
33914a309e3Sschwarze 	"Em",		"Eo",		"Fx",		"Ms",
34014a309e3Sschwarze 	"No",		"Ns",		"Nx",		"Ox",
34114a309e3Sschwarze 	"Pc",		"Pf",		"Po",		"Pq",
34214a309e3Sschwarze 	"Qc",		"Ql",		"Qo",		"Qq",
34314a309e3Sschwarze 	"Re",		"Rs",		"Sc",		"So",
34414a309e3Sschwarze 	"Sq",		"Sm",		"Sx",		"Sy",
34514a309e3Sschwarze 	"Tn",		"Ux",		"Xc",		"Xo",
34614a309e3Sschwarze 	"Fo",		"Fc",		"Oo",		"Oc",
34714a309e3Sschwarze 	"Bk",		"Ek",		"Bt",		"Hf",
34814a309e3Sschwarze 	"Fr",		"Ud",		"Lb",		"Lp",
34914a309e3Sschwarze 	"Lk",		"Mt",		"Brq",		"Bro",
35014a309e3Sschwarze 	"Brc",		"%C",		"Es",		"En",
3516561cb23Sschwarze 	"Dx",		"%Q",		"%U",		"Ta",
35292929bf6Sschwarze 	"Tg",		NULL,
35314a309e3Sschwarze 	"TH",		"SH",		"SS",		"TP",
354d991fc2cSschwarze 	"TQ",
35514a309e3Sschwarze 	"LP",		"PP",		"P",		"IP",
35614a309e3Sschwarze 	"HP",		"SM",		"SB",		"BI",
35714a309e3Sschwarze 	"IB",		"BR",		"RB",		"R",
35814a309e3Sschwarze 	"B",		"I",		"IR",		"RI",
35914a309e3Sschwarze 	"RE",		"RS",		"DT",		"UC",
360c4d3fa85Sschwarze 	"PD",		"AT",		"in",
3615e5a9c61Sschwarze 	"SY",		"YS",		"OP",
3625e5a9c61Sschwarze 	"EX",		"EE",		"UR",
363f6697133Sschwarze 	"UE",		"MT",		"ME",		"MR",
364f6697133Sschwarze 	NULL
36514a309e3Sschwarze };
36614a309e3Sschwarze const	char *const *roff_name = __roff_name;
36714a309e3Sschwarze 
36814a309e3Sschwarze static	struct roffmac	 roffs[TOKEN_NONE] = {
3690438bfdfSschwarze 	{ roff_noarg, NULL, NULL, 0 },  /* br */
370e13b4195Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* ce */
3710438bfdfSschwarze 	{ roff_noarg, NULL, NULL, 0 },  /* fi */
372c4d3fa85Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* ft */
373644b390bSschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* ll */
374edb0312fSschwarze 	{ roff_mc, NULL, NULL, 0 },  /* mc */
3750438bfdfSschwarze 	{ roff_noarg, NULL, NULL, 0 },  /* nf */
376af1e8f15Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* po */
3776de096f4Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* rj */
3786561cb23Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* sp */
379f7242c43Sschwarze 	{ roff_manyarg, NULL, NULL, 0 },  /* ta */
38011d70615Sschwarze 	{ roff_onearg, NULL, NULL, 0 },  /* ti */
38129478532Sschwarze 	{ NULL, NULL, NULL, 0 },  /* ROFF_MAX */
3826050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* ab */
3836050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ad */
3846050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* af */
3856050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* aln */
3860856de67Sschwarze 	{ roff_als, NULL, NULL, 0 },  /* als */
3876050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* am */
3886050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* am1 */
3896050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* ami */
3906050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* ami1 */
3916050a3daSschwarze 	{ roff_ds, NULL, NULL, 0 },  /* as */
3926050a3daSschwarze 	{ roff_ds, NULL, NULL, 0 },  /* as1 */
3936050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* asciify */
3946050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* backtrace */
3956050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* bd */
3966050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* bleedat */
3976050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* blm */
3986050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* box */
3996050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* boxa */
4006050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* bp */
4016050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* BP */
4027dd0ff50Sschwarze 	{ roff_break, NULL, NULL, 0 },  /* break */
4036050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* breakchar */
4046050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* brnl */
4050438bfdfSschwarze 	{ roff_noarg, NULL, NULL, 0 },  /* brp */
4066050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* brpnl */
4076050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* c2 */
4086050a3daSschwarze 	{ roff_cc, NULL, NULL, 0 },  /* cc */
4096050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* cf */
4106050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* cflags */
4116050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ch */
41229fcab3eSschwarze 	{ roff_char, NULL, NULL, 0 },  /* char */
4136050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* chop */
4146050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* class */
4156050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* close */
4166050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* CL */
4176050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* color */
4186050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* composite */
4196050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* continue */
4206050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* cp */
4216050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* cropat */
4226050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* cs */
4236050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* cu */
4246050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* da */
4256050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* dch */
4266050a3daSschwarze 	{ roff_Dd, NULL, NULL, 0 },  /* Dd */
4276050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* de */
4286050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* de1 */
4296050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* defcolor */
4306050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* dei */
4316050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* dei1 */
4326050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* device */
4336050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* devicem */
4346050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* di */
4356050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* do */
4366050a3daSschwarze 	{ roff_ds, NULL, NULL, 0 },  /* ds */
4376050a3daSschwarze 	{ roff_ds, NULL, NULL, 0 },  /* ds1 */
4386050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* dwh */
4396050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* dt */
440be477484Sschwarze 	{ roff_ec, NULL, NULL, 0 },  /* ec */
4416050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* ecr */
4426050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* ecs */
4436050a3daSschwarze 	{ roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT },  /* el */
4446050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* em */
4456050a3daSschwarze 	{ roff_EN, NULL, NULL, 0 },  /* EN */
446be477484Sschwarze 	{ roff_eo, NULL, NULL, 0 },  /* eo */
4476050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* EP */
4486050a3daSschwarze 	{ roff_EQ, NULL, NULL, 0 },  /* EQ */
4496050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* errprint */
4506050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* ev */
4516050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* evc */
4526050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* ex */
4536050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fallback */
4546050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fam */
4556050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* fc */
4566050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* fchar */
4576050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fcolor */
4586050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fdeferlig */
4596050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* feature */
4606050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fkern */
4616050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fl */
4626050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* flig */
4636050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fp */
4646050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fps */
4656050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* fschar */
4666050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fspacewidth */
4676050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fspecial */
4686050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ftr */
4696050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* fzoom */
4706050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* gcolor */
4716050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hc */
4726050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hcode */
4736050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hidechar */
4746050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hla */
4756050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hlm */
4766050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hpf */
4776050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hpfa */
4786050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hpfcode */
4796050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hw */
4806050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hy */
4816050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hylang */
4826050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hylen */
4836050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hym */
4846050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hypp */
4856050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* hys */
4866050a3daSschwarze 	{ roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT },  /* ie */
4876050a3daSschwarze 	{ roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT },  /* if */
4886050a3daSschwarze 	{ roff_block, roff_block_text, roff_block_sub, 0 },  /* ig */
4896050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* index */
4906050a3daSschwarze 	{ roff_it, NULL, NULL, 0 },  /* it */
4916050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* itc */
4926050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* IX */
4936050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* kern */
4946050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* kernafter */
4956050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* kernbefore */
4966050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* kernpair */
4976050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lc */
4986050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lc_ctype */
4996050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lds */
5006050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* length */
5016050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* letadj */
5026050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* lf */
5036050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* lg */
5046050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* lhang */
5056050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* linetabs */
5066050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lnr */
5076050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lnrf */
5086050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lpfx */
5096050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ls */
5106050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* lsm */
5116050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* lt */
5126050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* mediasize */
5136050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* minss */
5146050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* mk */
5156050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* mso */
5166050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* na */
5176050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ne */
5186050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* nh */
5196050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* nhychar */
5206050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* nm */
5216050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* nn */
522b20e20eaSschwarze 	{ roff_nop, NULL, NULL, 0 },  /* nop */
5236050a3daSschwarze 	{ roff_nr, NULL, NULL, 0 },  /* nr */
5246050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* nrf */
5256050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* nroff */
5266050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ns */
5276050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* nx */
5286050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* open */
5296050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* opena */
5306050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* os */
5316050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* output */
5326050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* padj */
5336050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* papersize */
5346050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pc */
5356050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pev */
5366050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* pi */
5376050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* PI */
5386050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pl */
5396050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pm */
5406050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pn */
5416050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pnr */
5426050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ps */
5436050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* psbb */
5446050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* pshape */
5456050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* pso */
5466050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ptr */
5476050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* pvs */
5486050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* rchar */
5496050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* rd */
5506050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* recursionlimit */
5513dc5225dSschwarze 	{ roff_return, NULL, NULL, 0 },  /* return */
5526050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* rfschar */
5536050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* rhang */
5546050a3daSschwarze 	{ roff_rm, NULL, NULL, 0 },  /* rm */
5557e291f10Sschwarze 	{ roff_rn, NULL, NULL, 0 },  /* rn */
5566050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* rnn */
5576050a3daSschwarze 	{ roff_rr, NULL, NULL, 0 },  /* rr */
5586050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* rs */
5596050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* rt */
5606050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* schar */
5616050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* sentchar */
5626050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* shc */
5633dc5225dSschwarze 	{ roff_shift, NULL, NULL, 0 },  /* shift */
5646050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* sizes */
5656050a3daSschwarze 	{ roff_so, NULL, NULL, 0 },  /* so */
5666050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* spacewidth */
5676050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* special */
5686050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* spreadwarn */
5696050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ss */
5706050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* sty */
5716050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* substring */
5726050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* sv */
5736050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* sy */
5746050a3daSschwarze 	{ roff_T_, NULL, NULL, 0 },  /* T& */
5756050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* tc */
5766050a3daSschwarze 	{ roff_TE, NULL, NULL, 0 },  /* TE */
57707530f0aSschwarze 	{ roff_Dd, NULL, NULL, 0 },  /* TH */
5786050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* tkf */
5796050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* tl */
5806050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* tm */
5816050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* tm1 */
5826050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* tmc */
5836050a3daSschwarze 	{ roff_tr, NULL, NULL, 0 },  /* tr */
5846050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* track */
5856050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* transchar */
5866050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* trf */
5876050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* trimat */
5886050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* trin */
5896050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* trnt */
5906050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* troff */
5916050a3daSschwarze 	{ roff_TS, NULL, NULL, 0 },  /* TS */
5926050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* uf */
5936050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* ul */
5946050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* unformat */
5956050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* unwatch */
5966050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* unwatchn */
5976050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* vpt */
5986050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* vs */
5996050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* warn */
6006050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* warnscale */
6016050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* watch */
6026050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* watchlength */
6036050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* watchn */
6046050a3daSschwarze 	{ roff_unsupp, NULL, NULL, 0 },  /* wh */
605b7f92c5fSschwarze 	{ roff_cond, roff_cond_text, roff_cond_sub, ROFFMAC_STRUCT }, /*while*/
6066050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* write */
6076050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* writec */
6086050a3daSschwarze 	{ roff_insec, NULL, NULL, 0 },  /* writem */
6096050a3daSschwarze 	{ roff_line_ignore, NULL, NULL, 0 },  /* xflag */
6106050a3daSschwarze 	{ roff_cblock, NULL, NULL, 0 },  /* . */
6117e291f10Sschwarze 	{ roff_renamed, NULL, NULL, 0 },
6126050a3daSschwarze 	{ roff_userdef, NULL, NULL, 0 }
613f673a3c7Sschwarze };
614f673a3c7Sschwarze 
615a5e11edeSschwarze /* Array of injected predefined strings. */
616a5e11edeSschwarze #define	PREDEFS_MAX	 38
617a5e11edeSschwarze static	const struct predef predefs[PREDEFS_MAX] = {
618a5e11edeSschwarze #include "predefs.in"
619a5e11edeSschwarze };
620a5e11edeSschwarze 
621e13b4195Sschwarze static	int	 roffce_lines;	/* number of input lines to center */
622e13b4195Sschwarze static	struct roff_node *roffce_node;  /* active request */
623a91e242cSschwarze static	int	 roffit_lines;  /* number of lines to delay */
624a91e242cSschwarze static	char	*roffit_macro;  /* nil-terminated macro line */
625a91e242cSschwarze 
62649aff9f8Sschwarze 
627fa2127f9Sschwarze /* --- request table ------------------------------------------------------ */
628fa2127f9Sschwarze 
6296050a3daSschwarze struct ohash *
6306050a3daSschwarze roffhash_alloc(enum roff_tok mintok, enum roff_tok maxtok)
631b31af00dSschwarze {
6326050a3daSschwarze 	struct ohash	*htab;
6336050a3daSschwarze 	struct roffreq	*req;
6346050a3daSschwarze 	enum roff_tok	 tok;
6356050a3daSschwarze 	size_t		 sz;
6366050a3daSschwarze 	unsigned int	 slot;
637b31af00dSschwarze 
6386050a3daSschwarze 	htab = mandoc_malloc(sizeof(*htab));
6396050a3daSschwarze 	mandoc_ohash_init(htab, 8, offsetof(struct roffreq, name));
640b31af00dSschwarze 
6416050a3daSschwarze 	for (tok = mintok; tok < maxtok; tok++) {
64229478532Sschwarze 		if (roff_name[tok] == NULL)
64329478532Sschwarze 			continue;
6446050a3daSschwarze 		sz = strlen(roff_name[tok]);
6456050a3daSschwarze 		req = mandoc_malloc(sizeof(*req) + sz + 1);
6466050a3daSschwarze 		req->tok = tok;
6476050a3daSschwarze 		memcpy(req->name, roff_name[tok], sz + 1);
6486050a3daSschwarze 		slot = ohash_qlookup(htab, req->name);
6496050a3daSschwarze 		ohash_insert(htab, slot, req);
6506050a3daSschwarze 	}
6516050a3daSschwarze 	return htab;
6526050a3daSschwarze }
653b31af00dSschwarze 
6546050a3daSschwarze void
6556050a3daSschwarze roffhash_free(struct ohash *htab)
6566050a3daSschwarze {
6576050a3daSschwarze 	struct roffreq	*req;
6586050a3daSschwarze 	unsigned int	 slot;
6596050a3daSschwarze 
6606050a3daSschwarze 	if (htab == NULL)
6616050a3daSschwarze 		return;
6626050a3daSschwarze 	for (req = ohash_first(htab, &slot); req != NULL;
6636050a3daSschwarze 	     req = ohash_next(htab, &slot))
6646050a3daSschwarze 		free(req);
6656050a3daSschwarze 	ohash_delete(htab);
6666050a3daSschwarze 	free(htab);
6676050a3daSschwarze }
6686050a3daSschwarze 
6696050a3daSschwarze enum roff_tok
6706050a3daSschwarze roffhash_find(struct ohash *htab, const char *name, size_t sz)
6716050a3daSschwarze {
6726050a3daSschwarze 	struct roffreq	*req;
6736050a3daSschwarze 	const char	*end;
6746050a3daSschwarze 
6756050a3daSschwarze 	if (sz) {
6766050a3daSschwarze 		end = name + sz;
6776050a3daSschwarze 		req = ohash_find(htab, ohash_qlookupi(htab, name, &end));
678b31af00dSschwarze 	} else
6796050a3daSschwarze 		req = ohash_find(htab, ohash_qlookup(htab, name));
6806050a3daSschwarze 	return req == NULL ? TOKEN_NONE : req->tok;
681f673a3c7Sschwarze }
682f673a3c7Sschwarze 
683fa2127f9Sschwarze /* --- stack of request blocks -------------------------------------------- */
684fa2127f9Sschwarze 
685f673a3c7Sschwarze /*
686f673a3c7Sschwarze  * Pop the current node off of the stack of roff instructions currently
6877dd0ff50Sschwarze  * pending.  Return 1 if it is a loop or 0 otherwise.
688f673a3c7Sschwarze  */
689b7f92c5fSschwarze static int
690f673a3c7Sschwarze roffnode_pop(struct roff *r)
691f673a3c7Sschwarze {
692f673a3c7Sschwarze 	struct roffnode	*p;
693b7f92c5fSschwarze 	int		 inloop;
694f673a3c7Sschwarze 
69562d9ccdbSschwarze 	p = r->last;
696b7f92c5fSschwarze 	inloop = p->tok == ROFF_while;
697b7f92c5fSschwarze 	r->last = p->parent;
698e53bb406Sschwarze 	free(p->name);
69962d9ccdbSschwarze 	free(p->end);
700f673a3c7Sschwarze 	free(p);
701b7f92c5fSschwarze 	return inloop;
702f673a3c7Sschwarze }
703f673a3c7Sschwarze 
704f673a3c7Sschwarze /*
705f673a3c7Sschwarze  * Push a roff node onto the instruction stack.  This must later be
706f673a3c7Sschwarze  * removed with roffnode_pop().
707f673a3c7Sschwarze  */
708cfd2bfaaSschwarze static void
70914a309e3Sschwarze roffnode_push(struct roff *r, enum roff_tok tok, const char *name,
710e53bb406Sschwarze 		int line, int col)
711f673a3c7Sschwarze {
712f673a3c7Sschwarze 	struct roffnode	*p;
713f673a3c7Sschwarze 
714cfd2bfaaSschwarze 	p = mandoc_calloc(1, sizeof(struct roffnode));
715f673a3c7Sschwarze 	p->tok = tok;
716e53bb406Sschwarze 	if (name)
717e53bb406Sschwarze 		p->name = mandoc_strdup(name);
718f673a3c7Sschwarze 	p->parent = r->last;
719f673a3c7Sschwarze 	p->line = line;
720f673a3c7Sschwarze 	p->col = col;
7211271a177Sschwarze 	p->rule = p->parent ? p->parent->rule : 0;
722f673a3c7Sschwarze 
723f673a3c7Sschwarze 	r->last = p;
724f673a3c7Sschwarze }
725f673a3c7Sschwarze 
726fa2127f9Sschwarze /* --- roff parser state data management ---------------------------------- */
727fa2127f9Sschwarze 
728f673a3c7Sschwarze static void
729f673a3c7Sschwarze roff_free1(struct roff *r)
730f673a3c7Sschwarze {
73104e980cbSschwarze 	int		 i;
7322791bd1cSschwarze 
733fb382a01Sschwarze 	tbl_free(r->first_tbl);
7342791bd1cSschwarze 	r->first_tbl = r->last_tbl = r->tbl = NULL;
735f673a3c7Sschwarze 
736bf9acac6Sschwarze 	eqn_free(r->last_eqn);
737bf9acac6Sschwarze 	r->last_eqn = r->eqn = NULL;
7388d973ab1Sschwarze 
7393dc5225dSschwarze 	while (r->mstackpos >= 0)
7403dc5225dSschwarze 		roff_userret(r);
7413dc5225dSschwarze 
742f673a3c7Sschwarze 	while (r->last)
743f673a3c7Sschwarze 		roffnode_pop(r);
7442791bd1cSschwarze 
7454bc98c72Sschwarze 	free (r->rstack);
7464bc98c72Sschwarze 	r->rstack = NULL;
7474bc98c72Sschwarze 	r->rstacksz = 0;
7484bc98c72Sschwarze 	r->rstackpos = -1;
74904e980cbSschwarze 
75022881299Sschwarze 	roff_freereg(r->regtab);
75122881299Sschwarze 	r->regtab = NULL;
75222881299Sschwarze 
7534bc98c72Sschwarze 	roff_freestr(r->strtab);
7547e291f10Sschwarze 	roff_freestr(r->rentab);
7554bc98c72Sschwarze 	roff_freestr(r->xmbtab);
7567e291f10Sschwarze 	r->strtab = r->rentab = r->xmbtab = NULL;
7574bc98c72Sschwarze 
75804e980cbSschwarze 	if (r->xtab)
75904e980cbSschwarze 		for (i = 0; i < 128; i++)
76004e980cbSschwarze 			free(r->xtab[i].p);
76104e980cbSschwarze 	free(r->xtab);
76204e980cbSschwarze 	r->xtab = NULL;
76304e980cbSschwarze }
764f673a3c7Sschwarze 
765f673a3c7Sschwarze void
766f673a3c7Sschwarze roff_reset(struct roff *r)
767f673a3c7Sschwarze {
768f673a3c7Sschwarze 	roff_free1(r);
76967217f5cSschwarze 	r->options |= MPARSE_COMMENT;
770f0d7487dSschwarze 	r->format = r->options & (MPARSE_MDOC | MPARSE_MAN);
771be477484Sschwarze 	r->control = '\0';
772be477484Sschwarze 	r->escape = '\\';
7735aaaa1efSschwarze 	roffce_lines = 0;
7745aaaa1efSschwarze 	roffce_node = NULL;
7755aaaa1efSschwarze 	roffit_lines = 0;
7765aaaa1efSschwarze 	roffit_macro = NULL;
777f673a3c7Sschwarze }
778f673a3c7Sschwarze 
779f673a3c7Sschwarze void
780f673a3c7Sschwarze roff_free(struct roff *r)
781f673a3c7Sschwarze {
7823dc5225dSschwarze 	int		 i;
7833dc5225dSschwarze 
784f673a3c7Sschwarze 	roff_free1(r);
7853dc5225dSschwarze 	for (i = 0; i < r->mstacksz; i++)
7863dc5225dSschwarze 		free(r->mstack[i].argv);
7873dc5225dSschwarze 	free(r->mstack);
7886050a3daSschwarze 	roffhash_free(r->reqtab);
789f673a3c7Sschwarze 	free(r);
790f673a3c7Sschwarze }
791f673a3c7Sschwarze 
792f673a3c7Sschwarze struct roff *
79391305757Sschwarze roff_alloc(int options)
794f673a3c7Sschwarze {
795f673a3c7Sschwarze 	struct roff	*r;
796f673a3c7Sschwarze 
797cfd2bfaaSschwarze 	r = mandoc_calloc(1, sizeof(struct roff));
7984c293873Sschwarze 	r->reqtab = roffhash_alloc(0, ROFF_RENAMED);
79967217f5cSschwarze 	r->options = options | MPARSE_COMMENT;
800f0d7487dSschwarze 	r->format = options & (MPARSE_MDOC | MPARSE_MAN);
8013dc5225dSschwarze 	r->mstackpos = -1;
80262d9ccdbSschwarze 	r->rstackpos = -1;
803be477484Sschwarze 	r->escape = '\\';
804526e306bSschwarze 	return r;
805f673a3c7Sschwarze }
806f673a3c7Sschwarze 
807fa2127f9Sschwarze /* --- syntax tree state data management ---------------------------------- */
808fa2127f9Sschwarze 
809405987fcSschwarze static void
810405987fcSschwarze roff_man_free1(struct roff_man *man)
811405987fcSschwarze {
8126b86842eSschwarze 	if (man->meta.first != NULL)
8136b86842eSschwarze 		roff_node_delete(man, man->meta.first);
814405987fcSschwarze 	free(man->meta.msec);
815405987fcSschwarze 	free(man->meta.vol);
816405987fcSschwarze 	free(man->meta.os);
817405987fcSschwarze 	free(man->meta.arch);
818405987fcSschwarze 	free(man->meta.title);
819405987fcSschwarze 	free(man->meta.name);
820405987fcSschwarze 	free(man->meta.date);
8216b86842eSschwarze 	free(man->meta.sodest);
822405987fcSschwarze }
823405987fcSschwarze 
82483d65a5aSschwarze void
82583d65a5aSschwarze roff_state_reset(struct roff_man *man)
82683d65a5aSschwarze {
82783d65a5aSschwarze 	man->last = man->meta.first;
82883d65a5aSschwarze 	man->last_es = NULL;
82983d65a5aSschwarze 	man->flags = 0;
83083d65a5aSschwarze 	man->lastsec = man->lastnamed = SEC_NONE;
83183d65a5aSschwarze 	man->next = ROFF_NEXT_CHILD;
83283d65a5aSschwarze 	roff_setreg(man->roff, "nS", 0, '=');
83383d65a5aSschwarze }
83483d65a5aSschwarze 
835405987fcSschwarze static void
836405987fcSschwarze roff_man_alloc1(struct roff_man *man)
837405987fcSschwarze {
838405987fcSschwarze 	memset(&man->meta, 0, sizeof(man->meta));
8396b86842eSschwarze 	man->meta.first = mandoc_calloc(1, sizeof(*man->meta.first));
8406b86842eSschwarze 	man->meta.first->type = ROFFT_ROOT;
8416b86842eSschwarze 	man->meta.macroset = MACROSET_NONE;
84283d65a5aSschwarze 	roff_state_reset(man);
843405987fcSschwarze }
844405987fcSschwarze 
845405987fcSschwarze void
846405987fcSschwarze roff_man_reset(struct roff_man *man)
847405987fcSschwarze {
848405987fcSschwarze 	roff_man_free1(man);
849405987fcSschwarze 	roff_man_alloc1(man);
850405987fcSschwarze }
851405987fcSschwarze 
852405987fcSschwarze void
853405987fcSschwarze roff_man_free(struct roff_man *man)
854405987fcSschwarze {
855405987fcSschwarze 	roff_man_free1(man);
8568055da74Sschwarze 	free(man->os_r);
857405987fcSschwarze 	free(man);
858405987fcSschwarze }
859405987fcSschwarze 
860405987fcSschwarze struct roff_man *
86191305757Sschwarze roff_man_alloc(struct roff *roff, const char *os_s, int quick)
862405987fcSschwarze {
863405987fcSschwarze 	struct roff_man *man;
864405987fcSschwarze 
865405987fcSschwarze 	man = mandoc_calloc(1, sizeof(*man));
866405987fcSschwarze 	man->roff = roff;
867f3476b07Sschwarze 	man->os_s = os_s;
868405987fcSschwarze 	man->quick = quick;
869405987fcSschwarze 	roff_man_alloc1(man);
87029478532Sschwarze 	roff->man = man;
871526e306bSschwarze 	return man;
872405987fcSschwarze }
873405987fcSschwarze 
874fa2127f9Sschwarze /* --- syntax tree handling ----------------------------------------------- */
875fa2127f9Sschwarze 
876fa2127f9Sschwarze struct roff_node *
877fa2127f9Sschwarze roff_node_alloc(struct roff_man *man, int line, int pos,
878fa2127f9Sschwarze 	enum roff_type type, int tok)
879fa2127f9Sschwarze {
880fa2127f9Sschwarze 	struct roff_node	*n;
881fa2127f9Sschwarze 
882fa2127f9Sschwarze 	n = mandoc_calloc(1, sizeof(*n));
883fa2127f9Sschwarze 	n->line = line;
884fa2127f9Sschwarze 	n->pos = pos;
885fa2127f9Sschwarze 	n->tok = tok;
886fa2127f9Sschwarze 	n->type = type;
887fa2127f9Sschwarze 	n->sec = man->lastsec;
888fa2127f9Sschwarze 
889fa2127f9Sschwarze 	if (man->flags & MDOC_SYNOPSIS)
890c4b0939cSschwarze 		n->flags |= NODE_SYNPRETTY;
891fa2127f9Sschwarze 	else
892c4b0939cSschwarze 		n->flags &= ~NODE_SYNPRETTY;
89394a3c318Sschwarze 	if ((man->flags & (ROFF_NOFILL | ROFF_NONOFILL)) == ROFF_NOFILL)
89421dcc2b4Sschwarze 		n->flags |= NODE_NOFILL;
89521dcc2b4Sschwarze 	else
89621dcc2b4Sschwarze 		n->flags &= ~NODE_NOFILL;
897fa2127f9Sschwarze 	if (man->flags & MDOC_NEWLINE)
898c4b0939cSschwarze 		n->flags |= NODE_LINE;
899fa2127f9Sschwarze 	man->flags &= ~MDOC_NEWLINE;
900fa2127f9Sschwarze 
901526e306bSschwarze 	return n;
902fa2127f9Sschwarze }
903fa2127f9Sschwarze 
904fa2127f9Sschwarze void
905fa2127f9Sschwarze roff_node_append(struct roff_man *man, struct roff_node *n)
906fa2127f9Sschwarze {
907fa2127f9Sschwarze 
908fa2127f9Sschwarze 	switch (man->next) {
909fa2127f9Sschwarze 	case ROFF_NEXT_SIBLING:
910396853b5Sschwarze 		if (man->last->next != NULL) {
911396853b5Sschwarze 			n->next = man->last->next;
912396853b5Sschwarze 			man->last->next->prev = n;
913396853b5Sschwarze 		} else
914396853b5Sschwarze 			man->last->parent->last = n;
915fa2127f9Sschwarze 		man->last->next = n;
916fa2127f9Sschwarze 		n->prev = man->last;
917fa2127f9Sschwarze 		n->parent = man->last->parent;
918fa2127f9Sschwarze 		break;
919fa2127f9Sschwarze 	case ROFF_NEXT_CHILD:
920816c3c54Sschwarze 		if (man->last->child != NULL) {
921816c3c54Sschwarze 			n->next = man->last->child;
922816c3c54Sschwarze 			man->last->child->prev = n;
923816c3c54Sschwarze 		} else
924816c3c54Sschwarze 			man->last->last = n;
925fa2127f9Sschwarze 		man->last->child = n;
926fa2127f9Sschwarze 		n->parent = man->last;
927fa2127f9Sschwarze 		break;
928fa2127f9Sschwarze 	default:
929fa2127f9Sschwarze 		abort();
930fa2127f9Sschwarze 	}
9313e642ba0Sschwarze 	man->last = n;
9323e642ba0Sschwarze 
9333e642ba0Sschwarze 	switch (n->type) {
9343e642ba0Sschwarze 	case ROFFT_HEAD:
9353e642ba0Sschwarze 		n->parent->head = n;
9363e642ba0Sschwarze 		break;
9373e642ba0Sschwarze 	case ROFFT_BODY:
9383e642ba0Sschwarze 		if (n->end != ENDBODY_NOT)
9393e642ba0Sschwarze 			return;
9403e642ba0Sschwarze 		n->parent->body = n;
9413e642ba0Sschwarze 		break;
9423e642ba0Sschwarze 	case ROFFT_TAIL:
9433e642ba0Sschwarze 		n->parent->tail = n;
9443e642ba0Sschwarze 		break;
9453e642ba0Sschwarze 	default:
9463e642ba0Sschwarze 		return;
9473e642ba0Sschwarze 	}
948fa2127f9Sschwarze 
949fa2127f9Sschwarze 	/*
950fa2127f9Sschwarze 	 * Copy over the normalised-data pointer of our parent.  Not
951fa2127f9Sschwarze 	 * everybody has one, but copying a null pointer is fine.
952fa2127f9Sschwarze 	 */
953fa2127f9Sschwarze 
954fa2127f9Sschwarze 	n->norm = n->parent->norm;
955fa2127f9Sschwarze 	assert(n->parent->type == ROFFT_BLOCK);
956fa2127f9Sschwarze }
957fa2127f9Sschwarze 
95869c34eaaSschwarze void
95969c34eaaSschwarze roff_word_alloc(struct roff_man *man, int line, int pos, const char *word)
96069c34eaaSschwarze {
96169c34eaaSschwarze 	struct roff_node	*n;
96269c34eaaSschwarze 
96369c34eaaSschwarze 	n = roff_node_alloc(man, line, pos, ROFFT_TEXT, TOKEN_NONE);
96469c34eaaSschwarze 	n->string = roff_strdup(man->roff, word);
96569c34eaaSschwarze 	roff_node_append(man, n);
966c4b0939cSschwarze 	n->flags |= NODE_VALID | NODE_ENDED;
96769c34eaaSschwarze 	man->next = ROFF_NEXT_SIBLING;
96869c34eaaSschwarze }
96969c34eaaSschwarze 
97069c34eaaSschwarze void
97169c34eaaSschwarze roff_word_append(struct roff_man *man, const char *word)
97269c34eaaSschwarze {
97369c34eaaSschwarze 	struct roff_node	*n;
97469c34eaaSschwarze 	char			*addstr, *newstr;
97569c34eaaSschwarze 
97669c34eaaSschwarze 	n = man->last;
97769c34eaaSschwarze 	addstr = roff_strdup(man->roff, word);
97869c34eaaSschwarze 	mandoc_asprintf(&newstr, "%s %s", n->string, addstr);
97969c34eaaSschwarze 	free(addstr);
98069c34eaaSschwarze 	free(n->string);
98169c34eaaSschwarze 	n->string = newstr;
98269c34eaaSschwarze 	man->next = ROFF_NEXT_SIBLING;
98369c34eaaSschwarze }
98469c34eaaSschwarze 
985e32c44d4Sschwarze void
986e32c44d4Sschwarze roff_elem_alloc(struct roff_man *man, int line, int pos, int tok)
987e32c44d4Sschwarze {
988e32c44d4Sschwarze 	struct roff_node	*n;
989e32c44d4Sschwarze 
990e32c44d4Sschwarze 	n = roff_node_alloc(man, line, pos, ROFFT_ELEM, tok);
991e32c44d4Sschwarze 	roff_node_append(man, n);
992e32c44d4Sschwarze 	man->next = ROFF_NEXT_CHILD;
993e32c44d4Sschwarze }
994e32c44d4Sschwarze 
995e32c44d4Sschwarze struct roff_node *
996e32c44d4Sschwarze roff_block_alloc(struct roff_man *man, int line, int pos, int tok)
997e32c44d4Sschwarze {
998e32c44d4Sschwarze 	struct roff_node	*n;
999e32c44d4Sschwarze 
1000e32c44d4Sschwarze 	n = roff_node_alloc(man, line, pos, ROFFT_BLOCK, tok);
1001e32c44d4Sschwarze 	roff_node_append(man, n);
1002e32c44d4Sschwarze 	man->next = ROFF_NEXT_CHILD;
1003526e306bSschwarze 	return n;
1004e32c44d4Sschwarze }
1005e32c44d4Sschwarze 
1006fa2127f9Sschwarze struct roff_node *
1007fa2127f9Sschwarze roff_head_alloc(struct roff_man *man, int line, int pos, int tok)
1008fa2127f9Sschwarze {
1009fa2127f9Sschwarze 	struct roff_node	*n;
1010fa2127f9Sschwarze 
1011fa2127f9Sschwarze 	n = roff_node_alloc(man, line, pos, ROFFT_HEAD, tok);
1012fa2127f9Sschwarze 	roff_node_append(man, n);
1013fa2127f9Sschwarze 	man->next = ROFF_NEXT_CHILD;
1014526e306bSschwarze 	return n;
1015fa2127f9Sschwarze }
1016fa2127f9Sschwarze 
1017fa2127f9Sschwarze struct roff_node *
1018fa2127f9Sschwarze roff_body_alloc(struct roff_man *man, int line, int pos, int tok)
1019fa2127f9Sschwarze {
1020fa2127f9Sschwarze 	struct roff_node	*n;
1021fa2127f9Sschwarze 
1022fa2127f9Sschwarze 	n = roff_node_alloc(man, line, pos, ROFFT_BODY, tok);
1023fa2127f9Sschwarze 	roff_node_append(man, n);
1024fa2127f9Sschwarze 	man->next = ROFF_NEXT_CHILD;
1025526e306bSschwarze 	return n;
1026fa2127f9Sschwarze }
1027fa2127f9Sschwarze 
1028d93f8561Sschwarze static void
1029fb382a01Sschwarze roff_addtbl(struct roff_man *man, int line, struct tbl_node *tbl)
103069c34eaaSschwarze {
103169c34eaaSschwarze 	struct roff_node	*n;
1032fb382a01Sschwarze 	struct tbl_span		*span;
103369c34eaaSschwarze 
10346b86842eSschwarze 	if (man->meta.macroset == MACROSET_MAN)
1035885ec428Sschwarze 		man_breakscope(man, ROFF_TS);
1036d93f8561Sschwarze 	while ((span = tbl_span(tbl)) != NULL) {
1037fb382a01Sschwarze 		n = roff_node_alloc(man, line, 0, ROFFT_TBL, TOKEN_NONE);
1038d93f8561Sschwarze 		n->span = span;
103969c34eaaSschwarze 		roff_node_append(man, n);
1040c4b0939cSschwarze 		n->flags |= NODE_VALID | NODE_ENDED;
104169c34eaaSschwarze 		man->next = ROFF_NEXT_SIBLING;
104269c34eaaSschwarze 	}
1043d93f8561Sschwarze }
104469c34eaaSschwarze 
104569c34eaaSschwarze void
1046fa2127f9Sschwarze roff_node_unlink(struct roff_man *man, struct roff_node *n)
1047fa2127f9Sschwarze {
1048fa2127f9Sschwarze 
1049fa2127f9Sschwarze 	/* Adjust siblings. */
1050fa2127f9Sschwarze 
1051fa2127f9Sschwarze 	if (n->prev)
1052fa2127f9Sschwarze 		n->prev->next = n->next;
1053fa2127f9Sschwarze 	if (n->next)
1054fa2127f9Sschwarze 		n->next->prev = n->prev;
1055fa2127f9Sschwarze 
1056fa2127f9Sschwarze 	/* Adjust parent. */
1057fa2127f9Sschwarze 
1058fa2127f9Sschwarze 	if (n->parent != NULL) {
1059fa2127f9Sschwarze 		if (n->parent->child == n)
1060fa2127f9Sschwarze 			n->parent->child = n->next;
1061fa2127f9Sschwarze 		if (n->parent->last == n)
1062fa2127f9Sschwarze 			n->parent->last = n->prev;
1063fa2127f9Sschwarze 	}
1064fa2127f9Sschwarze 
1065fa2127f9Sschwarze 	/* Adjust parse point. */
1066fa2127f9Sschwarze 
1067fa2127f9Sschwarze 	if (man == NULL)
1068fa2127f9Sschwarze 		return;
1069fa2127f9Sschwarze 	if (man->last == n) {
1070fa2127f9Sschwarze 		if (n->prev == NULL) {
1071fa2127f9Sschwarze 			man->last = n->parent;
1072fa2127f9Sschwarze 			man->next = ROFF_NEXT_CHILD;
1073fa2127f9Sschwarze 		} else {
1074fa2127f9Sschwarze 			man->last = n->prev;
1075fa2127f9Sschwarze 			man->next = ROFF_NEXT_SIBLING;
1076fa2127f9Sschwarze 		}
1077fa2127f9Sschwarze 	}
10786b86842eSschwarze 	if (man->meta.first == n)
10796b86842eSschwarze 		man->meta.first = NULL;
1080fa2127f9Sschwarze }
1081fa2127f9Sschwarze 
1082fa2127f9Sschwarze void
10838251afdeSschwarze roff_node_relink(struct roff_man *man, struct roff_node *n)
10848251afdeSschwarze {
10858251afdeSschwarze 	roff_node_unlink(man, n);
10868251afdeSschwarze 	n->prev = n->next = NULL;
10878251afdeSschwarze 	roff_node_append(man, n);
10888251afdeSschwarze }
10898251afdeSschwarze 
10908251afdeSschwarze void
1091fa2127f9Sschwarze roff_node_free(struct roff_node *n)
1092fa2127f9Sschwarze {
1093fa2127f9Sschwarze 
1094fa2127f9Sschwarze 	if (n->args != NULL)
1095fa2127f9Sschwarze 		mdoc_argv_free(n->args);
1096fa2127f9Sschwarze 	if (n->type == ROFFT_BLOCK || n->type == ROFFT_ELEM)
1097fa2127f9Sschwarze 		free(n->norm);
1098bf9acac6Sschwarze 	eqn_box_free(n->eqn);
1099fa2127f9Sschwarze 	free(n->string);
1100c220f9cfSschwarze 	free(n->tag);
1101fa2127f9Sschwarze 	free(n);
1102fa2127f9Sschwarze }
1103fa2127f9Sschwarze 
1104fa2127f9Sschwarze void
1105fa2127f9Sschwarze roff_node_delete(struct roff_man *man, struct roff_node *n)
1106fa2127f9Sschwarze {
1107fa2127f9Sschwarze 
1108fa2127f9Sschwarze 	while (n->child != NULL)
1109fa2127f9Sschwarze 		roff_node_delete(man, n->child);
1110fa2127f9Sschwarze 	roff_node_unlink(man, n);
1111fa2127f9Sschwarze 	roff_node_free(n);
1112fa2127f9Sschwarze }
1113fa2127f9Sschwarze 
11147ebbefbeSschwarze int
11157ebbefbeSschwarze roff_node_transparent(struct roff_node *n)
11167ebbefbeSschwarze {
11177ebbefbeSschwarze 	if (n == NULL)
11187ebbefbeSschwarze 		return 0;
11197ebbefbeSschwarze 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
11207ebbefbeSschwarze 		return 1;
1121e053e0fdSschwarze 	return roff_tok_transparent(n->tok);
1122e053e0fdSschwarze }
1123e053e0fdSschwarze 
1124e053e0fdSschwarze int
1125e053e0fdSschwarze roff_tok_transparent(enum roff_tok tok)
1126e053e0fdSschwarze {
1127e053e0fdSschwarze 	switch (tok) {
11287ebbefbeSschwarze 	case ROFF_ft:
11297ebbefbeSschwarze 	case ROFF_ll:
11307ebbefbeSschwarze 	case ROFF_mc:
11317ebbefbeSschwarze 	case ROFF_po:
11327ebbefbeSschwarze 	case ROFF_ta:
11337ebbefbeSschwarze 	case MDOC_Db:
11347ebbefbeSschwarze 	case MDOC_Es:
11357ebbefbeSschwarze 	case MDOC_Sm:
11367ebbefbeSschwarze 	case MDOC_Tg:
11377ebbefbeSschwarze 	case MAN_DT:
11387ebbefbeSschwarze 	case MAN_UC:
11397ebbefbeSschwarze 	case MAN_PD:
11407ebbefbeSschwarze 	case MAN_AT:
11417ebbefbeSschwarze 		return 1;
11427ebbefbeSschwarze 	default:
11437ebbefbeSschwarze 		return 0;
11447ebbefbeSschwarze 	}
11457ebbefbeSschwarze }
11467ebbefbeSschwarze 
11477ebbefbeSschwarze struct roff_node *
11487ebbefbeSschwarze roff_node_child(struct roff_node *n)
11497ebbefbeSschwarze {
11507ebbefbeSschwarze 	for (n = n->child; roff_node_transparent(n); n = n->next)
11517ebbefbeSschwarze 		continue;
11527ebbefbeSschwarze 	return n;
11537ebbefbeSschwarze }
11547ebbefbeSschwarze 
11557ebbefbeSschwarze struct roff_node *
11567ebbefbeSschwarze roff_node_prev(struct roff_node *n)
11577ebbefbeSschwarze {
11587ebbefbeSschwarze 	do {
11597ebbefbeSschwarze 		n = n->prev;
11607ebbefbeSschwarze 	} while (roff_node_transparent(n));
11617ebbefbeSschwarze 	return n;
11627ebbefbeSschwarze }
11637ebbefbeSschwarze 
11647ebbefbeSschwarze struct roff_node *
11657ebbefbeSschwarze roff_node_next(struct roff_node *n)
11667ebbefbeSschwarze {
11677ebbefbeSschwarze 	do {
11687ebbefbeSschwarze 		n = n->next;
11697ebbefbeSschwarze 	} while (roff_node_transparent(n));
11707ebbefbeSschwarze 	return n;
11717ebbefbeSschwarze }
11727ebbefbeSschwarze 
1173423631c9Sschwarze void
1174423631c9Sschwarze deroff(char **dest, const struct roff_node *n)
1175423631c9Sschwarze {
1176423631c9Sschwarze 	char	*cp;
1177423631c9Sschwarze 	size_t	 sz;
1178423631c9Sschwarze 
11799a542ed3Sschwarze 	if (n->string == NULL) {
1180423631c9Sschwarze 		for (n = n->child; n != NULL; n = n->next)
1181423631c9Sschwarze 			deroff(dest, n);
1182423631c9Sschwarze 		return;
1183423631c9Sschwarze 	}
1184423631c9Sschwarze 
11859fc28d5cSschwarze 	/* Skip leading whitespace. */
1186423631c9Sschwarze 
11879fc28d5cSschwarze 	for (cp = n->string; *cp != '\0'; cp++) {
1188972cfc25Sschwarze 		if (cp[0] == '\\' && cp[1] != '\0' &&
1189972cfc25Sschwarze 		    strchr(" %&0^|~", cp[1]) != NULL)
1190423631c9Sschwarze 			cp++;
11919fc28d5cSschwarze 		else if ( ! isspace((unsigned char)*cp))
1192423631c9Sschwarze 			break;
1193423631c9Sschwarze 	}
1194423631c9Sschwarze 
1195972cfc25Sschwarze 	/* Skip trailing backslash. */
1196972cfc25Sschwarze 
1197972cfc25Sschwarze 	sz = strlen(cp);
119830ffce23Sschwarze 	if (sz > 0 && cp[sz - 1] == '\\')
1199972cfc25Sschwarze 		sz--;
1200972cfc25Sschwarze 
1201423631c9Sschwarze 	/* Skip trailing whitespace. */
1202423631c9Sschwarze 
1203972cfc25Sschwarze 	for (; sz; sz--)
1204423631c9Sschwarze 		if ( ! isspace((unsigned char)cp[sz-1]))
1205423631c9Sschwarze 			break;
1206423631c9Sschwarze 
1207423631c9Sschwarze 	/* Skip empty strings. */
1208423631c9Sschwarze 
1209423631c9Sschwarze 	if (sz == 0)
1210423631c9Sschwarze 		return;
1211423631c9Sschwarze 
1212423631c9Sschwarze 	if (*dest == NULL) {
1213423631c9Sschwarze 		*dest = mandoc_strndup(cp, sz);
1214423631c9Sschwarze 		return;
1215423631c9Sschwarze 	}
1216423631c9Sschwarze 
1217423631c9Sschwarze 	mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp);
1218423631c9Sschwarze 	free(*dest);
1219423631c9Sschwarze 	*dest = cp;
1220423631c9Sschwarze }
1221423631c9Sschwarze 
1222fa2127f9Sschwarze /* --- main functions of the roff parser ---------------------------------- */
1223fa2127f9Sschwarze 
1224cd14d642Sschwarze /*
1225cd14d642Sschwarze  * Save comments preceding the title macro, for example in order to
1226cd14d642Sschwarze  * preserve Copyright and license headers in HTML output,
1227cd14d642Sschwarze  * provide diagnostics about RCS ids and trailing whitespace in comments,
1228cd14d642Sschwarze  * then discard comments including preceding whitespace.
1229cd14d642Sschwarze  * This function also handles input line continuation.
1230cd14d642Sschwarze  */
1231928431b4Sschwarze static int
1232cd14d642Sschwarze roff_parse_comment(struct roff *r, struct buf *buf, int ln, int pos, char ec)
1233928431b4Sschwarze {
1234928431b4Sschwarze 	struct roff_node *n;	/* used for header comments */
1235928431b4Sschwarze 	const char	*start;	/* start of the string to process */
1236928431b4Sschwarze 	const char	*cp;	/* for RCS id parsing */
1237928431b4Sschwarze 	char		*stesc;	/* start of an escape sequence ('\\') */
1238928431b4Sschwarze 	char		*ep;	/* end of comment string */
1239928431b4Sschwarze 	int		 rcsid;	/* kind of RCS id seen */
1240928431b4Sschwarze 
1241928431b4Sschwarze 	for (start = stesc = buf->buf + pos;; stesc++) {
1242cd14d642Sschwarze 		/*
1243cd14d642Sschwarze 		 * XXX Ugly hack: Remove the newline character that
1244cd14d642Sschwarze 		 * mparse_buf_r() appended to mark the end of input
1245cd14d642Sschwarze 		 * if it is not preceded by an escape character.
1246cd14d642Sschwarze 		 */
1247cd14d642Sschwarze 		if (stesc[0] == '\n') {
1248cd14d642Sschwarze 			assert(stesc[1] == '\0');
1249cd14d642Sschwarze 			stesc[0] = '\0';
1250cd14d642Sschwarze 		}
1251cd14d642Sschwarze 
1252928431b4Sschwarze 		/* The line ends without continuation or comment. */
1253928431b4Sschwarze 		if (stesc[0] == '\0')
1254928431b4Sschwarze 			return ROFF_CONT;
1255928431b4Sschwarze 
1256928431b4Sschwarze 		/* Unescaped byte: skip it. */
1257cd14d642Sschwarze 		if (stesc[0] != ec)
1258928431b4Sschwarze 			continue;
1259928431b4Sschwarze 
1260cd14d642Sschwarze 		/*
1261cd14d642Sschwarze 		 * XXX Ugly hack: Do not attempt to append another line
1262cd14d642Sschwarze 		 * if the function mparse_buf_r() appended a newline
1263cd14d642Sschwarze 		 * character to indicate the end of input.
1264cd14d642Sschwarze 		 */
1265cd14d642Sschwarze 		if (stesc[1] == '\n') {
1266cd14d642Sschwarze 			assert(stesc[2] == '\0');
1267cd14d642Sschwarze 			stesc[0] = '\0';
1268cd14d642Sschwarze 			return ROFF_CONT;
1269cd14d642Sschwarze 		}
1270cd14d642Sschwarze 
1271cd14d642Sschwarze 		/*
1272cd14d642Sschwarze 		 * An escape character at the end of an input line
1273cd14d642Sschwarze 		 * requests line continuation.
1274cd14d642Sschwarze 		 */
1275928431b4Sschwarze 		if (stesc[1] == '\0') {
1276928431b4Sschwarze 			stesc[0] = '\0';
1277928431b4Sschwarze 			return ROFF_IGN | ROFF_APPEND;
1278928431b4Sschwarze 		}
1279928431b4Sschwarze 
1280928431b4Sschwarze 		/* Found a comment: process it. */
1281928431b4Sschwarze 		if (stesc[1] == '"' || stesc[1] == '#')
1282928431b4Sschwarze 			break;
1283928431b4Sschwarze 
1284928431b4Sschwarze 		/* Escaped escape character: skip them both. */
1285cd14d642Sschwarze 		if (stesc[1] == ec)
1286928431b4Sschwarze 			stesc++;
1287928431b4Sschwarze 	}
1288928431b4Sschwarze 
1289928431b4Sschwarze 	/* Look for an RCS id in the comment. */
1290928431b4Sschwarze 
1291928431b4Sschwarze 	rcsid = 0;
1292928431b4Sschwarze 	if ((cp = strstr(stesc + 2, "$" "OpenBSD")) != NULL) {
1293928431b4Sschwarze 		rcsid = 1 << MANDOC_OS_OPENBSD;
1294928431b4Sschwarze 		cp += 8;
1295928431b4Sschwarze 	} else if ((cp = strstr(stesc + 2, "$" "NetBSD")) != NULL) {
1296928431b4Sschwarze 		rcsid = 1 << MANDOC_OS_NETBSD;
1297928431b4Sschwarze 		cp += 7;
1298928431b4Sschwarze 	}
1299928431b4Sschwarze 	if (cp != NULL && isalnum((unsigned char)*cp) == 0 &&
1300928431b4Sschwarze 	    strchr(cp, '$') != NULL) {
1301928431b4Sschwarze 		if (r->man->meta.rcsids & rcsid)
1302928431b4Sschwarze 			mandoc_msg(MANDOCERR_RCS_REP, ln,
1303928431b4Sschwarze 			    (int)(stesc - buf->buf) + 2, "%s", stesc + 1);
1304928431b4Sschwarze 		r->man->meta.rcsids |= rcsid;
1305928431b4Sschwarze 	}
1306928431b4Sschwarze 
1307928431b4Sschwarze 	/* Warn about trailing whitespace at the end of the comment. */
1308928431b4Sschwarze 
1309928431b4Sschwarze 	ep = strchr(stesc + 2, '\0') - 1;
1310928431b4Sschwarze 	if (*ep == '\n')
1311928431b4Sschwarze 		*ep-- = '\0';
1312928431b4Sschwarze 	if (*ep == ' ' || *ep == '\t')
1313928431b4Sschwarze 		mandoc_msg(MANDOCERR_SPACE_EOL,
1314928431b4Sschwarze 		    ln, (int)(ep - buf->buf), NULL);
1315928431b4Sschwarze 
1316928431b4Sschwarze 	/* Save comments preceding the title macro in the syntax tree. */
1317928431b4Sschwarze 
1318928431b4Sschwarze 	if (r->options & MPARSE_COMMENT) {
1319928431b4Sschwarze 		while (*ep == ' ' || *ep == '\t')
1320928431b4Sschwarze 			ep--;
1321928431b4Sschwarze 		ep[1] = '\0';
1322928431b4Sschwarze 		n = roff_node_alloc(r->man, ln, stesc + 1 - buf->buf,
1323928431b4Sschwarze 		    ROFFT_COMMENT, TOKEN_NONE);
1324928431b4Sschwarze 		n->string = mandoc_strdup(stesc + 2);
1325928431b4Sschwarze 		roff_node_append(r->man, n);
1326928431b4Sschwarze 		n->flags |= NODE_VALID | NODE_ENDED;
1327928431b4Sschwarze 		r->man->next = ROFF_NEXT_SIBLING;
1328928431b4Sschwarze 	}
1329928431b4Sschwarze 
1330928431b4Sschwarze 	/* The comment requests line continuation. */
1331928431b4Sschwarze 
1332928431b4Sschwarze 	if (stesc[1] == '#') {
1333928431b4Sschwarze 		*stesc = '\0';
1334928431b4Sschwarze 		return ROFF_IGN | ROFF_APPEND;
1335928431b4Sschwarze 	}
1336928431b4Sschwarze 
1337928431b4Sschwarze 	/* Discard the comment including preceding whitespace. */
1338928431b4Sschwarze 
1339928431b4Sschwarze 	while (stesc > start && stesc[-1] == ' ' &&
1340928431b4Sschwarze 	    (stesc == start + 1 || stesc[-2] != '\\'))
1341928431b4Sschwarze 		stesc--;
1342928431b4Sschwarze 	*stesc = '\0';
1343928431b4Sschwarze 	return ROFF_CONT;
1344928431b4Sschwarze }
1345928431b4Sschwarze 
1346769ee804Sschwarze /*
1347e94357f9Sschwarze  * In the current line, expand escape sequences that produce parsable
1348e94357f9Sschwarze  * input text.  Also check the syntax of the remaining escape sequences,
1349e94357f9Sschwarze  * which typically produce output glyphs or change formatter state.
1350769ee804Sschwarze  */
1351b7f92c5fSschwarze static int
1352cd14d642Sschwarze roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char ec)
1353769ee804Sschwarze {
1354cd14d642Sschwarze 	char		 ubuf[24];	/* buffer to print a number */
13553dc5225dSschwarze 	struct mctx	*ctx;		/* current macro call context */
1356cd14d642Sschwarze 	const char	*res;		/* the string to be pasted */
1357cd14d642Sschwarze 	const char	*src;		/* source for copying */
1358cd14d642Sschwarze 	char		*dst;		/* destination for copying */
13596d9b308dSschwarze 	enum mandoc_esc	 subtype;	/* return value from roff_escape */
1360cd14d642Sschwarze 	int		 iesc;		/* index of leading escape char */
1361cd14d642Sschwarze 	int		 inam;		/* index of the escape name */
1362cd14d642Sschwarze 	int		 iarg;		/* index beginning the argument */
1363cd14d642Sschwarze 	int		 iendarg;	/* index right after the argument */
1364cd14d642Sschwarze 	int		 iend;		/* index right after the sequence */
1365a72149c5Sschwarze 	int		 isrc, idst;	/* to reduce \\ and \. in names */
136607530f0aSschwarze 	int		 deftype;	/* type of definition to paste */
1367cd14d642Sschwarze 	int		 argi;		/* macro argument index */
1368cd14d642Sschwarze 	int		 quote_args;	/* true for \\$@, false for \\$* */
1369cd14d642Sschwarze 	int		 asz;		/* length of the replacement */
1370cd14d642Sschwarze 	int		 rsz;		/* length of the rest of the string */
1371cd14d642Sschwarze 	int		 npos;		/* position in numeric expression */
1372cd14d642Sschwarze 	int		 expand_count;	/* to avoid infinite loops */
1373be477484Sschwarze 
1374be477484Sschwarze 	expand_count = 0;
1375cd14d642Sschwarze 	while (buf->buf[pos] != '\0') {
13761631389bSschwarze 
1377e94357f9Sschwarze 		/*
1378cd14d642Sschwarze 		 * Skip plain ASCII characters.
1379e94357f9Sschwarze 		 * If we have a non-standard escape character,
1380cd14d642Sschwarze 		 * escape literal backslashes because all processing in
1381cd14d642Sschwarze 		 * subsequent functions uses the standard escaping rules.
1382e94357f9Sschwarze 		 */
13838376723aSschwarze 
1384cd14d642Sschwarze 		if (buf->buf[pos] != ec) {
13857bda13b1Sschwarze 			if (buf->buf[pos] == '\\') {
1386cd14d642Sschwarze 				roff_expand_patch(buf, pos, "\\e", pos + 1);
1387cd14d642Sschwarze 				pos++;
1388be477484Sschwarze 			}
1389cd14d642Sschwarze 			pos++;
13908376723aSschwarze 			continue;
1391be477484Sschwarze 		}
13928376723aSschwarze 
1393cd14d642Sschwarze 		/*
1394cd14d642Sschwarze 		 * Parse escape sequences,
1395cd14d642Sschwarze 		 * issue diagnostic messages when appropriate,
1396cd14d642Sschwarze 		 * and skip sequences that do not need expansion.
1397cd14d642Sschwarze 		 * If we have a non-standard escape character, translate
1398cd14d642Sschwarze 		 * it to backslashes and translate backslashes to \e.
1399cd14d642Sschwarze 		 */
14008376723aSschwarze 
14019784ce3eSschwarze 		if (roff_escape(buf->buf, ln, pos, &iesc, &inam,
14029784ce3eSschwarze 		    &iarg, &iendarg, &iend) != ESCAPE_EXPAND) {
1403cd14d642Sschwarze 			while (pos < iend) {
1404cd14d642Sschwarze 				if (buf->buf[pos] == ec) {
1405cd14d642Sschwarze 					buf->buf[pos] = '\\';
1406cd14d642Sschwarze 					if (pos + 1 < iend)
1407cd14d642Sschwarze 						pos++;
1408cd14d642Sschwarze 				} else if (buf->buf[pos] == '\\') {
1409cd14d642Sschwarze 					roff_expand_patch(buf,
1410cd14d642Sschwarze 					    pos, "\\e", pos + 1);
1411cd14d642Sschwarze 					pos++;
1412cd14d642Sschwarze 					iend++;
1413cd14d642Sschwarze 				}
1414cd14d642Sschwarze 				pos++;
1415cd14d642Sschwarze 			}
14168376723aSschwarze 			continue;
1417cd14d642Sschwarze 		}
14181eb5132dSschwarze 
1419a72149c5Sschwarze 		/* Reduce \\ and \. in names. */
1420a72149c5Sschwarze 
1421a72149c5Sschwarze 		if (buf->buf[inam] == '*' || buf->buf[inam] == 'n') {
1422a72149c5Sschwarze 			isrc = idst = iarg;
1423a72149c5Sschwarze 			while (isrc < iendarg) {
1424a72149c5Sschwarze 				if (isrc + 1 < iendarg &&
1425a72149c5Sschwarze 				    buf->buf[isrc] == '\\' &&
1426a72149c5Sschwarze 				    (buf->buf[isrc + 1] == '\\' ||
1427a72149c5Sschwarze 				     buf->buf[isrc + 1] == '.'))
1428a72149c5Sschwarze 					isrc++;
1429a72149c5Sschwarze 				buf->buf[idst++] = buf->buf[isrc++];
1430a72149c5Sschwarze 			}
1431a72149c5Sschwarze 			iendarg -= isrc - idst;
1432a72149c5Sschwarze 		}
1433a72149c5Sschwarze 
1434cd14d642Sschwarze 		/* Handle expansion. */
1435cd14d642Sschwarze 
1436c542e817Sschwarze 		res = NULL;
1437cd14d642Sschwarze 		switch (buf->buf[inam]) {
143849aff9f8Sschwarze 		case '*':
1439cd14d642Sschwarze 			if (iendarg == iarg)
1440cd14d642Sschwarze 				break;
144107530f0aSschwarze 			deftype = ROFFDEF_USER | ROFFDEF_PRE;
1442cd14d642Sschwarze 			if ((res = roff_getstrn(r, buf->buf + iarg,
1443cd14d642Sschwarze 			    iendarg - iarg, &deftype)) != NULL)
1444cd14d642Sschwarze 				break;
14458138dde8Sschwarze 
14468138dde8Sschwarze 			/*
1447d9a51c35Sjmc 			 * If not overridden,
1448cd14d642Sschwarze 			 * let \*(.T through to the formatters.
14498138dde8Sschwarze 			 */
14508138dde8Sschwarze 
1451cd14d642Sschwarze 			if (iendarg - iarg == 2 &&
1452cd14d642Sschwarze 			    buf->buf[iarg] == '.' &&
1453cd14d642Sschwarze 			    buf->buf[iarg + 1] == 'T') {
1454cd14d642Sschwarze 				roff_setstrn(&r->strtab, ".T", 2, NULL, 0, 0);
1455cd14d642Sschwarze 				pos = iend;
14568138dde8Sschwarze 				continue;
14578138dde8Sschwarze 			}
1458cd14d642Sschwarze 
1459cd14d642Sschwarze 			mandoc_msg(MANDOCERR_STR_UNDEF, ln, iesc,
1460cd14d642Sschwarze 			    "%.*s", iendarg - iarg, buf->buf + iarg);
1461778fe90eSschwarze 			break;
1462cd14d642Sschwarze 
14633dc5225dSschwarze 		case '$':
14643dc5225dSschwarze 			if (r->mstackpos < 0) {
1465cd14d642Sschwarze 				mandoc_msg(MANDOCERR_ARG_UNDEF, ln, iesc,
1466cd14d642Sschwarze 				    "%.*s", iend - iesc, buf->buf + iesc);
14673dc5225dSschwarze 				break;
14683dc5225dSschwarze 			}
14693dc5225dSschwarze 			ctx = r->mstack + r->mstackpos;
1470cd14d642Sschwarze 			argi = buf->buf[iarg] - '1';
1471cd14d642Sschwarze 			if (argi >= 0 && argi <= 8) {
1472cd14d642Sschwarze 				if (argi < ctx->argc)
1473cd14d642Sschwarze 					res = ctx->argv[argi];
14743dc5225dSschwarze 				break;
14753dc5225dSschwarze 			}
1476cd14d642Sschwarze 			if (buf->buf[iarg] == '*')
14773dc5225dSschwarze 				quote_args = 0;
1478cd14d642Sschwarze 			else if (buf->buf[iarg] == '@')
14793dc5225dSschwarze 				quote_args = 1;
14803dc5225dSschwarze 			else {
1481cd14d642Sschwarze 				mandoc_msg(MANDOCERR_ARG_NONUM, ln, iesc,
1482cd14d642Sschwarze 				    "%.*s", iend - iesc, buf->buf + iesc);
14833dc5225dSschwarze 				break;
14843dc5225dSschwarze 			}
14853dc5225dSschwarze 			asz = 0;
1486cd14d642Sschwarze 			for (argi = 0; argi < ctx->argc; argi++) {
1487cd14d642Sschwarze 				if (argi)
14883dc5225dSschwarze 					asz++;  /* blank */
14893dc5225dSschwarze 				if (quote_args)
14903dc5225dSschwarze 					asz += 2;  /* quotes */
1491cd14d642Sschwarze 				asz += strlen(ctx->argv[argi]);
14923dc5225dSschwarze 			}
1493cd14d642Sschwarze 			if (asz != iend - iesc) {
1494cd14d642Sschwarze 				rsz = buf->sz - iend;
1495cd14d642Sschwarze 				if (asz < iend - iesc)
1496cd14d642Sschwarze 					memmove(buf->buf + iesc + asz,
1497cd14d642Sschwarze 					    buf->buf + iend, rsz);
1498cd14d642Sschwarze 				buf->sz = iesc + asz + rsz;
1499cd14d642Sschwarze 				buf->buf = mandoc_realloc(buf->buf, buf->sz);
1500cd14d642Sschwarze 				if (asz > iend - iesc)
1501cd14d642Sschwarze 					memmove(buf->buf + iesc + asz,
1502cd14d642Sschwarze 					    buf->buf + iend, rsz);
15033dc5225dSschwarze 			}
1504cd14d642Sschwarze 			dst = buf->buf + iesc;
1505cd14d642Sschwarze 			for (argi = 0; argi < ctx->argc; argi++) {
1506cd14d642Sschwarze 				if (argi)
1507cd14d642Sschwarze 					*dst++ = ' ';
15083dc5225dSschwarze 				if (quote_args)
1509cd14d642Sschwarze 					*dst++ = '"';
1510cd14d642Sschwarze 				src = ctx->argv[argi];
1511cd14d642Sschwarze 				while (*src != '\0')
1512cd14d642Sschwarze 					*dst++ = *src++;
15133dc5225dSschwarze 				if (quote_args)
1514cd14d642Sschwarze 					*dst++ = '"';
15153dc5225dSschwarze 			}
15163dc5225dSschwarze 			continue;
151775a6bad9Sschwarze 		case 'A':
151875a6bad9Sschwarze 			ubuf[0] = iendarg > iarg ? '1' : '0';
151975a6bad9Sschwarze 			ubuf[1] = '\0';
152075a6bad9Sschwarze 			res = ubuf;
152175a6bad9Sschwarze 			break;
152249aff9f8Sschwarze 		case 'B':
1523778fe90eSschwarze 			npos = 0;
1524cd14d642Sschwarze 			ubuf[0] = iendarg > iarg && iend > iendarg &&
15253b5bd91eSschwarze 			    roff_evalnum(ln, buf->buf + iarg, &npos,
1526f4432377Sschwarze 					 NULL, 'u', 0) &&
1527cd14d642Sschwarze 			    npos == iendarg - iarg ? '1' : '0';
1528778fe90eSschwarze 			ubuf[1] = '\0';
1529cd14d642Sschwarze 			res = ubuf;
1530778fe90eSschwarze 			break;
153183a9dfe1Sschwarze 		case 'V':
153283a9dfe1Sschwarze 			mandoc_msg(MANDOCERR_UNSUPP, ln, iesc,
153383a9dfe1Sschwarze 			    "%.*s", iend - iesc, buf->buf + iesc);
153483a9dfe1Sschwarze 			roff_expand_patch(buf, iendarg, "}", iend);
153583a9dfe1Sschwarze 			roff_expand_patch(buf, iesc, "${", iarg);
153683a9dfe1Sschwarze 			continue;
15376f49ebc4Sschwarze 		case 'g':
15386f49ebc4Sschwarze 			break;
153949aff9f8Sschwarze 		case 'n':
1540cd14d642Sschwarze 			if (iendarg > iarg)
154147813146Sschwarze 				(void)snprintf(ubuf, sizeof(ubuf), "%d",
1542cd14d642Sschwarze 				    roff_getregn(r, buf->buf + iarg,
1543cd14d642Sschwarze 				    iendarg - iarg, buf->buf[inam + 1]));
1544ce205d5fSschwarze 			else
1545ce205d5fSschwarze 				ubuf[0] = '\0';
1546cd14d642Sschwarze 			res = ubuf;
1547778fe90eSschwarze 			break;
154849aff9f8Sschwarze 		case 'w':
15496d9b308dSschwarze 			rsz = 0;
15506d9b308dSschwarze 			subtype = ESCAPE_UNDEF;
15516d9b308dSschwarze 			while (iarg < iendarg) {
15526d9b308dSschwarze 				asz = subtype == ESCAPE_SKIPCHAR ? 0 : 1;
15536d9b308dSschwarze 				if (buf->buf[iarg] != '\\') {
15546d9b308dSschwarze 					rsz += asz;
15556d9b308dSschwarze 					iarg++;
15566d9b308dSschwarze 					continue;
15576d9b308dSschwarze 				}
15586d9b308dSschwarze 				switch ((subtype = roff_escape(buf->buf, 0,
15596d9b308dSschwarze 				    iarg, NULL, NULL, NULL, NULL, &iarg))) {
15606d9b308dSschwarze 				case ESCAPE_SPECIAL:
15616d9b308dSschwarze 				case ESCAPE_NUMBERED:
15626d9b308dSschwarze 				case ESCAPE_UNICODE:
15636d9b308dSschwarze 				case ESCAPE_OVERSTRIKE:
15646d9b308dSschwarze 				case ESCAPE_UNDEF:
15656d9b308dSschwarze 					break;
15666d9b308dSschwarze 				case ESCAPE_DEVICE:
15676d9b308dSschwarze 					asz *= 8;
15686d9b308dSschwarze 					break;
15696d9b308dSschwarze 				case ESCAPE_EXPAND:
15706d9b308dSschwarze 					abort();
15716d9b308dSschwarze 				default:
15726d9b308dSschwarze 					continue;
15736d9b308dSschwarze 				}
15746d9b308dSschwarze 				rsz += asz;
15756d9b308dSschwarze 			}
15766d9b308dSschwarze 			(void)snprintf(ubuf, sizeof(ubuf), "%d", rsz * 24);
1577cd14d642Sschwarze 			res = ubuf;
1578cd14d642Sschwarze 			break;
1579cd14d642Sschwarze 		default:
1580778fe90eSschwarze 			break;
1581778fe90eSschwarze 		}
1582cd14d642Sschwarze 		if (res == NULL)
1583a5e11edeSschwarze 			res = "";
1584cd14d642Sschwarze 		if (++expand_count > EXPAND_LIMIT ||
1585cd14d642Sschwarze 		    buf->sz + strlen(res) > SHRT_MAX) {
1586cd14d642Sschwarze 			mandoc_msg(MANDOCERR_ROFFLOOP, ln, iesc, NULL);
1587526e306bSschwarze 			return ROFF_IGN;
1588769ee804Sschwarze 		}
1589cd14d642Sschwarze 		roff_expand_patch(buf, iesc, res, iend);
159004e980cbSschwarze 	}
1591526e306bSschwarze 	return ROFF_CONT;
1592769ee804Sschwarze }
1593769ee804Sschwarze 
159404e980cbSschwarze /*
1595cd14d642Sschwarze  * Replace the substring from the start position (inclusive)
1596cd14d642Sschwarze  * to end position (exclusive) with the repl(acement) string.
1597cd14d642Sschwarze  */
1598cd14d642Sschwarze static void
1599cd14d642Sschwarze roff_expand_patch(struct buf *buf, int start, const char *repl, int end)
1600cd14d642Sschwarze {
1601cd14d642Sschwarze 	char	*nbuf;
1602cd14d642Sschwarze 
160383a9dfe1Sschwarze 	buf->sz = mandoc_asprintf(&nbuf, "%.*s%s%s", start, buf->buf,
160483a9dfe1Sschwarze 	    repl, buf->buf + end) + 1;
1605cd14d642Sschwarze 	free(buf->buf);
1606cd14d642Sschwarze 	buf->buf = nbuf;
1607cd14d642Sschwarze }
1608cd14d642Sschwarze 
1609cd14d642Sschwarze /*
16109485fb47Sschwarze  * Parse a quoted or unquoted roff-style request or macro argument.
16119485fb47Sschwarze  * Return a pointer to the parsed argument, which is either the original
16129485fb47Sschwarze  * pointer or advanced by one byte in case the argument is quoted.
16139485fb47Sschwarze  * NUL-terminate the argument in place.
16149485fb47Sschwarze  * Collapse pairs of quotes inside quoted arguments.
16159485fb47Sschwarze  * Advance the argument pointer to the next argument,
16169485fb47Sschwarze  * or to the NUL byte terminating the argument line.
16179485fb47Sschwarze  */
16189485fb47Sschwarze char *
1619e94357f9Sschwarze roff_getarg(struct roff *r, char **cpp, int ln, int *pos)
16209485fb47Sschwarze {
1621e94357f9Sschwarze 	struct buf	 buf;
1622e94357f9Sschwarze 	char		*cp, *start;
1623e94357f9Sschwarze 	int		 newesc, pairs, quoted, white;
16249485fb47Sschwarze 
16259485fb47Sschwarze 	/* Quoting can only start with a new word. */
16269485fb47Sschwarze 	start = *cpp;
16279485fb47Sschwarze 	quoted = 0;
16289485fb47Sschwarze 	if ('"' == *start) {
16299485fb47Sschwarze 		quoted = 1;
16309485fb47Sschwarze 		start++;
16319485fb47Sschwarze 	}
16329485fb47Sschwarze 
1633e94357f9Sschwarze 	newesc = pairs = white = 0;
16349485fb47Sschwarze 	for (cp = start; '\0' != *cp; cp++) {
16359485fb47Sschwarze 
16369485fb47Sschwarze 		/*
16379485fb47Sschwarze 		 * Move the following text left
16389485fb47Sschwarze 		 * after quoted quotes and after "\\" and "\t".
16399485fb47Sschwarze 		 */
16409485fb47Sschwarze 		if (pairs)
16419485fb47Sschwarze 			cp[-pairs] = cp[0];
16429485fb47Sschwarze 
16439485fb47Sschwarze 		if ('\\' == cp[0]) {
16449485fb47Sschwarze 			/*
16459485fb47Sschwarze 			 * In copy mode, translate double to single
16469485fb47Sschwarze 			 * backslashes and backslash-t to literal tabs.
16479485fb47Sschwarze 			 */
16489485fb47Sschwarze 			switch (cp[1]) {
16499485fb47Sschwarze 			case 'a':
16509485fb47Sschwarze 			case 't':
1651395bfb10Sschwarze 				cp[-pairs] = '\t';
1652e94357f9Sschwarze 				pairs++;
1653e94357f9Sschwarze 				cp++;
1654e94357f9Sschwarze 				break;
16559485fb47Sschwarze 			case '\\':
16567bda13b1Sschwarze 				cp[-pairs] = '\\';
16571c23c756Sschwarze 				newesc = 1;
16589485fb47Sschwarze 				pairs++;
16599485fb47Sschwarze 				cp++;
16609485fb47Sschwarze 				break;
16619485fb47Sschwarze 			case ' ':
16629485fb47Sschwarze 				/* Skip escaped blanks. */
16639485fb47Sschwarze 				if (0 == quoted)
16649485fb47Sschwarze 					cp++;
16659485fb47Sschwarze 				break;
16669485fb47Sschwarze 			default:
16679485fb47Sschwarze 				break;
16689485fb47Sschwarze 			}
16699485fb47Sschwarze 		} else if (0 == quoted) {
16709485fb47Sschwarze 			if (' ' == cp[0]) {
16719485fb47Sschwarze 				/* Unescaped blanks end unquoted args. */
16729485fb47Sschwarze 				white = 1;
16739485fb47Sschwarze 				break;
16749485fb47Sschwarze 			}
16759485fb47Sschwarze 		} else if ('"' == cp[0]) {
16769485fb47Sschwarze 			if ('"' == cp[1]) {
16779485fb47Sschwarze 				/* Quoted quotes collapse. */
16789485fb47Sschwarze 				pairs++;
16799485fb47Sschwarze 				cp++;
16809485fb47Sschwarze 			} else {
16819485fb47Sschwarze 				/* Unquoted quotes end quoted args. */
16829485fb47Sschwarze 				quoted = 2;
16839485fb47Sschwarze 				break;
16849485fb47Sschwarze 			}
16859485fb47Sschwarze 		}
16869485fb47Sschwarze 	}
16879485fb47Sschwarze 
16889485fb47Sschwarze 	/* Quoted argument without a closing quote. */
16899485fb47Sschwarze 	if (1 == quoted)
16909485fb47Sschwarze 		mandoc_msg(MANDOCERR_ARG_QUOTE, ln, *pos, NULL);
16919485fb47Sschwarze 
16929485fb47Sschwarze 	/* NUL-terminate this argument and move to the next one. */
16939485fb47Sschwarze 	if (pairs)
16949485fb47Sschwarze 		cp[-pairs] = '\0';
16959485fb47Sschwarze 	if ('\0' != *cp) {
16969485fb47Sschwarze 		*cp++ = '\0';
16979485fb47Sschwarze 		while (' ' == *cp)
16989485fb47Sschwarze 			cp++;
16999485fb47Sschwarze 	}
17009485fb47Sschwarze 	*pos += (int)(cp - start) + (quoted ? 1 : 0);
17019485fb47Sschwarze 	*cpp = cp;
17029485fb47Sschwarze 
17039485fb47Sschwarze 	if ('\0' == *cp && (white || ' ' == cp[-1]))
17049485fb47Sschwarze 		mandoc_msg(MANDOCERR_SPACE_EOL, ln, *pos, NULL);
17059485fb47Sschwarze 
1706e94357f9Sschwarze 	start = mandoc_strdup(start);
1707e94357f9Sschwarze 	if (newesc == 0)
17089485fb47Sschwarze 		return start;
1709e94357f9Sschwarze 
1710e94357f9Sschwarze 	buf.buf = start;
1711e94357f9Sschwarze 	buf.sz = strlen(start) + 1;
1712e94357f9Sschwarze 	buf.next = NULL;
171329079a11Sschwarze 	if (roff_expand(r, &buf, ln, 0, '\\') == ROFF_IGN) {
1714e94357f9Sschwarze 		free(buf.buf);
1715e94357f9Sschwarze 		buf.buf = mandoc_strdup("");
1716e94357f9Sschwarze 	}
1717e94357f9Sschwarze 	return buf.buf;
17189485fb47Sschwarze }
17199485fb47Sschwarze 
17209485fb47Sschwarze 
17219485fb47Sschwarze /*
1722f16a1164Sschwarze  * Process text streams.
172304e980cbSschwarze  */
1724b7f92c5fSschwarze static int
1725e13b4195Sschwarze roff_parsetext(struct roff *r, struct buf *buf, int pos, int *offs)
172604e980cbSschwarze {
172704e980cbSschwarze 	size_t		 sz;
172804e980cbSschwarze 	const char	*start;
1729a91e242cSschwarze 	char		*p;
1730a91e242cSschwarze 	int		 isz;
173104e980cbSschwarze 	enum mandoc_esc	 esc;
173204e980cbSschwarze 
1733f16a1164Sschwarze 	/* Spring the input line trap. */
1734f16a1164Sschwarze 
1735f16a1164Sschwarze 	if (roffit_lines == 1) {
1736f16a1164Sschwarze 		isz = mandoc_asprintf(&p, "%s\n.%s", buf->buf, roffit_macro);
1737f16a1164Sschwarze 		free(buf->buf);
1738f16a1164Sschwarze 		buf->buf = p;
1739f16a1164Sschwarze 		buf->sz = isz + 1;
1740f16a1164Sschwarze 		*offs = 0;
1741f16a1164Sschwarze 		free(roffit_macro);
1742f16a1164Sschwarze 		roffit_lines = 0;
1743526e306bSschwarze 		return ROFF_REPARSE;
1744f16a1164Sschwarze 	} else if (roffit_lines > 1)
1745f16a1164Sschwarze 		--roffit_lines;
1746f16a1164Sschwarze 
1747e13b4195Sschwarze 	if (roffce_node != NULL && buf->buf[pos] != '\0') {
1748e13b4195Sschwarze 		if (roffce_lines < 1) {
1749e13b4195Sschwarze 			r->man->last = roffce_node;
1750e13b4195Sschwarze 			r->man->next = ROFF_NEXT_SIBLING;
1751e13b4195Sschwarze 			roffce_lines = 0;
1752e13b4195Sschwarze 			roffce_node = NULL;
1753e13b4195Sschwarze 		} else
1754e13b4195Sschwarze 			roffce_lines--;
1755e13b4195Sschwarze 	}
1756e13b4195Sschwarze 
1757f16a1164Sschwarze 	/* Convert all breakable hyphens into ASCII_HYPH. */
1758f16a1164Sschwarze 
175928687875Sschwarze 	start = p = buf->buf + pos;
176004e980cbSschwarze 
176128687875Sschwarze 	while (*p != '\0') {
176204e980cbSschwarze 		sz = strcspn(p, "-\\");
176304e980cbSschwarze 		p += sz;
176404e980cbSschwarze 
176528687875Sschwarze 		if (*p == '\0')
176604e980cbSschwarze 			break;
176704e980cbSschwarze 
176828687875Sschwarze 		if (*p == '\\') {
176904e980cbSschwarze 			/* Skip over escapes. */
177004e980cbSschwarze 			p++;
1771c702e996Sschwarze 			esc = mandoc_escape((const char **)&p, NULL, NULL);
177228687875Sschwarze 			if (esc == ESCAPE_ERROR)
177304e980cbSschwarze 				break;
177452b834b3Sschwarze 			while (*p == '-')
177552b834b3Sschwarze 				p++;
177604e980cbSschwarze 			continue;
177704e980cbSschwarze 		} else if (p == start) {
177804e980cbSschwarze 			p++;
177904e980cbSschwarze 			continue;
1780769ee804Sschwarze 		}
1781769ee804Sschwarze 
1782d724826eSschwarze 		if (isalpha((unsigned char)p[-1]) &&
1783d724826eSschwarze 		    isalpha((unsigned char)p[1]))
178404e980cbSschwarze 			*p = ASCII_HYPH;
178504e980cbSschwarze 		p++;
178604e980cbSschwarze 	}
1787526e306bSschwarze 	return ROFF_CONT;
178804e980cbSschwarze }
1789769ee804Sschwarze 
1790b7f92c5fSschwarze int
1791dd9cc97dSschwarze roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs, size_t len)
1792f673a3c7Sschwarze {
179314a309e3Sschwarze 	enum roff_tok	 t;
1794b7f92c5fSschwarze 	int		 e;
179528687875Sschwarze 	int		 pos;	/* parse point */
1796a2c9ff1aSschwarze 	int		 spos;	/* saved parse point for messages */
179728687875Sschwarze 	int		 ppos;	/* original offset in buf->buf */
179828687875Sschwarze 	int		 ctl;	/* macro line (boolean) */
179928687875Sschwarze 
180028687875Sschwarze 	ppos = pos = *offs;
1801f673a3c7Sschwarze 
1802dd9cc97dSschwarze 	if (len > 80 && r->tbl == NULL && r->eqn == NULL &&
1803dd9cc97dSschwarze 	    (r->man->flags & ROFF_NOFILL) == 0 &&
1804dd9cc97dSschwarze 	    strchr(" .\\", buf->buf[pos]) == NULL &&
1805dd9cc97dSschwarze 	    buf->buf[pos] != r->control &&
1806dd9cc97dSschwarze 	    strcspn(buf->buf, " ") < 80)
1807dd9cc97dSschwarze 		mandoc_msg(MANDOCERR_TEXT_LONG, ln, (int)len - 1,
1808dd9cc97dSschwarze 		    "%.20s...", buf->buf + pos);
1809dd9cc97dSschwarze 
181057ca9bd5Sschwarze 	/* Handle in-line equation delimiters. */
181157ca9bd5Sschwarze 
18128e45305fSschwarze 	if (r->tbl == NULL &&
18138e45305fSschwarze 	    r->last_eqn != NULL && r->last_eqn->delim &&
181457ca9bd5Sschwarze 	    (r->eqn == NULL || r->eqn_inline)) {
181528687875Sschwarze 		e = roff_eqndelim(r, buf, pos);
181657ca9bd5Sschwarze 		if (e == ROFF_REPARSE)
1817526e306bSschwarze 			return e;
181857ca9bd5Sschwarze 		assert(e == ROFF_CONT);
181957ca9bd5Sschwarze 	}
182057ca9bd5Sschwarze 
1821928431b4Sschwarze 	/* Handle comments and escape sequences. */
1822928431b4Sschwarze 
1823928431b4Sschwarze 	e = roff_parse_comment(r, buf, ln, pos, r->escape);
1824928431b4Sschwarze 	if ((e & ROFF_MASK) == ROFF_IGN)
1825928431b4Sschwarze 		return e;
1826928431b4Sschwarze 	assert(e == ROFF_CONT);
1827769ee804Sschwarze 
1828e94357f9Sschwarze 	e = roff_expand(r, buf, ln, pos, r->escape);
1829b7f92c5fSschwarze 	if ((e & ROFF_MASK) == ROFF_IGN)
1830526e306bSschwarze 		return e;
183128687875Sschwarze 	assert(e == ROFF_CONT);
1832769ee804Sschwarze 
183328687875Sschwarze 	ctl = roff_getcontrol(r, buf->buf, &pos);
1834a35fc07aSschwarze 
1835769ee804Sschwarze 	/*
183662d9ccdbSschwarze 	 * First, if a scope is open and we're not a macro, pass the
1837328cf81fSschwarze 	 * text through the macro's filter.
1838328cf81fSschwarze 	 * Equations process all content themselves.
1839328cf81fSschwarze 	 * Tables process almost all content themselves, but we want
1840328cf81fSschwarze 	 * to warn about macros before passing it there.
1841f673a3c7Sschwarze 	 */
184262d9ccdbSschwarze 
1843328cf81fSschwarze 	if (r->last != NULL && ! ctl) {
184462d9ccdbSschwarze 		t = r->last->tok;
184528687875Sschwarze 		e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs);
1846b7f92c5fSschwarze 		if ((e & ROFF_MASK) == ROFF_IGN)
1847526e306bSschwarze 			return e;
1848b7f92c5fSschwarze 		e &= ~ROFF_MASK;
1849b7f92c5fSschwarze 	} else
1850b7f92c5fSschwarze 		e = ROFF_IGN;
1851bf9acac6Sschwarze 	if (r->eqn != NULL && strncmp(buf->buf + ppos, ".EN", 3)) {
1852bf9acac6Sschwarze 		eqn_read(r->eqn, buf->buf + ppos);
1853b7f92c5fSschwarze 		return e;
1854bf9acac6Sschwarze 	}
1855d93f8561Sschwarze 	if (r->tbl != NULL && (ctl == 0 || buf->buf[pos] == '\0')) {
1856d93f8561Sschwarze 		tbl_read(r->tbl, ln, buf->buf, ppos);
1857fb382a01Sschwarze 		roff_addtbl(r->man, ln, r->tbl);
1858b7f92c5fSschwarze 		return e;
1859d93f8561Sschwarze 	}
186067217f5cSschwarze 	if ( ! ctl) {
186167217f5cSschwarze 		r->options &= ~MPARSE_COMMENT;
1862b7f92c5fSschwarze 		return roff_parsetext(r, buf, pos, offs) | e;
186367217f5cSschwarze 	}
186462d9ccdbSschwarze 
18659d7b4fe8Sschwarze 	/* Skip empty request lines. */
18669d7b4fe8Sschwarze 
186728687875Sschwarze 	if (buf->buf[pos] == '"') {
1868a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_COMMENT_BAD, ln, pos, NULL);
1869526e306bSschwarze 		return ROFF_IGN;
187028687875Sschwarze 	} else if (buf->buf[pos] == '\0')
1871526e306bSschwarze 		return ROFF_IGN;
18729d7b4fe8Sschwarze 
187362d9ccdbSschwarze 	/*
187462d9ccdbSschwarze 	 * If a scope is open, go to the child handler for that macro,
187562d9ccdbSschwarze 	 * as it may want to preprocess before doing anything with it.
187662d9ccdbSschwarze 	 */
187762d9ccdbSschwarze 
187862d9ccdbSschwarze 	if (r->last) {
1879f673a3c7Sschwarze 		t = r->last->tok;
1880526e306bSschwarze 		return (*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs);
188162d9ccdbSschwarze 	}
188262d9ccdbSschwarze 
188367217f5cSschwarze 	r->options &= ~MPARSE_COMMENT;
1884a2c9ff1aSschwarze 	spos = pos;
1885a2c9ff1aSschwarze 	t = roff_parse(r, buf->buf, &pos, ln, ppos);
1886f79258f3Sschwarze 	return roff_req_or_macro(r, t, buf, ln, spos, pos, offs);
1887f79258f3Sschwarze }
1888a2c9ff1aSschwarze 
1889f79258f3Sschwarze /*
1890f79258f3Sschwarze  * Handle a new request or macro.
1891f79258f3Sschwarze  * May be called outside any scope or from inside a conditional scope.
1892f79258f3Sschwarze  */
1893f79258f3Sschwarze static int
1894f79258f3Sschwarze roff_req_or_macro(ROFF_ARGS) {
1895a2c9ff1aSschwarze 
1896f79258f3Sschwarze 	/* For now, tables ignore most macros and some request. */
1897f79258f3Sschwarze 
1898f79258f3Sschwarze 	if (r->tbl != NULL && (tok == TOKEN_NONE || tok == ROFF_TS ||
1899f79258f3Sschwarze 	    tok == ROFF_br || tok == ROFF_ce || tok == ROFF_rj ||
1900f79258f3Sschwarze 	    tok == ROFF_sp)) {
1901a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_TBLMACRO,
1902f79258f3Sschwarze 		    ln, ppos, "%s", buf->buf + ppos);
1903f79258f3Sschwarze 		if (tok != TOKEN_NONE)
1904526e306bSschwarze 			return ROFF_IGN;
190539aede77Sschwarze 		while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ')
190639aede77Sschwarze 			pos++;
19079b423113Sschwarze 		while (buf->buf[pos] == ' ')
190839aede77Sschwarze 			pos++;
1909d93f8561Sschwarze 		tbl_read(r->tbl, ln, buf->buf, pos);
1910fb382a01Sschwarze 		roff_addtbl(r->man, ln, r->tbl);
1911d93f8561Sschwarze 		return ROFF_IGN;
1912a2c9ff1aSschwarze 	}
1913a2c9ff1aSschwarze 
1914e13b4195Sschwarze 	/* For now, let high level macros abort .ce mode. */
1915e13b4195Sschwarze 
1916f79258f3Sschwarze 	if (roffce_node != NULL &&
1917f79258f3Sschwarze 	    (tok == TOKEN_NONE || tok == ROFF_Dd || tok == ROFF_EQ ||
1918f79258f3Sschwarze 	     tok == ROFF_TH || tok == ROFF_TS)) {
1919e13b4195Sschwarze 		r->man->last = roffce_node;
1920e13b4195Sschwarze 		r->man->next = ROFF_NEXT_SIBLING;
1921e13b4195Sschwarze 		roffce_lines = 0;
1922e13b4195Sschwarze 		roffce_node = NULL;
1923e13b4195Sschwarze 	}
1924e13b4195Sschwarze 
192562d9ccdbSschwarze 	/*
1926a2c9ff1aSschwarze 	 * This is neither a roff request nor a user-defined macro.
1927a2c9ff1aSschwarze 	 * Let the standard macro set parsers handle it.
192862d9ccdbSschwarze 	 */
192962d9ccdbSschwarze 
1930f79258f3Sschwarze 	if (tok == TOKEN_NONE)
1931526e306bSschwarze 		return ROFF_CONT;
1932f673a3c7Sschwarze 
1933f79258f3Sschwarze 	/* Execute a roff request or a user-defined macro. */
1934a2c9ff1aSschwarze 
1935f79258f3Sschwarze 	return (*roffs[tok].proc)(r, tok, buf, ln, ppos, pos, offs);
193662d9ccdbSschwarze }
1937f673a3c7Sschwarze 
19383dc5225dSschwarze /*
19393dc5225dSschwarze  * Internal interface function to tell the roff parser that execution
19403dc5225dSschwarze  * of the current macro ended.  This is required because macro
19413dc5225dSschwarze  * definitions usually do not end with a .return request.
19423dc5225dSschwarze  */
19433dc5225dSschwarze void
19443dc5225dSschwarze roff_userret(struct roff *r)
19453dc5225dSschwarze {
19463dc5225dSschwarze 	struct mctx	*ctx;
19473dc5225dSschwarze 	int		 i;
19483dc5225dSschwarze 
19493dc5225dSschwarze 	assert(r->mstackpos >= 0);
19503dc5225dSschwarze 	ctx = r->mstack + r->mstackpos;
19513dc5225dSschwarze 	for (i = 0; i < ctx->argc; i++)
19523dc5225dSschwarze 		free(ctx->argv[i]);
19533dc5225dSschwarze 	ctx->argc = 0;
19543dc5225dSschwarze 	r->mstackpos--;
19553dc5225dSschwarze }
19563dc5225dSschwarze 
19572791bd1cSschwarze void
195862d9ccdbSschwarze roff_endparse(struct roff *r)
195962d9ccdbSschwarze {
1960d93f8561Sschwarze 	if (r->last != NULL)
1961a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOEND, r->last->line,
1962a5a5f808Sschwarze 		    r->last->col, "%s", roff_name[r->last->tok]);
19632791bd1cSschwarze 
1964d93f8561Sschwarze 	if (r->eqn != NULL) {
1965a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOEND,
1966bf9acac6Sschwarze 		    r->eqn->node->line, r->eqn->node->pos, "EQ");
1967bf9acac6Sschwarze 		eqn_parse(r->eqn);
1968bf9acac6Sschwarze 		r->eqn = NULL;
19698d973ab1Sschwarze 	}
19708d973ab1Sschwarze 
1971d93f8561Sschwarze 	if (r->tbl != NULL) {
1972fb382a01Sschwarze 		tbl_end(r->tbl, 1);
1973d93f8561Sschwarze 		r->tbl = NULL;
19742791bd1cSschwarze 	}
1975f673a3c7Sschwarze }
1976f673a3c7Sschwarze 
1977f673a3c7Sschwarze /*
1978f79258f3Sschwarze  * Parse the request or macro name at buf[*pos].
1979f79258f3Sschwarze  * Return ROFF_RENAMED, ROFF_USERDEF, or a ROFF_* token value.
1980f79258f3Sschwarze  * For empty, undefined, mdoc(7), and man(7) macros, return TOKEN_NONE.
1981f79258f3Sschwarze  * As a side effect, set r->current_string to the definition or to NULL.
1982f673a3c7Sschwarze  */
198314a309e3Sschwarze static enum roff_tok
19847963e2c1Sschwarze roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
1985f673a3c7Sschwarze {
19867963e2c1Sschwarze 	char		*cp;
1987e53bb406Sschwarze 	const char	*mac;
1988e53bb406Sschwarze 	size_t		 maclen;
198907530f0aSschwarze 	int		 deftype;
199014a309e3Sschwarze 	enum roff_tok	 t;
1991f673a3c7Sschwarze 
19927963e2c1Sschwarze 	cp = buf + *pos;
19937963e2c1Sschwarze 
19947963e2c1Sschwarze 	if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp)
199514a309e3Sschwarze 		return TOKEN_NONE;
1996f673a3c7Sschwarze 
19977963e2c1Sschwarze 	mac = cp;
19983b5bd91eSschwarze 	maclen = roff_getname(&cp, ln, ppos);
1999f673a3c7Sschwarze 
200007530f0aSschwarze 	deftype = ROFFDEF_USER | ROFFDEF_REN;
200107530f0aSschwarze 	r->current_string = roff_getstrn(r, mac, maclen, &deftype);
200207530f0aSschwarze 	switch (deftype) {
200307530f0aSschwarze 	case ROFFDEF_USER:
200407530f0aSschwarze 		t = ROFF_USERDEF;
200507530f0aSschwarze 		break;
200607530f0aSschwarze 	case ROFFDEF_REN:
200707530f0aSschwarze 		t = ROFF_RENAMED;
200807530f0aSschwarze 		break;
200907530f0aSschwarze 	default:
201007530f0aSschwarze 		t = roffhash_find(r->reqtab, mac, maclen);
201107530f0aSschwarze 		break;
201207530f0aSschwarze 	}
201314a309e3Sschwarze 	if (t != TOKEN_NONE)
20147963e2c1Sschwarze 		*pos = cp - buf;
20150cdab9e9Sschwarze 	else if (deftype == ROFFDEF_UNDEF) {
20160cdab9e9Sschwarze 		/* Using an undefined macro defines it to be empty. */
20170cdab9e9Sschwarze 		roff_setstrn(&r->strtab, mac, maclen, "", 0, 0);
20180cdab9e9Sschwarze 		roff_setstrn(&r->rentab, mac, maclen, NULL, 0, 0);
20190cdab9e9Sschwarze 	}
2020526e306bSschwarze 	return t;
2021f673a3c7Sschwarze }
2022f673a3c7Sschwarze 
2023fa2127f9Sschwarze /* --- handling of request blocks ----------------------------------------- */
2024fa2127f9Sschwarze 
2025fd9d9245Sschwarze /*
2026fd9d9245Sschwarze  * Close a macro definition block or an "ignore" block.
2027fd9d9245Sschwarze  */
2028b7f92c5fSschwarze static int
202962d9ccdbSschwarze roff_cblock(ROFF_ARGS)
2030f673a3c7Sschwarze {
2031fd9d9245Sschwarze 	int	 rr;
203262d9ccdbSschwarze 
203328687875Sschwarze 	if (r->last == NULL) {
2034a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
2035526e306bSschwarze 		return ROFF_IGN;
203662d9ccdbSschwarze 	}
2037f673a3c7Sschwarze 
203862d9ccdbSschwarze 	switch (r->last->tok) {
203949aff9f8Sschwarze 	case ROFF_am:
204049aff9f8Sschwarze 	case ROFF_ami:
204149aff9f8Sschwarze 	case ROFF_de:
204249aff9f8Sschwarze 	case ROFF_dei:
204349aff9f8Sschwarze 	case ROFF_ig:
204462d9ccdbSschwarze 		break;
2045fd9d9245Sschwarze 	case ROFF_am1:
2046fd9d9245Sschwarze 	case ROFF_de1:
2047fd9d9245Sschwarze 		/* Remapped in roff_block(). */
2048fd9d9245Sschwarze 		abort();
204962d9ccdbSschwarze 	default:
2050a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
2051526e306bSschwarze 		return ROFF_IGN;
205262d9ccdbSschwarze 	}
205362d9ccdbSschwarze 
2054fd9d9245Sschwarze 	roffnode_pop(r);
2055fd9d9245Sschwarze 	roffnode_cleanscope(r);
2056fd9d9245Sschwarze 
2057fd9d9245Sschwarze 	/*
2058fd9d9245Sschwarze 	 * If a conditional block with braces is still open,
2059fd9d9245Sschwarze 	 * check for "\}" block end markers.
2060fd9d9245Sschwarze 	 */
2061fd9d9245Sschwarze 
2062fd9d9245Sschwarze 	if (r->last != NULL && r->last->endspan < 0) {
2063fd9d9245Sschwarze 		rr = 1;  /* If arguments follow "\}", warn about them. */
2064fd9d9245Sschwarze 		roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
2065fd9d9245Sschwarze 	}
2066fd9d9245Sschwarze 
206728687875Sschwarze 	if (buf->buf[pos] != '\0')
2068a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
206928687875Sschwarze 		    ".. %s", buf->buf + pos);
207062d9ccdbSschwarze 
2071526e306bSschwarze 	return ROFF_IGN;
207262d9ccdbSschwarze }
207362d9ccdbSschwarze 
20747dd0ff50Sschwarze /*
20757dd0ff50Sschwarze  * Pop all nodes ending at the end of the current input line.
20767dd0ff50Sschwarze  * Return the number of loops ended.
20777dd0ff50Sschwarze  */
2078b7f92c5fSschwarze static int
207962d9ccdbSschwarze roffnode_cleanscope(struct roff *r)
208062d9ccdbSschwarze {
2081b7f92c5fSschwarze 	int inloop;
208262d9ccdbSschwarze 
2083b7f92c5fSschwarze 	inloop = 0;
2084fd9d9245Sschwarze 	while (r->last != NULL && r->last->endspan > 0) {
208544f8358dSschwarze 		if (--r->last->endspan != 0)
208662d9ccdbSschwarze 			break;
2087b7f92c5fSschwarze 		inloop += roffnode_pop(r);
208862d9ccdbSschwarze 	}
2089b7f92c5fSschwarze 	return inloop;
209062d9ccdbSschwarze }
209162d9ccdbSschwarze 
20927dd0ff50Sschwarze /*
2093fd9d9245Sschwarze  * Handle the closing "\}" of a conditional block.
20947dd0ff50Sschwarze  * Apart from generating warnings, this only pops nodes.
20957dd0ff50Sschwarze  * Return the number of loops ended.
20967dd0ff50Sschwarze  */
2097b7f92c5fSschwarze static int
20984d112f14Sschwarze roff_ccond(struct roff *r, int ln, int ppos)
209962d9ccdbSschwarze {
210062d9ccdbSschwarze 	if (NULL == r->last) {
2101a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
2102b7f92c5fSschwarze 		return 0;
210362d9ccdbSschwarze 	}
210462d9ccdbSschwarze 
210562d9ccdbSschwarze 	switch (r->last->tok) {
210649aff9f8Sschwarze 	case ROFF_el:
210749aff9f8Sschwarze 	case ROFF_ie:
210849aff9f8Sschwarze 	case ROFF_if:
2109b7f92c5fSschwarze 	case ROFF_while:
211062d9ccdbSschwarze 		break;
211162d9ccdbSschwarze 	default:
2112a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
2113b7f92c5fSschwarze 		return 0;
211462d9ccdbSschwarze 	}
211562d9ccdbSschwarze 
211662d9ccdbSschwarze 	if (r->last->endspan > -1) {
2117a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "\\}");
2118b7f92c5fSschwarze 		return 0;
211962d9ccdbSschwarze 	}
212062d9ccdbSschwarze 
2121b7f92c5fSschwarze 	return roffnode_pop(r) + roffnode_cleanscope(r);
2122f673a3c7Sschwarze }
2123f673a3c7Sschwarze 
2124b7f92c5fSschwarze static int
212562d9ccdbSschwarze roff_block(ROFF_ARGS)
2126f673a3c7Sschwarze {
212707530f0aSschwarze 	const char	*name, *value;
212807530f0aSschwarze 	char		*call, *cp, *iname, *rname;
212907530f0aSschwarze 	size_t		 csz, namesz, rsz;
213007530f0aSschwarze 	int		 deftype;
213162d9ccdbSschwarze 
2132803e5876Sschwarze 	/* Ignore groff compatibility mode for now. */
2133e53bb406Sschwarze 
213428687875Sschwarze 	if (tok == ROFF_de1)
2135803e5876Sschwarze 		tok = ROFF_de;
2136d04ca39fSschwarze 	else if (tok == ROFF_dei1)
2137d04ca39fSschwarze 		tok = ROFF_dei;
213828687875Sschwarze 	else if (tok == ROFF_am1)
2139803e5876Sschwarze 		tok = ROFF_am;
2140d04ca39fSschwarze 	else if (tok == ROFF_ami1)
2141d04ca39fSschwarze 		tok = ROFF_ami;
2142803e5876Sschwarze 
2143803e5876Sschwarze 	/* Parse the macro name argument. */
2144803e5876Sschwarze 
214528687875Sschwarze 	cp = buf->buf + pos;
214628687875Sschwarze 	if (tok == ROFF_ig) {
2147803e5876Sschwarze 		iname = NULL;
2148803e5876Sschwarze 		namesz = 0;
2149803e5876Sschwarze 	} else {
2150803e5876Sschwarze 		iname = cp;
21513b5bd91eSschwarze 		namesz = roff_getname(&cp, ln, ppos);
2152803e5876Sschwarze 		iname[namesz] = '\0';
2153803e5876Sschwarze 	}
2154803e5876Sschwarze 
2155803e5876Sschwarze 	/* Resolve the macro name argument if it is indirect. */
2156803e5876Sschwarze 
215728687875Sschwarze 	if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
215807530f0aSschwarze 		deftype = ROFFDEF_USER;
215907530f0aSschwarze 		name = roff_getstrn(r, iname, namesz, &deftype);
216007530f0aSschwarze 		if (name == NULL) {
2161a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_STR_UNDEF,
2162a5a5f808Sschwarze 			    ln, (int)(iname - buf->buf),
2163803e5876Sschwarze 			    "%.*s", (int)namesz, iname);
2164803e5876Sschwarze 			namesz = 0;
2165803e5876Sschwarze 		} else
2166803e5876Sschwarze 			namesz = strlen(name);
2167803e5876Sschwarze 	} else
2168803e5876Sschwarze 		name = iname;
2169803e5876Sschwarze 
217028687875Sschwarze 	if (namesz == 0 && tok != ROFF_ig) {
2171a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY,
2172a5a5f808Sschwarze 		    ln, ppos, "%s", roff_name[tok]);
2173526e306bSschwarze 		return ROFF_IGN;
2174e53bb406Sschwarze 	}
2175b58a7f4fSschwarze 
2176e53bb406Sschwarze 	roffnode_push(r, tok, name, ln, ppos);
2177e53bb406Sschwarze 
2178e53bb406Sschwarze 	/*
2179e53bb406Sschwarze 	 * At the beginning of a `de' macro, clear the existing string
2180e53bb406Sschwarze 	 * with the same name, if there is one.  New content will be
21810c05b9caSschwarze 	 * appended from roff_block_text() in multiline mode.
2182e53bb406Sschwarze 	 */
2183b58a7f4fSschwarze 
21840856de67Sschwarze 	if (tok == ROFF_de || tok == ROFF_dei) {
2185d08dc9e5Sschwarze 		roff_setstrn(&r->strtab, name, namesz, "", 0, 0);
21860856de67Sschwarze 		roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
218707530f0aSschwarze 	} else if (tok == ROFF_am || tok == ROFF_ami) {
218807530f0aSschwarze 		deftype = ROFFDEF_ANY;
218907530f0aSschwarze 		value = roff_getstrn(r, iname, namesz, &deftype);
219007530f0aSschwarze 		switch (deftype) {  /* Before appending, ... */
219107530f0aSschwarze 		case ROFFDEF_PRE: /* copy predefined to user-defined. */
219207530f0aSschwarze 			roff_setstrn(&r->strtab, name, namesz,
219307530f0aSschwarze 			    value, strlen(value), 0);
219407530f0aSschwarze 			break;
219507530f0aSschwarze 		case ROFFDEF_REN: /* call original standard macro. */
219607530f0aSschwarze 			csz = mandoc_asprintf(&call, ".%.*s \\$* \\\"\n",
219707530f0aSschwarze 			    (int)strlen(value), value);
219807530f0aSschwarze 			roff_setstrn(&r->strtab, name, namesz, call, csz, 0);
219907530f0aSschwarze 			roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
220007530f0aSschwarze 			free(call);
220107530f0aSschwarze 			break;
220207530f0aSschwarze 		case ROFFDEF_STD:  /* rename and call standard macro. */
220307530f0aSschwarze 			rsz = mandoc_asprintf(&rname, "__%s_renamed", name);
220407530f0aSschwarze 			roff_setstrn(&r->rentab, rname, rsz, name, namesz, 0);
220507530f0aSschwarze 			csz = mandoc_asprintf(&call, ".%.*s \\$* \\\"\n",
220607530f0aSschwarze 			    (int)rsz, rname);
220707530f0aSschwarze 			roff_setstrn(&r->strtab, name, namesz, call, csz, 0);
220807530f0aSschwarze 			free(call);
220907530f0aSschwarze 			free(rname);
221007530f0aSschwarze 			break;
221107530f0aSschwarze 		default:
221207530f0aSschwarze 			break;
221307530f0aSschwarze 		}
22140856de67Sschwarze 	}
2215f673a3c7Sschwarze 
221628687875Sschwarze 	if (*cp == '\0')
2217526e306bSschwarze 		return ROFF_IGN;
2218f673a3c7Sschwarze 
2219803e5876Sschwarze 	/* Get the custom end marker. */
2220b58a7f4fSschwarze 
2221803e5876Sschwarze 	iname = cp;
22223b5bd91eSschwarze 	namesz = roff_getname(&cp, ln, ppos);
2223803e5876Sschwarze 
2224803e5876Sschwarze 	/* Resolve the end marker if it is indirect. */
2225803e5876Sschwarze 
222628687875Sschwarze 	if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
222707530f0aSschwarze 		deftype = ROFFDEF_USER;
222807530f0aSschwarze 		name = roff_getstrn(r, iname, namesz, &deftype);
222907530f0aSschwarze 		if (name == NULL) {
2230a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_STR_UNDEF,
2231a5a5f808Sschwarze 			    ln, (int)(iname - buf->buf),
2232803e5876Sschwarze 			    "%.*s", (int)namesz, iname);
2233803e5876Sschwarze 			namesz = 0;
2234803e5876Sschwarze 		} else
2235803e5876Sschwarze 			namesz = strlen(name);
2236803e5876Sschwarze 	} else
2237803e5876Sschwarze 		name = iname;
2238803e5876Sschwarze 
2239d08dc9e5Sschwarze 	if (namesz)
2240d08dc9e5Sschwarze 		r->last->end = mandoc_strndup(name, namesz);
2241f673a3c7Sschwarze 
224228687875Sschwarze 	if (*cp != '\0')
2243a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
22446050a3daSschwarze 		    ln, pos, ".%s ... %s", roff_name[tok], cp);
224562d9ccdbSschwarze 
2246526e306bSschwarze 	return ROFF_IGN;
224762d9ccdbSschwarze }
224862d9ccdbSschwarze 
2249b7f92c5fSschwarze static int
225062d9ccdbSschwarze roff_block_sub(ROFF_ARGS)
225162d9ccdbSschwarze {
225214a309e3Sschwarze 	enum roff_tok	t;
225362d9ccdbSschwarze 	int		i, j;
225462d9ccdbSschwarze 
225562d9ccdbSschwarze 	/*
2256ed59e75bSschwarze 	 * If a custom end marker is a user-defined or predefined macro
2257ed59e75bSschwarze 	 * or a request, interpret it.
225862d9ccdbSschwarze 	 */
225962d9ccdbSschwarze 
226062d9ccdbSschwarze 	if (r->last->end) {
2261a35fc07aSschwarze 		for (i = pos, j = 0; r->last->end[j]; j++, i++)
226228687875Sschwarze 			if (buf->buf[i] != r->last->end[j])
226362d9ccdbSschwarze 				break;
226462d9ccdbSschwarze 
226528687875Sschwarze 		if (r->last->end[j] == '\0' &&
226628687875Sschwarze 		    (buf->buf[i] == '\0' ||
226728687875Sschwarze 		     buf->buf[i] == ' ' ||
226828687875Sschwarze 		     buf->buf[i] == '\t')) {
226962d9ccdbSschwarze 			roffnode_pop(r);
227062d9ccdbSschwarze 			roffnode_cleanscope(r);
227162d9ccdbSschwarze 
227228687875Sschwarze 			while (buf->buf[i] == ' ' || buf->buf[i] == '\t')
2273a35fc07aSschwarze 				i++;
2274a35fc07aSschwarze 
2275a35fc07aSschwarze 			pos = i;
227628687875Sschwarze 			if (roff_parse(r, buf->buf, &pos, ln, ppos) !=
227714a309e3Sschwarze 			    TOKEN_NONE)
2278526e306bSschwarze 				return ROFF_RERUN;
2279526e306bSschwarze 			return ROFF_IGN;
228062d9ccdbSschwarze 		}
228162d9ccdbSschwarze 	}
228262d9ccdbSschwarze 
2283ed59e75bSschwarze 	/* Handle the standard end marker. */
228462d9ccdbSschwarze 
228528687875Sschwarze 	t = roff_parse(r, buf->buf, &pos, ln, ppos);
2286ed59e75bSschwarze 	if (t == ROFF_cblock)
2287ed59e75bSschwarze 		return roff_cblock(r, t, buf, ln, ppos, pos, offs);
228862d9ccdbSschwarze 
2289ed59e75bSschwarze 	/* Not an end marker, so append the line to the block. */
2290ed59e75bSschwarze 
229128687875Sschwarze 	if (tok != ROFF_ig)
229228687875Sschwarze 		roff_setstr(r, r->last->name, buf->buf + ppos, 2);
2293526e306bSschwarze 	return ROFF_IGN;
2294e53bb406Sschwarze }
229562d9ccdbSschwarze 
2296b7f92c5fSschwarze static int
229762d9ccdbSschwarze roff_block_text(ROFF_ARGS)
229862d9ccdbSschwarze {
229962d9ccdbSschwarze 
230028687875Sschwarze 	if (tok != ROFF_ig)
230128687875Sschwarze 		roff_setstr(r, r->last->name, buf->buf + pos, 2);
2302e53bb406Sschwarze 
2303526e306bSschwarze 	return ROFF_IGN;
230462d9ccdbSschwarze }
230562d9ccdbSschwarze 
2306fd9d9245Sschwarze /*
2307fd9d9245Sschwarze  * Check for a closing "\}" and handle it.
2308fd9d9245Sschwarze  * In this function, the final "int *offs" argument is used for
2309fd9d9245Sschwarze  * different purposes than elsewhere:
2310fd9d9245Sschwarze  * Input: *offs == 0: caller wants to discard arguments following \}
2311fd9d9245Sschwarze  *        *offs == 1: caller wants to preserve text following \}
2312fd9d9245Sschwarze  * Output: *offs = 0: tell caller to discard input line
2313fd9d9245Sschwarze  *         *offs = 1: tell caller to use input line
2314fd9d9245Sschwarze  */
2315b7f92c5fSschwarze static int
2316fd9d9245Sschwarze roff_cond_checkend(ROFF_ARGS)
231762d9ccdbSschwarze {
2318a5e11edeSschwarze 	char		*ep;
2319b7f92c5fSschwarze 	int		 endloop, irc, rr;
232062d9ccdbSschwarze 
2321b7f92c5fSschwarze 	irc = ROFF_IGN;
232262d9ccdbSschwarze 	rr = r->last->rule;
2323b7f92c5fSschwarze 	endloop = tok != ROFF_while ? ROFF_IGN :
2324b7f92c5fSschwarze 	    rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
2325b7f92c5fSschwarze 	if (roffnode_cleanscope(r))
2326b7f92c5fSschwarze 		irc |= endloop;
2327e4baf46eSschwarze 
23281f60b4bfSschwarze 	/*
2329fd9d9245Sschwarze 	 * If "\}" occurs on a macro line without a preceding macro or
2330fd9d9245Sschwarze 	 * a text line contains nothing else, drop the line completely.
23311f60b4bfSschwarze 	 */
23321f60b4bfSschwarze 
233328687875Sschwarze 	ep = buf->buf + pos;
2334fd9d9245Sschwarze 	if (ep[0] == '\\' && ep[1] == '}' && (ep[2] == '\0' || *offs == 0))
23351271a177Sschwarze 		rr = 0;
23361f60b4bfSschwarze 
23376ac4ca49Sschwarze 	/*
2338fd9d9245Sschwarze 	 * The closing delimiter "\}" rewinds the conditional scope
23396ac4ca49Sschwarze 	 * but is otherwise ignored when interpreting the line.
23406ac4ca49Sschwarze 	 */
2341e4baf46eSschwarze 
234228687875Sschwarze 	while ((ep = strchr(ep, '\\')) != NULL) {
2343a6f1c49aSschwarze 		switch (ep[1]) {
2344a6f1c49aSschwarze 		case '}':
2345fbe6102bSschwarze 			if (ep[2] == '\0')
2346fbe6102bSschwarze 				ep[0] = '\0';
2347fbe6102bSschwarze 			else if (rr)
2348fd9d9245Sschwarze 				ep[1] = '&';
2349fd9d9245Sschwarze 			else
2350a6f1c49aSschwarze 				memmove(ep, ep + 2, strlen(ep + 2) + 1);
2351b7f92c5fSschwarze 			if (roff_ccond(r, ln, ep - buf->buf))
2352b7f92c5fSschwarze 				irc |= endloop;
2353a6f1c49aSschwarze 			break;
2354a6f1c49aSschwarze 		case '\0':
23557be1115fSschwarze 			++ep;
2356a6f1c49aSschwarze 			break;
2357a6f1c49aSschwarze 		default:
2358a6f1c49aSschwarze 			ep += 2;
2359a6f1c49aSschwarze 			break;
2360a5e11edeSschwarze 		}
2361a6f1c49aSschwarze 	}
2362fd9d9245Sschwarze 	*offs = rr;
2363fd9d9245Sschwarze 	return irc;
2364fd9d9245Sschwarze }
2365a6f1c49aSschwarze 
2366fd9d9245Sschwarze /*
2367fd9d9245Sschwarze  * Parse and process a request or macro line in conditional scope.
2368fd9d9245Sschwarze  */
2369fd9d9245Sschwarze static int
2370fd9d9245Sschwarze roff_cond_sub(ROFF_ARGS)
2371fd9d9245Sschwarze {
2372fd9d9245Sschwarze 	struct roffnode	*bl;
2373f79258f3Sschwarze 	int		 irc, rr, spos;
2374fd9d9245Sschwarze 	enum roff_tok	 t;
2375fd9d9245Sschwarze 
2376fd9d9245Sschwarze 	rr = 0;  /* If arguments follow "\}", skip them. */
2377fd9d9245Sschwarze 	irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
2378f79258f3Sschwarze 	spos = pos;
2379b19021b2Sschwarze 	t = roff_parse(r, buf->buf, &pos, ln, ppos);
2380b19021b2Sschwarze 
2381a6f1c49aSschwarze 	/*
2382f79258f3Sschwarze 	 * Handle requests and macros if the conditional evaluated
2383f79258f3Sschwarze 	 * to true or if they are structurally required.
2384f79258f3Sschwarze 	 * The .break request is always handled specially.
2385a6f1c49aSschwarze 	 */
2386a6f1c49aSschwarze 
23877dd0ff50Sschwarze 	if (t == ROFF_break) {
23887dd0ff50Sschwarze 		if (irc & ROFF_LOOPMASK)
23897dd0ff50Sschwarze 			irc = ROFF_IGN | ROFF_LOOPEXIT;
23907dd0ff50Sschwarze 		else if (rr) {
23917dd0ff50Sschwarze 			for (bl = r->last; bl != NULL; bl = bl->parent) {
23927dd0ff50Sschwarze 				bl->rule = 0;
23937dd0ff50Sschwarze 				if (bl->tok == ROFF_while)
23947dd0ff50Sschwarze 					break;
23957dd0ff50Sschwarze 			}
23967dd0ff50Sschwarze 		}
2397f79258f3Sschwarze 	} else if (rr || (t < TOKEN_NONE && roffs[t].flags & ROFFMAC_STRUCT)) {
2398f79258f3Sschwarze 		irc |= roff_req_or_macro(r, t, buf, ln, spos, pos, offs);
2399a68c8a85Sschwarze 		if (irc & ROFF_WHILE)
2400a68c8a85Sschwarze 			irc &= ~(ROFF_LOOPCONT | ROFF_LOOPEXIT);
2401f79258f3Sschwarze 	}
2402b7f92c5fSschwarze 	return irc;
240392853774Sschwarze }
240462d9ccdbSschwarze 
2405fd9d9245Sschwarze /*
2406fd9d9245Sschwarze  * Parse and process a text line in conditional scope.
2407fd9d9245Sschwarze  */
2408b7f92c5fSschwarze static int
240962d9ccdbSschwarze roff_cond_text(ROFF_ARGS)
241062d9ccdbSschwarze {
2411fd9d9245Sschwarze 	int	 irc, rr;
241262d9ccdbSschwarze 
2413fd9d9245Sschwarze 	rr = 1;  /* If arguments follow "\}", preserve them. */
2414fd9d9245Sschwarze 	irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
2415b7f92c5fSschwarze 	if (rr)
2416b7f92c5fSschwarze 		irc |= ROFF_CONT;
2417b7f92c5fSschwarze 	return irc;
241862d9ccdbSschwarze }
241962d9ccdbSschwarze 
2420fa2127f9Sschwarze /* --- handling of numeric and conditional expressions -------------------- */
2421fa2127f9Sschwarze 
2422b6a36a9bSschwarze /*
2423*7c5f3f4eSschwarze  * Parse a single signed decimal number.  Stop at the first non-digit.
2424b6a36a9bSschwarze  * If there is at least one digit, return success and advance the
2425b6a36a9bSschwarze  * parse point, else return failure and let the parse point unchanged.
2426b6a36a9bSschwarze  * Ignore overflows, treat them just like the C language.
2427b6a36a9bSschwarze  */
2428541e3395Sschwarze static int
2429f4432377Sschwarze roff_getnum(const char *v, int *pos, int *res, char unit, int skipspace)
2430541e3395Sschwarze {
2431*7c5f3f4eSschwarze 	double	 frac, myres;
2432*7c5f3f4eSschwarze 	int	 n, p;
2433541e3395Sschwarze 
2434541e3395Sschwarze 	p = *pos;
2435541e3395Sschwarze 	n = v[p] == '-';
24363a1681a6Sschwarze 	if (n || v[p] == '+')
24373a1681a6Sschwarze 		p++;
24383a1681a6Sschwarze 
2439f4432377Sschwarze 	if (skipspace)
24403a1681a6Sschwarze 		while (isspace((unsigned char)v[p]))
2441541e3395Sschwarze 			p++;
2442541e3395Sschwarze 
2443*7c5f3f4eSschwarze 	for (myres = 0.0; isdigit((unsigned char)v[p]); p++)
2444*7c5f3f4eSschwarze 		myres = myres * 10.0 + (v[p] - '0');
2445*7c5f3f4eSschwarze 	if (v[p] == '.')
2446*7c5f3f4eSschwarze 		for (frac = 0.1; isdigit((unsigned char)v[++p]); frac *= 0.1)
2447*7c5f3f4eSschwarze 			myres += frac * (v[p] - '0');
2448*7c5f3f4eSschwarze 
2449541e3395Sschwarze 	if (p == *pos + n)
2450541e3395Sschwarze 		return 0;
2451541e3395Sschwarze 
2452541e3395Sschwarze 	if (n)
2453*7c5f3f4eSschwarze 		myres *= -1.0;
2454541e3395Sschwarze 
24554a635483Sschwarze 	/* Each number may be followed by one optional scaling unit. */
24564a635483Sschwarze 
2457f4432377Sschwarze 	if (v[p] != '\0' && strchr("ficvPmnpuM", v[p]) != NULL) {
2458f4432377Sschwarze 		if (unit != '\0')
2459f4432377Sschwarze 			unit = v[p];
2460f4432377Sschwarze 		p++;
2461f4432377Sschwarze 	}
2462f4432377Sschwarze 
2463f4432377Sschwarze 	switch (unit) {
24644a635483Sschwarze 	case 'f':
2465*7c5f3f4eSschwarze 		myres *= 65536.0;
24664a635483Sschwarze 		break;
24674a635483Sschwarze 	case 'i':
2468*7c5f3f4eSschwarze 		myres *= 240.0;
24694a635483Sschwarze 		break;
24704a635483Sschwarze 	case 'c':
2471*7c5f3f4eSschwarze 		myres *= 240.0 / 2.54;
24724a635483Sschwarze 		break;
24734a635483Sschwarze 	case 'v':
24744a635483Sschwarze 	case 'P':
2475*7c5f3f4eSschwarze 		myres *= 40.0;
24764a635483Sschwarze 		break;
24774a635483Sschwarze 	case 'm':
24784a635483Sschwarze 	case 'n':
2479*7c5f3f4eSschwarze 		myres *= 24.0;
24804a635483Sschwarze 		break;
24814a635483Sschwarze 	case 'p':
2482*7c5f3f4eSschwarze 		myres *= 40.0 / 12.0;
24834a635483Sschwarze 		break;
24844a635483Sschwarze 	case 'u':
24854a635483Sschwarze 		break;
24864a635483Sschwarze 	case 'M':
2487*7c5f3f4eSschwarze 		myres *= 24.0 / 100.0;
24884a635483Sschwarze 		break;
24894a635483Sschwarze 	default:
24904a635483Sschwarze 		break;
24914a635483Sschwarze 	}
2492*7c5f3f4eSschwarze 	if (res != NULL)
2493*7c5f3f4eSschwarze 		*res = myres;
2494f4432377Sschwarze 	*pos = p;
2495526e306bSschwarze 	return 1;
2496541e3395Sschwarze }
2497541e3395Sschwarze 
24981271a177Sschwarze /*
24991271a177Sschwarze  * Evaluate a string comparison condition.
25001271a177Sschwarze  * The first character is the delimiter.
25011271a177Sschwarze  * Succeed if the string up to its second occurrence
2502d9a51c35Sjmc  * matches the string up to its third occurrence.
25031271a177Sschwarze  * Advance the cursor after the third occurrence
25041271a177Sschwarze  * or lacking that, to the end of the line.
25051271a177Sschwarze  */
25061271a177Sschwarze static int
25071271a177Sschwarze roff_evalstrcond(const char *v, int *pos)
25081271a177Sschwarze {
25091271a177Sschwarze 	const char	*s1, *s2, *s3;
25101271a177Sschwarze 	int		 match;
25111271a177Sschwarze 
25121271a177Sschwarze 	match = 0;
25131271a177Sschwarze 	s1 = v + *pos;		/* initial delimiter */
25141271a177Sschwarze 	s2 = s1 + 1;		/* for scanning the first string */
25151271a177Sschwarze 	s3 = strchr(s2, *s1);	/* for scanning the second string */
25161271a177Sschwarze 
25171271a177Sschwarze 	if (NULL == s3)		/* found no middle delimiter */
25181271a177Sschwarze 		goto out;
25191271a177Sschwarze 
25201271a177Sschwarze 	while ('\0' != *++s3) {
25211271a177Sschwarze 		if (*s2 != *s3) {  /* mismatch */
25221271a177Sschwarze 			s3 = strchr(s3, *s1);
25231271a177Sschwarze 			break;
25241271a177Sschwarze 		}
25251271a177Sschwarze 		if (*s3 == *s1) {  /* found the final delimiter */
25261271a177Sschwarze 			match = 1;
25271271a177Sschwarze 			break;
25281271a177Sschwarze 		}
25291271a177Sschwarze 		s2++;
25301271a177Sschwarze 	}
25311271a177Sschwarze 
25321271a177Sschwarze out:
25331271a177Sschwarze 	if (NULL == s3)
25341271a177Sschwarze 		s3 = strchr(s2, '\0');
253597111628Sschwarze 	else if (*s3 != '\0')
25361271a177Sschwarze 		s3++;
25371271a177Sschwarze 	*pos = s3 - v;
2538526e306bSschwarze 	return match;
25391271a177Sschwarze }
25401271a177Sschwarze 
2541b6a36a9bSschwarze /*
2542b6a36a9bSschwarze  * Evaluate an optionally negated single character, numerical,
2543b6a36a9bSschwarze  * or string condition.
2544b6a36a9bSschwarze  */
25451271a177Sschwarze static int
254624ab75f2Sschwarze roff_evalcond(struct roff *r, int ln, char *v, int *pos)
254731e23753Sschwarze {
2548a24ea326Sschwarze 	const char	*start, *end;
254924ab75f2Sschwarze 	char		*cp, *name;
255024ab75f2Sschwarze 	size_t		 sz;
2551a24ea326Sschwarze 	int		 deftype, len, number, savepos, istrue, wanttrue;
255231e23753Sschwarze 
25531271a177Sschwarze 	if ('!' == v[*pos]) {
25541271a177Sschwarze 		wanttrue = 0;
25551271a177Sschwarze 		(*pos)++;
25561271a177Sschwarze 	} else
25571271a177Sschwarze 		wanttrue = 1;
25581271a177Sschwarze 
255931e23753Sschwarze 	switch (v[*pos]) {
2560f7d129ddSschwarze 	case '\0':
2561526e306bSschwarze 		return 0;
256249aff9f8Sschwarze 	case 'n':
256349aff9f8Sschwarze 	case 'o':
25641271a177Sschwarze 		(*pos)++;
2565526e306bSschwarze 		return wanttrue;
256649aff9f8Sschwarze 	case 'e':
256749aff9f8Sschwarze 	case 't':
2568f660dfc5Sschwarze 	case 'v':
256931e23753Sschwarze 		(*pos)++;
2570526e306bSschwarze 		return !wanttrue;
2571a24ea326Sschwarze 	case 'c':
2572a24ea326Sschwarze 		do {
2573a24ea326Sschwarze 			(*pos)++;
2574a24ea326Sschwarze 		} while (v[*pos] == ' ');
2575a24ea326Sschwarze 
2576a24ea326Sschwarze 		/*
2577a24ea326Sschwarze 		 * Quirk for groff compatibility:
2578a24ea326Sschwarze 		 * The horizontal tab is neither available nor unavailable.
2579a24ea326Sschwarze 		 */
2580a24ea326Sschwarze 
2581a24ea326Sschwarze 		if (v[*pos] == '\t') {
2582a24ea326Sschwarze 			(*pos)++;
2583a24ea326Sschwarze 			return 0;
2584a24ea326Sschwarze 		}
2585a24ea326Sschwarze 
2586a24ea326Sschwarze 		/* Printable ASCII characters are available. */
2587a24ea326Sschwarze 
2588a24ea326Sschwarze 		if (v[*pos] != '\\') {
2589a24ea326Sschwarze 			(*pos)++;
2590a24ea326Sschwarze 			return wanttrue;
2591a24ea326Sschwarze 		}
2592a24ea326Sschwarze 
2593a24ea326Sschwarze 		end = v + ++*pos;
2594a24ea326Sschwarze 		switch (mandoc_escape(&end, &start, &len)) {
2595a24ea326Sschwarze 		case ESCAPE_SPECIAL:
2596a24ea326Sschwarze 			istrue = mchars_spec2cp(start, len) != -1;
2597a24ea326Sschwarze 			break;
2598a24ea326Sschwarze 		case ESCAPE_UNICODE:
2599a24ea326Sschwarze 			istrue = 1;
2600a24ea326Sschwarze 			break;
2601a24ea326Sschwarze 		case ESCAPE_NUMBERED:
2602a24ea326Sschwarze 			istrue = mchars_num2char(start, len) != -1;
2603a24ea326Sschwarze 			break;
2604a24ea326Sschwarze 		default:
2605a24ea326Sschwarze 			istrue = !wanttrue;
2606a24ea326Sschwarze 			break;
2607a24ea326Sschwarze 		}
2608a24ea326Sschwarze 		*pos = end - v;
2609a24ea326Sschwarze 		return istrue == wanttrue;
2610c2f35d21Sschwarze 	case 'd':
261124ab75f2Sschwarze 	case 'r':
2612c2f35d21Sschwarze 		cp = v + *pos + 1;
2613c2f35d21Sschwarze 		while (*cp == ' ')
2614c2f35d21Sschwarze 			cp++;
2615c2f35d21Sschwarze 		name = cp;
26163b5bd91eSschwarze 		sz = roff_getname(&cp, ln, cp - v);
261707530f0aSschwarze 		if (sz == 0)
261807530f0aSschwarze 			istrue = 0;
261907530f0aSschwarze 		else if (v[*pos] == 'r')
262007530f0aSschwarze 			istrue = roff_hasregn(r, name, sz);
262107530f0aSschwarze 		else {
262207530f0aSschwarze 			deftype = ROFFDEF_ANY;
262307530f0aSschwarze 		        roff_getstrn(r, name, sz, &deftype);
262407530f0aSschwarze 			istrue = !!deftype;
262507530f0aSschwarze 		}
26267ad9cfd6Sschwarze 		*pos = (name + sz) - v;
2627c2f35d21Sschwarze 		return istrue == wanttrue;
262831e23753Sschwarze 	default:
262931e23753Sschwarze 		break;
263031e23753Sschwarze 	}
263131e23753Sschwarze 
2632583f2f70Sschwarze 	savepos = *pos;
2633f4432377Sschwarze 	if (roff_evalnum(ln, v, pos, &number, 'u', 0))
2634526e306bSschwarze 		return (number > 0) == wanttrue;
2635583f2f70Sschwarze 	else if (*pos == savepos)
2636526e306bSschwarze 		return roff_evalstrcond(v, pos) == wanttrue;
2637583f2f70Sschwarze 	else
2638526e306bSschwarze 		return 0;
263931e23753Sschwarze }
264031e23753Sschwarze 
2641b7f92c5fSschwarze static int
264224483150Sschwarze roff_line_ignore(ROFF_ARGS)
26439830bf9fSschwarze {
26449830bf9fSschwarze 
2645526e306bSschwarze 	return ROFF_IGN;
26469830bf9fSschwarze }
26479830bf9fSschwarze 
2648b7f92c5fSschwarze static int
2649d04ca39fSschwarze roff_insec(ROFF_ARGS)
2650d04ca39fSschwarze {
2651d04ca39fSschwarze 
2652a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_REQ_INSEC, ln, ppos, "%s", roff_name[tok]);
2653526e306bSschwarze 	return ROFF_IGN;
2654d04ca39fSschwarze }
2655d04ca39fSschwarze 
2656b7f92c5fSschwarze static int
2657d04ca39fSschwarze roff_unsupp(ROFF_ARGS)
2658d04ca39fSschwarze {
2659d04ca39fSschwarze 
2660a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_REQ_UNSUPP, ln, ppos, "%s", roff_name[tok]);
2661526e306bSschwarze 	return ROFF_IGN;
2662d04ca39fSschwarze }
2663d04ca39fSschwarze 
2664b7f92c5fSschwarze static int
266562d9ccdbSschwarze roff_cond(ROFF_ARGS)
266662d9ccdbSschwarze {
2667b7f92c5fSschwarze 	int	 irc;
266844f8358dSschwarze 
266944f8358dSschwarze 	roffnode_push(r, tok, NULL, ln, ppos);
267062d9ccdbSschwarze 
267162d9ccdbSschwarze 	/*
2672a35fc07aSschwarze 	 * An `.el' has no conditional body: it will consume the value
2673a35fc07aSschwarze 	 * of the current rstack entry set in prior `ie' calls or
2674a35fc07aSschwarze 	 * defaults to DENY.
2675a35fc07aSschwarze 	 *
2676a35fc07aSschwarze 	 * If we're not an `el', however, then evaluate the conditional.
267762d9ccdbSschwarze 	 */
2678a35fc07aSschwarze 
267928687875Sschwarze 	r->last->rule = tok == ROFF_el ?
26801271a177Sschwarze 	    (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
268128687875Sschwarze 	    roff_evalcond(r, ln, buf->buf, &pos);
268262d9ccdbSschwarze 
268362d9ccdbSschwarze 	/*
2684a35fc07aSschwarze 	 * An if-else will put the NEGATION of the current evaluated
2685a35fc07aSschwarze 	 * conditional into the stack of rules.
268662d9ccdbSschwarze 	 */
2687a35fc07aSschwarze 
268828687875Sschwarze 	if (tok == ROFF_ie) {
26894bc98c72Sschwarze 		if (r->rstackpos + 1 == r->rstacksz) {
26904bc98c72Sschwarze 			r->rstacksz += 16;
26914bc98c72Sschwarze 			r->rstack = mandoc_reallocarray(r->rstack,
26924bc98c72Sschwarze 			    r->rstacksz, sizeof(int));
2693a35fc07aSschwarze 		}
26941271a177Sschwarze 		r->rstack[++r->rstackpos] = !r->last->rule;
269562d9ccdbSschwarze 	}
269631e23753Sschwarze 
269731e23753Sschwarze 	/* If the parent has false as its rule, then so do we. */
269831e23753Sschwarze 
26991271a177Sschwarze 	if (r->last->parent && !r->last->parent->rule)
27001271a177Sschwarze 		r->last->rule = 0;
270162d9ccdbSschwarze 
270231e23753Sschwarze 	/*
270344f8358dSschwarze 	 * Determine scope.
270444f8358dSschwarze 	 * If there is nothing on the line after the conditional,
270544f8358dSschwarze 	 * not even whitespace, use next-line scope.
2706b7f92c5fSschwarze 	 * Except that .while does not support next-line scope.
270731e23753Sschwarze 	 */
270831e23753Sschwarze 
2709b7f92c5fSschwarze 	if (buf->buf[pos] == '\0' && tok != ROFF_while) {
271044f8358dSschwarze 		r->last->endspan = 2;
271144f8358dSschwarze 		goto out;
271244f8358dSschwarze 	}
271344f8358dSschwarze 
271428687875Sschwarze 	while (buf->buf[pos] == ' ')
271544f8358dSschwarze 		pos++;
271644f8358dSschwarze 
271744f8358dSschwarze 	/* An opening brace requests multiline scope. */
271862d9ccdbSschwarze 
271928687875Sschwarze 	if (buf->buf[pos] == '\\' && buf->buf[pos + 1] == '{') {
272062d9ccdbSschwarze 		r->last->endspan = -1;
272162d9ccdbSschwarze 		pos += 2;
2722099e02ebSschwarze 		while (buf->buf[pos] == ' ')
2723099e02ebSschwarze 			pos++;
272444f8358dSschwarze 		goto out;
272562d9ccdbSschwarze 	}
272662d9ccdbSschwarze 
272762d9ccdbSschwarze 	/*
272844f8358dSschwarze 	 * Anything else following the conditional causes
272944f8358dSschwarze 	 * single-line scope.  Warn if the scope contains
273044f8358dSschwarze 	 * nothing but trailing whitespace.
273162d9ccdbSschwarze 	 */
273262d9ccdbSschwarze 
273328687875Sschwarze 	if (buf->buf[pos] == '\0')
2734a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_COND_EMPTY,
2735a5a5f808Sschwarze 		    ln, ppos, "%s", roff_name[tok]);
273662d9ccdbSschwarze 
273744f8358dSschwarze 	r->last->endspan = 1;
273862d9ccdbSschwarze 
273944f8358dSschwarze out:
274062d9ccdbSschwarze 	*offs = pos;
2741b7f92c5fSschwarze 	irc = ROFF_RERUN;
2742b7f92c5fSschwarze 	if (tok == ROFF_while)
2743b7f92c5fSschwarze 		irc |= ROFF_WHILE;
2744b7f92c5fSschwarze 	return irc;
274562d9ccdbSschwarze }
274662d9ccdbSschwarze 
2747b7f92c5fSschwarze static int
2748b49ce91bSschwarze roff_ds(ROFF_ARGS)
2749b49ce91bSschwarze {
275059b3009eSschwarze 	char		*string;
275159b3009eSschwarze 	const char	*name;
275259b3009eSschwarze 	size_t		 namesz;
27538cd724fbSschwarze 
2754d04ca39fSschwarze 	/* Ignore groff compatibility mode for now. */
2755d04ca39fSschwarze 
2756d04ca39fSschwarze 	if (tok == ROFF_ds1)
2757d04ca39fSschwarze 		tok = ROFF_ds;
2758d04ca39fSschwarze 	else if (tok == ROFF_as1)
2759d04ca39fSschwarze 		tok = ROFF_as;
2760d04ca39fSschwarze 
27618cd724fbSschwarze 	/*
276259b3009eSschwarze 	 * The first word is the name of the string.
276359b3009eSschwarze 	 * If it is empty or terminated by an escape sequence,
276459b3009eSschwarze 	 * abort the `ds' request without defining anything.
27658cd724fbSschwarze 	 */
2766b49ce91bSschwarze 
276728687875Sschwarze 	name = string = buf->buf + pos;
276828687875Sschwarze 	if (*name == '\0')
2769526e306bSschwarze 		return ROFF_IGN;
2770b49ce91bSschwarze 
27713b5bd91eSschwarze 	namesz = roff_getname(&string, ln, pos);
27727ad9cfd6Sschwarze 	switch (name[namesz]) {
27737ad9cfd6Sschwarze 	case '\\':
2774526e306bSschwarze 		return ROFF_IGN;
27757ad9cfd6Sschwarze 	case '\t':
27767ad9cfd6Sschwarze 		string = buf->buf + pos + namesz;
27777ad9cfd6Sschwarze 		break;
27787ad9cfd6Sschwarze 	default:
27797ad9cfd6Sschwarze 		break;
27807ad9cfd6Sschwarze 	}
278159b3009eSschwarze 
278259b3009eSschwarze 	/* Read past the initial double-quote, if any. */
278328687875Sschwarze 	if (*string == '"')
27848cd724fbSschwarze 		string++;
27858cd724fbSschwarze 
27868cd724fbSschwarze 	/* The rest is the value. */
278759b3009eSschwarze 	roff_setstrn(&r->strtab, name, namesz, string, strlen(string),
278859b3009eSschwarze 	    ROFF_as == tok);
27890856de67Sschwarze 	roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
2790526e306bSschwarze 	return ROFF_IGN;
2791b49ce91bSschwarze }
2792b49ce91bSschwarze 
2793b6a36a9bSschwarze /*
2794b6a36a9bSschwarze  * Parse a single operator, one or two characters long.
2795b6a36a9bSschwarze  * If the operator is recognized, return success and advance the
2796b6a36a9bSschwarze  * parse point, else return failure and let the parse point unchanged.
2797b6a36a9bSschwarze  */
2798b6a36a9bSschwarze static int
2799b6a36a9bSschwarze roff_getop(const char *v, int *pos, char *res)
2800b6a36a9bSschwarze {
2801b6a36a9bSschwarze 
2802b6a36a9bSschwarze 	*res = v[*pos];
2803b6a36a9bSschwarze 
2804b6a36a9bSschwarze 	switch (*res) {
280549aff9f8Sschwarze 	case '+':
280649aff9f8Sschwarze 	case '-':
280749aff9f8Sschwarze 	case '*':
280849aff9f8Sschwarze 	case '/':
280949aff9f8Sschwarze 	case '%':
281049aff9f8Sschwarze 	case '&':
281149aff9f8Sschwarze 	case ':':
2812b6a36a9bSschwarze 		break;
2813b6a36a9bSschwarze 	case '<':
2814b6a36a9bSschwarze 		switch (v[*pos + 1]) {
281549aff9f8Sschwarze 		case '=':
2816b6a36a9bSschwarze 			*res = 'l';
2817b6a36a9bSschwarze 			(*pos)++;
2818b6a36a9bSschwarze 			break;
281949aff9f8Sschwarze 		case '>':
2820b6a36a9bSschwarze 			*res = '!';
2821b6a36a9bSschwarze 			(*pos)++;
2822b6a36a9bSschwarze 			break;
282349aff9f8Sschwarze 		case '?':
2824b6a36a9bSschwarze 			*res = 'i';
2825b6a36a9bSschwarze 			(*pos)++;
2826b6a36a9bSschwarze 			break;
2827b6a36a9bSschwarze 		default:
2828b6a36a9bSschwarze 			break;
2829b6a36a9bSschwarze 		}
2830b6a36a9bSschwarze 		break;
2831b6a36a9bSschwarze 	case '>':
2832b6a36a9bSschwarze 		switch (v[*pos + 1]) {
283349aff9f8Sschwarze 		case '=':
2834b6a36a9bSschwarze 			*res = 'g';
2835b6a36a9bSschwarze 			(*pos)++;
2836b6a36a9bSschwarze 			break;
283749aff9f8Sschwarze 		case '?':
2838b6a36a9bSschwarze 			*res = 'a';
2839b6a36a9bSschwarze 			(*pos)++;
2840b6a36a9bSschwarze 			break;
2841b6a36a9bSschwarze 		default:
2842b6a36a9bSschwarze 			break;
2843b6a36a9bSschwarze 		}
2844b6a36a9bSschwarze 		break;
2845b6a36a9bSschwarze 	case '=':
2846b6a36a9bSschwarze 		if ('=' == v[*pos + 1])
2847b6a36a9bSschwarze 			(*pos)++;
2848b6a36a9bSschwarze 		break;
2849b6a36a9bSschwarze 	default:
2850526e306bSschwarze 		return 0;
2851b6a36a9bSschwarze 	}
2852b6a36a9bSschwarze 	(*pos)++;
2853b6a36a9bSschwarze 
2854526e306bSschwarze 	return *res;
2855b6a36a9bSschwarze }
2856b6a36a9bSschwarze 
2857b6a36a9bSschwarze /*
2858b6a36a9bSschwarze  * Evaluate either a parenthesized numeric expression
2859b6a36a9bSschwarze  * or a single signed integer number.
2860b6a36a9bSschwarze  */
2861b6a36a9bSschwarze static int
2862f4432377Sschwarze roff_evalpar(int ln, const char *v, int *pos, int *res, char unit,
2863f4432377Sschwarze     int skipspace)
2864b6a36a9bSschwarze {
2865b6a36a9bSschwarze 
2866b6a36a9bSschwarze 	if ('(' != v[*pos])
2867f4432377Sschwarze 		return roff_getnum(v, pos, res, unit, skipspace);
2868b6a36a9bSschwarze 
2869b6a36a9bSschwarze 	(*pos)++;
2870f4432377Sschwarze 	if ( ! roff_evalnum(ln, v, pos, res, unit, 1))
2871526e306bSschwarze 		return 0;
2872b6a36a9bSschwarze 
2873778fe90eSschwarze 	/*
2874778fe90eSschwarze 	 * Omission of the closing parenthesis
2875778fe90eSschwarze 	 * is an error in validation mode,
2876778fe90eSschwarze 	 * but ignored in evaluation mode.
2877778fe90eSschwarze 	 */
2878778fe90eSschwarze 
2879b6a36a9bSschwarze 	if (')' == v[*pos])
2880b6a36a9bSschwarze 		(*pos)++;
2881778fe90eSschwarze 	else if (NULL == res)
2882526e306bSschwarze 		return 0;
2883b6a36a9bSschwarze 
2884526e306bSschwarze 	return 1;
2885b6a36a9bSschwarze }
2886b6a36a9bSschwarze 
2887b6a36a9bSschwarze /*
2888b6a36a9bSschwarze  * Evaluate a complete numeric expression.
2889b6a36a9bSschwarze  * Proceed left to right, there is no concept of precedence.
2890b6a36a9bSschwarze  */
2891f4432377Sschwarze int
2892f4432377Sschwarze roff_evalnum(int ln, const char *v, int *pos, int *res, char unit,
2893f4432377Sschwarze     int skipspace)
2894b6a36a9bSschwarze {
2895b6a36a9bSschwarze 	int		 mypos, operand2;
2896b6a36a9bSschwarze 	char		 operator;
2897b6a36a9bSschwarze 
2898b6a36a9bSschwarze 	if (NULL == pos) {
2899b6a36a9bSschwarze 		mypos = 0;
2900b6a36a9bSschwarze 		pos = &mypos;
2901b6a36a9bSschwarze 	}
2902b6a36a9bSschwarze 
2903f4432377Sschwarze 	if (skipspace)
2904b6a36a9bSschwarze 		while (isspace((unsigned char)v[*pos]))
2905b6a36a9bSschwarze 			(*pos)++;
2906b6a36a9bSschwarze 
2907f4432377Sschwarze 	if ( ! roff_evalpar(ln, v, pos, res, unit, skipspace))
2908526e306bSschwarze 		return 0;
2909b6a36a9bSschwarze 
2910b6a36a9bSschwarze 	while (1) {
2911f4432377Sschwarze 		if (skipspace)
2912b6a36a9bSschwarze 			while (isspace((unsigned char)v[*pos]))
2913b6a36a9bSschwarze 				(*pos)++;
2914b6a36a9bSschwarze 
2915b6a36a9bSschwarze 		if ( ! roff_getop(v, pos, &operator))
2916b6a36a9bSschwarze 			break;
2917b6a36a9bSschwarze 
2918f4432377Sschwarze 		if (skipspace)
2919b6a36a9bSschwarze 			while (isspace((unsigned char)v[*pos]))
2920b6a36a9bSschwarze 				(*pos)++;
2921b6a36a9bSschwarze 
2922f4432377Sschwarze 		if ( ! roff_evalpar(ln, v, pos, &operand2, unit, skipspace))
2923526e306bSschwarze 			return 0;
2924b6a36a9bSschwarze 
2925f4432377Sschwarze 		if (skipspace)
2926b6a36a9bSschwarze 			while (isspace((unsigned char)v[*pos]))
2927b6a36a9bSschwarze 				(*pos)++;
2928b6a36a9bSschwarze 
2929778fe90eSschwarze 		if (NULL == res)
2930778fe90eSschwarze 			continue;
2931778fe90eSschwarze 
2932b6a36a9bSschwarze 		switch (operator) {
293349aff9f8Sschwarze 		case '+':
2934b6a36a9bSschwarze 			*res += operand2;
2935b6a36a9bSschwarze 			break;
293649aff9f8Sschwarze 		case '-':
2937b6a36a9bSschwarze 			*res -= operand2;
2938b6a36a9bSschwarze 			break;
293949aff9f8Sschwarze 		case '*':
2940b6a36a9bSschwarze 			*res *= operand2;
2941b6a36a9bSschwarze 			break;
294249aff9f8Sschwarze 		case '/':
29434fcca957Sschwarze 			if (operand2 == 0) {
294496f45df3Sschwarze 				mandoc_msg(MANDOCERR_DIVZERO,
2945a5a5f808Sschwarze 					ln, *pos, "%s", v);
294696f45df3Sschwarze 				*res = 0;
294796f45df3Sschwarze 				break;
294896f45df3Sschwarze 			}
2949b6a36a9bSschwarze 			*res /= operand2;
2950b6a36a9bSschwarze 			break;
295149aff9f8Sschwarze 		case '%':
29524fcca957Sschwarze 			if (operand2 == 0) {
29534fcca957Sschwarze 				mandoc_msg(MANDOCERR_DIVZERO,
2954a5a5f808Sschwarze 					ln, *pos, "%s", v);
29554fcca957Sschwarze 				*res = 0;
29564fcca957Sschwarze 				break;
29574fcca957Sschwarze 			}
2958b6a36a9bSschwarze 			*res %= operand2;
2959b6a36a9bSschwarze 			break;
296049aff9f8Sschwarze 		case '<':
2961b6a36a9bSschwarze 			*res = *res < operand2;
2962b6a36a9bSschwarze 			break;
296349aff9f8Sschwarze 		case '>':
2964b6a36a9bSschwarze 			*res = *res > operand2;
2965b6a36a9bSschwarze 			break;
296649aff9f8Sschwarze 		case 'l':
2967b6a36a9bSschwarze 			*res = *res <= operand2;
2968b6a36a9bSschwarze 			break;
296949aff9f8Sschwarze 		case 'g':
2970b6a36a9bSschwarze 			*res = *res >= operand2;
2971b6a36a9bSschwarze 			break;
297249aff9f8Sschwarze 		case '=':
2973b6a36a9bSschwarze 			*res = *res == operand2;
2974b6a36a9bSschwarze 			break;
297549aff9f8Sschwarze 		case '!':
2976b6a36a9bSschwarze 			*res = *res != operand2;
2977b6a36a9bSschwarze 			break;
297849aff9f8Sschwarze 		case '&':
2979b6a36a9bSschwarze 			*res = *res && operand2;
2980b6a36a9bSschwarze 			break;
298149aff9f8Sschwarze 		case ':':
2982b6a36a9bSschwarze 			*res = *res || operand2;
2983b6a36a9bSschwarze 			break;
298449aff9f8Sschwarze 		case 'i':
2985b6a36a9bSschwarze 			if (operand2 < *res)
2986b6a36a9bSschwarze 				*res = operand2;
2987b6a36a9bSschwarze 			break;
298849aff9f8Sschwarze 		case 'a':
2989b6a36a9bSschwarze 			if (operand2 > *res)
2990b6a36a9bSschwarze 				*res = operand2;
2991b6a36a9bSschwarze 			break;
2992b6a36a9bSschwarze 		default:
2993b6a36a9bSschwarze 			abort();
2994b6a36a9bSschwarze 		}
2995b6a36a9bSschwarze 	}
2996526e306bSschwarze 	return 1;
2997b6a36a9bSschwarze }
2998b6a36a9bSschwarze 
2999fa2127f9Sschwarze /* --- register management ------------------------------------------------ */
3000fa2127f9Sschwarze 
300122881299Sschwarze void
300275088a49Sschwarze roff_setreg(struct roff *r, const char *name, int val, char sign)
3003f8618d99Sschwarze {
3004dcd9eb04Sschwarze 	roff_setregn(r, name, strlen(name), val, sign, INT_MIN);
30059f6d70bcSschwarze }
30069f6d70bcSschwarze 
30079f6d70bcSschwarze static void
30089f6d70bcSschwarze roff_setregn(struct roff *r, const char *name, size_t len,
3009dcd9eb04Sschwarze     int val, char sign, int step)
30109f6d70bcSschwarze {
301122881299Sschwarze 	struct roffreg	*reg;
3012f8618d99Sschwarze 
301322881299Sschwarze 	/* Search for an existing register with the same name. */
301422881299Sschwarze 	reg = r->regtab;
301522881299Sschwarze 
30169f6d70bcSschwarze 	while (reg != NULL && (reg->key.sz != len ||
30179f6d70bcSschwarze 	    strncmp(reg->key.p, name, len) != 0))
301822881299Sschwarze 		reg = reg->next;
301922881299Sschwarze 
302022881299Sschwarze 	if (NULL == reg) {
302122881299Sschwarze 		/* Create a new register. */
302222881299Sschwarze 		reg = mandoc_malloc(sizeof(struct roffreg));
30239f6d70bcSschwarze 		reg->key.p = mandoc_strndup(name, len);
30249f6d70bcSschwarze 		reg->key.sz = len;
302575088a49Sschwarze 		reg->val = 0;
3026dcd9eb04Sschwarze 		reg->step = 0;
302722881299Sschwarze 		reg->next = r->regtab;
302822881299Sschwarze 		r->regtab = reg;
302922881299Sschwarze 	}
303022881299Sschwarze 
303175088a49Sschwarze 	if ('+' == sign)
303275088a49Sschwarze 		reg->val += val;
303375088a49Sschwarze 	else if ('-' == sign)
303475088a49Sschwarze 		reg->val -= val;
303575088a49Sschwarze 	else
3036c542e817Sschwarze 		reg->val = val;
3037dcd9eb04Sschwarze 	if (step != INT_MIN)
3038dcd9eb04Sschwarze 		reg->step = step;
3039f8618d99Sschwarze }
3040f8618d99Sschwarze 
3041b382ec6eSschwarze /*
3042b382ec6eSschwarze  * Handle some predefined read-only number registers.
3043b382ec6eSschwarze  * For now, return -1 if the requested register is not predefined;
3044b382ec6eSschwarze  * in case a predefined read-only register having the value -1
3045b382ec6eSschwarze  * were to turn up, another special value would have to be chosen.
3046b382ec6eSschwarze  */
3047b382ec6eSschwarze static int
304845ff8f42Sschwarze roff_getregro(const struct roff *r, const char *name)
3049b382ec6eSschwarze {
3050b382ec6eSschwarze 
3051b382ec6eSschwarze 	switch (*name) {
305245ff8f42Sschwarze 	case '$':  /* Number of arguments of the last macro evaluated. */
30533dc5225dSschwarze 		return r->mstackpos < 0 ? 0 : r->mstack[r->mstackpos].argc;
305449aff9f8Sschwarze 	case 'A':  /* ASCII approximation mode is always off. */
3055526e306bSschwarze 		return 0;
305649aff9f8Sschwarze 	case 'g':  /* Groff compatibility mode is always on. */
3057526e306bSschwarze 		return 1;
305849aff9f8Sschwarze 	case 'H':  /* Fixed horizontal resolution. */
3059526e306bSschwarze 		return 24;
306049aff9f8Sschwarze 	case 'j':  /* Always adjust left margin only. */
3061526e306bSschwarze 		return 0;
30622e4bcbabSschwarze 	case 'l':  /* Fixed line width for DocBook. */
30632e4bcbabSschwarze 		return 78 * 24;
306449aff9f8Sschwarze 	case 'T':  /* Some output device is always defined. */
3065526e306bSschwarze 		return 1;
306649aff9f8Sschwarze 	case 'V':  /* Fixed vertical resolution. */
3067526e306bSschwarze 		return 40;
3068b382ec6eSschwarze 	default:
3069526e306bSschwarze 		return -1;
3070b382ec6eSschwarze 	}
3071b382ec6eSschwarze }
3072b382ec6eSschwarze 
3073c542e817Sschwarze int
30749f6d70bcSschwarze roff_getreg(struct roff *r, const char *name)
3075f8618d99Sschwarze {
3076dcd9eb04Sschwarze 	return roff_getregn(r, name, strlen(name), '\0');
3077c542e817Sschwarze }
3078c542e817Sschwarze 
3079c542e817Sschwarze static int
3080dcd9eb04Sschwarze roff_getregn(struct roff *r, const char *name, size_t len, char sign)
3081c542e817Sschwarze {
3082c542e817Sschwarze 	struct roffreg	*reg;
3083b382ec6eSschwarze 	int		 val;
3084b382ec6eSschwarze 
3085b382ec6eSschwarze 	if ('.' == name[0] && 2 == len) {
308645ff8f42Sschwarze 		val = roff_getregro(r, name + 1);
3087b382ec6eSschwarze 		if (-1 != val)
3088526e306bSschwarze 			return val;
3089b382ec6eSschwarze 	}
3090c542e817Sschwarze 
3091dcd9eb04Sschwarze 	for (reg = r->regtab; reg; reg = reg->next) {
3092c542e817Sschwarze 		if (len == reg->key.sz &&
3093dcd9eb04Sschwarze 		    0 == strncmp(name, reg->key.p, len)) {
3094dcd9eb04Sschwarze 			switch (sign) {
3095dcd9eb04Sschwarze 			case '+':
3096dcd9eb04Sschwarze 				reg->val += reg->step;
3097dcd9eb04Sschwarze 				break;
3098dcd9eb04Sschwarze 			case '-':
3099dcd9eb04Sschwarze 				reg->val -= reg->step;
3100dcd9eb04Sschwarze 				break;
3101dcd9eb04Sschwarze 			default:
3102dcd9eb04Sschwarze 				break;
3103dcd9eb04Sschwarze 			}
3104526e306bSschwarze 			return reg->val;
3105dcd9eb04Sschwarze 		}
3106dcd9eb04Sschwarze 	}
310722881299Sschwarze 
3108dcd9eb04Sschwarze 	roff_setregn(r, name, len, 0, '\0', INT_MIN);
3109526e306bSschwarze 	return 0;
3110f8618d99Sschwarze }
3111f8618d99Sschwarze 
311224ab75f2Sschwarze static int
311324ab75f2Sschwarze roff_hasregn(const struct roff *r, const char *name, size_t len)
311424ab75f2Sschwarze {
311524ab75f2Sschwarze 	struct roffreg	*reg;
311624ab75f2Sschwarze 	int		 val;
311724ab75f2Sschwarze 
311824ab75f2Sschwarze 	if ('.' == name[0] && 2 == len) {
311945ff8f42Sschwarze 		val = roff_getregro(r, name + 1);
312024ab75f2Sschwarze 		if (-1 != val)
3121526e306bSschwarze 			return 1;
312224ab75f2Sschwarze 	}
312324ab75f2Sschwarze 
312424ab75f2Sschwarze 	for (reg = r->regtab; reg; reg = reg->next)
312524ab75f2Sschwarze 		if (len == reg->key.sz &&
312624ab75f2Sschwarze 		    0 == strncmp(name, reg->key.p, len))
3127526e306bSschwarze 			return 1;
312824ab75f2Sschwarze 
3129526e306bSschwarze 	return 0;
313024ab75f2Sschwarze }
313124ab75f2Sschwarze 
313222881299Sschwarze static void
313322881299Sschwarze roff_freereg(struct roffreg *reg)
3134f8618d99Sschwarze {
313522881299Sschwarze 	struct roffreg	*old_reg;
3136f8618d99Sschwarze 
313722881299Sschwarze 	while (NULL != reg) {
313822881299Sschwarze 		free(reg->key.p);
313922881299Sschwarze 		old_reg = reg;
314022881299Sschwarze 		reg = reg->next;
314122881299Sschwarze 		free(old_reg);
314222881299Sschwarze 	}
3143f8618d99Sschwarze }
3144b49ce91bSschwarze 
3145b7f92c5fSschwarze static int
31469830bf9fSschwarze roff_nr(ROFF_ARGS)
3147f673a3c7Sschwarze {
3148dcd9eb04Sschwarze 	char		*key, *val, *step;
314959b3009eSschwarze 	size_t		 keysz;
3150dcd9eb04Sschwarze 	int		 iv, is, len;
315175088a49Sschwarze 	char		 sign;
31529830bf9fSschwarze 
315328687875Sschwarze 	key = val = buf->buf + pos;
315428687875Sschwarze 	if (*key == '\0')
3155526e306bSschwarze 		return ROFF_IGN;
315659b3009eSschwarze 
31573b5bd91eSschwarze 	keysz = roff_getname(&val, ln, pos);
31587ad9cfd6Sschwarze 	if (key[keysz] == '\\' || key[keysz] == '\t')
3159526e306bSschwarze 		return ROFF_IGN;
31609830bf9fSschwarze 
316175088a49Sschwarze 	sign = *val;
316228687875Sschwarze 	if (sign == '+' || sign == '-')
316375088a49Sschwarze 		val++;
316422881299Sschwarze 
3165dcd9eb04Sschwarze 	len = 0;
3166f4432377Sschwarze 	if (roff_evalnum(ln, val, &len, &iv, 'u', 0) == 0)
3167dcd9eb04Sschwarze 		return ROFF_IGN;
3168f673a3c7Sschwarze 
3169dcd9eb04Sschwarze 	step = val + len;
3170dcd9eb04Sschwarze 	while (isspace((unsigned char)*step))
3171dcd9eb04Sschwarze 		step++;
3172f4432377Sschwarze 	if (roff_evalnum(ln, step, NULL, &is, '\0', 0) == 0)
3173dcd9eb04Sschwarze 		is = INT_MIN;
3174dcd9eb04Sschwarze 
3175dcd9eb04Sschwarze 	roff_setregn(r, key, keysz, iv, sign, is);
3176526e306bSschwarze 	return ROFF_IGN;
3177f673a3c7Sschwarze }
3178b49ce91bSschwarze 
3179b7f92c5fSschwarze static int
3180e7693622Sschwarze roff_rr(ROFF_ARGS)
3181e7693622Sschwarze {
3182e7693622Sschwarze 	struct roffreg	*reg, **prev;
318359b3009eSschwarze 	char		*name, *cp;
318459b3009eSschwarze 	size_t		 namesz;
3185e7693622Sschwarze 
318628687875Sschwarze 	name = cp = buf->buf + pos;
318728687875Sschwarze 	if (*name == '\0')
3188526e306bSschwarze 		return ROFF_IGN;
31893b5bd91eSschwarze 	namesz = roff_getname(&cp, ln, pos);
319059b3009eSschwarze 	name[namesz] = '\0';
3191e7693622Sschwarze 
3192e7693622Sschwarze 	prev = &r->regtab;
3193e7693622Sschwarze 	while (1) {
3194e7693622Sschwarze 		reg = *prev;
319528687875Sschwarze 		if (reg == NULL || !strcmp(name, reg->key.p))
3196e7693622Sschwarze 			break;
3197e7693622Sschwarze 		prev = &reg->next;
3198e7693622Sschwarze 	}
319928687875Sschwarze 	if (reg != NULL) {
3200e7693622Sschwarze 		*prev = reg->next;
3201e7693622Sschwarze 		free(reg->key.p);
3202e7693622Sschwarze 		free(reg);
3203e7693622Sschwarze 	}
3204526e306bSschwarze 	return ROFF_IGN;
3205e7693622Sschwarze }
3206e7693622Sschwarze 
3207fa2127f9Sschwarze /* --- handler functions for roff requests -------------------------------- */
3208fa2127f9Sschwarze 
3209b7f92c5fSschwarze static int
3210dce4c3c7Sschwarze roff_rm(ROFF_ARGS)
3211dce4c3c7Sschwarze {
3212dce4c3c7Sschwarze 	const char	 *name;
3213dce4c3c7Sschwarze 	char		 *cp;
321459b3009eSschwarze 	size_t		  namesz;
3215dce4c3c7Sschwarze 
321628687875Sschwarze 	cp = buf->buf + pos;
321728687875Sschwarze 	while (*cp != '\0') {
321859b3009eSschwarze 		name = cp;
32193b5bd91eSschwarze 		namesz = roff_getname(&cp, ln, (int)(cp - buf->buf));
322059b3009eSschwarze 		roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0);
32210856de67Sschwarze 		roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
32227ad9cfd6Sschwarze 		if (name[namesz] == '\\' || name[namesz] == '\t')
322359b3009eSschwarze 			break;
3224dce4c3c7Sschwarze 	}
3225526e306bSschwarze 	return ROFF_IGN;
3226dce4c3c7Sschwarze }
3227dce4c3c7Sschwarze 
3228b7f92c5fSschwarze static int
3229a91e242cSschwarze roff_it(ROFF_ARGS)
3230a91e242cSschwarze {
3231a91e242cSschwarze 	int		 iv;
3232a91e242cSschwarze 
3233a91e242cSschwarze 	/* Parse the number of lines. */
32343a1681a6Sschwarze 
3235f4432377Sschwarze 	if ( ! roff_evalnum(ln, buf->buf, &pos, &iv, '\0', 0)) {
3236a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_IT_NONUM,
3237a5a5f808Sschwarze 		    ln, ppos, "%s", buf->buf + 1);
3238526e306bSschwarze 		return ROFF_IGN;
3239a91e242cSschwarze 	}
3240a91e242cSschwarze 
3241c527ae42Sschwarze 	while (isspace((unsigned char)buf->buf[pos]))
3242c527ae42Sschwarze 		pos++;
3243c527ae42Sschwarze 
3244c527ae42Sschwarze 	/*
3245c527ae42Sschwarze 	 * Arm the input line trap.
3246c527ae42Sschwarze 	 * Special-casing "an-trap" is an ugly workaround to cope
3247c527ae42Sschwarze 	 * with DocBook stupidly fiddling with man(7) internals.
3248c527ae42Sschwarze 	 */
32493a1681a6Sschwarze 
3250a91e242cSschwarze 	roffit_lines = iv;
3251c527ae42Sschwarze 	roffit_macro = mandoc_strdup(iv != 1 ||
3252c527ae42Sschwarze 	    strcmp(buf->buf + pos, "an-trap") ?
3253c527ae42Sschwarze 	    buf->buf + pos : "br");
3254526e306bSschwarze 	return ROFF_IGN;
3255a91e242cSschwarze }
3256a91e242cSschwarze 
3257b7f92c5fSschwarze static int
325893aaf9baSschwarze roff_Dd(ROFF_ARGS)
325993aaf9baSschwarze {
326007530f0aSschwarze 	int		 mask;
326107530f0aSschwarze 	enum roff_tok	 t, te;
326293aaf9baSschwarze 
326307530f0aSschwarze 	switch (tok) {
326407530f0aSschwarze 	case ROFF_Dd:
326507530f0aSschwarze 		tok = MDOC_Dd;
326607530f0aSschwarze 		te = MDOC_MAX;
3267f0d7487dSschwarze 		if (r->format == 0)
3268f0d7487dSschwarze 			r->format = MPARSE_MDOC;
326907530f0aSschwarze 		mask = MPARSE_MDOC | MPARSE_QUICK;
327007530f0aSschwarze 		break;
327107530f0aSschwarze 	case ROFF_TH:
327207530f0aSschwarze 		tok = MAN_TH;
327307530f0aSschwarze 		te = MAN_MAX;
3274f0d7487dSschwarze 		if (r->format == 0)
3275f0d7487dSschwarze 			r->format = MPARSE_MAN;
327607530f0aSschwarze 		mask = MPARSE_QUICK;
327707530f0aSschwarze 		break;
327807530f0aSschwarze 	default:
327907530f0aSschwarze 		abort();
328007530f0aSschwarze 	}
328107530f0aSschwarze 	if ((r->options & mask) == 0)
328207530f0aSschwarze 		for (t = tok; t < te; t++)
328307530f0aSschwarze 			roff_setstr(r, roff_name[t], NULL, 0);
3284526e306bSschwarze 	return ROFF_CONT;
328593aaf9baSschwarze }
328693aaf9baSschwarze 
3287b7f92c5fSschwarze static int
32882791bd1cSschwarze roff_TE(ROFF_ARGS)
32892791bd1cSschwarze {
32906bce724aSschwarze 	r->man->flags &= ~ROFF_NONOFILL;
3291d93f8561Sschwarze 	if (r->tbl == NULL) {
3292a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "TE");
3293d93f8561Sschwarze 		return ROFF_IGN;
3294d93f8561Sschwarze 	}
3295fb382a01Sschwarze 	if (tbl_end(r->tbl, 0) == 0) {
3296d93f8561Sschwarze 		r->tbl = NULL;
329792ff8da6Sschwarze 		free(buf->buf);
329892ff8da6Sschwarze 		buf->buf = mandoc_strdup(".sp");
329992ff8da6Sschwarze 		buf->sz = 4;
3300d0365455Sschwarze 		*offs = 0;
3301526e306bSschwarze 		return ROFF_REPARSE;
330292ff8da6Sschwarze 	}
3303d93f8561Sschwarze 	r->tbl = NULL;
3304526e306bSschwarze 	return ROFF_IGN;
33052791bd1cSschwarze }
33062791bd1cSschwarze 
3307b7f92c5fSschwarze static int
33082791bd1cSschwarze roff_T_(ROFF_ARGS)
33092791bd1cSschwarze {
33102791bd1cSschwarze 
33112791bd1cSschwarze 	if (NULL == r->tbl)
3312a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "T&");
33132791bd1cSschwarze 	else
331429478532Sschwarze 		tbl_restart(ln, ppos, r->tbl);
33152791bd1cSschwarze 
3316526e306bSschwarze 	return ROFF_IGN;
33172791bd1cSschwarze }
33182791bd1cSschwarze 
331957ca9bd5Sschwarze /*
332057ca9bd5Sschwarze  * Handle in-line equation delimiters.
332157ca9bd5Sschwarze  */
3322b7f92c5fSschwarze static int
332328687875Sschwarze roff_eqndelim(struct roff *r, struct buf *buf, int pos)
3324f8618d99Sschwarze {
332557ca9bd5Sschwarze 	char		*cp1, *cp2;
332655e7d5daSschwarze 	const char	*bef_pr, *bef_nl, *mac, *aft_nl, *aft_pr;
3327f8618d99Sschwarze 
332857ca9bd5Sschwarze 	/*
332957ca9bd5Sschwarze 	 * Outside equations, look for an opening delimiter.
333057ca9bd5Sschwarze 	 * If we are inside an equation, we already know it is
333157ca9bd5Sschwarze 	 * in-line, or this function wouldn't have been called;
333257ca9bd5Sschwarze 	 * so look for a closing delimiter.
333357ca9bd5Sschwarze 	 */
333457ca9bd5Sschwarze 
333528687875Sschwarze 	cp1 = buf->buf + pos;
333657ca9bd5Sschwarze 	cp2 = strchr(cp1, r->eqn == NULL ?
333757ca9bd5Sschwarze 	    r->last_eqn->odelim : r->last_eqn->cdelim);
333857ca9bd5Sschwarze 	if (cp2 == NULL)
3339526e306bSschwarze 		return ROFF_CONT;
334057ca9bd5Sschwarze 
334196e1823cSschwarze 	*cp2++ = '\0';
334255e7d5daSschwarze 	bef_pr = bef_nl = aft_nl = aft_pr = "";
334355e7d5daSschwarze 
334455e7d5daSschwarze 	/* Handle preceding text, protecting whitespace. */
334555e7d5daSschwarze 
334628687875Sschwarze 	if (*buf->buf != '\0') {
334755e7d5daSschwarze 		if (r->eqn == NULL)
334855e7d5daSschwarze 			bef_pr = "\\&";
334955e7d5daSschwarze 		bef_nl = "\n";
335055e7d5daSschwarze 	}
335155e7d5daSschwarze 
335255e7d5daSschwarze 	/*
335355e7d5daSschwarze 	 * Prepare replacing the delimiter with an equation macro
335455e7d5daSschwarze 	 * and drop leading white space from the equation.
335555e7d5daSschwarze 	 */
335655e7d5daSschwarze 
335755e7d5daSschwarze 	if (r->eqn == NULL) {
335855e7d5daSschwarze 		while (*cp2 == ' ')
335955e7d5daSschwarze 			cp2++;
336055e7d5daSschwarze 		mac = ".EQ";
336155e7d5daSschwarze 	} else
336255e7d5daSschwarze 		mac = ".EN";
336355e7d5daSschwarze 
336455e7d5daSschwarze 	/* Handle following text, protecting whitespace. */
336555e7d5daSschwarze 
336655e7d5daSschwarze 	if (*cp2 != '\0') {
336755e7d5daSschwarze 		aft_nl = "\n";
336855e7d5daSschwarze 		if (r->eqn != NULL)
336955e7d5daSschwarze 			aft_pr = "\\&";
337055e7d5daSschwarze 	}
337155e7d5daSschwarze 
337255e7d5daSschwarze 	/* Do the actual replacement. */
337355e7d5daSschwarze 
337428687875Sschwarze 	buf->sz = mandoc_asprintf(&cp1, "%s%s%s%s%s%s%s", buf->buf,
337555e7d5daSschwarze 	    bef_pr, bef_nl, mac, aft_nl, aft_pr, cp2) + 1;
337628687875Sschwarze 	free(buf->buf);
337728687875Sschwarze 	buf->buf = cp1;
337857ca9bd5Sschwarze 
337957ca9bd5Sschwarze 	/* Toggle the in-line state of the eqn subsystem. */
338057ca9bd5Sschwarze 
338157ca9bd5Sschwarze 	r->eqn_inline = r->eqn == NULL;
3382526e306bSschwarze 	return ROFF_REPARSE;
3383f8618d99Sschwarze }
3384f8618d99Sschwarze 
3385b7f92c5fSschwarze static int
3386a64ea134Sschwarze roff_EQ(ROFF_ARGS)
33878d973ab1Sschwarze {
3388bf9acac6Sschwarze 	struct roff_node	*n;
3389bf9acac6Sschwarze 
33906b86842eSschwarze 	if (r->man->meta.macroset == MACROSET_MAN)
339101647224Sschwarze 		man_breakscope(r->man, ROFF_EQ);
3392bf9acac6Sschwarze 	n = roff_node_alloc(r->man, ln, ppos, ROFFT_EQN, TOKEN_NONE);
3393bf9acac6Sschwarze 	if (ln > r->man->last->line)
3394bf9acac6Sschwarze 		n->flags |= NODE_LINE;
3395d4c8d4a3Sschwarze 	n->eqn = eqn_box_new();
3396bf9acac6Sschwarze 	roff_node_append(r->man, n);
3397bf9acac6Sschwarze 	r->man->next = ROFF_NEXT_SIBLING;
33988d973ab1Sschwarze 
339928687875Sschwarze 	assert(r->eqn == NULL);
3400bf9acac6Sschwarze 	if (r->last_eqn == NULL)
340191305757Sschwarze 		r->last_eqn = eqn_alloc();
3402bf9acac6Sschwarze 	else
3403bf9acac6Sschwarze 		eqn_reset(r->last_eqn);
3404bf9acac6Sschwarze 	r->eqn = r->last_eqn;
3405bf9acac6Sschwarze 	r->eqn->node = n;
3406f8618d99Sschwarze 
340728687875Sschwarze 	if (buf->buf[pos] != '\0')
3408a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
340928687875Sschwarze 		    ".EQ %s", buf->buf + pos);
3410f8618d99Sschwarze 
3411526e306bSschwarze 	return ROFF_IGN;
34128d973ab1Sschwarze }
34138d973ab1Sschwarze 
3414b7f92c5fSschwarze static int
34158d973ab1Sschwarze roff_EN(ROFF_ARGS)
34168d973ab1Sschwarze {
3417bf9acac6Sschwarze 	if (r->eqn != NULL) {
3418bf9acac6Sschwarze 		eqn_parse(r->eqn);
3419bf9acac6Sschwarze 		r->eqn = NULL;
3420bf9acac6Sschwarze 	} else
3421a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "EN");
3422bf9acac6Sschwarze 	if (buf->buf[pos] != '\0')
3423a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
3424bf9acac6Sschwarze 		    "EN %s", buf->buf + pos);
3425526e306bSschwarze 	return ROFF_IGN;
34268d973ab1Sschwarze }
34278d973ab1Sschwarze 
3428b7f92c5fSschwarze static int
34292791bd1cSschwarze roff_TS(ROFF_ARGS)
34302791bd1cSschwarze {
3431d93f8561Sschwarze 	if (r->tbl != NULL) {
3432a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_BLK_BROKEN, ln, ppos, "TS breaks TS");
3433fb382a01Sschwarze 		tbl_end(r->tbl, 0);
34342791bd1cSschwarze 	}
34356bce724aSschwarze 	r->man->flags |= ROFF_NONOFILL;
343691305757Sschwarze 	r->tbl = tbl_alloc(ppos, ln, r->last_tbl);
3437fb382a01Sschwarze 	if (r->last_tbl == NULL)
3438d93f8561Sschwarze 		r->first_tbl = r->tbl;
3439d93f8561Sschwarze 	r->last_tbl = r->tbl;
3440526e306bSschwarze 	return ROFF_IGN;
34412791bd1cSschwarze }
34422791bd1cSschwarze 
3443b7f92c5fSschwarze static int
34440438bfdfSschwarze roff_noarg(ROFF_ARGS)
34450438bfdfSschwarze {
34460438bfdfSschwarze 	if (r->man->flags & (MAN_BLINE | MAN_ELINE))
34470438bfdfSschwarze 		man_breakscope(r->man, tok);
34480438bfdfSschwarze 	if (tok == ROFF_brp)
34490438bfdfSschwarze 		tok = ROFF_br;
34500438bfdfSschwarze 	roff_elem_alloc(r->man, ln, ppos, tok);
34510438bfdfSschwarze 	if (buf->buf[pos] != '\0')
34520438bfdfSschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
34530438bfdfSschwarze 		   "%s %s", roff_name[tok], buf->buf + pos);
34540438bfdfSschwarze 	if (tok == ROFF_nf)
34550438bfdfSschwarze 		r->man->flags |= ROFF_NOFILL;
34560438bfdfSschwarze 	else if (tok == ROFF_fi)
34570438bfdfSschwarze 		r->man->flags &= ~ROFF_NOFILL;
34580438bfdfSschwarze 	r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
34590438bfdfSschwarze 	r->man->next = ROFF_NEXT_SIBLING;
34600438bfdfSschwarze 	return ROFF_IGN;
34610438bfdfSschwarze }
34620438bfdfSschwarze 
34630438bfdfSschwarze static int
3464c4d3fa85Sschwarze roff_onearg(ROFF_ARGS)
3465c4d3fa85Sschwarze {
3466c4d3fa85Sschwarze 	struct roff_node	*n;
3467c4d3fa85Sschwarze 	char			*cp;
3468e13b4195Sschwarze 	int			 npos;
3469c4d3fa85Sschwarze 
3470885ec428Sschwarze 	if (r->man->flags & (MAN_BLINE | MAN_ELINE) &&
347139dd362eSschwarze 	    (tok == ROFF_ce || tok == ROFF_rj || tok == ROFF_sp ||
347239dd362eSschwarze 	     tok == ROFF_ti))
3473885ec428Sschwarze 		man_breakscope(r->man, tok);
3474885ec428Sschwarze 
34756de096f4Sschwarze 	if (roffce_node != NULL && (tok == ROFF_ce || tok == ROFF_rj)) {
3476e13b4195Sschwarze 		r->man->last = roffce_node;
3477e13b4195Sschwarze 		r->man->next = ROFF_NEXT_SIBLING;
3478e13b4195Sschwarze 	}
3479e13b4195Sschwarze 
3480c4d3fa85Sschwarze 	roff_elem_alloc(r->man, ln, ppos, tok);
3481c4d3fa85Sschwarze 	n = r->man->last;
3482c4d3fa85Sschwarze 
3483c4d3fa85Sschwarze 	cp = buf->buf + pos;
3484c4d3fa85Sschwarze 	if (*cp != '\0') {
3485c4d3fa85Sschwarze 		while (*cp != '\0' && *cp != ' ')
3486c4d3fa85Sschwarze 			cp++;
3487c4d3fa85Sschwarze 		while (*cp == ' ')
3488c4d3fa85Sschwarze 			*cp++ = '\0';
3489c4d3fa85Sschwarze 		if (*cp != '\0')
3490a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_ARG_EXCESS,
3491a5a5f808Sschwarze 			    ln, (int)(cp - buf->buf),
3492c4d3fa85Sschwarze 			    "%s ... %s", roff_name[tok], cp);
3493c4d3fa85Sschwarze 		roff_word_alloc(r->man, ln, pos, buf->buf + pos);
3494c4d3fa85Sschwarze 	}
3495c4d3fa85Sschwarze 
34966de096f4Sschwarze 	if (tok == ROFF_ce || tok == ROFF_rj) {
34976de096f4Sschwarze 		if (r->man->last->type == ROFFT_ELEM) {
3498e13b4195Sschwarze 			roff_word_alloc(r->man, ln, pos, "1");
3499e13b4195Sschwarze 			r->man->last->flags |= NODE_NOSRC;
3500e13b4195Sschwarze 		}
3501e13b4195Sschwarze 		npos = 0;
35023b5bd91eSschwarze 		if (roff_evalnum(ln, r->man->last->string, &npos,
3503f4432377Sschwarze 		    &roffce_lines, '\0', 0) == 0) {
3504a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_CE_NONUM,
3505a5a5f808Sschwarze 			    ln, pos, "ce %s", buf->buf + pos);
3506e13b4195Sschwarze 			roffce_lines = 1;
3507e13b4195Sschwarze 		}
3508e13b4195Sschwarze 		if (roffce_lines < 1) {
3509e13b4195Sschwarze 			r->man->last = r->man->last->parent;
3510e13b4195Sschwarze 			roffce_node = NULL;
3511e13b4195Sschwarze 			roffce_lines = 0;
3512e13b4195Sschwarze 		} else
3513e13b4195Sschwarze 			roffce_node = r->man->last->parent;
3514e13b4195Sschwarze 	} else {
3515e13b4195Sschwarze 		n->flags |= NODE_VALID | NODE_ENDED;
3516c4d3fa85Sschwarze 		r->man->last = n;
3517e13b4195Sschwarze 	}
3518e13b4195Sschwarze 	n->flags |= NODE_LINE;
3519c4d3fa85Sschwarze 	r->man->next = ROFF_NEXT_SIBLING;
3520c4d3fa85Sschwarze 	return ROFF_IGN;
3521c4d3fa85Sschwarze }
3522c4d3fa85Sschwarze 
3523b7f92c5fSschwarze static int
3524f7242c43Sschwarze roff_manyarg(ROFF_ARGS)
3525f7242c43Sschwarze {
3526f7242c43Sschwarze 	struct roff_node	*n;
3527f7242c43Sschwarze 	char			*sp, *ep;
3528f7242c43Sschwarze 
3529f7242c43Sschwarze 	roff_elem_alloc(r->man, ln, ppos, tok);
3530f7242c43Sschwarze 	n = r->man->last;
3531f7242c43Sschwarze 
3532f7242c43Sschwarze 	for (sp = ep = buf->buf + pos; *sp != '\0'; sp = ep) {
3533f7242c43Sschwarze 		while (*ep != '\0' && *ep != ' ')
3534f7242c43Sschwarze 			ep++;
3535f7242c43Sschwarze 		while (*ep == ' ')
3536f7242c43Sschwarze 			*ep++ = '\0';
3537f7242c43Sschwarze 		roff_word_alloc(r->man, ln, sp - buf->buf, sp);
3538f7242c43Sschwarze 	}
3539f7242c43Sschwarze 
3540f7242c43Sschwarze 	n->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
3541f7242c43Sschwarze 	r->man->last = n;
3542f7242c43Sschwarze 	r->man->next = ROFF_NEXT_SIBLING;
3543f7242c43Sschwarze 	return ROFF_IGN;
3544f7242c43Sschwarze }
3545f7242c43Sschwarze 
3546b7f92c5fSschwarze static int
35470856de67Sschwarze roff_als(ROFF_ARGS)
35480856de67Sschwarze {
35490856de67Sschwarze 	char		*oldn, *newn, *end, *value;
35500856de67Sschwarze 	size_t		 oldsz, newsz, valsz;
35510856de67Sschwarze 
35520856de67Sschwarze 	newn = oldn = buf->buf + pos;
35530856de67Sschwarze 	if (*newn == '\0')
35540856de67Sschwarze 		return ROFF_IGN;
35550856de67Sschwarze 
35563b5bd91eSschwarze 	newsz = roff_getname(&oldn, ln, pos);
35577ad9cfd6Sschwarze 	if (newn[newsz] == '\\' || newn[newsz] == '\t' || *oldn == '\0')
35580856de67Sschwarze 		return ROFF_IGN;
35590856de67Sschwarze 
35600856de67Sschwarze 	end = oldn;
35613b5bd91eSschwarze 	oldsz = roff_getname(&end, ln, oldn - buf->buf);
35620856de67Sschwarze 	if (oldsz == 0)
35630856de67Sschwarze 		return ROFF_IGN;
35640856de67Sschwarze 
3565e1866606Sschwarze 	valsz = mandoc_asprintf(&value, ".%.*s \\$@\\\"\n",
356607530f0aSschwarze 	    (int)oldsz, oldn);
35670856de67Sschwarze 	roff_setstrn(&r->strtab, newn, newsz, value, valsz, 0);
35680856de67Sschwarze 	roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
35690856de67Sschwarze 	free(value);
35700856de67Sschwarze 	return ROFF_IGN;
35710856de67Sschwarze }
35720856de67Sschwarze 
35737dd0ff50Sschwarze /*
35747dd0ff50Sschwarze  * The .break request only makes sense inside conditionals,
35757dd0ff50Sschwarze  * and that case is already handled in roff_cond_sub().
35767dd0ff50Sschwarze  */
35777dd0ff50Sschwarze static int
35787dd0ff50Sschwarze roff_break(ROFF_ARGS)
35797dd0ff50Sschwarze {
35807dd0ff50Sschwarze 	mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break");
35817dd0ff50Sschwarze 	return ROFF_IGN;
35827dd0ff50Sschwarze }
35837dd0ff50Sschwarze 
3584b7f92c5fSschwarze static int
35851e6d02e2Sschwarze roff_cc(ROFF_ARGS)
35861e6d02e2Sschwarze {
35871e6d02e2Sschwarze 	const char	*p;
35881e6d02e2Sschwarze 
358928687875Sschwarze 	p = buf->buf + pos;
35901e6d02e2Sschwarze 
359128687875Sschwarze 	if (*p == '\0' || (r->control = *p++) == '.')
3592be477484Sschwarze 		r->control = '\0';
35931e6d02e2Sschwarze 
359428687875Sschwarze 	if (*p != '\0')
3595a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_EXCESS,
3596bce599dfSschwarze 		    ln, p - buf->buf, "cc ... %s", p);
35971e6d02e2Sschwarze 
3598526e306bSschwarze 	return ROFF_IGN;
35991e6d02e2Sschwarze }
36001e6d02e2Sschwarze 
3601b7f92c5fSschwarze static int
360229fcab3eSschwarze roff_char(ROFF_ARGS)
360329fcab3eSschwarze {
360429fcab3eSschwarze 	const char	*p, *kp, *vp;
360529fcab3eSschwarze 	size_t		 ksz, vsz;
360629fcab3eSschwarze 	int		 font;
360729fcab3eSschwarze 
360829fcab3eSschwarze 	/* Parse the character to be replaced. */
360929fcab3eSschwarze 
361029fcab3eSschwarze 	kp = buf->buf + pos;
361129fcab3eSschwarze 	p = kp + 1;
361229fcab3eSschwarze 	if (*kp == '\0' || (*kp == '\\' &&
361329fcab3eSschwarze 	     mandoc_escape(&p, NULL, NULL) != ESCAPE_SPECIAL) ||
361429fcab3eSschwarze 	    (*p != ' ' && *p != '\0')) {
3615a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_CHAR_ARG, ln, pos, "char %s", kp);
361629fcab3eSschwarze 		return ROFF_IGN;
361729fcab3eSschwarze 	}
361829fcab3eSschwarze 	ksz = p - kp;
361929fcab3eSschwarze 	while (*p == ' ')
362029fcab3eSschwarze 		p++;
362129fcab3eSschwarze 
362229fcab3eSschwarze 	/*
362329fcab3eSschwarze 	 * If the replacement string contains a font escape sequence,
362429fcab3eSschwarze 	 * we have to restore the font at the end.
362529fcab3eSschwarze 	 */
362629fcab3eSschwarze 
362729fcab3eSschwarze 	vp = p;
362829fcab3eSschwarze 	vsz = strlen(p);
362929fcab3eSschwarze 	font = 0;
363029fcab3eSschwarze 	while (*p != '\0') {
363129fcab3eSschwarze 		if (*p++ != '\\')
363229fcab3eSschwarze 			continue;
363329fcab3eSschwarze 		switch (mandoc_escape(&p, NULL, NULL)) {
363429fcab3eSschwarze 		case ESCAPE_FONT:
363529fcab3eSschwarze 		case ESCAPE_FONTROMAN:
363629fcab3eSschwarze 		case ESCAPE_FONTITALIC:
363729fcab3eSschwarze 		case ESCAPE_FONTBOLD:
363829fcab3eSschwarze 		case ESCAPE_FONTBI:
36397d063611Sschwarze 		case ESCAPE_FONTCR:
36407d063611Sschwarze 		case ESCAPE_FONTCB:
36417d063611Sschwarze 		case ESCAPE_FONTCI:
364229fcab3eSschwarze 		case ESCAPE_FONTPREV:
364329fcab3eSschwarze 			font++;
364429fcab3eSschwarze 			break;
364529fcab3eSschwarze 		default:
364629fcab3eSschwarze 			break;
364729fcab3eSschwarze 		}
364829fcab3eSschwarze 	}
364929fcab3eSschwarze 	if (font > 1)
3650a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_CHAR_FONT,
3651a5a5f808Sschwarze 		    ln, (int)(vp - buf->buf), "%s", vp);
365229fcab3eSschwarze 
365329fcab3eSschwarze 	/*
365429fcab3eSschwarze 	 * Approximate the effect of .char using the .tr tables.
365529fcab3eSschwarze 	 * XXX In groff, .char and .tr interact differently.
365629fcab3eSschwarze 	 */
365729fcab3eSschwarze 
365829fcab3eSschwarze 	if (ksz == 1) {
365929fcab3eSschwarze 		if (r->xtab == NULL)
366029fcab3eSschwarze 			r->xtab = mandoc_calloc(128, sizeof(*r->xtab));
366129fcab3eSschwarze 		assert((unsigned int)*kp < 128);
366229fcab3eSschwarze 		free(r->xtab[(int)*kp].p);
366329fcab3eSschwarze 		r->xtab[(int)*kp].sz = mandoc_asprintf(&r->xtab[(int)*kp].p,
366429fcab3eSschwarze 		    "%s%s", vp, font ? "\fP" : "");
366529fcab3eSschwarze 	} else {
366629fcab3eSschwarze 		roff_setstrn(&r->xmbtab, kp, ksz, vp, vsz, 0);
366729fcab3eSschwarze 		if (font)
366829fcab3eSschwarze 			roff_setstrn(&r->xmbtab, kp, ksz, "\\fP", 3, 1);
366929fcab3eSschwarze 	}
367029fcab3eSschwarze 	return ROFF_IGN;
367129fcab3eSschwarze }
367229fcab3eSschwarze 
367329fcab3eSschwarze static int
3674be477484Sschwarze roff_ec(ROFF_ARGS)
3675be477484Sschwarze {
3676be477484Sschwarze 	const char	*p;
3677be477484Sschwarze 
3678be477484Sschwarze 	p = buf->buf + pos;
3679be477484Sschwarze 	if (*p == '\0')
3680be477484Sschwarze 		r->escape = '\\';
3681be477484Sschwarze 	else {
3682be477484Sschwarze 		r->escape = *p;
3683be477484Sschwarze 		if (*++p != '\0')
3684a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_ARG_EXCESS, ln,
3685a5a5f808Sschwarze 			    (int)(p - buf->buf), "ec ... %s", p);
3686be477484Sschwarze 	}
3687be477484Sschwarze 	return ROFF_IGN;
3688be477484Sschwarze }
3689be477484Sschwarze 
3690b7f92c5fSschwarze static int
3691be477484Sschwarze roff_eo(ROFF_ARGS)
3692be477484Sschwarze {
3693be477484Sschwarze 	r->escape = '\0';
3694be477484Sschwarze 	if (buf->buf[pos] != '\0')
3695a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_ARG_SKIP,
3696be477484Sschwarze 		    ln, pos, "eo %s", buf->buf + pos);
3697be477484Sschwarze 	return ROFF_IGN;
3698be477484Sschwarze }
3699be477484Sschwarze 
3700b7f92c5fSschwarze static int
3701edb0312fSschwarze roff_mc(ROFF_ARGS)
3702edb0312fSschwarze {
3703edb0312fSschwarze 	struct roff_node	*n;
3704edb0312fSschwarze 	char			*cp;
3705edb0312fSschwarze 
3706edb0312fSschwarze 	/* Parse the first argument. */
3707edb0312fSschwarze 
3708edb0312fSschwarze 	cp = buf->buf + pos;
3709edb0312fSschwarze 	if (*cp != '\0')
3710edb0312fSschwarze 		cp++;
3711edb0312fSschwarze 	if (buf->buf[pos] == '\\') {
3712edb0312fSschwarze 		switch (mandoc_escape((const char **)&cp, NULL, NULL)) {
3713edb0312fSschwarze 		case ESCAPE_SPECIAL:
3714edb0312fSschwarze 		case ESCAPE_UNICODE:
3715edb0312fSschwarze 		case ESCAPE_NUMBERED:
3716edb0312fSschwarze 			break;
3717edb0312fSschwarze 		default:
3718edb0312fSschwarze 			*cp = '\0';
3719edb0312fSschwarze 			mandoc_msg(MANDOCERR_MC_ESC, ln, pos,
3720edb0312fSschwarze 			    "mc %s", buf->buf + pos);
3721edb0312fSschwarze 			buf->buf[pos] = '\0';
3722edb0312fSschwarze 			break;
3723edb0312fSschwarze 		}
3724edb0312fSschwarze 	}
3725edb0312fSschwarze 
3726edb0312fSschwarze 	/* Ignore additional arguments. */
3727edb0312fSschwarze 
3728edb0312fSschwarze 	while (*cp == ' ')
3729edb0312fSschwarze 		*cp++ = '\0';
3730edb0312fSschwarze 	if (*cp != '\0') {
3731edb0312fSschwarze 		mandoc_msg(MANDOCERR_MC_DIST, ln, (int)(cp - buf->buf),
3732edb0312fSschwarze 		    "mc ... %s", cp);
3733edb0312fSschwarze 		*cp = '\0';
3734edb0312fSschwarze 	}
3735edb0312fSschwarze 
3736edb0312fSschwarze 	/* Create the .mc node. */
3737edb0312fSschwarze 
3738edb0312fSschwarze 	roff_elem_alloc(r->man, ln, ppos, tok);
3739edb0312fSschwarze 	n = r->man->last;
3740edb0312fSschwarze 	if (buf->buf[pos] != '\0')
3741edb0312fSschwarze 		roff_word_alloc(r->man, ln, pos, buf->buf + pos);
3742edb0312fSschwarze 	n->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
3743edb0312fSschwarze 	r->man->last = n;
3744edb0312fSschwarze 	r->man->next = ROFF_NEXT_SIBLING;
3745edb0312fSschwarze 	return ROFF_IGN;
3746edb0312fSschwarze }
3747edb0312fSschwarze 
3748edb0312fSschwarze static int
3749b20e20eaSschwarze roff_nop(ROFF_ARGS)
3750b20e20eaSschwarze {
3751b20e20eaSschwarze 	while (buf->buf[pos] == ' ')
3752b20e20eaSschwarze 		pos++;
3753b20e20eaSschwarze 	*offs = pos;
3754b20e20eaSschwarze 	return ROFF_RERUN;
3755b20e20eaSschwarze }
3756b20e20eaSschwarze 
3757b7f92c5fSschwarze static int
375804e980cbSschwarze roff_tr(ROFF_ARGS)
375904e980cbSschwarze {
376004e980cbSschwarze 	const char	*p, *first, *second;
376104e980cbSschwarze 	size_t		 fsz, ssz;
376204e980cbSschwarze 
376328687875Sschwarze 	p = buf->buf + pos;
376404e980cbSschwarze 
376528687875Sschwarze 	if (*p == '\0') {
3766a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_EMPTY, ln, ppos, "tr");
3767526e306bSschwarze 		return ROFF_IGN;
376804e980cbSschwarze 	}
376904e980cbSschwarze 
377028687875Sschwarze 	while (*p != '\0') {
377104e980cbSschwarze 		fsz = ssz = 1;
377204e980cbSschwarze 
377304e980cbSschwarze 		first = p++;
377428687875Sschwarze 		if (*first == '\\') {
3775140f9883Sschwarze 			if (mandoc_escape(&p, NULL, NULL) == ESCAPE_ERROR)
3776526e306bSschwarze 				return ROFF_IGN;
377704e980cbSschwarze 			fsz = (size_t)(p - first);
377804e980cbSschwarze 		}
377904e980cbSschwarze 
378004e980cbSschwarze 		second = p++;
378128687875Sschwarze 		if (*second == '\\') {
3782140f9883Sschwarze 			if (mandoc_escape(&p, NULL, NULL) == ESCAPE_ERROR)
3783526e306bSschwarze 				return ROFF_IGN;
378404e980cbSschwarze 			ssz = (size_t)(p - second);
378528687875Sschwarze 		} else if (*second == '\0') {
3786a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_TR_ODD, ln,
3787a5a5f808Sschwarze 			    (int)(first - buf->buf), "tr %s", first);
378804e980cbSschwarze 			second = " ";
378904e980cbSschwarze 			p--;
379004e980cbSschwarze 		}
379104e980cbSschwarze 
379204e980cbSschwarze 		if (fsz > 1) {
379349aff9f8Sschwarze 			roff_setstrn(&r->xmbtab, first, fsz,
379449aff9f8Sschwarze 			    second, ssz, 0);
379504e980cbSschwarze 			continue;
379604e980cbSschwarze 		}
379704e980cbSschwarze 
379828687875Sschwarze 		if (r->xtab == NULL)
379949aff9f8Sschwarze 			r->xtab = mandoc_calloc(128,
380049aff9f8Sschwarze 			    sizeof(struct roffstr));
380104e980cbSschwarze 
380204e980cbSschwarze 		free(r->xtab[(int)*first].p);
380304e980cbSschwarze 		r->xtab[(int)*first].p = mandoc_strndup(second, ssz);
380404e980cbSschwarze 		r->xtab[(int)*first].sz = ssz;
380504e980cbSschwarze 	}
380604e980cbSschwarze 
3807526e306bSschwarze 	return ROFF_IGN;
380804e980cbSschwarze }
380904e980cbSschwarze 
38103dc5225dSschwarze /*
38113dc5225dSschwarze  * Implementation of the .return request.
38123dc5225dSschwarze  * There is no need to call roff_userret() from here.
38133dc5225dSschwarze  * The read module will call that after rewinding the reader stack
38143dc5225dSschwarze  * to the place from where the current macro was called.
38153dc5225dSschwarze  */
3816b7f92c5fSschwarze static int
38173dc5225dSschwarze roff_return(ROFF_ARGS)
38183dc5225dSschwarze {
38193dc5225dSschwarze 	if (r->mstackpos >= 0)
3820b7f92c5fSschwarze 		return ROFF_IGN | ROFF_USERRET;
38213dc5225dSschwarze 
3822a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "return");
38233dc5225dSschwarze 	return ROFF_IGN;
38243dc5225dSschwarze }
38253dc5225dSschwarze 
3826b7f92c5fSschwarze static int
38277e291f10Sschwarze roff_rn(ROFF_ARGS)
38287e291f10Sschwarze {
38297e291f10Sschwarze 	const char	*value;
38307e291f10Sschwarze 	char		*oldn, *newn, *end;
38317e291f10Sschwarze 	size_t		 oldsz, newsz;
383207530f0aSschwarze 	int		 deftype;
38337e291f10Sschwarze 
38347e291f10Sschwarze 	oldn = newn = buf->buf + pos;
38357e291f10Sschwarze 	if (*oldn == '\0')
38367e291f10Sschwarze 		return ROFF_IGN;
38377e291f10Sschwarze 
38383b5bd91eSschwarze 	oldsz = roff_getname(&newn, ln, pos);
38397ad9cfd6Sschwarze 	if (oldn[oldsz] == '\\' || oldn[oldsz] == '\t' || *newn == '\0')
38407e291f10Sschwarze 		return ROFF_IGN;
38417e291f10Sschwarze 
38427e291f10Sschwarze 	end = newn;
38433b5bd91eSschwarze 	newsz = roff_getname(&end, ln, newn - buf->buf);
38447e291f10Sschwarze 	if (newsz == 0)
38457e291f10Sschwarze 		return ROFF_IGN;
38467e291f10Sschwarze 
384707530f0aSschwarze 	deftype = ROFFDEF_ANY;
384807530f0aSschwarze 	value = roff_getstrn(r, oldn, oldsz, &deftype);
384907530f0aSschwarze 	switch (deftype) {
385007530f0aSschwarze 	case ROFFDEF_USER:
38517e291f10Sschwarze 		roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0);
38527e291f10Sschwarze 		roff_setstrn(&r->strtab, oldn, oldsz, NULL, 0, 0);
38537e291f10Sschwarze 		roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
385407530f0aSschwarze 		break;
385507530f0aSschwarze 	case ROFFDEF_PRE:
385607530f0aSschwarze 		roff_setstrn(&r->strtab, newn, newsz, value, strlen(value), 0);
385707530f0aSschwarze 		roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
385807530f0aSschwarze 		break;
385907530f0aSschwarze 	case ROFFDEF_REN:
38607e291f10Sschwarze 		roff_setstrn(&r->rentab, newn, newsz, value, strlen(value), 0);
38617e291f10Sschwarze 		roff_setstrn(&r->rentab, oldn, oldsz, NULL, 0, 0);
386207530f0aSschwarze 		roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
386307530f0aSschwarze 		break;
386407530f0aSschwarze 	case ROFFDEF_STD:
38657e291f10Sschwarze 		roff_setstrn(&r->rentab, newn, newsz, oldn, oldsz, 0);
38667e291f10Sschwarze 		roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
386707530f0aSschwarze 		break;
386807530f0aSschwarze 	default:
386907530f0aSschwarze 		roff_setstrn(&r->strtab, newn, newsz, NULL, 0, 0);
387007530f0aSschwarze 		roff_setstrn(&r->rentab, newn, newsz, NULL, 0, 0);
387107530f0aSschwarze 		break;
387207530f0aSschwarze 	}
38737e291f10Sschwarze 	return ROFF_IGN;
38747e291f10Sschwarze }
38757e291f10Sschwarze 
3876b7f92c5fSschwarze static int
38773dc5225dSschwarze roff_shift(ROFF_ARGS)
38783dc5225dSschwarze {
38793dc5225dSschwarze 	struct mctx	*ctx;
3880c1a68d52Sschwarze 	int		 argpos, levels, i;
38813dc5225dSschwarze 
3882c1a68d52Sschwarze 	argpos = pos;
38833dc5225dSschwarze 	levels = 1;
38843dc5225dSschwarze 	if (buf->buf[pos] != '\0' &&
3885f4432377Sschwarze 	    roff_evalnum(ln, buf->buf, &pos, &levels, '\0', 0) == 0) {
3886a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_CE_NONUM,
38873dc5225dSschwarze 		    ln, pos, "shift %s", buf->buf + pos);
38883dc5225dSschwarze 		levels = 1;
38893dc5225dSschwarze 	}
38903dc5225dSschwarze 	if (r->mstackpos < 0) {
3891a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_REQ_NOMAC, ln, ppos, "shift");
38923dc5225dSschwarze 		return ROFF_IGN;
38933dc5225dSschwarze 	}
38943dc5225dSschwarze 	ctx = r->mstack + r->mstackpos;
38953dc5225dSschwarze 	if (levels > ctx->argc) {
3896a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SHIFT,
3897c1a68d52Sschwarze 		    ln, argpos, "%d, but max is %d", levels, ctx->argc);
38983dc5225dSschwarze 		levels = ctx->argc;
38993dc5225dSschwarze 	}
3900c1a68d52Sschwarze 	if (levels < 0) {
3901c1a68d52Sschwarze 		mandoc_msg(MANDOCERR_ARG_NEG, ln, argpos, "shift %d", levels);
3902c1a68d52Sschwarze 		levels = 0;
3903c1a68d52Sschwarze 	}
39043dc5225dSschwarze 	if (levels == 0)
39053dc5225dSschwarze 		return ROFF_IGN;
39063dc5225dSschwarze 	for (i = 0; i < levels; i++)
39073dc5225dSschwarze 		free(ctx->argv[i]);
39083dc5225dSschwarze 	ctx->argc -= levels;
39093dc5225dSschwarze 	for (i = 0; i < ctx->argc; i++)
39103dc5225dSschwarze 		ctx->argv[i] = ctx->argv[i + levels];
39113dc5225dSschwarze 	return ROFF_IGN;
39123dc5225dSschwarze }
39133dc5225dSschwarze 
3914b7f92c5fSschwarze static int
3915aeef6c13Sschwarze roff_so(ROFF_ARGS)
3916aeef6c13Sschwarze {
3917f8fd269eSschwarze 	char *name, *cp;
3918aeef6c13Sschwarze 
391928687875Sschwarze 	name = buf->buf + pos;
3920a5a5f808Sschwarze 	mandoc_msg(MANDOCERR_SO, ln, ppos, "so %s", name);
3921d494779eSschwarze 
3922b58a7f4fSschwarze 	/*
3923b58a7f4fSschwarze 	 * Handle `so'.  Be EXTREMELY careful, as we shouldn't be
3924b58a7f4fSschwarze 	 * opening anything that's not in our cwd or anything beneath
3925b58a7f4fSschwarze 	 * it.  Thus, explicitly disallow traversing up the file-system
3926b58a7f4fSschwarze 	 * or using absolute paths.
3927b58a7f4fSschwarze 	 */
3928b58a7f4fSschwarze 
392928687875Sschwarze 	if (*name == '/' || strstr(name, "../") || strstr(name, "/..")) {
3930a5a5f808Sschwarze 		mandoc_msg(MANDOCERR_SO_PATH, ln, ppos, ".so %s", name);
3931f8fd269eSschwarze 		buf->sz = mandoc_asprintf(&cp,
3932f8fd269eSschwarze 		    ".sp\nSee the file %s.\n.sp", name) + 1;
3933f8fd269eSschwarze 		free(buf->buf);
3934f8fd269eSschwarze 		buf->buf = cp;
3935f8fd269eSschwarze 		*offs = 0;
3936526e306bSschwarze 		return ROFF_REPARSE;
3937aeef6c13Sschwarze 	}
3938aeef6c13Sschwarze 
3939aeef6c13Sschwarze 	*offs = pos;
3940526e306bSschwarze 	return ROFF_SO;
3941aeef6c13Sschwarze }
3942aeef6c13Sschwarze 
3943fa2127f9Sschwarze /* --- user defined strings and macros ------------------------------------ */
3944fa2127f9Sschwarze 
3945b7f92c5fSschwarze static int
3946e53bb406Sschwarze roff_userdef(ROFF_ARGS)
394792853774Sschwarze {
39483dc5225dSschwarze 	struct mctx	 *ctx;
39493dc5225dSschwarze 	char		 *arg, *ap, *dst, *src;
39503dc5225dSschwarze 	size_t		  sz;
395192853774Sschwarze 
395261dc5079Sschwarze 	/* If the macro is empty, ignore it altogether. */
395361dc5079Sschwarze 
395461dc5079Sschwarze 	if (*r->current_string == '\0')
395561dc5079Sschwarze 		return ROFF_IGN;
395661dc5079Sschwarze 
39573dc5225dSschwarze 	/* Initialize a new macro stack context. */
3958633c28f2Sschwarze 
39593dc5225dSschwarze 	if (++r->mstackpos == r->mstacksz) {
39603dc5225dSschwarze 		r->mstack = mandoc_recallocarray(r->mstack,
39613dc5225dSschwarze 		    r->mstacksz, r->mstacksz + 8, sizeof(*r->mstack));
39623dc5225dSschwarze 		r->mstacksz += 8;
396345ff8f42Sschwarze 	}
39643dc5225dSschwarze 	ctx = r->mstack + r->mstackpos;
39653dc5225dSschwarze 	ctx->argc = 0;
39663dc5225dSschwarze 
39673dc5225dSschwarze 	/*
39683dc5225dSschwarze 	 * Collect pointers to macro argument strings,
39693dc5225dSschwarze 	 * NUL-terminating them and escaping quotes.
39703dc5225dSschwarze 	 */
39713dc5225dSschwarze 
39723dc5225dSschwarze 	src = buf->buf + pos;
39733dc5225dSschwarze 	while (*src != '\0') {
39743dc5225dSschwarze 		if (ctx->argc == ctx->argsz) {
39753dc5225dSschwarze 			ctx->argsz += 8;
39763dc5225dSschwarze 			ctx->argv = mandoc_reallocarray(ctx->argv,
39773dc5225dSschwarze 			    ctx->argsz, sizeof(*ctx->argv));
397845ff8f42Sschwarze 		}
3979e94357f9Sschwarze 		arg = roff_getarg(r, &src, ln, &pos);
39803dc5225dSschwarze 		sz = 1;  /* For the terminating NUL. */
39813dc5225dSschwarze 		for (ap = arg; *ap != '\0'; ap++)
39823dc5225dSschwarze 			sz += *ap == '"' ? 4 : 1;
39833dc5225dSschwarze 		ctx->argv[ctx->argc++] = dst = mandoc_malloc(sz);
39843dc5225dSschwarze 		for (ap = arg; *ap != '\0'; ap++) {
3985633c28f2Sschwarze 			if (*ap == '"') {
39863dc5225dSschwarze 				memcpy(dst, "\\(dq", 4);
39873dc5225dSschwarze 				dst += 4;
3988633c28f2Sschwarze 			} else
39893dc5225dSschwarze 				*dst++ = *ap;
3990633c28f2Sschwarze 		}
39913dc5225dSschwarze 		*dst = '\0';
3992e94357f9Sschwarze 		free(arg);
3993e53bb406Sschwarze 	}
3994e53bb406Sschwarze 
39953dc5225dSschwarze 	/* Replace the macro invocation by the macro definition. */
3996633c28f2Sschwarze 
399728687875Sschwarze 	free(buf->buf);
39983dc5225dSschwarze 	buf->buf = mandoc_strdup(r->current_string);
39993dc5225dSschwarze 	buf->sz = strlen(buf->buf) + 1;
4000c65a03d4Sschwarze 	*offs = 0;
4001e53bb406Sschwarze 
400261dc5079Sschwarze 	return buf->buf[buf->sz - 2] == '\n' ?
4003b7f92c5fSschwarze 	    ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND;
4004e53bb406Sschwarze }
4005e53bb406Sschwarze 
40067e291f10Sschwarze /*
40077e291f10Sschwarze  * Calling a high-level macro that was renamed with .rn.
40087e291f10Sschwarze  * r->current_string has already been set up by roff_parse().
40097e291f10Sschwarze  */
4010b7f92c5fSschwarze static int
40117e291f10Sschwarze roff_renamed(ROFF_ARGS)
40127e291f10Sschwarze {
40137e291f10Sschwarze 	char	*nbuf;
40147e291f10Sschwarze 
401507530f0aSschwarze 	buf->sz = mandoc_asprintf(&nbuf, ".%s%s%s", r->current_string,
401607530f0aSschwarze 	    buf->buf[pos] == '\0' ? "" : " ", buf->buf + pos) + 1;
40177e291f10Sschwarze 	free(buf->buf);
40187e291f10Sschwarze 	buf->buf = nbuf;
4019d0365455Sschwarze 	*offs = 0;
40207e291f10Sschwarze 	return ROFF_CONT;
40217e291f10Sschwarze }
40227e291f10Sschwarze 
4023aac18afaSschwarze /*
4024aac18afaSschwarze  * Measure the length in bytes of the roff identifier at *cpp
4025aac18afaSschwarze  * and advance the pointer to the next word.
4026aac18afaSschwarze  */
402759b3009eSschwarze static size_t
40283b5bd91eSschwarze roff_getname(char **cpp, int ln, int pos)
402980364048Sschwarze {
403080364048Sschwarze 	char	 *name, *cp;
4031a72149c5Sschwarze 	int	  namesz, inam, iend;
403280364048Sschwarze 
403380364048Sschwarze 	name = *cpp;
4034aac18afaSschwarze 	if (*name == '\0')
4035526e306bSschwarze 		return 0;
403680364048Sschwarze 
4037aac18afaSschwarze 	/* Advance cp to the byte after the end of the name. */
4038aac18afaSschwarze 
4039a72149c5Sschwarze 	cp = name;
4040a72149c5Sschwarze 	namesz = 0;
4041a72149c5Sschwarze 	for (;;) {
40427ad9cfd6Sschwarze 		if (*cp == '\0')
404359b3009eSschwarze 			break;
40447ad9cfd6Sschwarze 		if (*cp == ' ' || *cp == '\t') {
40457ad9cfd6Sschwarze 			cp++;
40467ad9cfd6Sschwarze 			break;
40477ad9cfd6Sschwarze 		}
4048a72149c5Sschwarze 		if (*cp != '\\') {
4049a72149c5Sschwarze 			if (name + namesz < cp) {
4050a72149c5Sschwarze 				name[namesz] = *cp;
4051a72149c5Sschwarze 				*cp = ' ';
4052a72149c5Sschwarze 			}
4053a72149c5Sschwarze 			namesz++;
4054a72149c5Sschwarze 			cp++;
405580364048Sschwarze 			continue;
4056a72149c5Sschwarze 		}
4057aac18afaSschwarze 		if (cp[1] == '{' || cp[1] == '}')
4058b34deaa1Sschwarze 			break;
4059a72149c5Sschwarze 		if (roff_escape(cp, 0, 0, NULL, &inam,
4060a72149c5Sschwarze 		    NULL, NULL, &iend) != ESCAPE_UNDEF) {
4061a5a5f808Sschwarze 			mandoc_msg(MANDOCERR_NAMESC, ln, pos,
4062a72149c5Sschwarze 			    "%.*s%.*s", namesz, name, iend, cp);
4063a72149c5Sschwarze 			cp += iend;
406459b3009eSschwarze 			break;
406580364048Sschwarze 		}
406680364048Sschwarze 
4067a72149c5Sschwarze 		/*
4068a72149c5Sschwarze 		 * In an identifier, \\, \., \G and so on
4069a72149c5Sschwarze 		 * are reduced to \, ., G and so on,
4070a72149c5Sschwarze 		 * vaguely similar to copy mode.
4071a72149c5Sschwarze 		 */
4072a72149c5Sschwarze 
4073a72149c5Sschwarze 		name[namesz++] = cp[inam];
4074a72149c5Sschwarze 		while (iend--) {
4075a72149c5Sschwarze 			if (cp >= name + namesz)
4076a72149c5Sschwarze 				*cp = ' ';
4077a72149c5Sschwarze 			cp++;
4078a72149c5Sschwarze 		}
4079a72149c5Sschwarze 	}
4080a72149c5Sschwarze 
408180364048Sschwarze 	/* Read past spaces. */
4082aac18afaSschwarze 
4083aac18afaSschwarze 	while (*cp == ' ')
408480364048Sschwarze 		cp++;
408580364048Sschwarze 
408680364048Sschwarze 	*cpp = cp;
4087526e306bSschwarze 	return namesz;
408880364048Sschwarze }
408980364048Sschwarze 
4090e53bb406Sschwarze /*
4091e53bb406Sschwarze  * Store *string into the user-defined string called *name.
4092e53bb406Sschwarze  * To clear an existing entry, call with (*r, *name, NULL, 0).
40930c05b9caSschwarze  * append == 0: replace mode
40940c05b9caSschwarze  * append == 1: single-line append mode
40950c05b9caSschwarze  * append == 2: multiline append mode, append '\n' after each call
4096e53bb406Sschwarze  */
4097769ee804Sschwarze static void
4098e53bb406Sschwarze roff_setstr(struct roff *r, const char *name, const char *string,
40990c05b9caSschwarze 	int append)
4100b49ce91bSschwarze {
410107530f0aSschwarze 	size_t	 namesz;
4102b49ce91bSschwarze 
410307530f0aSschwarze 	namesz = strlen(name);
410407530f0aSschwarze 	roff_setstrn(&r->strtab, name, namesz, string,
41050c05b9caSschwarze 	    string ? strlen(string) : 0, append);
410607530f0aSschwarze 	roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
410782475fc3Sschwarze }
410882475fc3Sschwarze 
410904e980cbSschwarze static void
411004e980cbSschwarze roff_setstrn(struct roffkv **r, const char *name, size_t namesz,
41110c05b9caSschwarze 		const char *string, size_t stringsz, int append)
411204e980cbSschwarze {
411304e980cbSschwarze 	struct roffkv	*n;
411404e980cbSschwarze 	char		*c;
411504e980cbSschwarze 	int		 i;
411604e980cbSschwarze 	size_t		 oldch, newch;
411704e980cbSschwarze 
4118e53bb406Sschwarze 	/* Search for an existing string with the same name. */
411904e980cbSschwarze 	n = *r;
412004e980cbSschwarze 
4121a8590a9aSschwarze 	while (n && (namesz != n->key.sz ||
4122a8590a9aSschwarze 			strncmp(n->key.p, name, namesz)))
4123b49ce91bSschwarze 		n = n->next;
4124769ee804Sschwarze 
4125769ee804Sschwarze 	if (NULL == n) {
4126e53bb406Sschwarze 		/* Create a new string table entry. */
412704e980cbSschwarze 		n = mandoc_malloc(sizeof(struct roffkv));
412804e980cbSschwarze 		n->key.p = mandoc_strndup(name, namesz);
412904e980cbSschwarze 		n->key.sz = namesz;
413004e980cbSschwarze 		n->val.p = NULL;
413104e980cbSschwarze 		n->val.sz = 0;
413204e980cbSschwarze 		n->next = *r;
413304e980cbSschwarze 		*r = n;
41340c05b9caSschwarze 	} else if (0 == append) {
413504e980cbSschwarze 		free(n->val.p);
413604e980cbSschwarze 		n->val.p = NULL;
413704e980cbSschwarze 		n->val.sz = 0;
4138e53bb406Sschwarze 	}
4139769ee804Sschwarze 
4140e53bb406Sschwarze 	if (NULL == string)
4141e53bb406Sschwarze 		return;
4142e53bb406Sschwarze 
4143e53bb406Sschwarze 	/*
4144e53bb406Sschwarze 	 * One additional byte for the '\n' in multiline mode,
4145e53bb406Sschwarze 	 * and one for the terminating '\0'.
4146e53bb406Sschwarze 	 */
41470c05b9caSschwarze 	newch = stringsz + (1 < append ? 2u : 1u);
414804e980cbSschwarze 
414904e980cbSschwarze 	if (NULL == n->val.p) {
415004e980cbSschwarze 		n->val.p = mandoc_malloc(newch);
415104e980cbSschwarze 		*n->val.p = '\0';
4152e53bb406Sschwarze 		oldch = 0;
4153e53bb406Sschwarze 	} else {
415404e980cbSschwarze 		oldch = n->val.sz;
415504e980cbSschwarze 		n->val.p = mandoc_realloc(n->val.p, oldch + newch);
4156e53bb406Sschwarze 	}
4157e53bb406Sschwarze 
4158e53bb406Sschwarze 	/* Skip existing content in the destination buffer. */
415904e980cbSschwarze 	c = n->val.p + (int)oldch;
4160e53bb406Sschwarze 
4161e53bb406Sschwarze 	/* Append new content to the destination buffer. */
416204e980cbSschwarze 	i = 0;
416304e980cbSschwarze 	while (i < (int)stringsz) {
4164e53bb406Sschwarze 		/*
4165e53bb406Sschwarze 		 * Rudimentary roff copy mode:
4166e53bb406Sschwarze 		 * Handle escaped backslashes.
4167e53bb406Sschwarze 		 */
416804e980cbSschwarze 		if ('\\' == string[i] && '\\' == string[i + 1])
416904e980cbSschwarze 			i++;
417004e980cbSschwarze 		*c++ = string[i++];
4171e53bb406Sschwarze 	}
4172e53bb406Sschwarze 
4173e53bb406Sschwarze 	/* Append terminating bytes. */
41740c05b9caSschwarze 	if (1 < append)
4175e53bb406Sschwarze 		*c++ = '\n';
417604e980cbSschwarze 
4177e53bb406Sschwarze 	*c = '\0';
417804e980cbSschwarze 	n->val.sz = (int)(c - n->val.p);
4179b49ce91bSschwarze }
4180b49ce91bSschwarze 
4181769ee804Sschwarze static const char *
41820cdab9e9Sschwarze roff_getstrn(struct roff *r, const char *name, size_t len,
418307530f0aSschwarze     int *deftype)
4184b49ce91bSschwarze {
418504e980cbSschwarze 	const struct roffkv	*n;
41860cdab9e9Sschwarze 	int			 found, i;
418707530f0aSschwarze 	enum roff_tok		 tok;
4188b49ce91bSschwarze 
41890cdab9e9Sschwarze 	found = 0;
419007530f0aSschwarze 	for (n = r->strtab; n != NULL; n = n->next) {
41910cdab9e9Sschwarze 		if (strncmp(name, n->key.p, len) != 0 ||
41920cdab9e9Sschwarze 		    n->key.p[len] != '\0' || n->val.p == NULL)
41930cdab9e9Sschwarze 			continue;
41940cdab9e9Sschwarze 		if (*deftype & ROFFDEF_USER) {
419507530f0aSschwarze 			*deftype = ROFFDEF_USER;
4196526e306bSschwarze 			return n->val.p;
41970cdab9e9Sschwarze 		} else {
41980cdab9e9Sschwarze 			found = 1;
41990cdab9e9Sschwarze 			break;
420007530f0aSschwarze 		}
420107530f0aSschwarze 	}
420207530f0aSschwarze 	for (n = r->rentab; n != NULL; n = n->next) {
42030cdab9e9Sschwarze 		if (strncmp(name, n->key.p, len) != 0 ||
42040cdab9e9Sschwarze 		    n->key.p[len] != '\0' || n->val.p == NULL)
42050cdab9e9Sschwarze 			continue;
42060cdab9e9Sschwarze 		if (*deftype & ROFFDEF_REN) {
420707530f0aSschwarze 			*deftype = ROFFDEF_REN;
420807530f0aSschwarze 			return n->val.p;
42090cdab9e9Sschwarze 		} else {
42100cdab9e9Sschwarze 			found = 1;
42110cdab9e9Sschwarze 			break;
421207530f0aSschwarze 		}
421307530f0aSschwarze 	}
42140cdab9e9Sschwarze 	for (i = 0; i < PREDEFS_MAX; i++) {
42150cdab9e9Sschwarze 		if (strncmp(name, predefs[i].name, len) != 0 ||
42160cdab9e9Sschwarze 		    predefs[i].name[len] != '\0')
42170cdab9e9Sschwarze 			continue;
42180cdab9e9Sschwarze 		if (*deftype & ROFFDEF_PRE) {
42190cdab9e9Sschwarze 			*deftype = ROFFDEF_PRE;
42200cdab9e9Sschwarze 			return predefs[i].str;
42210cdab9e9Sschwarze 		} else {
42220cdab9e9Sschwarze 			found = 1;
42230cdab9e9Sschwarze 			break;
422407530f0aSschwarze 		}
42250cdab9e9Sschwarze 	}
42266b86842eSschwarze 	if (r->man->meta.macroset != MACROSET_MAN) {
422707530f0aSschwarze 		for (tok = MDOC_Dd; tok < MDOC_MAX; tok++) {
42280cdab9e9Sschwarze 			if (strncmp(name, roff_name[tok], len) != 0 ||
42290cdab9e9Sschwarze 			    roff_name[tok][len] != '\0')
42300cdab9e9Sschwarze 				continue;
42310cdab9e9Sschwarze 			if (*deftype & ROFFDEF_STD) {
423207530f0aSschwarze 				*deftype = ROFFDEF_STD;
4233526e306bSschwarze 				return NULL;
42340cdab9e9Sschwarze 			} else {
42350cdab9e9Sschwarze 				found = 1;
42360cdab9e9Sschwarze 				break;
4237b49ce91bSschwarze 			}
423807530f0aSschwarze 		}
423907530f0aSschwarze 	}
42406b86842eSschwarze 	if (r->man->meta.macroset != MACROSET_MDOC) {
424107530f0aSschwarze 		for (tok = MAN_TH; tok < MAN_MAX; tok++) {
42420cdab9e9Sschwarze 			if (strncmp(name, roff_name[tok], len) != 0 ||
42430cdab9e9Sschwarze 			    roff_name[tok][len] != '\0')
42440cdab9e9Sschwarze 				continue;
42450cdab9e9Sschwarze 			if (*deftype & ROFFDEF_STD) {
424607530f0aSschwarze 				*deftype = ROFFDEF_STD;
424707530f0aSschwarze 				return NULL;
42480cdab9e9Sschwarze 			} else {
42490cdab9e9Sschwarze 				found = 1;
42500cdab9e9Sschwarze 				break;
425107530f0aSschwarze 			}
425207530f0aSschwarze 		}
425307530f0aSschwarze 	}
42540cdab9e9Sschwarze 
42550cdab9e9Sschwarze 	if (found == 0 && *deftype != ROFFDEF_ANY) {
42560cdab9e9Sschwarze 		if (*deftype & ROFFDEF_REN) {
42570cdab9e9Sschwarze 			/*
42580cdab9e9Sschwarze 			 * This might still be a request,
42590cdab9e9Sschwarze 			 * so do not treat it as undefined yet.
42600cdab9e9Sschwarze 			 */
42610cdab9e9Sschwarze 			*deftype = ROFFDEF_UNDEF;
42620cdab9e9Sschwarze 			return NULL;
426307530f0aSschwarze 		}
42640cdab9e9Sschwarze 
42650cdab9e9Sschwarze 		/* Using an undefined string defines it to be empty. */
42660cdab9e9Sschwarze 
42670cdab9e9Sschwarze 		roff_setstrn(&r->strtab, name, len, "", 0, 0);
42680cdab9e9Sschwarze 		roff_setstrn(&r->rentab, name, len, NULL, 0, 0);
42690cdab9e9Sschwarze 	}
42700cdab9e9Sschwarze 
427107530f0aSschwarze 	*deftype = 0;
42727e291f10Sschwarze 	return NULL;
42737e291f10Sschwarze }
42747e291f10Sschwarze 
4275769ee804Sschwarze static void
427604e980cbSschwarze roff_freestr(struct roffkv *r)
4277b49ce91bSschwarze {
427804e980cbSschwarze 	struct roffkv	 *n, *nn;
4279b49ce91bSschwarze 
428004e980cbSschwarze 	for (n = r; n; n = nn) {
428104e980cbSschwarze 		free(n->key.p);
428204e980cbSschwarze 		free(n->val.p);
4283b49ce91bSschwarze 		nn = n->next;
4284b49ce91bSschwarze 		free(n);
4285b49ce91bSschwarze 	}
4286b49ce91bSschwarze }
42872791bd1cSschwarze 
4288fa2127f9Sschwarze /* --- accessors and utility functions ------------------------------------ */
4289fa2127f9Sschwarze 
429004e980cbSschwarze /*
429104e980cbSschwarze  * Duplicate an input string, making the appropriate character
429204e980cbSschwarze  * conversations (as stipulated by `tr') along the way.
429304e980cbSschwarze  * Returns a heap-allocated string with all the replacements made.
429404e980cbSschwarze  */
429504e980cbSschwarze char *
429604e980cbSschwarze roff_strdup(const struct roff *r, const char *p)
429704e980cbSschwarze {
429804e980cbSschwarze 	const struct roffkv *cp;
429904e980cbSschwarze 	char		*res;
430004e980cbSschwarze 	const char	*pp;
430104e980cbSschwarze 	size_t		 ssz, sz;
430204e980cbSschwarze 	enum mandoc_esc	 esc;
430304e980cbSschwarze 
430404e980cbSschwarze 	if (NULL == r->xmbtab && NULL == r->xtab)
4305526e306bSschwarze 		return mandoc_strdup(p);
430604e980cbSschwarze 	else if ('\0' == *p)
4307526e306bSschwarze 		return mandoc_strdup("");
430804e980cbSschwarze 
430904e980cbSschwarze 	/*
431004e980cbSschwarze 	 * Step through each character looking for term matches
431104e980cbSschwarze 	 * (remember that a `tr' can be invoked with an escape, which is
431204e980cbSschwarze 	 * a glyph but the escape is multi-character).
431304e980cbSschwarze 	 * We only do this if the character hash has been initialised
431404e980cbSschwarze 	 * and the string is >0 length.
431504e980cbSschwarze 	 */
431604e980cbSschwarze 
431704e980cbSschwarze 	res = NULL;
431804e980cbSschwarze 	ssz = 0;
431904e980cbSschwarze 
432004e980cbSschwarze 	while ('\0' != *p) {
4321972cfc25Sschwarze 		assert((unsigned int)*p < 128);
4322972cfc25Sschwarze 		if ('\\' != *p && r->xtab && r->xtab[(unsigned int)*p].p) {
432304e980cbSschwarze 			sz = r->xtab[(int)*p].sz;
432404e980cbSschwarze 			res = mandoc_realloc(res, ssz + sz + 1);
432504e980cbSschwarze 			memcpy(res + ssz, r->xtab[(int)*p].p, sz);
432604e980cbSschwarze 			ssz += sz;
432704e980cbSschwarze 			p++;
432804e980cbSschwarze 			continue;
432904e980cbSschwarze 		} else if ('\\' != *p) {
433004e980cbSschwarze 			res = mandoc_realloc(res, ssz + 2);
433104e980cbSschwarze 			res[ssz++] = *p++;
433204e980cbSschwarze 			continue;
433304e980cbSschwarze 		}
433404e980cbSschwarze 
433504e980cbSschwarze 		/* Search for term matches. */
433604e980cbSschwarze 		for (cp = r->xmbtab; cp; cp = cp->next)
433704e980cbSschwarze 			if (0 == strncmp(p, cp->key.p, cp->key.sz))
433804e980cbSschwarze 				break;
433904e980cbSschwarze 
434004e980cbSschwarze 		if (NULL != cp) {
434104e980cbSschwarze 			/*
434204e980cbSschwarze 			 * A match has been found.
434304e980cbSschwarze 			 * Append the match to the array and move
434404e980cbSschwarze 			 * forward by its keysize.
434504e980cbSschwarze 			 */
434649aff9f8Sschwarze 			res = mandoc_realloc(res,
434749aff9f8Sschwarze 			    ssz + cp->val.sz + 1);
434804e980cbSschwarze 			memcpy(res + ssz, cp->val.p, cp->val.sz);
434904e980cbSschwarze 			ssz += cp->val.sz;
435004e980cbSschwarze 			p += (int)cp->key.sz;
435104e980cbSschwarze 			continue;
435204e980cbSschwarze 		}
435304e980cbSschwarze 
435404e980cbSschwarze 		/*
435504e980cbSschwarze 		 * Handle escapes carefully: we need to copy
435604e980cbSschwarze 		 * over just the escape itself, or else we might
435704e980cbSschwarze 		 * do replacements within the escape itself.
435804e980cbSschwarze 		 * Make sure to pass along the bogus string.
435904e980cbSschwarze 		 */
436004e980cbSschwarze 		pp = p++;
436104e980cbSschwarze 		esc = mandoc_escape(&p, NULL, NULL);
436204e980cbSschwarze 		if (ESCAPE_ERROR == esc) {
436304e980cbSschwarze 			sz = strlen(pp);
436404e980cbSschwarze 			res = mandoc_realloc(res, ssz + sz + 1);
436504e980cbSschwarze 			memcpy(res + ssz, pp, sz);
436604e980cbSschwarze 			break;
436704e980cbSschwarze 		}
436804e980cbSschwarze 		/*
436904e980cbSschwarze 		 * We bail out on bad escapes.
437004e980cbSschwarze 		 * No need to warn: we already did so when
4371e94357f9Sschwarze 		 * roff_expand() was called.
437204e980cbSschwarze 		 */
437304e980cbSschwarze 		sz = (int)(p - pp);
437404e980cbSschwarze 		res = mandoc_realloc(res, ssz + sz + 1);
437504e980cbSschwarze 		memcpy(res + ssz, pp, sz);
437604e980cbSschwarze 		ssz += sz;
437704e980cbSschwarze 	}
437804e980cbSschwarze 
437904e980cbSschwarze 	res[(int)ssz] = '\0';
4380526e306bSschwarze 	return res;
438104e980cbSschwarze }
43821e6d02e2Sschwarze 
4383f0d7487dSschwarze int
4384f0d7487dSschwarze roff_getformat(const struct roff *r)
4385f0d7487dSschwarze {
4386f0d7487dSschwarze 
4387526e306bSschwarze 	return r->format;
4388f0d7487dSschwarze }
4389f0d7487dSschwarze 
43901e6d02e2Sschwarze /*
43911e6d02e2Sschwarze  * Find out whether a line is a macro line or not.
43921e6d02e2Sschwarze  * If it is, adjust the current position and return one; if it isn't,
43931e6d02e2Sschwarze  * return zero and don't change the current position.
43941e6d02e2Sschwarze  * If the control character has been set with `.cc', then let that grain
43951e6d02e2Sschwarze  * precedence.
4396d9a51c35Sjmc  * This is slightly contrary to groff, where using the non-breaking
43971e6d02e2Sschwarze  * control character when `cc' has been invoked will cause the
43981e6d02e2Sschwarze  * non-breaking macro contents to be printed verbatim.
43991e6d02e2Sschwarze  */
44001e6d02e2Sschwarze int
44011e6d02e2Sschwarze roff_getcontrol(const struct roff *r, const char *cp, int *ppos)
44021e6d02e2Sschwarze {
44031e6d02e2Sschwarze 	int		pos;
44041e6d02e2Sschwarze 
44051e6d02e2Sschwarze 	pos = *ppos;
44061e6d02e2Sschwarze 
4407be477484Sschwarze 	if (r->control != '\0' && cp[pos] == r->control)
44081e6d02e2Sschwarze 		pos++;
4409be477484Sschwarze 	else if (r->control != '\0')
4410526e306bSschwarze 		return 0;
44111e6d02e2Sschwarze 	else if ('\\' == cp[pos] && '.' == cp[pos + 1])
44121e6d02e2Sschwarze 		pos += 2;
44131e6d02e2Sschwarze 	else if ('.' == cp[pos] || '\'' == cp[pos])
44141e6d02e2Sschwarze 		pos++;
44151e6d02e2Sschwarze 	else
4416526e306bSschwarze 		return 0;
44171e6d02e2Sschwarze 
44181e6d02e2Sschwarze 	while (' ' == cp[pos] || '\t' == cp[pos])
44191e6d02e2Sschwarze 		pos++;
44201e6d02e2Sschwarze 
44211e6d02e2Sschwarze 	*ppos = pos;
4422526e306bSschwarze 	return 1;
44231e6d02e2Sschwarze }
4424