261 lines
5.8 KiB
C
261 lines
5.8 KiB
C
|
/* $Vendor-Id: tbl_opts.c,v 1.8 2011/01/09 05:38:23 joerg Exp $ */
|
||
|
/*
|
||
|
* Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
|
||
|
*
|
||
|
* 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 <ctype.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#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] = 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);
|
||
|
}
|