mandoc.c revision 371584c2eae4cf827fd406ba26c14f021adaaa70
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov/* $Id: mandoc.c,v 1.98 2015/11/12 22:44:27 schwarze Exp $ */
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * Copyright (c) 2011-2015 Ingo Schwarze <schwarze@openbsd.org>
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Permission to use, copy, modify, and distribute this software for any
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * purpose with or without fee is hereby granted, provided that the above
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * copyright notice and this permission notice appear in all copies.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amorestatic int a2time(time_t *, const char *, const char *);
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amoremandoc_escape(const char **end, const char **start, int *sz)
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * When the caller doesn't provide return storage,
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * use local storage.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Beyond the backslash, at least one input character
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * is part of the escape sequence. With one exception
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * (see below), that character won't be returned.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * First the glyphs. There are several different forms of
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * these, but each eventually returns a substring of the glyph
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Escapes taking no arguments at all.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * The \z escape is supposed to output the following
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * character without advancing the cursor position.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Since we are mostly dealing with terminal mode,
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * let us just skip the next character.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * 'X' is the trigger. These have opaque sub-strings.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* FALLTHROUGH */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * These escapes are of the form \X'Y', where 'X' is the trigger
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * and 'Y' is any string. These have opaque sub-strings.
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * The \B and \w escapes are handled in roff.c, roff_res().
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* FALLTHROUGH */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * These escapes are of the form \X'N', where 'X' is the trigger
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * and 'N' resolves to a numerical expression.
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov if (strchr(" %&()*+-./0123456789:<=>", **start)) {
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Special handling for the numbered character escape.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * XXX Do any other escapes need similar handling?
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Sizes get a special category of their own.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* See +/- counts as a sign. */
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore if ('+' == **end || '-' == **end || ASCII_HYPH == **end)
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Anything else is assumed to be a glyph.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * In this case, pass back the character after the backslash.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Read up to the terminating character,
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * paying attention to nested escapes.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Run post-processors. */
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Treat constant-width font modes
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * just like regular font modes.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore if ('B' == (*start)[0] && 'I' == (*start)[1])
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * Unicode escapes are defined in groff as \[u0000]
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * to \[u10FFFF], where the contained value must be
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * a valid Unicode codepoint. Here, however, only
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov * check the length and range.
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov if (*sz == 7 && ((*start)[1] != '1' || (*start)[2] != '0'))
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov if ((int)strspn(*start + 1, "0123456789ABCDEFabcdef")
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Parse a quoted or unquoted roff-style request or macro argument.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Return a pointer to the parsed argument, which is either the original
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * pointer or advanced by one byte in case the argument is quoted.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * NUL-terminate the argument in place.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Collapse pairs of quotes inside quoted arguments.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Advance the argument pointer to the next argument,
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * or to the NUL byte terminating the argument line.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amoremandoc_getarg(struct mparse *parse, char **cpp, int ln, int *pos)
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Quoting can only start with a new word. */
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * Move the following text left
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * after quoted quotes and after "\\" and "\t".
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * In copy mode, translate double to single
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore * backslashes and backslash-t to literal tabs.
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore /* FALLTHROUGH */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Skip escaped blanks. */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore } else if (0 == quoted) {
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Unescaped blanks end unquoted args. */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Quoted quotes collapse. */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Unquoted quotes end quoted args. */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore /* Quoted argument without a closing quote. */
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov mandoc_msg(MANDOCERR_ARG_QUOTE, parse, ln, *pos, NULL);
698f87a48e2e945bfe5493ce168e0d0ae1cedd5cGarrett D'Amore /* NUL-terminate this argument and move to the next one. */
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore *pos += (int)(cp - start) + (quoted ? 1 : 0);
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore if ('\0' == *cp && (white || ' ' == cp[-1]))
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov mandoc_msg(MANDOCERR_SPACE_EOL, parse, ln, *pos, NULL);
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amorea2time(time_t *t, const char *fmt, const char *p)
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Reserve space:
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * up to 9 characters for the month (September) + blank
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * up to 2 characters for the day + comma + blank
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * 4 characters for the year and a terminating '\0'
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov if ((ssz = strftime(p, 10 + 1, "%B ", tm)) == 0)
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * The output format is just "%d" here, not "%2d" or "%02d".
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * That's also the reason why we can't just format the
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * date as a whole with "%B %e, %Y" or "%B %d, %Y".
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * Besides, the present approach is less prone to buffer
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * overflows, in case anybody should ever introduce the bug
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov * of looking at LC_TIME.
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov if ((isz = snprintf(p, 4 + 1, "%d, ", tm->tm_mday)) == -1)
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amoremandoc_normdate(struct mparse *parse, char *in, int ln, int pos)
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov /* No date specified: use today's date. */
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov if (in == NULL || *in == '\0' || strcmp(in, "$" "Mdocdate$") == 0) {
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov mandoc_msg(MANDOCERR_DATE_MISSING, parse, ln, pos, NULL);
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov /* Valid mdoc(7) date format. */
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov if (a2time(&t, "$" "Mdocdate: %b %d %Y $", in) ||
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov /* Do not warn about the legacy man(7) format. */
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov mandoc_msg(MANDOCERR_DATE_BAD, parse, ln, pos, in);
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov /* Use any non-mdoc(7) date verbatim. */
260e9a87725c090ba5835b1f9f0b62fa2f96036fYuri Pankov const char *q;
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * End-of-sentence recognition must include situations where
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * some symbols, such as `)', allow prior EOS punctuation to
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * propagate outward.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore switch (*q) {
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * Convert a string to a long that may not be <0.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amore * If the string is invalid, or is less than 0, return -1.
95c635efb7c3b86efc493e0447eaec7aecca3f0fGarrett D'Amoremandoc_strntoi(const char *p, size_t sz, int base)
371584c2eae4cf827fd406ba26c14f021adaaa70Yuri Pankov return (int)v;