]> git.cameronkatri.com Git - mandoc.git/blob - roff.c
Small fixes to output.
[mandoc.git] / roff.c
1 /* $Id: roff.c,v 1.25 2008/11/30 23:05:57 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 (0 != *buf && 0 != *(buf + 1) && 0 != *(buf + 2))
522 if (0 == strncmp(buf, ".\\\"", 3))
523 return(1);
524
525 if (ROFF_MAX == (tok = rofffindtok(buf + 1))) {
526 roff_err(tree, buf + 1, "bogus line macro");
527 return(0);
528 } else if (NULL == tokens[tok].cb) {
529 roff_err(tree, buf + 1, "unsupported macro `%s'",
530 toknames[tok]);
531 return(0);
532 }
533
534 assert(ROFF___ != tok);
535 if ( ! roffargs(tree, tok, buf, argv))
536 return(0);
537
538 argvp = (char **)argv;
539
540 /*
541 * Prelude macros break some assumptions, so branch now.
542 */
543
544 if (ROFF_PRELUDE & tree->state) {
545 assert(NULL == tree->last);
546 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
547 } else
548 assert(tree->last);
549
550 assert(ROFF_BODY & tree->state);
551
552 /*
553 * First check that our possible parents and parent's possible
554 * children are satisfied.
555 */
556
557 if ( ! roffscan(tree->last->tok, tokens[tok].parents)) {
558 roff_err(tree, *argvp, "`%s' has invalid parent `%s'",
559 toknames[tok],
560 toknames[tree->last->tok]);
561 return(0);
562 }
563
564 if ( ! roffscan(tok, tokens[tree->last->tok].children)) {
565 roff_err(tree, *argvp, "`%s' is invalid child of `%s'",
566 toknames[tok],
567 toknames[tree->last->tok]);
568 return(0);
569 }
570
571 /*
572 * Branch if we're not a layout token.
573 */
574
575 if (ROFF_LAYOUT != tokens[tok].type)
576 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
577
578 /*
579 * Check our scope rules.
580 */
581
582 if (0 == tokens[tok].ctx)
583 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
584
585 /*
586 * First consider implicit-end tags, like as follows:
587 * .Sh SECTION 1
588 * .Sh SECTION 2
589 * In this, we want to close the scope of the NAME section. If
590 * there's an intermediary implicit-end tag, such as
591 * .Sh SECTION 1
592 * .Ss Subsection 1
593 * .Sh SECTION 2
594 * then it must be closed as well.
595 */
596
597 if (tok == tokens[tok].ctx) {
598 /*
599 * First search up to the point where we must close.
600 * If one doesn't exist, then we can open a new scope.
601 */
602
603 for (n = tree->last; n; n = n->parent) {
604 assert(0 == tokens[n->tok].ctx ||
605 n->tok == tokens[n->tok].ctx);
606 if (n->tok == tok)
607 break;
608 if (ROFF_SHALLOW & tokens[tok].flags) {
609 n = NULL;
610 break;
611 }
612 }
613
614 /*
615 * Create a new scope, as no previous one exists to
616 * close out.
617 */
618
619 if (NULL == n)
620 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
621
622 /*
623 * Close out all intermediary scoped blocks, then hang
624 * the current scope from our predecessor's parent.
625 */
626
627 do {
628 t = tree->last->tok;
629 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))
630 return(0);
631 } while (t != tok);
632
633 return((*tokens[tok].cb)(tok, tree, argvp, ROFF_ENTER));
634 }
635
636 /*
637 * Now consider explicit-end tags, where we want to close back
638 * to a specific tag. Example:
639 * .Bl
640 * .It Item.
641 * .El
642 * In this, the `El' tag closes out the scope of `Bl'.
643 */
644
645 assert(tree->last);
646 assert(tok != tokens[tok].ctx && 0 != tokens[tok].ctx);
647
648 /* LINTED */
649 do {
650 t = tree->last->tok;
651 if ( ! (*tokens[t].cb)(t, tree, NULL, ROFF_EXIT))
652 return(0);
653 } while (t != tokens[tok].ctx);
654
655 assert(tree->last);
656 return(1);
657 }
658
659
660 static int
661 rofffindarg(const char *name)
662 {
663 size_t i;
664
665 /* FIXME: use a table, this is slow but ok for now. */
666
667 /* LINTED */
668 for (i = 0; i < ROFF_ARGMAX; i++)
669 /* LINTED */
670 if (0 == strcmp(name, tokargnames[i]))
671 return((int)i);
672
673 return(ROFF_ARGMAX);
674 }
675
676
677 static int
678 rofffindtok(const char *buf)
679 {
680 char token[4];
681 int i;
682
683 for (i = 0; *buf && ! isspace(*buf) && i < 3; i++, buf++)
684 token[i] = *buf;
685
686 if (i == 3)
687 return(ROFF_MAX);
688
689 token[i] = 0;
690
691 /* FIXME: use a table, this is slow but ok for now. */
692
693 /* LINTED */
694 for (i = 0; i < ROFF_MAX; i++)
695 /* LINTED */
696 if (0 == strcmp(toknames[i], token))
697 return((int)i);
698
699 return(ROFF_MAX);
700 }
701
702
703 static int
704 roffispunct(const char *p)
705 {
706
707 if (0 == *p)
708 return(0);
709 if (0 != *(p + 1))
710 return(0);
711
712 switch (*p) {
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 /* FALLTHROUGH */
733 case(']'):
734 /* FALLTHROUGH */
735 case('}'):
736 return(1);
737 default:
738 break;
739 }
740
741 return(0);
742 }
743
744
745 static int
746 rofffindcallable(const char *name)
747 {
748 int c;
749
750 if (ROFF_MAX == (c = rofffindtok(name)))
751 return(ROFF_MAX);
752 assert(c >= 0 && c < ROFF_MAX);
753 return(ROFF_CALLABLE & tokens[c].flags ? c : ROFF_MAX);
754 }
755
756
757 static struct roffnode *
758 roffnode_new(int tokid, struct rofftree *tree)
759 {
760 struct roffnode *p;
761
762 if (NULL == (p = malloc(sizeof(struct roffnode))))
763 err(1, "malloc");
764
765 p->tok = tokid;
766 p->parent = tree->last;
767 tree->last = p;
768
769 return(p);
770 }
771
772
773 static int
774 roffargok(int tokid, int argid)
775 {
776 const int *c;
777
778 if (NULL == (c = tokens[tokid].args))
779 return(0);
780
781 for ( ; ROFF_ARGMAX != *c; c++)
782 if (argid == *c)
783 return(1);
784
785 return(0);
786 }
787
788
789 static void
790 roffnode_free(struct rofftree *tree)
791 {
792 struct roffnode *p;
793
794 assert(tree->last);
795
796 p = tree->last;
797 tree->last = tree->last->parent;
798 free(p);
799 }
800
801
802 static int
803 roffnextopt(const struct rofftree *tree, int tok,
804 char ***in, char **val)
805 {
806 char *arg, **argv;
807 int v;
808
809 *val = NULL;
810 argv = *in;
811 assert(argv);
812
813 if (NULL == (arg = *argv))
814 return(-1);
815 if ('-' != *arg)
816 return(-1);
817
818 if (ROFF_ARGMAX == (v = rofffindarg(arg + 1))) {
819 roff_warn(tree, arg, "argument-like parameter `%s' to "
820 "`%s'", &arg[1], toknames[tok]);
821 return(-1);
822 }
823
824 if ( ! roffargok(tok, v)) {
825 roff_warn(tree, arg, "invalid argument parameter `%s' to "
826 "`%s'", tokargnames[v], toknames[tok]);
827 return(-1);
828 }
829
830 if ( ! (ROFF_VALUE & tokenargs[v]))
831 return(v);
832
833 *in = ++argv;
834
835 if (NULL == *argv) {
836 roff_err(tree, arg, "empty value of `%s' for `%s'",
837 tokargnames[v], toknames[tok]);
838 return(ROFF_ARGMAX);
839 }
840
841 return(v);
842 }
843
844
845 /* ARGSUSED */
846 static int
847 roff_Dd(ROFFCALL_ARGS)
848 {
849
850 if (ROFF_BODY & tree->state) {
851 assert( ! (ROFF_PRELUDE & tree->state));
852 assert(ROFF_PRELUDE_Dd & tree->state);
853 return(roff_text(tok, tree, argv, type));
854 }
855
856 assert(ROFF_PRELUDE & tree->state);
857 assert( ! (ROFF_BODY & tree->state));
858
859 if (ROFF_PRELUDE_Dd & tree->state) {
860 roff_err(tree, *argv, "repeated `Dd' in prelude");
861 return(0);
862 } else if (ROFF_PRELUDE_Dt & tree->state) {
863 roff_err(tree, *argv, "out-of-order `Dd' in prelude");
864 return(0);
865 }
866
867 /* TODO: parse date. */
868
869 assert(NULL == tree->last);
870 tree->state |= ROFF_PRELUDE_Dd;
871
872 return(1);
873 }
874
875
876 /* ARGSUSED */
877 static int
878 roff_Dt(ROFFCALL_ARGS)
879 {
880
881 if (ROFF_BODY & tree->state) {
882 assert( ! (ROFF_PRELUDE & tree->state));
883 assert(ROFF_PRELUDE_Dt & tree->state);
884 return(roff_text(tok, tree, argv, type));
885 }
886
887 assert(ROFF_PRELUDE & tree->state);
888 assert( ! (ROFF_BODY & tree->state));
889
890 if ( ! (ROFF_PRELUDE_Dd & tree->state)) {
891 roff_err(tree, *argv, "out-of-order `Dt' in prelude");
892 return(0);
893 } else if (ROFF_PRELUDE_Dt & tree->state) {
894 roff_err(tree, *argv, "repeated `Dt' in prelude");
895 return(0);
896 }
897
898 /* TODO: parse date. */
899
900 assert(NULL == tree->last);
901 tree->state |= ROFF_PRELUDE_Dt;
902
903 return(1);
904 }
905
906
907 /* ARGSUSED */
908 static int
909 roff_Os(ROFFCALL_ARGS)
910 {
911
912 if (ROFF_EXIT == type) {
913 roffnode_free(tree);
914 return((*tree->cb.rofftail)(tree->arg));
915 } else if (ROFF_BODY & tree->state) {
916 assert( ! (ROFF_PRELUDE & tree->state));
917 assert(ROFF_PRELUDE_Os & tree->state);
918 return(roff_text(tok, tree, argv, type));
919 }
920
921 assert(ROFF_PRELUDE & tree->state);
922 if ( ! (ROFF_PRELUDE_Dt & tree->state) ||
923 ! (ROFF_PRELUDE_Dd & tree->state)) {
924 roff_err(tree, *argv, "out-of-order `Os' in prelude");
925 return(0);
926 }
927
928 /* TODO: extract OS. */
929
930 tree->state |= ROFF_PRELUDE_Os;
931 tree->state &= ~ROFF_PRELUDE;
932 tree->state |= ROFF_BODY;
933
934 assert(NULL == tree->last);
935
936 if (NULL == roffnode_new(tok, tree))
937 return(0);
938
939 return((*tree->cb.roffhead)(tree->arg));
940 }
941
942
943 /* ARGSUSED */
944 static int
945 roff_layout(ROFFCALL_ARGS)
946 {
947 int i, c, argcp[ROFF_MAXARG];
948 char *v, *argvp[ROFF_MAXARG];
949
950 if (ROFF_PRELUDE & tree->state) {
951 roff_err(tree, *argv, "`%s' disallowed in prelude",
952 toknames[tok]);
953 return(0);
954 }
955
956 if (ROFF_EXIT == type) {
957 roffnode_free(tree);
958 return((*tree->cb.roffblkout)(tree->arg, tok));
959 }
960
961 i = 0;
962 argv++;
963
964 while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {
965 if (ROFF_ARGMAX == c)
966 return(0);
967
968 argcp[i] = c;
969 argvp[i] = v;
970 i++;
971 argv++;
972 }
973
974 argcp[i] = ROFF_ARGMAX;
975 argvp[i] = NULL;
976
977 if (NULL == roffnode_new(tok, tree))
978 return(0);
979
980 if ( ! (*tree->cb.roffblkin)(tree->arg, tok, argcp, argvp))
981 return(0);
982
983 if (NULL == *argv)
984 return(1);
985
986 if ( ! (*tree->cb.roffin)(tree->arg, tok, argcp, argvp))
987 return(0);
988
989 if ( ! (ROFF_PARSED & tokens[tok].flags)) {
990 i = 0;
991 while (*argv) {
992 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
993 return(0);
994 i = 1;
995 }
996 return((*tree->cb.roffout)(tree->arg, tok));
997 }
998
999 i = 0;
1000 while (*argv) {
1001 if (ROFF_MAX != (c = rofffindcallable(*argv))) {
1002 if (NULL == tokens[c].cb) {
1003 roff_err(tree, *argv, "unsupported "
1004 "macro `%s'",
1005 toknames[c]);
1006 return(0);
1007 }
1008 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))
1009 return(0);
1010 break;
1011 }
1012
1013 assert(tree->arg);
1014 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1015 return(0);
1016 i = 1;
1017 }
1018
1019 /*
1020 * If we're the first parser (*argv == tree->cur) then purge out
1021 * any additional punctuation, should there be any remaining at
1022 * the end of line.
1023 */
1024
1025 if (ROFF_PARSED & tokens[tok].flags && *argv) {
1026 i = 0;
1027 while (argv[i])
1028 i++;
1029
1030 assert(i > 0);
1031 if ( ! roffispunct(argv[--i]))
1032 return((*tree->cb.roffout)(tree->arg, tok));
1033
1034 while (i >= 0 && roffispunct(argv[i]))
1035 i--;
1036
1037 assert(0 != i);
1038 i++;
1039
1040 /* LINTED */
1041 while (argv[i])
1042 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))
1043 return(0);
1044 }
1045
1046 return((*tree->cb.roffout)(tree->arg, tok));
1047 }
1048
1049
1050 /* ARGSUSED */
1051 static int
1052 roff_text(ROFFCALL_ARGS)
1053 {
1054 int i, j, first, c, argcp[ROFF_MAXARG];
1055 char *v, *argvp[ROFF_MAXARG];
1056
1057 if (ROFF_PRELUDE & tree->state) {
1058 roff_err(tree, *argv, "`%s' disallowed in prelude",
1059 toknames[tok]);
1060 return(0);
1061 }
1062
1063 /* FIXME: breaks if passed from roff_layout. */
1064 first = *argv == tree->cur;
1065
1066 i = 0;
1067 argv++;
1068
1069 while (-1 != (c = roffnextopt(tree, tok, &argv, &v))) {
1070 if (ROFF_ARGMAX == c)
1071 return(0);
1072
1073 argcp[i] = c;
1074 argvp[i] = v;
1075 i++;
1076 argv++;
1077 }
1078
1079 argcp[i] = ROFF_ARGMAX;
1080 argvp[i] = NULL;
1081
1082 if ( ! (*tree->cb.roffin)(tree->arg, tok, argcp, argvp))
1083 return(0);
1084
1085 if ( ! (ROFF_PARSED & tokens[tok].flags)) {
1086 i = 0;
1087 while (*argv) {
1088 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1089 return(0);
1090 i = 1;
1091 }
1092 return((*tree->cb.roffout)(tree->arg, tok));
1093 }
1094
1095 i = 0;
1096 while (*argv) {
1097 if (ROFF_MAX == (c = rofffindcallable(*argv))) {
1098 /*
1099 * If all that remains is roff punctuation, then
1100 * close out our scope and return.
1101 */
1102 if (roffispunct(*argv)) {
1103 for (j = 0; argv[j]; j++)
1104 if ( ! roffispunct(argv[j]))
1105 break;
1106 if (NULL == argv[j])
1107 break;
1108 i = 1;
1109 }
1110
1111 if ( ! (*tree->cb.roffdata)(tree->arg, i, *argv++))
1112 return(0);
1113
1114 i = 1;
1115 continue;
1116 }
1117
1118 /*
1119 * A sub-command has been found. Execute it and
1120 * discontinue parsing for arguments.
1121 */
1122
1123 if (NULL == tokens[c].cb) {
1124 roff_err(tree, *argv, "unsupported macro `%s'",
1125 toknames[c]);
1126 return(0);
1127 }
1128
1129 if ( ! (*tokens[c].cb)(c, tree, argv, ROFF_ENTER))
1130 return(0);
1131
1132 break;
1133 }
1134
1135 if ( ! (*tree->cb.roffout)(tree->arg, tok))
1136 return(0);
1137
1138 /*
1139 * If we're the first parser (*argv == tree->cur) then purge out
1140 * any additional punctuation, should there be any remaining at
1141 * the end of line.
1142 */
1143
1144 if (first && *argv) {
1145 i = 0;
1146 while (argv[i])
1147 i++;
1148
1149 assert(i > 0);
1150 if ( ! roffispunct(argv[--i]))
1151 return(1);
1152
1153 while (i >= 0 && roffispunct(argv[i]))
1154 i--;
1155
1156 assert(0 != i);
1157 i++;
1158
1159 /* LINTED */
1160 while (argv[i])
1161 if ( ! (*tree->cb.roffdata)(tree->arg, 0, argv[i++]))
1162 return(0);
1163 }
1164
1165 return(1);
1166 }
1167
1168
1169 /* ARGSUSED */
1170 static int
1171 roff_comment(ROFFCALL_ARGS)
1172 {
1173
1174 return(1);
1175 }
1176
1177
1178 /* ARGSUSED */
1179 static int
1180 roff_close(ROFFCALL_ARGS)
1181 {
1182
1183 return(1);
1184 }
1185
1186
1187 /* ARGSUSED */
1188 static int
1189 roff_special(ROFFCALL_ARGS)
1190 {
1191
1192 return((*tree->cb.roffspecial)(tree->arg, tok));
1193 }
1194
1195
1196 static void
1197 roff_warn(const struct rofftree *tree, const char *pos, char *fmt, ...)
1198 {
1199 va_list ap;
1200 char buf[128];
1201
1202 va_start(ap, fmt);
1203 (void)vsnprintf(buf, sizeof(buf), fmt, ap);
1204 va_end(ap);
1205
1206 (*tree->cb.roffmsg)(tree->arg,
1207 ROFF_WARN, tree->cur, pos, buf);
1208 }
1209
1210
1211 static void
1212 roff_err(const struct rofftree *tree, const char *pos, char *fmt, ...)
1213 {
1214 va_list ap;
1215 char buf[128];
1216
1217 va_start(ap, fmt);
1218 (void)vsnprintf(buf, sizeof(buf), fmt, ap);
1219 va_end(ap);
1220
1221 (*tree->cb.roffmsg)(tree->arg,
1222 ROFF_ERROR, tree->cur, pos, buf);
1223 }