mandoc.c revision 95c635efb7c3b86efc493e0447eaec7aecca3f0f
/* $Id: mandoc.c,v 1.62 2011/12/03 16:08:51 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
* Copyright (c) 2011 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
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* 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 <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "mandoc.h"
#include "libmandoc.h"
#define DATESIZE 32
static int numescape(const char *);
/*
* Pass over recursive numerical expressions. This context of this
* function is important: it's only called within character-terminating
* escapes (e.g., \s[xxxyyy]), so all we need to do is handle initial
* recursion: we don't care about what's in these blocks.
* This returns the number of characters skipped or -1 if an error
* occurs (the caller should bail).
*/
static int
{
int i;
const char *cp;
i = 0;
/* The expression consists of a subexpression. */
if ('\\' == start[i]) {
/*
* Read past the end of the subexpression.
* Bail immediately on errors.
*/
return(-1);
}
if ('(' != start[i++])
return(0);
/*
* A parenthesised subexpression. Read until the closing
* parenthesis, making sure to handle any nested subexpressions
* that might ruin our parse.
*/
while (')' != start[i]) {
i += (int)sz;
if ('\0' == start[i])
return(-1);
else if ('\\' != start[i])
continue;
return(-1);
}
/* Read past the terminating ')'. */
return(++i);
}
enum mandoc_esc
{
enum mandoc_esc gly;
if (start)
i = lim = 0;
gly = ESCAPE_ERROR;
switch ((c = cp[i++])) {
/*
* First the glyphs. There are several different forms of
* these, but each eventually returns a substring of the glyph
* name.
*/
case ('('):
lim = 2;
break;
case ('['):
/*
* 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.
*/
term = ']';
break;
case ('C'):
if ('\'' != cp[i])
return(ESCAPE_ERROR);
term = '\'';
break;
/*
* Handle all triggers matching \X(xy, \Xx, and \X[xxxx], where
* 'X' is the trigger. These have opaque sub-strings.
*/
case ('F'):
/* FALLTHROUGH */
case ('g'):
/* FALLTHROUGH */
case ('k'):
/* FALLTHROUGH */
case ('M'):
/* FALLTHROUGH */
case ('m'):
/* FALLTHROUGH */
case ('n'):
/* FALLTHROUGH */
case ('V'):
/* FALLTHROUGH */
case ('Y'):
gly = ESCAPE_IGNORE;
/* FALLTHROUGH */
case ('f'):
if (ESCAPE_ERROR == gly)
gly = ESCAPE_FONT;
if (start)
switch (cp[i++]) {
case ('('):
lim = 2;
break;
case ('['):
term = ']';
break;
default:
lim = 1;
i--;
break;
}
break;
/*
* These escapes are of the form \X'Y', where 'X' is the trigger
* and 'Y' is any string. These have opaque sub-strings.
*/
case ('A'):
/* FALLTHROUGH */
case ('b'):
/* FALLTHROUGH */
case ('D'):
/* FALLTHROUGH */
case ('o'):
/* FALLTHROUGH */
case ('R'):
/* FALLTHROUGH */
case ('X'):
/* FALLTHROUGH */
case ('Z'):
if ('\'' != cp[i++])
return(ESCAPE_ERROR);
gly = ESCAPE_IGNORE;
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'):
/* FALLTHROUGH */
case ('H'):
/* FALLTHROUGH */
case ('L'):
/* FALLTHROUGH */
case ('l'):
/* FALLTHROUGH */
case ('S'):
/* FALLTHROUGH */
case ('v'):
/* FALLTHROUGH */
case ('w'):
/* FALLTHROUGH */
case ('x'):
if (ESCAPE_ERROR == gly)
gly = ESCAPE_IGNORE;
if ('\'' != cp[i++])
return(ESCAPE_ERROR);
break;
/*
* Special handling for the numbered character escape.
* XXX Do any other escapes need similar handling?
*/
case ('N'):
if ('\0' == cp[i])
return(ESCAPE_ERROR);
return(ESCAPE_IGNORE);
(*end)++;
if (start)
if (sz)
if ('\0' != **end)
(*end)++;
return(ESCAPE_NUMBERED);
/*
* Sizes get a special category of their own.
*/
case ('s'):
gly = ESCAPE_IGNORE;
if (start)
/* See +/- counts as a sign. */
c = cp[i];
++i;
switch (cp[i++]) {
case ('('):
lim = 2;
break;
case ('['):
break;
case ('\''):
break;
default:
lim = 1;
i--;
break;
}
/* See +/- counts as a sign. */
c = cp[i];
++i;
break;
/*
* Anything else is assumed to be a glyph.
*/
default:
lim = 1;
i--;
break;
}
if (start)
/*
* If a terminating block has been specified, we need to
* handle the case of recursion, which could have their
* own terminating blocks that mess up our parse. This, by the
* way, means that the "start" and "size" values will be
* effectively meaningless.
*/
ssz = 0;
return(ESCAPE_ERROR);
i += ssz;
rlim = -1;
/*
* We have a character terminator. Try to read up to that
* character. If we can't (i.e., we hit the nil), then return
* an error; if we can, calculate our length, read past the
* terminating character, and exit.
*/
if ('\0' != term) {
if ('\0' == *end)
return(ESCAPE_ERROR);
if (sz)
(*end)++;
goto out;
}
/*
* We have a numeric limit. If the string is shorter than that,
* stop and return an error. Else adjust our endpoint, length,
* and return the current glyph.
*/
return(ESCAPE_ERROR);
if (sz)
out:
/* Run post-processors. */
switch (gly) {
case (ESCAPE_FONT):
/*
* Pretend that the constant-width font modes are the
* same as the regular font modes.
*/
rstart++;
else if (1 != rlim)
break;
switch (*rstart) {
case ('3'):
/* FALLTHROUGH */
case ('B'):
break;
case ('2'):
/* FALLTHROUGH */
case ('I'):
break;
case ('P'):
break;
case ('1'):
/* FALLTHROUGH */
case ('R'):
break;
}
break;
case (ESCAPE_SPECIAL):
if (1 != rlim)
break;
if ('c' == *rstart)
break;
default:
break;
}
return(gly);
}
void *
{
void *ptr;
exit((int)MANDOCLEVEL_SYSERR);
}
return(ptr);
}
void *
{
void *ptr;
exit((int)MANDOCLEVEL_SYSERR);
}
return(ptr);
}
void *
{
exit((int)MANDOCLEVEL_SYSERR);
}
return(ptr);
}
char *
{
char *p;
p[(int)sz] = '\0';
return(p);
}
char *
mandoc_strdup(const char *ptr)
{
char *p;
if (NULL == p) {
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.
* Collapse pairs of quotes inside quoted arguments.
* Advance the argument pointer to the next argument,
* or to the null byte terminating the argument line.
*/
char *
{
/* Quoting can only start with a new word. */
quoted = 0;
if ('"' == *start) {
quoted = 1;
start++;
}
pairs = 0;
white = 0;
/* Move left after quoted quotes and escaped backslashes. */
if (pairs)
if ('\\' == cp[0]) {
/* Poor man's copy mode. */
pairs++;
cp++;
/* Skip escaped blanks. */
cp++;
} else if (0 == quoted) {
if (' ' == cp[0]) {
/* Unescaped blanks end unquoted args. */
white = 1;
break;
}
} else if ('"' == cp[0]) {
/* Quoted quotes collapse. */
pairs++;
cp++;
} else {
/* Unquoted quotes end quoted args. */
quoted = 2;
break;
}
}
}
/* Quoted argument without a closing quote. */
if (1 == quoted)
/* Null-terminate this argument and move to the next one. */
if (pairs)
if ('\0' != *cp) {
*cp++ = '\0';
while (' ' == *cp)
cp++;
}
return(start);
}
static int
{
char *pp;
#ifdef HAVE_STRPTIME
#endif
return(1);
}
return(0);
}
static char *
{
char *buf, *p;
int isz;
/*
* Reserve space:
* up to 9 characters for the month (September) + blank
* up to 2 characters for the day + comma + blank
* 4 characters for the year and a terminating '\0'
*/
goto fail;
p += (int)ssz;
goto fail;
p += isz;
goto fail;
return(buf);
fail:
return(NULL);
}
char *
{
char *out;
time_t t;
time(&t);
}
t = 0;
t = 0;
}
}
int
{
const char *q;
int found;
if (0 == sz)
return(0);
/*
* End-of-sentence recognition must include situations where
* some symbols, such as `)', allow prior EOS punctuation to
* propagate outward.
*/
found = 0;
for (q = p + (int)sz - 1; q >= p; q--) {
switch (*q) {
case ('\"'):
/* FALLTHROUGH */
case ('\''):
/* FALLTHROUGH */
case (']'):
/* FALLTHROUGH */
case (')'):
if (0 == found)
enclosed = 1;
break;
case ('.'):
/* FALLTHROUGH */
case ('!'):
/* FALLTHROUGH */
case ('?'):
found = 1;
break;
default:
}
}
}
/*
* 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
{
int pos;
pos += 2;
pos++;
else
return(0);
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.
*/
int
{
char buf[32];
char *ep;
long v;
if (sz > 31)
return(-1);
errno = 0;
return(-1);
if (v > INT_MAX)
v = INT_MAX;
if (v < INT_MIN)
v = INT_MIN;
return((int)v);
}