etc/services - sync with NetBSD-8
[minix.git] / external / bsd / mdocml / dist / mandoc.c
blobf5f95c332d61d448fddb005487976d1c380b375c
1 /* Id: mandoc.c,v 1.75 2013/12/31 23:23:10 schwarze Exp */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2011, 2012, 2013 Ingo Schwarze <schwarze@openbsd.org>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
22 #include <sys/types.h>
24 #include <assert.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <limits.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <time.h>
33 #include "mandoc.h"
34 #include "libmandoc.h"
36 #define DATESIZE 32
38 static int a2time(time_t *, const char *, const char *);
39 static char *time2a(time_t);
42 enum mandoc_esc
43 mandoc_escape(const char **end, const char **start, int *sz)
45 const char *local_start;
46 int local_sz;
47 char term;
48 enum mandoc_esc gly;
51 * When the caller doesn't provide return storage,
52 * use local storage.
55 if (NULL == start)
56 start = &local_start;
57 if (NULL == sz)
58 sz = &local_sz;
61 * Beyond the backslash, at least one input character
62 * is part of the escape sequence. With one exception
63 * (see below), that character won't be returned.
66 gly = ESCAPE_ERROR;
67 *start = ++*end;
68 *sz = 0;
69 term = '\0';
71 switch ((*start)[-1]) {
73 * First the glyphs. There are several different forms of
74 * these, but each eventually returns a substring of the glyph
75 * name.
77 case ('('):
78 gly = ESCAPE_SPECIAL;
79 *sz = 2;
80 break;
81 case ('['):
82 gly = ESCAPE_SPECIAL;
84 * Unicode escapes are defined in groff as \[uXXXX] to
85 * \[u10FFFF], where the contained value must be a valid
86 * Unicode codepoint. Here, however, only check whether
87 * it's not a zero-width escape.
89 if ('u' == (*start)[0] && ']' != (*start)[1])
90 gly = ESCAPE_UNICODE;
91 term = ']';
92 break;
93 case ('C'):
94 if ('\'' != **start)
95 return(ESCAPE_ERROR);
96 *start = ++*end;
97 if ('u' == (*start)[0] && '\'' != (*start)[1])
98 gly = ESCAPE_UNICODE;
99 else
100 gly = ESCAPE_SPECIAL;
101 term = '\'';
102 break;
105 * Escapes taking no arguments at all.
107 case ('d'):
108 /* FALLTHROUGH */
109 case ('u'):
110 return(ESCAPE_IGNORE);
113 * The \z escape is supposed to output the following
114 * character without advancing the cursor position.
115 * Since we are mostly dealing with terminal mode,
116 * let us just skip the next character.
118 case ('z'):
119 return(ESCAPE_SKIPCHAR);
122 * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
123 * 'X' is the trigger. These have opaque sub-strings.
125 case ('F'):
126 /* FALLTHROUGH */
127 case ('g'):
128 /* FALLTHROUGH */
129 case ('k'):
130 /* FALLTHROUGH */
131 case ('M'):
132 /* FALLTHROUGH */
133 case ('m'):
134 /* FALLTHROUGH */
135 case ('n'):
136 /* FALLTHROUGH */
137 case ('V'):
138 /* FALLTHROUGH */
139 case ('Y'):
140 gly = ESCAPE_IGNORE;
141 /* FALLTHROUGH */
142 case ('f'):
143 if (ESCAPE_ERROR == gly)
144 gly = ESCAPE_FONT;
145 switch (**start) {
146 case ('('):
147 *start = ++*end;
148 *sz = 2;
149 break;
150 case ('['):
151 *start = ++*end;
152 term = ']';
153 break;
154 default:
155 *sz = 1;
156 break;
158 break;
161 * These escapes are of the form \X'Y', where 'X' is the trigger
162 * and 'Y' is any string. These have opaque sub-strings.
164 case ('A'):
165 /* FALLTHROUGH */
166 case ('b'):
167 /* FALLTHROUGH */
168 case ('B'):
169 /* FALLTHROUGH */
170 case ('D'):
171 /* FALLTHROUGH */
172 case ('o'):
173 /* FALLTHROUGH */
174 case ('R'):
175 /* FALLTHROUGH */
176 case ('w'):
177 /* FALLTHROUGH */
178 case ('X'):
179 /* FALLTHROUGH */
180 case ('Z'):
181 if ('\'' != **start)
182 return(ESCAPE_ERROR);
183 gly = ESCAPE_IGNORE;
184 *start = ++*end;
185 term = '\'';
186 break;
189 * These escapes are of the form \X'N', where 'X' is the trigger
190 * and 'N' resolves to a numerical expression.
192 case ('h'):
193 /* FALLTHROUGH */
194 case ('H'):
195 /* FALLTHROUGH */
196 case ('L'):
197 /* FALLTHROUGH */
198 case ('l'):
199 /* FALLTHROUGH */
200 case ('S'):
201 /* FALLTHROUGH */
202 case ('v'):
203 /* FALLTHROUGH */
204 case ('x'):
205 if ('\'' != **start)
206 return(ESCAPE_ERROR);
207 gly = ESCAPE_IGNORE;
208 *start = ++*end;
209 term = '\'';
210 break;
213 * Special handling for the numbered character escape.
214 * XXX Do any other escapes need similar handling?
216 case ('N'):
217 if ('\0' == **start)
218 return(ESCAPE_ERROR);
219 (*end)++;
220 if (isdigit((unsigned char)**start)) {
221 *sz = 1;
222 return(ESCAPE_IGNORE);
224 (*start)++;
225 while (isdigit((unsigned char)**end))
226 (*end)++;
227 *sz = *end - *start;
228 if ('\0' != **end)
229 (*end)++;
230 return(ESCAPE_NUMBERED);
233 * Sizes get a special category of their own.
235 case ('s'):
236 gly = ESCAPE_IGNORE;
238 /* See +/- counts as a sign. */
239 if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
240 (*end)++;
242 switch (**end) {
243 case ('('):
244 *start = ++*end;
245 *sz = 2;
246 break;
247 case ('['):
248 *start = ++*end;
249 term = ']';
250 break;
251 case ('\''):
252 *start = ++*end;
253 term = '\'';
254 break;
255 default:
256 *sz = 1;
257 break;
260 break;
263 * Anything else is assumed to be a glyph.
264 * In this case, pass back the character after the backslash.
266 default:
267 gly = ESCAPE_SPECIAL;
268 *start = --*end;
269 *sz = 1;
270 break;
273 assert(ESCAPE_ERROR != gly);
276 * Read up to the terminating character,
277 * paying attention to nested escapes.
280 if ('\0' != term) {
281 while (**end != term) {
282 switch (**end) {
283 case ('\0'):
284 return(ESCAPE_ERROR);
285 case ('\\'):
286 (*end)++;
287 if (ESCAPE_ERROR ==
288 mandoc_escape(end, NULL, NULL))
289 return(ESCAPE_ERROR);
290 break;
291 default:
292 (*end)++;
293 break;
296 *sz = (*end)++ - *start;
297 } else {
298 assert(*sz > 0);
299 if ((size_t)*sz > strlen(*start))
300 return(ESCAPE_ERROR);
301 *end += *sz;
304 /* Run post-processors. */
306 switch (gly) {
307 case (ESCAPE_FONT):
308 if (2 == *sz) {
309 if ('C' == **start) {
311 * Treat constant-width font modes
312 * just like regular font modes.
314 (*start)++;
315 (*sz)--;
316 } else {
317 if ('B' == (*start)[0] && 'I' == (*start)[1])
318 gly = ESCAPE_FONTBI;
319 break;
321 } else if (1 != *sz)
322 break;
324 switch (**start) {
325 case ('3'):
326 /* FALLTHROUGH */
327 case ('B'):
328 gly = ESCAPE_FONTBOLD;
329 break;
330 case ('2'):
331 /* FALLTHROUGH */
332 case ('I'):
333 gly = ESCAPE_FONTITALIC;
334 break;
335 case ('P'):
336 gly = ESCAPE_FONTPREV;
337 break;
338 case ('1'):
339 /* FALLTHROUGH */
340 case ('R'):
341 gly = ESCAPE_FONTROMAN;
342 break;
344 break;
345 case (ESCAPE_SPECIAL):
346 if (1 == *sz && 'c' == **start)
347 gly = ESCAPE_NOSPACE;
348 break;
349 default:
350 break;
353 return(gly);
356 void *
357 mandoc_calloc(size_t num, size_t size)
359 void *ptr;
361 ptr = calloc(num, size);
362 if (NULL == ptr) {
363 perror(NULL);
364 exit((int)MANDOCLEVEL_SYSERR);
367 return(ptr);
371 void *
372 mandoc_malloc(size_t size)
374 void *ptr;
376 ptr = malloc(size);
377 if (NULL == ptr) {
378 perror(NULL);
379 exit((int)MANDOCLEVEL_SYSERR);
382 return(ptr);
386 void *
387 mandoc_realloc(void *ptr, size_t size)
390 ptr = realloc(ptr, size);
391 if (NULL == ptr) {
392 perror(NULL);
393 exit((int)MANDOCLEVEL_SYSERR);
396 return(ptr);
399 char *
400 mandoc_strndup(const char *ptr, size_t sz)
402 char *p;
404 p = mandoc_malloc(sz + 1);
405 memcpy(p, ptr, sz);
406 p[(int)sz] = '\0';
407 return(p);
410 char *
411 mandoc_strdup(const char *ptr)
413 char *p;
415 p = strdup(ptr);
416 if (NULL == p) {
417 perror(NULL);
418 exit((int)MANDOCLEVEL_SYSERR);
421 return(p);
425 * Parse a quoted or unquoted roff-style request or macro argument.
426 * Return a pointer to the parsed argument, which is either the original
427 * pointer or advanced by one byte in case the argument is quoted.
428 * NUL-terminate the argument in place.
429 * Collapse pairs of quotes inside quoted arguments.
430 * Advance the argument pointer to the next argument,
431 * or to the NUL byte terminating the argument line.
433 char *
434 mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
436 char *start, *cp;
437 int quoted, pairs, white;
439 /* Quoting can only start with a new word. */
440 start = *cpp;
441 quoted = 0;
442 if ('"' == *start) {
443 quoted = 1;
444 start++;
447 pairs = 0;
448 white = 0;
449 for (cp = start; '\0' != *cp; cp++) {
452 * Move the following text left
453 * after quoted quotes and after "\\" and "\t".
455 if (pairs)
456 cp[-pairs] = cp[0];
458 if ('\\' == cp[0]) {
460 * In copy mode, translate double to single
461 * backslashes and backslash-t to literal tabs.
463 switch (cp[1]) {
464 case ('t'):
465 cp[0] = '\t';
466 /* FALLTHROUGH */
467 case ('\\'):
468 pairs++;
469 cp++;
470 break;
471 case (' '):
472 /* Skip escaped blanks. */
473 if (0 == quoted)
474 cp++;
475 break;
476 default:
477 break;
479 } else if (0 == quoted) {
480 if (' ' == cp[0]) {
481 /* Unescaped blanks end unquoted args. */
482 white = 1;
483 break;
485 } else if ('"' == cp[0]) {
486 if ('"' == cp[1]) {
487 /* Quoted quotes collapse. */
488 pairs++;
489 cp++;
490 } else {
491 /* Unquoted quotes end quoted args. */
492 quoted = 2;
493 break;
498 /* Quoted argument without a closing quote. */
499 if (1 == quoted)
500 mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
502 /* NUL-terminate this argument and move to the next one. */
503 if (pairs)
504 cp[-pairs] = '\0';
505 if ('\0' != *cp) {
506 *cp++ = '\0';
507 while (' ' == *cp)
508 cp++;
510 *pos += (int)(cp - start) + (quoted ? 1 : 0);
511 *cpp = cp;
513 if ('\0' == *cp && (white || ' ' == cp[-1]))
514 mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
516 return(start);
519 static int
520 a2time(time_t *t, const char *fmt, const char *p)
522 struct tm tm;
523 char *pp;
525 memset(&tm, 0, sizeof(struct tm));
527 pp = NULL;
528 #ifdef HAVE_STRPTIME
529 pp = strptime(p, fmt, &tm);
530 #endif
531 if (NULL != pp && '\0' == *pp) {
532 *t = mktime(&tm);
533 return(1);
536 return(0);
539 static char *
540 time2a(time_t t)
542 struct tm *tm;
543 char *buf, *p;
544 size_t ssz;
545 int isz;
547 tm = localtime(&t);
550 * Reserve space:
551 * up to 9 characters for the month (September) + blank
552 * up to 2 characters for the day + comma + blank
553 * 4 characters for the year and a terminating '\0'
555 p = buf = mandoc_malloc(10 + 4 + 4 + 1);
557 if (0 == (ssz = strftime(p, 10 + 1, "%B ", tm)))
558 goto fail;
559 p += (int)ssz;
561 if (-1 == (isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)))
562 goto fail;
563 p += isz;
565 if (0 == strftime(p, 4 + 1, "%Y", tm))
566 goto fail;
567 return(buf);
569 fail:
570 free(buf);
571 return(NULL);
574 char *
575 mandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
577 char *out;
578 time_t t;
580 if (NULL == in || '\0' == *in ||
581 0 == strcmp(in, "$" "Mdocdate$")) {
582 mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
583 time(&t);
585 else if (a2time(&t, "%Y-%m-%d", in))
586 t = 0;
587 else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
588 !a2time(&t, "%b %d, %Y", in)) {
589 mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
590 t = 0;
592 out = t ? time2a(t) : NULL;
593 return(out ? out : mandoc_strdup(in));
597 mandoc_eos(const char *p, size_t sz)
599 const char *q;
600 int enclosed, found;
602 if (0 == sz)
603 return(0);
606 * End-of-sentence recognition must include situations where
607 * some symbols, such as `)', allow prior EOS punctuation to
608 * propagate outward.
611 enclosed = found = 0;
612 for (q = p + (int)sz - 1; q >= p; q--) {
613 switch (*q) {
614 case ('\"'):
615 /* FALLTHROUGH */
616 case ('\''):
617 /* FALLTHROUGH */
618 case (']'):
619 /* FALLTHROUGH */
620 case (')'):
621 if (0 == found)
622 enclosed = 1;
623 break;
624 case ('.'):
625 /* FALLTHROUGH */
626 case ('!'):
627 /* FALLTHROUGH */
628 case ('?'):
629 found = 1;
630 break;
631 default:
632 return(found && (!enclosed || isalnum((unsigned char)*q)));
636 return(found && !enclosed);
640 * Convert a string to a long that may not be <0.
641 * If the string is invalid, or is less than 0, return -1.
644 mandoc_strntoi(const char *p, size_t sz, int base)
646 char buf[32];
647 char *ep;
648 long v;
650 if (sz > 31)
651 return(-1);
653 memcpy(buf, p, sz);
654 buf[(int)sz] = '\0';
656 errno = 0;
657 v = strtol(buf, &ep, base);
659 if (buf[0] == '\0' || *ep != '\0')
660 return(-1);
662 if (v > INT_MAX)
663 v = INT_MAX;
664 if (v < INT_MIN)
665 v = INT_MIN;
667 return((int)v);