]> git.cameronkatri.com Git - mandoc.git/blob - roff.c
Cleaned up presentation with mbuf_putstring & al.
[mandoc.git] / roff.c
1 /* $Id: roff.c,v 1.23 2008/11/30 20:53:34 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdarg.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <time.h>
27
28 #include "libmdocml.h"
29 #include "private.h"
30
31 /* FIXME: warn if Pp occurs before/after Sh etc. (see mdoc.samples). */
32
33 /* FIXME: warn about "X section only" macros. */
34
35 /* FIXME: warn about empty lists. */
36
37 /* FIXME: roff_layout and roff_text have identical-ish lower bodies. */
38
39 /* FIXME: NAME section needs specific elements. */
40
41 #define ROFF_MAXARG 32
42
43 enum roffd {
44 ROFF_ENTER = 0,
45 ROFF_EXIT
46 };
47
48 enum rofftype {
49 ROFF_COMMENT,
50 ROFF_TEXT,
51 ROFF_LAYOUT,
52 ROFF_SPECIAL
53 };
54
55 #define ROFFCALL_ARGS \
56 int tok, struct rofftree *tree, \
57 char *argv[], enum roffd type
58
59 struct rofftree;
60
61 struct rofftok {
62 int (*cb)(ROFFCALL_ARGS); /* Callback. */
63 const int *args; /* Args (or NULL). */
64 const int *parents;
65 const int *children;
66 int ctx;
67 enum rofftype type; /* Type of macro. */
68 int flags;
69 #define ROFF_PARSED (1 << 0) /* "Parsed". */
70 #define ROFF_CALLABLE (1 << 1) /* "Callable". */
71 #define ROFF_SHALLOW (1 << 2) /* Nesting block. */
72 };
73
74 struct roffarg {
75 int flags;
76 #define ROFF_VALUE (1 << 0) /* Has a value. */
77 };
78
79 struct roffnode {
80 int tok; /* Token id. */
81 struct roffnode *parent; /* Parent (or NULL). */
82 };
83
84 struct rofftree {
85 struct roffnode *last; /* Last parsed node. */
86 char *cur;
87
88 time_t date; /* `Dd' results. */
89 char os[64]; /* `Os' results. */
90 char title[64]; /* `Dt' results. */
91 char section[64]; /* `Dt' results. */
92 char volume[64]; /* `Dt' results. */
93
94 int state;
95 #define ROFF_PRELUDE (1 << 1) /* In roff prelude. */
96 #define ROFF_PRELUDE_Os (1 << 2) /* `Os' is parsed. */
97 #define ROFF_PRELUDE_Dt (1 << 3) /* `Dt' is parsed. */
98 #define ROFF_PRELUDE_Dd (1 << 4) /* `Dd' is parsed. */
99 #define ROFF_BODY (1 << 5) /* In roff body. */
100
101 struct roffcb cb;
102 void *arg;
103 };
104
105 static int roff_Dd(ROFFCALL_ARGS);
106 static int roff_Dt(ROFFCALL_ARGS);
107 static int roff_Os(ROFFCALL_ARGS);
108
109 static int roff_layout(ROFFCALL_ARGS);
110 static int roff_text(ROFFCALL_ARGS);
111 static int roff_comment(ROFFCALL_ARGS);
112 static int roff_close(ROFFCALL_ARGS);
113 static int roff_special(ROFFCALL_ARGS);
114
115 static struct roffnode *roffnode_new(int, struct rofftree *);
116 static void roffnode_free(struct rofftree *);
117
118 static void roff_warn(const struct rofftree *,
119 const char *, char *, ...);
120 static void roff_err(const struct rofftree *,
121 const char *, char *, ...);
122
123 static int roffscan(int, const int *);
124 static int rofffindtok(const char *);
125 static int rofffindarg(const char *);
126 static int rofffindcallable(const char *);
127 static int roffargs(const struct rofftree *,
128 int, char *, char **);
129 static int roffargok(int, int);
130 static int roffnextopt(const struct rofftree *,
131 int, char ***, char **);
132 static int roffparse(struct rofftree *, char *);
133 static int textparse(const struct rofftree *, char *);
134
135
136 static const int roffarg_An[] = { ROFF_Split, ROFF_Nosplit,
137 ROFF_ARGMAX };
138 static const int roffarg_Bd[] = { ROFF_Ragged, ROFF_Unfilled,
139 ROFF_Literal, ROFF_File, ROFF_Offset, ROFF_Filled,
140 ROFF_Compact, ROFF_ARGMAX };
141 static const int roffarg_Bk[] = { ROFF_Words, ROFF_ARGMAX };
142 static const int roffarg_Ex[] = { ROFF_Std, ROFF_ARGMAX };
143 static const int roffarg_Rv[] = { ROFF_Std, ROFF_ARGMAX };
144 static const int roffarg_Bl[] = { ROFF_Bullet, ROFF_Dash,
145 ROFF_Hyphen, ROFF_Item, ROFF_Enum, ROFF_Tag, ROFF_Diag,
146 ROFF_Hang, ROFF_Ohang, ROFF_Inset, ROFF_Column, ROFF_Offset,
147 ROFF_Width, ROFF_Compact, ROFF_ARGMAX };
148 static const int roffarg_St[] = {
149 ROFF_p1003_1_88, ROFF_p1003_1_90, ROFF_p1003_1_96,
150 ROFF_p1003_1_2001, ROFF_p1003_1_2004, ROFF_p1003_1,
151 ROFF_p1003_1b, ROFF_p1003_1b_93, ROFF_p1003_1c_95,
152 ROFF_p1003_1g_2000, ROFF_p1003_2_92, ROFF_p1387_2_95,
153 ROFF_p1003_2, ROFF_p1387_2, ROFF_isoC_90, ROFF_isoC_amd1,
154 ROFF_isoC_tcor1, ROFF_isoC_tcor2, ROFF_isoC_99, ROFF_ansiC,
155 ROFF_ansiC_89, ROFF_ansiC_99, ROFF_ieee754, ROFF_iso8802_3,
156 ROFF_xpg3, ROFF_xpg4, ROFF_xpg4_2, ROFF_xpg4_3, ROFF_xbd5,
157 ROFF_xcu5, ROFF_xsh5, ROFF_xns5, ROFF_xns5_2d2_0,
158 ROFF_xcurses4_2, ROFF_susv2, ROFF_susv3, ROFF_svid4,
159 ROFF_ARGMAX };
160
161 static const int roffchild_Bl[] = { ROFF_It, ROFF_El, ROFF_MAX };
162 static const int roffchild_Fo[] = { ROFF_Fa, ROFF_Fc, ROFF_MAX };
163 static const int roffchild_Oo[] = { ROFF_Op, ROFF_Oc, ROFF_MAX };
164 static const int roffchild_Rs[] = { ROFF_Re, ROFF__A, ROFF__B,
165 ROFF__D, ROFF__I, ROFF__J, ROFF__N, ROFF__O, ROFF__P,
166 ROFF__R, ROFF__T, ROFF__V, ROFF_MAX };
167
168 static const int roffparent_El[] = { ROFF_Bl, ROFF_It, ROFF_MAX };
169 static const int roffparent_Fc[] = { ROFF_Fo, ROFF_Fa, ROFF_MAX };
170 static const int roffparent_Oc[] = { ROFF_Oo, ROFF_Oc, ROFF_MAX };
171 static const int roffparent_It[] = { ROFF_Bl, ROFF_It, ROFF_MAX };
172 static const int roffparent_Re[] = { ROFF_Rs, ROFF_MAX };
173
174 /* Table of all known tokens. */
175 static const struct rofftok tokens[ROFF_MAX] = {
176 {roff_comment, NULL, NULL, NULL, 0, ROFF_COMMENT, 0 }, /* \" */
177 { roff_Dd, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Dd */
178 { roff_Dt, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Dt */
179 { roff_Os, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Os */
180 { roff_layout, NULL, NULL, NULL, ROFF_Sh, ROFF_LAYOUT, 0 }, /* Sh */
181 { roff_layout, NULL, NULL, NULL, ROFF_Ss, ROFF_LAYOUT, 0 }, /* Ss */
182 { roff_text, NULL, NULL, NULL, ROFF_Pp, ROFF_TEXT, 0 }, /* Pp */
183 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* D1 */
184 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Dl */
185 { roff_layout, roffarg_Bd, NULL, NULL, 0, ROFF_LAYOUT, 0 }, /* Bd */
186 { roff_close, NULL, NULL, NULL, ROFF_Bd, ROFF_LAYOUT, 0 }, /* Ed */
187 { roff_layout, roffarg_Bl, NULL, roffchild_Bl, 0, ROFF_LAYOUT, 0 }, /* Bl */
188 { roff_close, NULL, roffparent_El, NULL, ROFF_Bl, ROFF_LAYOUT, 0 }, /* El */
189 { roff_layout, NULL, roffparent_It, NULL, ROFF_It, ROFF_LAYOUT, ROFF_PARSED | ROFF_SHALLOW }, /* It */
190 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ad */ /* FIXME */
191 { roff_text, roffarg_An, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* An */
192 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ar */
193 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Cd */ /* XXX man.4 only */
194 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Cm */
195 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dv */ /* XXX needs arg */
196 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Er */ /* XXX needs arg */
197 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ev */ /* XXX needs arg */
198 { roff_text, roffarg_Ex, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Ex */
199 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fa */ /* XXX needs arg */
200 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Fd */
201 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fl */
202 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Fn */ /* XXX needs arg */ /* FIXME */
203 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ft */
204 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ic */ /* XXX needs arg */
205 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* In */
206 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Li */
207 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Nd */
208 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Nm */ /* FIXME */
209 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Op */
210 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Ot */ /* XXX deprecated */
211 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pa */
212 { roff_text, roffarg_Rv, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Rv */
213 { roff_text, roffarg_St, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* St */
214 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Va */
215 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Vt */ /* XXX needs arg */
216 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xr */ /* XXX needs arg */
217 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %A */
218 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %B */
219 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %D */
220 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %I */
221 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE}, /* %J */
222 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %N */
223 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %O */
224 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %P */
225 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %R */
226 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* %T */
227 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* %V */
228 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ac */
229 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ao */
230 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Aq */
231 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* At */ /* XXX at most 2 args */
232 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bc */
233 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Bf */ /* FIXME */
234 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bo */
235 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Bq */
236 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bsx */
237 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Bx */
238 {roff_special, NULL, NULL, NULL, 0, ROFF_SPECIAL, 0 }, /* Db */
239 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dc */
240 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Do */
241 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Dq */
242 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ec */
243 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, 0 }, /* Ef */ /* FIXME */
244 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Em */ /* XXX needs arg */
245 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Eo */
246 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Fx */
247 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ms */
248 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* No */
249 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ns */
250 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Nx */
251 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ox */
252 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pc */
253 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Pf */
254 { roff_text, NULL, NULL, NULL, 0, ROFF_LAYOUT, ROFF_PARSED | ROFF_CALLABLE }, /* Po */
255 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Pq */
256 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qc */
257 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Ql */
258 { roff_layout, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qo */
259 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Qq */
260 { roff_close, NULL, roffparent_Re, NULL, ROFF_Rs, ROFF_LAYOUT, 0 }, /* Re */
261 { roff_layout, NULL, NULL, roffchild_Rs, 0, ROFF_LAYOUT, 0 }, /* Rs */
262 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sc */
263 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* So */
264 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sq */
265 {roff_special, NULL, NULL, NULL, 0, ROFF_SPECIAL, 0 }, /* Sm */
266 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sx */
267 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Sy */
268 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Tn */
269 { roff_text, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED }, /* Ux */
270 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xc */
271 { NULL, NULL, NULL, NULL, 0, ROFF_TEXT, ROFF_PARSED | ROFF_CALLABLE }, /* Xo */
272 { roff_layout, NULL, NULL, roffchild_Fo, 0, ROFF_LAYOUT, 0 }, /* Fo */
273 { roff_close, NULL, roffparent_Fc, NULL, ROFF_Fo, ROFF_LAYOUT, 0 }, /* Fc */
274 { roff_layout, NULL, NULL, roffchild_Oo, 0, ROFF_LAYOUT, 0 }, /* Oo */
275 { roff_close, NULL, roffparent_Oc, NULL, ROFF_Oo, ROFF_LAYOUT, 0 }, /* Oc */
276 { roff_layout, roffarg_Bk, NULL, NULL, 0, ROFF_LAYOUT, 0 }, /* Bk */
277 { roff_close, NULL, NULL, NULL, ROFF_Bk, ROFF_LAYOUT, 0 }, /* Ek */
278 };
279
280 /* Table of all known token arguments. */
281 static const int tokenargs[ROFF_ARGMAX] = {
282 0, 0, 0, 0,
283 0, ROFF_VALUE, ROFF_VALUE, 0,
284 0, 0, 0, 0,
285 0, 0, 0, 0,
286 0, 0, ROFF_VALUE, 0,
287 0, 0, 0, 0,
288 0, 0, 0, 0,
289 0, 0, 0, 0,
290 0, 0, 0, 0,
291 0, 0, 0, 0,
292 0, 0, 0, 0,
293 0, 0, 0, 0,
294 0, 0, 0, 0,
295 0, 0, 0, 0,
296 0, 0, 0, 0,
297 };
298
299 const char *const toknamesp[ROFF_MAX] = {
300 "\\\"", "Dd", "Dt", "Os",
301 "Sh", "Ss", "Pp", "D1",
302 "Dl", "Bd", "Ed", "Bl",
303 "El", "It", "Ad", "An",
304 "Ar", "Cd", "Cm", "Dv",
305 "Er", "Ev", "Ex", "Fa",
306 "Fd", "Fl", "Fn", "Ft",
307 "Ic", "In", "Li", "Nd",
308 "Nm", "Op", "Ot", "Pa",
309 "Rv", "St", "Va", "Vt",
310 /* LINTED */
311 "Xr", "\%A", "\%B", "\%D",
312 /* LINTED */
313 "\%I", "\%J", "\%N", "\%O",
314 /* LINTED */
315 "\%P", "\%R", "\%T", "\%V",
316 "Ac", "Ao", "Aq", "At",
317 "Bc", "Bf", "Bo", "Bq",
318 "Bsx", "Bx", "Db", "Dc",
319 "Do", "Dq", "Ec", "Ef",
320 "Em", "Eo", "Fx", "Ms",
321 "No", "Ns", "Nx", "Ox",
322 "Pc", "Pf", "Po", "Pq",
323 "Qc", "Ql", "Qo", "Qq",
324 "Re", "Rs", "Sc", "So",
325 "Sq", "Sm", "Sx", "Sy",
326 "Tn", "Ux", "Xc", "Xo",
327 "Fo", "Fc", "Oo", "Oc",
328 "Bk", "Ek",
329 };
330
331 const char *const tokargnamesp[ROFF_ARGMAX] = {
332 "split", "nosplit", "ragged",
333 "unfilled", "literal", "file",
334 "offset", "bullet", "dash",
335 "hyphen", "item", "enum",
336 "tag", "diag", "hang",
337 "ohang", "inset", "column",
338 "width", "compact", "std",
339 "p1003.1-88", "p1003.1-90", "p1003.1-96",
340 "p1003.1-2001", "p1003.1-2004", "p1003.1",
341 "p1003.1b", "p1003.1b-93", "p1003.1c-95",
342 "p1003.1g-2000", "p1003.2-92", "p1387.2-95",
343 "p1003.2", "p1387.2", "isoC-90",
344 "isoC-amd1", "isoC-tcor1", "isoC-tcor2",
345 "isoC-99", "ansiC", "ansiC-89",
346 "ansiC-99", "ieee754", "iso8802-3",
347 "xpg3", "xpg4", "xpg4.2",
348 "xpg4.3", "xbd5", "xcu5",
349 "xsh5", "xns5", "xns5.2d2.0",
350 "xcurses4.2", "susv2", "susv3",
351 "svid4", "filled", "words",
352 };
353
354 const char *const *toknames = toknamesp;
355 const char *const *tokargnames = tokargnamesp;
356
357
358 int
359 roff_free(struct rofftree *tree, int flush)
360 {
361 int error, t;
362 struct roffnode *n;
363
364 error = 0;
365
366 if ( ! flush)
367 goto end;
368
369 error = 1;
370
371 if (ROFF_PRELUDE & tree->state) {
372 roff_warn(tree, NULL, "prelude never finished");
373 goto end;
374 }
375
376 for (n = tree->last; n->parent; n = n->parent) {
377 if (0 != tokens[n->tok].ctx)
378 continue;
379 roff_warn(tree, NULL, "closing explicit scope `%s'",
380 toknames[n->tok]);
381 goto end;
382 }
383
384 while (tree->last) {
385 t = tree->last->tok;
386 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))
387 goto end;
388 }
389
390 error = 0;
391
392 end:
393
394 while (tree->last)
395 roffnode_free(tree);
396
397 free(tree);
398
399 return(error ? 0 : 1);
400 }
401
402
403 struct rofftree *
404 roff_alloc(const struct roffcb *cb, void *args)
405 {
406 struct rofftree *tree;
407
408 assert(args);
409 assert(cb);
410
411 if (NULL == (tree = calloc(1, sizeof(struct rofftree))))
412 err(1, "calloc");
413
414 tree->state = ROFF_PRELUDE;
415 tree->arg = args;
416
417 (void)memcpy(&tree->cb, cb, sizeof(struct roffcb));
418
419 return(tree);
420 }
421
422
423 int
424 roff_engine(struct rofftree *tree, char *buf)
425 {
426
427 tree->cur = buf;
428 assert(buf);
429
430 if (0 == *buf) {
431 roff_warn(tree, buf, "blank line");
432 return(0);
433 } else if ('.' != *buf)
434 return(textparse(tree, buf));
435
436 return(roffparse(tree, buf));
437 }
438
439
440 static int
441 textparse(const struct rofftree *tree, char *buf)
442 {
443
444 return((*tree->cb.roffdata)(tree->arg, 1, buf));
445 }
446
447
448 static int
449 roffargs(const struct rofftree *tree,
450 int tok, char *buf, char **argv)
451 {
452 int i;
453 char *p;
454
455 assert(tok >= 0 && tok < ROFF_MAX);
456 assert('.' == *buf);
457
458 p = buf;
459
460 /* LINTED */
461 for (i = 0; *buf && i < ROFF_MAXARG; i++) {
462 if ('\"' == *buf) {
463 argv[i] = ++buf;
464 while (*buf && '\"' != *buf)
465 buf++;
466 if (0 == *buf) {
467 roff_err(tree, argv[i], "unclosed "
468 "quote in argument "
469 "list for `%s'",
470 toknames[tok]);
471 return(0);
472 }
473 } else {
474 argv[i] = buf++;
475 while (*buf && ! isspace(*buf))
476 buf++;
477 if (0 == *buf)
478 continue;
479 }
480 *buf++ = 0;
481 while (*buf && isspace(*buf))
482 buf++;
483 }
484
485 assert(i > 0);
486 if (ROFF_MAXARG == i && *buf) {
487 roff_err(tree, p, "too many arguments for `%s'", toknames
488 [tok]);
489 return(0);
490 }
491
492 argv[i] = NULL;
493 return(1);
494 }
495
496
497 /* XXX */
498 static int
499 roffscan(int tok, const int *tokv)
500 {
501
502 if (NULL == tokv)
503 return(1);
504
505 for ( ; ROFF_MAX != *tokv; tokv++)
506 if (tok == *tokv)
507 return(1);
508
509 return(0);
510 }
511
512
513 static int
514 roffparse(struct rofftree *tree, char *buf)
515 {
516 int tok, t;
517 struct roffnode *n;
518 char *argv[ROFF_MAXARG];
519 char **argvp;
520
521 if (ROFF_MAX == (tok = rofffindtok(buf + 1))) {
522 roff_err(tree, buf + 1, "bogus line macro");
523 return(0);
524 } else if (NULL == tokens[tok].cb) {
525 roff_err(tree, buf + 1, "unsupported macro `%s'",
526 toknames[tok]);
527 return(0);
528 } else if (ROFF_COMMENT == tokens[tok].type)
529 return(1);
530
531 if ( ! roffargs(tree, tok, buf, argv))
532 return(0);
533
534 argvp = (char **)argv;
535
536 /*
537 * Prelude macros break some assumptions, so branch now.
538 */
539
540 if (ROFF_PRELUDE & tree->state) {
541 assert(NULL == tree->last);
542 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
543 } else
544 assert(tree->last);
545
546 assert(ROFF_BODY & tree->state);
547
548 /*
549 * First check that our possible parents and parent's possible
550 * children are satisfied.
551 */
552
553 if ( ! roffscan(tree->last->tok, tokens[tok].parents)) {
554 roff_err(tree, *argvp, "`%s' has invalid parent `%s'",
555 toknames[tok],
556 toknames[tree->last->tok]);
557 return(0);
558 }
559
560 if ( ! roffscan(tok, tokens[tree->last->tok].children)) {
561 roff_err(tree, *argvp, "`%s' is invalid child of `%s'",
562 toknames[tok],
563 toknames[tree->last->tok]);
564 return(0);
565 }
566
567 /*
568 * Branch if we're not a layout token.
569 */
570
571 if (ROFF_LAYOUT != tokens[tok].type)
572 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
573
574 /*
575 * Check our scope rules.
576 */
577
578 if (0 == tokens[tok].ctx)
579 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
580
581 /*
582 * First consider implicit-end tags, like as follows:
583 * .Sh SECTION 1
584 * .Sh SECTION 2
585 * In this, we want to close the scope of the NAME section. If
586 * there's an intermediary implicit-end tag, such as
587 * .Sh SECTION 1
588 * .Ss Subsection 1
589 * .Sh SECTION 2
590 * then it must be closed as well.
591 */
592
593 if (tok == tokens[tok].ctx) {
594 /*
595 * First search up to the point where we must close.
596 * If one doesn't exist, then we can open a new scope.
597 */
598
599 for (n = tree->last; n; n = n->parent) {
600 assert(0 == tokens[n->tok].ctx ||
601 n->tok == tokens[n->tok].ctx);
602 if (n->tok == tok)
603 break;
604 if (ROFF_SHALLOW & tokens[tok].flags) {
605 n = NULL;
606 break;
607 }
608 }
609
610 /*
611 * Create a new scope, as no previous one exists to
612 * close out.
613 */
614
615 if (NULL == n)
616 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
617
618 /*
619 * Close out all intermediary scoped blocks, then hang
620 * the current scope from our predecessor's parent.
621 */
622
623 do {
624 t = tree->last->tok;
625 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))
626 return(0);
627 } while (t != tok);
628
629 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
630 }
631
632 /*
633 * Now consider explicit-end tags, where we want to close back
634 * to a specific tag. Example:
635 * .Bl
636 * .It Item.
637 * .El
638 * In this, the `El' tag closes out the scope of `Bl'.
639 */
640
641 assert(tree->last);
642 assert(tok != tokens[tok].ctx && 0 != tokens[tok].ctx);
643
644 /* LINTED */
645 do {
646 t = tree->last->tok;
647 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))
648 return(0);
649 } while (t != tokens[tok].ctx);
650
651 assert(tree->last);
652 return(1);
653 }
654
655
656 static int
657 rofffindarg(const char *name)
658 {
659 size_t i;
660
661 /* FIXME: use a table, this is slow but ok for now. */
662
663 /* LINTED */
664 for (i = 0; i < ROFF_ARGMAX; i++)
665 /* LINTED */
666 if (0 == strcmp(name, tokargnames[i]))
667 return((int)i);
668
669 return(ROFF_ARGMAX);
670 }
671
672
673 static int
674 rofffindtok(const char *buf)
675 {
676 char token[4];
677 int i;
678
679 for (i = 0; *buf && ! isspace(*buf) && i < 3; i++, buf++)
680 token[i] = *buf;
681
682 if (i == 3)
683 return(ROFF_MAX);
684
685 token[i] = 0;
686
687 /* FIXME: use a table, this is slow but ok for now. */
688
689 /* LINTED */
690 for (i = 0; i < ROFF_MAX; i++)
691 /* LINTED */
692 if (0 == strcmp(toknames[i], token))
693 return((int)i);
694
695 return(ROFF_MAX);
696 }
697
698
699 static int
700 roffispunct(const char *p)
701 {
702
703 if (0 == *p)
704 return(0);
705 if (0 != *(p + 1))
706 return(0);
707
708 switch (*p) {
709 case('{'):
710 /* FALLTHROUGH */
711 case('.'):
712 /* FALLTHROUGH */
713 case(','):
714 /* FALLTHROUGH */
715 case(';'):
716 /* FALLTHROUGH */
717 case(':'):
718 /* FALLTHROUGH */
719 case('?'):
720 /* FALLTHROUGH */
721 case('!'):
722 /* FALLTHROUGH */
723 case('('):
724 /* FALLTHROUGH */
725 case(')'):
726 /* FALLTHROUGH */
727 case('['):
728 /* FALLTHROUGH */
729 case(']'):
730 /* FALLTHROUGH */
731 case('}'):
732 return(1);
733 default:
734 break;
735 }
736
737 return(0);
738 }
739
740
741 static int
742 rofffindcallable(const char *name)
743 {
744 int c;
745
746 if (ROFF_MAX == (c = rofffindtok(name)))
747 return(ROFF_MAX);
748 assert(c >= 0 && c < ROFF_MAX);
749 return(ROFF_CALLABLE & tokens[c].flags ? c : ROFF_MAX);
750 }
751
752
753 static struct roffnode *
754 roffnode_new(int tokid, struct rofftree *tree)
755 {
756 struct roffnode *p;
757
758 if (NULL == (p = malloc(sizeof(struct roffnode))))
759 err(1, "malloc");
760
761 p->tok = tokid;
762 p->parent = tree->last;
763 tree->last = p;
764
765 return(p);
766 }
767
768
769 static int
770 roffargok(int tokid, int argid)
771 {
772 const int *c;
773
774 if (NULL == (c = tokens[tokid].args))
775 return(0);
776
777 for ( ; ROFF_ARGMAX != *c; c++)
778 if (argid == *c)
779 return(1);
780
781 return(0);
782 }
783
784
785 static void
786 roffnode_free(struct rofftree *tree)
787 {
788 struct roffnode *p;
789
790 assert(tree->last);
791
792 p = tree->last;
793 tree->last = tree->last->parent;
794 free(p);
795 }
796
797
798 static int
799 roffnextopt(const struct rofftree *tree, int tok,
800 char ***in, char **val)
801 {
802 char *arg, **argv;
803 int v;
804
805 *val = NULL;
806 argv = *in;
807 assert(argv);
808
809 if (NULL == (arg = *argv))
810 return(-1);
811 if ('-' != *arg)
812 return(-1);
813
814 if (ROFF_ARGMAX == (v = rofffindarg(arg + 1))) {
815 roff_warn(tree, arg, "argument-like parameter `%s' to "
816 "`%s'", &arg[1], toknames[tok]);
817 return(-1);
818 }
819
820 if ( ! roffargok(tok, v)) {
821 roff_warn(tree, arg, "invalid argument parameter `%s' to "
822 "`%s'", tokargnames[v], toknames[tok]);
823 return(-1);
824 }
825
826 if ( ! (ROFF_VALUE & tokenargs[v]))
827 return(v);
828
829 *in = ++argv;
830
831 if (NULL == *argv) {
832 roff_err(tree, arg, "empty value of `%s' for `%s'",
833 tokargnames[v], toknames[tok]);
834 return(ROFF_ARGMAX);
835 }
836
837 return(v);
838 }
839
840
841 /* ARGSUSED */
842 static int
843 roff_Dd(ROFFCALL_ARGS)
844 {
845
846 if (ROFF_BODY & tree->state) {
847 assert( ! (ROFF_PRELUDE & tree->state));
848 assert(ROFF_PRELUDE_Dd & tree->state);
849 return(roff_text(tok, tree, argv, type));
850 }
851
852 assert(ROFF_PRELUDE & tree->state);
853 assert( ! (ROFF_BODY & tree->state));
854
855 if (ROFF_PRELUDE_Dd & tree->state) {
856 roff_err(tree, *argv, "repeated `Dd' in prelude");
857 return(0);
858 } else if (ROFF_PRELUDE_Dt & tree->state) {
859 roff_err(tree, *argv, "out-of-order `Dd' in prelude");
860 return(0);
861 }
862
863 /* TODO: parse date. */
864
865 assert(NULL == tree->last);
866 tree->state |= ROFF_PRELUDE_Dd;
867
868 return(1);
869 }
870
871
872 /* ARGSUSED */
873 static int
874 roff_Dt(ROFFCALL_ARGS)
875 {
876
877 if (ROFF_BODY & tree->state) {
878 assert( ! (ROFF_PRELUDE & tree->state));
879 assert(ROFF_PRELUDE_Dt & tree->state);
880 return(roff_text(tok, tree, argv, type));
881 }
882
883 assert(ROFF_PRELUDE & tree->state);
884 assert( ! (ROFF_BODY & tree->state));
885
886 if ( ! (ROFF_PRELUDE_Dd & tree->state)) {
887 roff_err(tree, *argv, "out-of-order `Dt' in prelude");
888 return(0);
889 } else if (ROFF_PRELUDE_Dt & tree->state) {
890 roff_err(tree, *argv, "repeated `Dt' in prelude");
891 return(0);
892 }
893
894 /* TODO: parse date. */
895
896 assert(NULL == tree->last);
897 tree->state |= ROFF_PRELUDE_Dt;
898
899 return(1);
900 }
901
902
903 /* ARGSUSED */
904 static int
905 roff_Os(ROFFCALL_ARGS)
906 {
907
908 if (ROFF_EXIT == type) {
909 roffnode_free(tree);
910 return((*tree->cb.rofftail)(tree->arg));
911 } else if (ROFF_BODY & tree->state) {
912 assert( ! (ROFF_PRELUDE & tree->state));
913 assert(ROFF_PRELUDE_Os & tree->state);
914 return(roff_text(tok, tree, argv, type));
915 }
916
917 assert(ROFF_PRELUDE & tree->state);
918 if ( ! (ROFF_PRELUDE_Dt & tree->state) ||
919 ! (ROFF_PRELUDE_Dd & tree->state)) {
920 roff_err(tree, *argv, "out-of-order `Os' in prelude");
921 return(0);
922 }
923
924 /* TODO: extract OS. */
925
926 tree->state |= ROFF_PRELUDE_Os;
927 tree->state &= ~ROFF_PRELUDE;
928 tree->state |= ROFF_BODY;
929
930 assert(NULL == tree->last);
931
932 if (NULL == roffnode_new(tok, tree))
933 return(0);
934
935 return((*tree->cb.roffhead)(tree->arg));
936 }
937
938
939 /* ARGSUSED */
940 static int
941 roff_layout(ROFFCALL_ARGS)
942 {
943 int i, c, argcp[ROFF_MAXARG];
944 char *v, *argvp[ROFF_MAXARG];
945
946 if (ROFF_PRELUDE & tree->state) {
947 roff_err(tree, *argv, "`%s' disallowed in prelude",
948 toknames[tok]);
949 return(0);
950 }
951
952 if (ROFF_EXIT == type) {
953 roffnode_free(tree);
954 return((*tree->cb.roffblkout)(tree->arg, tok));
955 }
956
957 i = 0;
958 argv++;
959
960 while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {
961 if (ROFF_ARGMAX == c)
962 return(0);
963
964 argcp[i] = c;
965 argvp[i] = v;
966 i++;
967 argv++;
968 }
969
970 argcp[i] = ROFF_ARGMAX;
971 argvp[i] = NULL;
972
973 if (NULL == roffnode_new(tok, tree))
974 return(0);
975
976 if ( ! (*tree->cb.roffblkin)(tree->arg, tok, argcp, argvp))
977 return(0);
978
979 if (NULL == *argv)
980 return(1);
981
982 if ( ! (*tree->cb.roffin)(tree->arg, tok, 0, argcp, argvp))
983 return(0);
984
985 if ( ! (ROFF_PARSED & tokens[tok].flags)) {
986 i = 0;
987 while (*argv) {
988 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
989 return(0);
990 i = 1;
991 }
992 return((*tree->cb.roffout)(tree->arg, tok));
993 }
994
995 i = 0;
996 while (*argv) {
997 if (ROFF_MAX != (c = rofffindcallable(*argv))) {
998 if (NULL == tokens[c].cb) {
999 roff_err(tree, *argv, "unsupported "
1000 "macro `%s'",
1001 toknames[c]);
1002 return(0);
1003 }
1004 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))
1005 return(0);
1006 break;
1007 }
1008
1009 assert(tree->arg);
1010 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1011 return(0);
1012 i = 1;
1013 }
1014
1015 /*
1016 * If we're the first parser (*argv == tree->cur) then purge out
1017 * any additional punctuation, should there be any remaining at
1018 * the end of line.
1019 */
1020
1021 if (ROFF_PARSED & tokens[tok].flags && *argv) {
1022 i = 0;
1023 while (argv[i])
1024 i++;
1025
1026 assert(i > 0);
1027 if ( ! roffispunct(argv[--i]))
1028 return((*tree->cb.roffout)(tree->arg, tok));
1029
1030 while (i >= 0 && roffispunct(argv[i]))
1031 i--;
1032
1033 assert(0 != i);
1034 i++;
1035
1036 /* LINTED */
1037 while (argv[i])
1038 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))
1039 return(0);
1040 }
1041
1042 return((*tree->cb.roffout)(tree->arg, tok));
1043 }
1044
1045
1046 /* ARGSUSED */
1047 static int
1048 roff_text(ROFFCALL_ARGS)
1049 {
1050 int i, j, first, c, argcp[ROFF_MAXARG];
1051 char *v, *argvp[ROFF_MAXARG];
1052
1053 if (ROFF_PRELUDE & tree->state) {
1054 roff_err(tree, *argv, "`%s' disallowed in prelude",
1055 toknames[tok]);
1056 return(0);
1057 }
1058
1059 /* FIXME: breaks if passed from roff_layout. */
1060 first = *argv == tree->cur;
1061
1062 i = 0;
1063 argv++;
1064
1065 while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {
1066 if (ROFF_ARGMAX == c)
1067 return(0);
1068
1069 argcp[i] = c;
1070 argvp[i] = v;
1071 i++;
1072 argv++;
1073 }
1074
1075 argcp[i] = ROFF_ARGMAX;
1076 argvp[i] = NULL;
1077
1078 if ( ! (*tree->cb.roffin)(tree->arg, tok, 1, argcp, argvp))
1079 return(0);
1080
1081 if ( ! (ROFF_PARSED & tokens[tok].flags)) {
1082 i = 0;
1083 while (*argv) {
1084 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1085 return(0);
1086 i = 1;
1087 }
1088 return((*tree->cb.roffout)(tree->arg, tok));
1089 }
1090
1091 i = 0;
1092 while (*argv) {
1093 if (ROFF_MAX == (c = rofffindcallable(*argv))) {
1094 /*
1095 * If all that remains is roff punctuation, then
1096 * close out our scope and return.
1097 */
1098 if (roffispunct(*argv)) {
1099 for (j = 0; argv[j]; j++)
1100 if ( ! roffispunct(argv[j]))
1101 break;
1102 if (NULL == argv[j])
1103 break;
1104 i = 1;
1105 }
1106
1107 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1108 return(0);
1109
1110 i = 1;
1111 continue;
1112 }
1113
1114 /*
1115 * A sub-command has been found. Execute it and
1116 * discontinue parsing for arguments.
1117 */
1118
1119 if (NULL == tokens[c].cb) {
1120 roff_err(tree, *argv, "unsupported macro `%s'",
1121 toknames[c]);
1122 return(0);
1123 }
1124
1125 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))
1126 return(0);
1127
1128 break;
1129 }
1130
1131 if ( ! (*tree->cb.roffout)(tree->arg, tok))
1132 return(0);
1133
1134 /*
1135 * If we're the first parser (*argv == tree->cur) then purge out
1136 * any additional punctuation, should there be any remaining at
1137 * the end of line.
1138 */
1139
1140 if (first && *argv) {
1141 i = 0;
1142 while (argv[i])
1143 i++;
1144
1145 assert(i > 0);
1146 if ( ! roffispunct(argv[--i]))
1147 return(1);
1148
1149 while (i >= 0 && roffispunct(argv[i]))
1150 i--;
1151
1152 assert(0 != i);
1153 i++;
1154
1155 /* LINTED */
1156 while (argv[i])
1157 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))
1158 return(0);
1159 }
1160
1161 return(1);
1162 }
1163
1164
1165 /* ARGSUSED */
1166 static int
1167 roff_comment(ROFFCALL_ARGS)
1168 {
1169
1170 return(1);
1171 }
1172
1173
1174 /* ARGSUSED */
1175 static int
1176 roff_close(ROFFCALL_ARGS)
1177 {
1178
1179 return(1);
1180 }
1181
1182
1183 /* ARGSUSED */
1184 static int
1185 roff_special(ROFFCALL_ARGS)
1186 {
1187
1188 return((*tree->cb.roffspecial)(tree->arg, tok));
1189 }
1190
1191
1192 static void
1193 roff_warn(const struct rofftree *tree, const char *pos, char *fmt, ...)
1194 {
1195 va_list ap;
1196 char buf[128];
1197
1198 va_start(ap, fmt);
1199 (void)vsnprintf(buf, sizeof(buf), fmt, ap);
1200 va_end(ap);
1201
1202 (*tree->cb.roffmsg)(tree->arg,
1203 ROFF_WARN, tree->cur, pos, buf);
1204 }
1205
1206
1207 static void
1208 roff_err(const struct rofftree *tree, const char *pos, char *fmt, ...)
1209 {
1210 va_list ap;
1211 char buf[128];
1212
1213 va_start(ap, fmt);
1214 (void)vsnprintf(buf, sizeof(buf), fmt, ap);
1215 va_end(ap);
1216
1217 (*tree->cb.roffmsg)(tree->arg,
1218 ROFF_ERROR, tree->cur, pos, buf);
1219 }