-/* $Id: mandoc.c,v 1.65 2012/05/31 22:38:16 schwarze Exp $ */
+/* $Id: mandoc.c,v 1.92 2015/02/20 23:55:10 schwarze Exp $ */
/*
- * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2011, 2012 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2011-2015 Ingo Schwarze <schwarze@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
#include <sys/types.h>
#include <time.h>
#include "mandoc.h"
+#include "mandoc_aux.h"
#include "libmandoc.h"
#define DATESIZE 32
const char *local_start;
int local_sz;
char term;
- enum mandoc_esc gly;
+ enum mandoc_esc gly;
/*
* When the caller doesn't provide return storage,
* these, but each eventually returns a substring of the glyph
* name.
*/
- case ('('):
+ case '(':
gly = ESCAPE_SPECIAL;
*sz = 2;
break;
- case ('['):
+ case '[':
gly = ESCAPE_SPECIAL;
- /*
- * Unicode escapes are defined in groff as \[uXXXX] to
- * \[u10FFFF], where the contained value must be a valid
- * Unicode codepoint. Here, however, only check whether
- * it's not a zero-width escape.
- */
- if ('u' == (*start)[0] && ']' != (*start)[1])
- gly = ESCAPE_UNICODE;
term = ']';
break;
- case ('C'):
+ case 'C':
if ('\'' != **start)
return(ESCAPE_ERROR);
- gly = ESCAPE_SPECIAL;
*start = ++*end;
+ gly = ESCAPE_SPECIAL;
term = '\'';
break;
+ /*
+ * Escapes taking no arguments at all.
+ */
+ case 'd':
+ /* FALLTHROUGH */
+ case 'u':
+ return(ESCAPE_IGNORE);
+
/*
* The \z escape is supposed to output the following
- * character without advancing the cursor position.
+ * character without advancing the cursor position.
* Since we are mostly dealing with terminal mode,
* let us just skip the next character.
*/
- case ('z'):
+ case 'z':
return(ESCAPE_SKIPCHAR);
/*
* Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
* 'X' is the trigger. These have opaque sub-strings.
*/
- case ('F'):
+ case 'F':
/* FALLTHROUGH */
- case ('g'):
+ case 'g':
/* FALLTHROUGH */
- case ('k'):
+ case 'k':
/* FALLTHROUGH */
- case ('M'):
+ case 'M':
/* FALLTHROUGH */
- case ('m'):
+ case 'm':
/* FALLTHROUGH */
- case ('n'):
+ case 'n':
/* FALLTHROUGH */
- case ('V'):
+ case 'V':
/* FALLTHROUGH */
- case ('Y'):
+ case 'Y':
gly = ESCAPE_IGNORE;
/* FALLTHROUGH */
- case ('f'):
+ case 'f':
if (ESCAPE_ERROR == gly)
gly = ESCAPE_FONT;
switch (**start) {
- case ('('):
+ case '(':
*start = ++*end;
*sz = 2;
break;
- case ('['):
+ case '[':
*start = ++*end;
term = ']';
break;
/*
* These escapes are of the form \X'Y', where 'X' is the trigger
* and 'Y' is any string. These have opaque sub-strings.
+ * The \B and \w escapes are handled in roff.c, roff_res().
*/
- case ('A'):
+ case 'A':
/* FALLTHROUGH */
- case ('b'):
+ case 'b':
/* FALLTHROUGH */
- case ('D'):
+ case 'D':
/* FALLTHROUGH */
- case ('o'):
+ case 'R':
/* FALLTHROUGH */
- case ('R'):
+ case 'X':
/* FALLTHROUGH */
- case ('X'):
+ case 'Z':
+ gly = ESCAPE_IGNORE;
/* FALLTHROUGH */
- case ('Z'):
- if ('\'' != **start)
+ case 'o':
+ if (**start == '\0')
return(ESCAPE_ERROR);
- gly = ESCAPE_IGNORE;
+ if (gly == ESCAPE_ERROR)
+ gly = ESCAPE_OVERSTRIKE;
+ term = **start;
*start = ++*end;
- term = '\'';
break;
/*
* These escapes are of the form \X'N', where 'X' is the trigger
* and 'N' resolves to a numerical expression.
*/
- case ('B'):
- /* FALLTHROUGH */
- case ('h'):
+ case 'h':
/* FALLTHROUGH */
- case ('H'):
+ case 'H':
/* FALLTHROUGH */
- case ('L'):
+ case 'L':
/* FALLTHROUGH */
- case ('l'):
- gly = ESCAPE_NUMBERED;
+ case 'l':
/* FALLTHROUGH */
- case ('S'):
+ case 'S':
/* FALLTHROUGH */
- case ('v'):
+ case 'v':
/* FALLTHROUGH */
- case ('w'):
- /* FALLTHROUGH */
- case ('x'):
- if ('\'' != **start)
+ case 'x':
+ if (strchr(" %&()*+-./0123456789:<=>", **start)) {
+ if ('\0' != **start)
+ ++*end;
return(ESCAPE_ERROR);
- if (ESCAPE_ERROR == gly)
- gly = ESCAPE_IGNORE;
+ }
+ gly = ESCAPE_IGNORE;
+ term = **start;
*start = ++*end;
- term = '\'';
break;
/*
* Special handling for the numbered character escape.
* XXX Do any other escapes need similar handling?
*/
- case ('N'):
+ case 'N':
if ('\0' == **start)
return(ESCAPE_ERROR);
(*end)++;
(*end)++;
return(ESCAPE_NUMBERED);
- /*
+ /*
* Sizes get a special category of their own.
*/
- case ('s'):
+ case 's':
gly = ESCAPE_IGNORE;
/* See +/- counts as a sign. */
if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
- (*end)++;
+ *start = ++*end;
switch (**end) {
- case ('('):
+ case '(':
*start = ++*end;
*sz = 2;
break;
- case ('['):
+ case '[':
*start = ++*end;
term = ']';
break;
- case ('\''):
+ case '\'':
*start = ++*end;
term = '\'';
break;
+ case '3':
+ /* FALLTHROUGH */
+ case '2':
+ /* FALLTHROUGH */
+ case '1':
+ *sz = (*end)[-1] == 's' &&
+ isdigit((unsigned char)(*end)[1]) ? 2 : 1;
+ break;
default:
*sz = 1;
break;
if ('\0' != term) {
while (**end != term) {
switch (**end) {
- case ('\0'):
+ case '\0':
return(ESCAPE_ERROR);
- case ('\\'):
+ case '\\':
(*end)++;
if (ESCAPE_ERROR ==
mandoc_escape(end, NULL, NULL))
/* Run post-processors. */
switch (gly) {
- case (ESCAPE_FONT):
- /*
- * Pretend that the constant-width font modes are the
- * same as the regular font modes.
- */
- if (2 == *sz && 'C' == **start) {
- (*start)++;
- (*sz)--;
+ case ESCAPE_FONT:
+ if (2 == *sz) {
+ if ('C' == **start) {
+ /*
+ * Treat constant-width font modes
+ * just like regular font modes.
+ */
+ (*start)++;
+ (*sz)--;
+ } else {
+ if ('B' == (*start)[0] && 'I' == (*start)[1])
+ gly = ESCAPE_FONTBI;
+ break;
+ }
} else if (1 != *sz)
break;
switch (**start) {
- case ('3'):
+ case '3':
/* FALLTHROUGH */
- case ('B'):
+ case 'B':
gly = ESCAPE_FONTBOLD;
break;
- case ('2'):
+ case '2':
/* FALLTHROUGH */
- case ('I'):
+ case 'I':
gly = ESCAPE_FONTITALIC;
break;
- case ('P'):
+ case 'P':
gly = ESCAPE_FONTPREV;
break;
- case ('1'):
+ case '1':
/* FALLTHROUGH */
- case ('R'):
+ case 'R':
gly = ESCAPE_FONTROMAN;
break;
}
break;
- case (ESCAPE_SPECIAL):
+ case ESCAPE_SPECIAL:
if (1 == *sz && 'c' == **start)
gly = ESCAPE_NOSPACE;
+ /*
+ * Unicode escapes are defined in groff as \[u0000]
+ * to \[u10FFFF], where the contained value must be
+ * a valid Unicode codepoint. Here, however, only
+ * check the length and range.
+ */
+ if (**start != 'u' || *sz < 5 || *sz > 7)
+ break;
+ if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
+ break;
+ if (*sz == 6 && (*start)[1] == '0')
+ break;
+ if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
+ + 1 == *sz)
+ gly = ESCAPE_UNICODE;
break;
default:
break;
return(gly);
}
-void *
-mandoc_calloc(size_t num, size_t size)
-{
- void *ptr;
-
- ptr = calloc(num, size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-
-void *
-mandoc_malloc(size_t size)
-{
- void *ptr;
-
- ptr = malloc(size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-
-void *
-mandoc_realloc(void *ptr, size_t size)
-{
-
- ptr = realloc(ptr, size);
- if (NULL == ptr) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(ptr);
-}
-
-char *
-mandoc_strndup(const char *ptr, size_t sz)
-{
- char *p;
-
- p = mandoc_malloc(sz + 1);
- memcpy(p, ptr, sz);
- p[(int)sz] = '\0';
- return(p);
-}
-
-char *
-mandoc_strdup(const char *ptr)
-{
- char *p;
-
- p = strdup(ptr);
- if (NULL == p) {
- perror(NULL);
- exit((int)MANDOCLEVEL_SYSERR);
- }
-
- return(p);
-}
-
/*
* Parse a quoted or unquoted roff-style request or macro argument.
* Return a pointer to the parsed argument, which is either the original
* pointer or advanced by one byte in case the argument is quoted.
- * Null-terminate the argument in place.
+ * NUL-terminate the argument in place.
* Collapse pairs of quotes inside quoted arguments.
* Advance the argument pointer to the next argument,
- * or to the null byte terminating the argument line.
+ * or to the NUL byte terminating the argument line.
*/
char *
mandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
if ('"' == *start) {
quoted = 1;
start++;
- }
+ }
pairs = 0;
white = 0;
for (cp = start; '\0' != *cp; cp++) {
- /* Move left after quoted quotes and escaped backslashes. */
+
+ /*
+ * Move the following text left
+ * after quoted quotes and after "\\" and "\t".
+ */
if (pairs)
cp[-pairs] = cp[0];
+
if ('\\' == cp[0]) {
- if ('\\' == cp[1]) {
- /* Poor man's copy mode. */
+ /*
+ * In copy mode, translate double to single
+ * backslashes and backslash-t to literal tabs.
+ */
+ switch (cp[1]) {
+ case 't':
+ cp[0] = '\t';
+ /* FALLTHROUGH */
+ case '\\':
pairs++;
cp++;
- } else if (0 == quoted && ' ' == cp[1])
+ break;
+ case ' ':
/* Skip escaped blanks. */
- cp++;
+ if (0 == quoted)
+ cp++;
+ break;
+ default:
+ break;
+ }
} else if (0 == quoted) {
if (' ' == cp[0]) {
/* Unescaped blanks end unquoted args. */
/* Quoted argument without a closing quote. */
if (1 == quoted)
- mandoc_msg(MANDOCERR_BADQUOTE, parse, ln, *pos, NULL);
+ mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);
- /* Null-terminate this argument and move to the next one. */
+ /* NUL-terminate this argument and move to the next one. */
if (pairs)
cp[-pairs] = '\0';
if ('\0' != *cp) {
*cpp = cp;
if ('\0' == *cp && (white || ' ' == cp[-1]))
- mandoc_msg(MANDOCERR_EOLNSPACE, parse, ln, *pos, NULL);
+ mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);
return(start);
}
memset(&tm, 0, sizeof(struct tm));
pp = NULL;
-#ifdef HAVE_STRPTIME
+#if HAVE_STRPTIME
pp = strptime(p, fmt, &tm);
#endif
if (NULL != pp && '\0' == *pp) {
int isz;
tm = localtime(&t);
+ if (tm == NULL)
+ return(NULL);
/*
* Reserve space:
if (NULL == in || '\0' == *in ||
0 == strcmp(in, "$" "Mdocdate$")) {
- mandoc_msg(MANDOCERR_NODATE, parse, ln, pos, NULL);
+ mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
time(&t);
}
else if (a2time(&t, "%Y-%m-%d", in))
t = 0;
else if (!a2time(&t, "$" "Mdocdate: %b %d %Y $", in) &&
!a2time(&t, "%b %d, %Y", in)) {
- mandoc_msg(MANDOCERR_BADDATE, parse, ln, pos, NULL);
+ mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);
t = 0;
}
out = t ? time2a(t) : NULL;
}
int
-mandoc_eos(const char *p, size_t sz, int enclosed)
+mandoc_eos(const char *p, size_t sz)
{
- const char *q;
- int found;
+ const char *q;
+ int enclosed, found;
if (0 == sz)
return(0);
* propagate outward.
*/
- found = 0;
+ enclosed = found = 0;
for (q = p + (int)sz - 1; q >= p; q--) {
switch (*q) {
- case ('\"'):
+ case '\"':
/* FALLTHROUGH */
- case ('\''):
+ case '\'':
/* FALLTHROUGH */
- case (']'):
+ case ']':
/* FALLTHROUGH */
- case (')'):
+ case ')':
if (0 == found)
enclosed = 1;
break;
- case ('.'):
+ case '.':
/* FALLTHROUGH */
- case ('!'):
+ case '!':
/* FALLTHROUGH */
- case ('?'):
+ case '?':
found = 1;
break;
default:
return(found && !enclosed);
}
-/*
- * Find out whether a line is a macro line or not. If it is, adjust the
- * current position and return one; if it isn't, return zero and don't
- * change the current position.
- */
-int
-mandoc_getcontrol(const char *cp, int *ppos)
-{
- int pos;
-
- pos = *ppos;
-
- if ('\\' == cp[pos] && '.' == cp[pos + 1])
- pos += 2;
- else if ('.' == cp[pos] || '\'' == cp[pos])
- pos++;
- else
- return(0);
-
- while (' ' == cp[pos] || '\t' == cp[pos])
- pos++;
-
- *ppos = pos;
- return(1);
-}
-
/*
* Convert a string to a long that may not be <0.
* If the string is invalid, or is less than 0, return -1.