]> git.cameronkatri.com Git - mandoc.git/blob - main.c
Stripping of Xo/Xc macros in main.c (ifdef STRIP_XO).
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.8 2009/03/22 19:01:11 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
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 <sys/stat.h>
20
21 #include <assert.h>
22 #include <err.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "mdoc.h"
30
31 #ifdef __linux__
32 extern int getsubopt(char **, char * const *, char **);
33 # ifndef __dead
34 # define __dead __attribute__((__noreturn__))
35 # endif
36 #endif
37
38 struct buf {
39 char *buf;
40 size_t sz;
41 };
42
43 struct curparse {
44 const char *file;
45 int wflags;
46 #define WARN_WALL 0x03 /* All-warnings mask. */
47 #define WARN_WCOMPAT (1 << 0) /* Compatibility warnings. */
48 #define WARN_WSYNTAX (1 << 1) /* Syntax warnings. */
49 #define WARN_WERR (1 << 2) /* Warnings->errors. */
50 };
51
52 enum outt {
53 OUTT_ASCII,
54 OUTT_LATIN1,
55 OUTT_UTF8,
56 OUTT_TREE,
57 OUTT_LINT
58 };
59
60 typedef int (*out_run)(void *, const struct mdoc *);
61 typedef void (*out_free)(void *);
62
63 extern char *__progname;
64
65 extern void *ascii_alloc(void);
66 extern void *latin1_alloc(void);
67 extern void *utf8_alloc(void);
68 extern int terminal_run(void *, const struct mdoc *);
69 extern int tree_run(void *, const struct mdoc *);
70 extern void terminal_free(void *);
71
72 __dead static void version(void);
73 __dead static void usage(void);
74 static int foptions(int *, char *);
75 static int toptions(enum outt *, char *);
76 static int woptions(int *, char *);
77 static int merr(void *, int, int, const char *);
78 static int mwarn(void *, int, int,
79 enum mdoc_warn, const char *);
80 static int file(struct buf *, struct buf *,
81 const char *, struct mdoc *);
82 static int fdesc(struct buf *, struct buf *,
83 const char *, int, struct mdoc *);
84
85
86 int
87 main(int argc, char *argv[])
88 {
89 int c, rc, fflags;
90 struct mdoc_cb cb;
91 struct mdoc *mdoc;
92 void *outdata;
93 enum outt outtype;
94 struct buf ln, blk;
95 out_run outrun;
96 out_free outfree;
97 struct curparse curp;
98
99 fflags = 0;
100 outtype = OUTT_ASCII;
101
102 bzero(&curp, sizeof(struct curparse));
103
104 /* LINTED */
105 while (-1 != (c = getopt(argc, argv, "f:VW:T:")))
106 switch (c) {
107 case ('f'):
108 if ( ! foptions(&fflags, optarg))
109 return(0);
110 break;
111 case ('T'):
112 if ( ! toptions(&outtype, optarg))
113 return(0);
114 break;
115 case ('W'):
116 if ( ! woptions(&curp.wflags, optarg))
117 return(0);
118 break;
119 case ('V'):
120 version();
121 /* NOTREACHED */
122 default:
123 usage();
124 /* NOTREACHED */
125 }
126
127 argc -= optind;
128 argv += optind;
129
130 /*
131 * Allocate the appropriate front-end. Note that utf8, ascii
132 * and latin1 all resolve to the terminal front-end with
133 * different encodings (see terminal.c). Not all frontends have
134 * cleanup or alloc routines.
135 */
136
137 switch (outtype) {
138 case (OUTT_LATIN1):
139 outdata = latin1_alloc();
140 outrun = terminal_run;
141 outfree = terminal_free;
142 break;
143 case (OUTT_UTF8):
144 outdata = utf8_alloc();
145 outrun = terminal_run;
146 outfree = terminal_free;
147 break;
148 case (OUTT_TREE):
149 outdata = NULL;
150 outrun = tree_run;
151 outfree = NULL;
152 break;
153 case (OUTT_LINT):
154 outdata = NULL;
155 outrun = NULL;
156 outfree = NULL;
157 break;
158 default:
159 outdata = ascii_alloc();
160 outrun = terminal_run;
161 outfree = terminal_free;
162 break;
163 }
164
165 /*
166 * All callbacks route into here, where we print them onto the
167 * screen. XXX - for now, no path for debugging messages.
168 */
169
170 cb.mdoc_msg = NULL;
171 cb.mdoc_err = merr;
172 cb.mdoc_warn = mwarn;
173
174 bzero(&ln, sizeof(struct buf));
175 bzero(&blk, sizeof(struct buf));
176
177 mdoc = mdoc_alloc(&curp, fflags, &cb);
178
179 /*
180 * Loop around available files.
181 */
182
183 if (NULL == *argv) {
184 curp.file = "<stdin>";
185 c = fdesc(&blk, &ln, "stdin", STDIN_FILENO, mdoc);
186 rc = 0;
187 if (c && NULL == outrun)
188 rc = 1;
189 else if (c && outrun && (*outrun)(outdata, mdoc))
190 rc = 1;
191 } else {
192 while (*argv) {
193 curp.file = *argv;
194 c = file(&blk, &ln, *argv, mdoc);
195 if ( ! c)
196 break;
197 if (outrun && ! (*outrun)(outdata, mdoc))
198 break;
199 /* Reset the parser for another file. */
200 mdoc_reset(mdoc);
201 argv++;
202 }
203 rc = NULL == *argv;
204 }
205
206 if (blk.buf)
207 free(blk.buf);
208 if (ln.buf)
209 free(ln.buf);
210 if (outfree)
211 (*outfree)(outdata);
212
213 mdoc_free(mdoc);
214
215 return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
216 }
217
218
219 __dead static void
220 version(void)
221 {
222
223 (void)printf("%s %s\n", __progname, VERSION);
224 exit(0);
225 /* NOTREACHED */
226 }
227
228
229 __dead static void
230 usage(void)
231 {
232
233 (void)fprintf(stderr, "usage: %s\n", __progname);
234 exit(1);
235 /* NOTREACHED */
236 }
237
238
239 static int
240 file(struct buf *blk, struct buf *ln,
241 const char *file, struct mdoc *mdoc)
242 {
243 int fd, c;
244
245 if (-1 == (fd = open(file, O_RDONLY, 0))) {
246 warn("%s", file);
247 return(0);
248 }
249
250 c = fdesc(blk, ln, file, fd, mdoc);
251
252 if (-1 == close(fd))
253 warn("%s", file);
254
255 return(c);
256 }
257
258
259 static int
260 fdesc(struct buf *blk, struct buf *ln,
261 const char *f, int fd, struct mdoc *mdoc)
262 {
263 size_t sz;
264 ssize_t ssz;
265 struct stat st;
266 int j, i, pos, lnn;
267 #ifdef STRIP_XO
268 int macro, xo, xeoln;
269 #endif
270
271 /*
272 * Two buffers: ln and buf. buf is the input buffer, optimised
273 * for each file's block size. ln is a line buffer. Both
274 * growable, hence passed in by ptr-ptr.
275 */
276
277 sz = BUFSIZ;
278
279 if (-1 == fstat(fd, &st))
280 warnx("%s", f);
281 else if ((size_t)st.st_blksize > sz)
282 sz = st.st_blksize;
283
284 if (sz > blk->sz) {
285 blk->buf = realloc(blk->buf, sz);
286 if (NULL == blk->buf)
287 err(1, "realloc");
288 blk->sz = sz;
289 }
290
291 /*
292 * Fill buf with file blocksize and parse newlines into ln.
293 */
294 #ifdef STRIP_XO
295 macro = xo = xeoln = 0;
296 #endif
297
298 for (lnn = 1, pos = 0; ; ) {
299 if (-1 == (ssz = read(fd, blk->buf, sz))) {
300 warn("%s", f);
301 return(0);
302 } else if (0 == ssz)
303 break;
304
305 for (i = 0; i < (int)ssz; i++) {
306 if (pos >= (int)ln->sz) {
307 ln->sz += 256; /* Step-size. */
308 ln->buf = realloc(ln->buf, ln->sz);
309 if (NULL == ln->buf)
310 err(1, "realloc");
311 }
312
313 if ('\n' != blk->buf[i]) {
314 /*
315 * Ugly of uglies. Here we handle the
316 * dreaded `Xo/Xc' scoping. Cover the
317 * eyes of any nearby children. This
318 * makes `Xo/Xc' enclosures look like
319 * one huge line.
320 */
321 #ifdef STRIP_XO
322 /*
323 * First, note whether we're in a macro
324 * line.
325 */
326 if (0 == pos && '.' == blk->buf[i])
327 macro = 1;
328
329 /*
330 * If we're in an `Xo' context and just
331 * nixed a newline, remove the control
332 * character for new macro lines:
333 * they're going to show up as all part
334 * of the same line.
335 */
336 if (xo && xeoln && '.' == blk->buf[i]) {
337 xeoln = 0;
338 continue;
339 }
340 xeoln = 0;
341
342 /*
343 * If we've parsed `Xo', enter an xo
344 * context. `Xo' must be in a parsable
345 * state. This is the ugly part. IT IS
346 * NOT SMART ENOUGH TO HANDLE ESCAPED
347 * WHITESPACE.
348 */
349 if (macro && pos && 'o' == blk->buf[i]) {
350 if (xo && 'X' == ln->buf[pos - 1]) {
351 if (' ' == ln->buf[pos - 2])
352 xo++;
353 } else if ('X' == ln->buf[pos - 1]) {
354 if (2 == pos && '.' == ln->buf[pos - 2])
355 xo++;
356 else if (' ' == ln->buf[pos - 2])
357 xo++;
358 }
359 }
360
361 /*
362 * If we're parsed `Xc', leave an xo
363 * context if one's already pending.
364 * `Xc' must be in a parsable state.
365 * THIS IS NOT SMART ENOUGH TO HANDLE
366 * ESCAPED WHITESPACE.
367 */
368 if (macro && pos && 'c' == blk->buf[i])
369 if (xo && 'X' == ln->buf[pos - 1])
370 if (' ' == ln->buf[pos - 2])
371 xo--;
372 #endif /* STRIP_XO */
373
374 ln->buf[pos++] = blk->buf[i];
375 continue;
376 }
377
378 /* Check for CPP-escaped newline. */
379
380 if (pos > 0 && '\\' == ln->buf[pos - 1]) {
381 for (j = pos - 1; j >= 0; j--)
382 if ('\\' != ln->buf[j])
383 break;
384
385 if ( ! ((pos - j) % 2)) {
386 pos--;
387 lnn++;
388 continue;
389 }
390 }
391
392 #ifdef STRIP_XO
393 /*
394 * If we're in an xo context, put a space in
395 * place of the newline and continue parsing.
396 * Mark that we just did a newline.
397 */
398 if (xo) {
399 xeoln = 1;
400 ln->buf[pos++] = ' ';
401 lnn++;
402 continue;
403 }
404 #endif /* STRIP_XO */
405
406 ln->buf[pos] = 0;
407 if ( ! mdoc_parseln(mdoc, lnn, ln->buf))
408 return(0);
409 lnn++;
410 macro = pos = 0;
411 }
412 }
413
414 return(mdoc_endparse(mdoc));
415 }
416
417
418 static int
419 toptions(enum outt *tflags, char *arg)
420 {
421
422 if (0 == strcmp(arg, "ascii"))
423 *tflags = OUTT_ASCII;
424 else if (0 == strcmp(arg, "latin1"))
425 *tflags = OUTT_LATIN1;
426 else if (0 == strcmp(arg, "utf8"))
427 *tflags = OUTT_UTF8;
428 else if (0 == strcmp(arg, "lint"))
429 *tflags = OUTT_LINT;
430 else if (0 == strcmp(arg, "tree"))
431 *tflags = OUTT_TREE;
432 else {
433 warnx("bad argument: -T%s", arg);
434 return(0);
435 }
436
437 return(1);
438 }
439
440
441 /*
442 * Parse out the options for [-fopt...] setting compiler options. These
443 * can be comma-delimited or called again.
444 */
445 static int
446 foptions(int *fflags, char *arg)
447 {
448 char *v;
449 char *toks[4];
450
451 toks[0] = "ign-scope";
452 toks[1] = "ign-escape";
453 toks[2] = "ign-macro";
454 toks[3] = NULL;
455
456 while (*arg)
457 switch (getsubopt(&arg, toks, &v)) {
458 case (0):
459 *fflags |= MDOC_IGN_SCOPE;
460 break;
461 case (1):
462 *fflags |= MDOC_IGN_ESCAPE;
463 break;
464 case (2):
465 *fflags |= MDOC_IGN_MACRO;
466 break;
467 default:
468 warnx("bad argument: -f%s", arg);
469 return(0);
470 }
471
472 return(1);
473 }
474
475
476 /*
477 * Parse out the options for [-Werr...], which sets warning modes.
478 * These can be comma-delimited or called again.
479 */
480 static int
481 woptions(int *wflags, char *arg)
482 {
483 char *v;
484 char *toks[5];
485
486 toks[0] = "all";
487 toks[1] = "compat";
488 toks[2] = "syntax";
489 toks[3] = "error";
490 toks[4] = NULL;
491
492 while (*arg)
493 switch (getsubopt(&arg, toks, &v)) {
494 case (0):
495 *wflags |= WARN_WALL;
496 break;
497 case (1):
498 *wflags |= WARN_WCOMPAT;
499 break;
500 case (2):
501 *wflags |= WARN_WSYNTAX;
502 break;
503 case (3):
504 *wflags |= WARN_WERR;
505 break;
506 default:
507 warnx("bad argument: -W%s", arg);
508 return(0);
509 }
510
511 return(1);
512 }
513
514
515 /* ARGSUSED */
516 static int
517 merr(void *arg, int line, int col, const char *msg)
518 {
519 struct curparse *curp;
520
521 curp = (struct curparse *)arg;
522
523 warnx("%s:%d: error: %s (column %d)",
524 curp->file, line, msg, col);
525 return(0);
526 }
527
528
529 static int
530 mwarn(void *arg, int line, int col,
531 enum mdoc_warn type, const char *msg)
532 {
533 struct curparse *curp;
534 char *wtype;
535
536 curp = (struct curparse *)arg;
537 wtype = NULL;
538
539 switch (type) {
540 case (WARN_COMPAT):
541 wtype = "compat";
542 if (curp->wflags & WARN_WCOMPAT)
543 break;
544 return(1);
545 case (WARN_SYNTAX):
546 wtype = "syntax";
547 if (curp->wflags & WARN_WSYNTAX)
548 break;
549 return(1);
550 }
551
552 assert(wtype);
553 warnx("%s:%d: %s warning: %s (column %d)",
554 curp->file, line, wtype, msg, col);
555
556 if ( ! (curp->wflags & WARN_WERR))
557 return(1);
558
559 warnx("%s: considering warnings as errors",
560 __progname);
561 return(0);
562 }
563
564