/* $Id$ */ /* * Copyright (c) 2009, 2010 Kristaps Dzonsons * * 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 AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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. */ #include #include #include #include #include "mandoc.h" #include "libroff.h" enum tbl_ident { KEY_CENTRE = 0, KEY_DELIM, KEY_EXPAND, KEY_BOX, KEY_DBOX, KEY_ALLBOX, KEY_TAB, KEY_LINESIZE, KEY_NOKEEP, KEY_DPOINT, KEY_NOSPACE, KEY_FRAME, KEY_DFRAME, KEY_MAX }; struct tbl_phrase { const char *name; int key; enum tbl_ident ident; }; /* Handle Commonwealth/American spellings. */ #define KEY_MAXKEYS 14 /* Maximum length of key name string. */ #define KEY_MAXNAME 13 /* Maximum length of key number size. */ #define KEY_MAXNUMSZ 10 static const struct tbl_phrase keys[KEY_MAXKEYS] = { { "center", TBL_OPT_CENTRE, KEY_CENTRE}, { "centre", TBL_OPT_CENTRE, KEY_CENTRE}, { "delim", 0, KEY_DELIM}, { "expand", TBL_OPT_EXPAND, KEY_EXPAND}, { "box", TBL_OPT_BOX, KEY_BOX}, { "doublebox", TBL_OPT_DBOX, KEY_DBOX}, { "allbox", TBL_OPT_ALLBOX, KEY_ALLBOX}, { "frame", TBL_OPT_BOX, KEY_FRAME}, { "doubleframe", TBL_OPT_DBOX, KEY_DFRAME}, { "tab", 0, KEY_TAB}, { "linesize", 0, KEY_LINESIZE}, { "nokeep", TBL_OPT_NOKEEP, KEY_NOKEEP}, { "decimalpoint", 0, KEY_DPOINT}, { "nospaces", TBL_OPT_NOSPACE, KEY_NOSPACE}, }; static int arg(struct tbl_node *, int, const char *, int *, enum tbl_ident); static void opt(struct tbl_node *, int, const char *, int *); static int arg(struct tbl_node *tbl, int ln, const char *p, int *pos, enum tbl_ident key) { int i; char buf[KEY_MAXNUMSZ]; while (isspace((unsigned char)p[*pos])) (*pos)++; /* Arguments always begin with a parenthesis. */ if ('(' != p[*pos]) { TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); return(0); } (*pos)++; /* * The arguments can be ANY value, so we can't just stop at the * next close parenthesis (the argument can be a closed * parenthesis itself). */ switch (key) { case (KEY_DELIM): if ('\0' == p[(*pos)++]) { TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); return(0); } if ('\0' == p[(*pos)++]) { TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); return(0); } break; case (KEY_TAB): if ('\0' != (tbl->opts.tab = p[(*pos)++])) break; TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); return(0); case (KEY_LINESIZE): for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) { buf[i] = p[*pos]; if ( ! isdigit((unsigned char)buf[i])) break; } if (i < KEY_MAXNUMSZ) { buf[i] = '\0'; tbl->opts.linesize = atoi(buf); break; } (*tbl->msg)(MANDOCERR_TBL, tbl->data, ln, *pos, NULL); return(0); case (KEY_DPOINT): if ('\0' != (tbl->opts.decimal = p[(*pos)++])) break; TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); return(0); default: abort(); /* NOTREACHED */ } /* End with a close parenthesis. */ if (')' == p[(*pos)++]) return(1); TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); return(0); } static void opt(struct tbl_node *tbl, int ln, const char *p, int *pos) { int i, sv; char buf[KEY_MAXNAME]; /* * Parse individual options from the stream as surrounded by * this goto. Each pass through the routine parses out a single * option and registers it. Option arguments are processed in * the arg() function. */ again: /* * EBNF describing this section: * * options ::= option_list [:space:]* [;][\n] * option_list ::= option option_tail * option_tail ::= [:space:]+ option_list | * ::= epsilon * option ::= [:alpha:]+ args * args ::= [:space:]* [(] [:alpha:]+ [)] */ while (isspace((unsigned char)p[*pos])) (*pos)++; /* Safe exit point. */ if (';' == p[*pos]) return; /* Copy up to first non-alpha character. */ for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) { buf[i] = (char)tolower((unsigned char)p[*pos]); if ( ! isalpha((unsigned char)buf[i])) break; } /* Exit if buffer is empty (or overrun). */ if (KEY_MAXNAME == i || 0 == i) { TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); return; } buf[i] = '\0'; while (isspace((unsigned char)p[*pos])) (*pos)++; /* * Look through all of the available keys to find one that * matches the input. FIXME: hashtable this. */ for (i = 0; i < KEY_MAXKEYS; i++) { if (strcmp(buf, keys[i].name)) continue; /* * Note: this is more difficult to recover from, as we * can be anywhere in the option sequence and it's * harder to jump to the next. Meanwhile, just bail out * of the sequence altogether. */ if (keys[i].key) tbl->opts.opts |= keys[i].key; else if ( ! arg(tbl, ln, p, pos, keys[i].ident)) return; break; } /* * Allow us to recover from bad options by continuing to another * parse sequence. */ if (KEY_MAXKEYS == i) TBL_MSG(tbl, MANDOCERR_TBLOPT, ln, sv); goto again; /* NOTREACHED */ } int tbl_option(struct tbl_node *tbl, int ln, const char *p) { int pos; /* * Table options are always on just one line, so automatically * switch into the next input mode here. */ tbl->part = TBL_PART_LAYOUT; pos = 0; opt(tbl, ln, p, &pos); /* Always succeed. */ return(1); }