version 1.8, 2011/01/09 05:38:23 |
version 1.24, 2018/12/14 05:18:03 |
|
|
/* $Id$ */ |
/* $Id$ */ |
/* |
/* |
* Copyright (c) 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> |
* Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
|
* Copyright (c) 2015 Ingo Schwarze <schwarze@openbsd.org> |
* |
* |
* Permission to use, copy, modify, and distribute this software for any |
* Permission to use, copy, modify, and distribute this software for any |
* purpose with or without fee is hereby granted, provided that the above |
* purpose with or without fee is hereby granted, provided that the above |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
*/ |
*/ |
|
#include "config.h" |
|
|
|
#include <sys/types.h> |
|
|
#include <ctype.h> |
#include <ctype.h> |
#include <stdio.h> |
#include <stdio.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <string.h> |
#include <string.h> |
|
|
#include "mandoc.h" |
#include "mandoc.h" |
#include "libroff.h" |
#include "tbl.h" |
|
#include "libmandoc.h" |
|
#include "tbl_int.h" |
|
|
enum tbl_ident { |
#define KEY_DPOINT 0 |
KEY_CENTRE = 0, |
#define KEY_DELIM 1 |
KEY_DELIM, |
#define KEY_LINESIZE 2 |
KEY_EXPAND, |
#define KEY_TAB 3 |
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 { |
struct tbl_phrase { |
const char *name; |
const char *name; |
int key; |
int key; |
enum tbl_ident ident; |
|
}; |
}; |
|
|
/* Handle Commonwealth/American spellings. */ |
static const struct tbl_phrase keys[] = { |
#define KEY_MAXKEYS 14 |
{"decimalpoint", 0}, |
|
{"delim", 0}, |
|
{"linesize", 0}, |
|
{"tab", 0}, |
|
{"allbox", TBL_OPT_ALLBOX | TBL_OPT_BOX}, |
|
{"box", TBL_OPT_BOX}, |
|
{"frame", TBL_OPT_BOX}, |
|
{"center", TBL_OPT_CENTRE}, |
|
{"centre", TBL_OPT_CENTRE}, |
|
{"doublebox", TBL_OPT_DBOX}, |
|
{"doubleframe", TBL_OPT_DBOX}, |
|
{"expand", TBL_OPT_EXPAND}, |
|
{"nokeep", TBL_OPT_NOKEEP}, |
|
{"nospaces", TBL_OPT_NOSPACE}, |
|
{"nowarn", TBL_OPT_NOWARN}, |
|
}; |
|
|
/* Maximum length of key name string. */ |
#define KEY_MAXKEYS ((int)(sizeof(keys)/sizeof(keys[0]))) |
#define KEY_MAXNAME 13 |
|
|
|
/* Maximum length of key number size. */ |
static void arg(struct tbl_node *, int, const char *, int *, int); |
#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, |
static void |
const char *, int *, enum tbl_ident); |
arg(struct tbl_node *tbl, int ln, const char *p, int *pos, int key) |
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; |
int len, want; |
char buf[KEY_MAXNUMSZ]; |
|
|
|
while (isspace((unsigned char)p[*pos])) |
while (p[*pos] == ' ' || p[*pos] == '\t') |
(*pos)++; |
(*pos)++; |
|
|
/* Arguments always begin with a parenthesis. */ |
/* Arguments are enclosed in parentheses. */ |
|
|
if ('(' != p[*pos]) { |
len = 0; |
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); |
if (p[*pos] == '(') { |
return(0); |
(*pos)++; |
|
while (p[*pos + len] != ')') |
|
len++; |
} |
} |
|
|
(*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) { |
switch (key) { |
case (KEY_DELIM): |
case KEY_DELIM: |
if ('\0' == p[(*pos)++]) { |
mandoc_msg(MANDOCERR_TBLOPT_EQN, |
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); |
ln, *pos, "%.*s", len, p + *pos); |
return(0); |
want = 2; |
} |
|
|
|
if ('\0' == p[(*pos)++]) { |
|
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); |
|
return(0); |
|
} |
|
break; |
break; |
case (KEY_TAB): |
case KEY_TAB: |
if ('\0' != (tbl->opts.tab = p[(*pos)++])) |
want = 1; |
break; |
if (len == want) |
|
tbl->opts.tab = p[*pos]; |
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); |
break; |
return(0); |
case KEY_LINESIZE: |
case (KEY_LINESIZE): |
want = 0; |
for (i = 0; i < KEY_MAXNUMSZ && p[*pos]; i++, (*pos)++) { |
break; |
buf[i] = p[*pos]; |
case KEY_DPOINT: |
if ( ! isdigit((unsigned char)buf[i])) |
want = 1; |
break; |
if (len == want) |
} |
tbl->opts.decimal = p[*pos]; |
|
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: |
default: |
abort(); |
abort(); |
/* NOTREACHED */ |
|
} |
} |
|
|
/* End with a close parenthesis. */ |
if (len == 0) |
|
mandoc_msg(MANDOCERR_TBLOPT_NOARG, ln, *pos, |
|
"%s", keys[key].name); |
|
else if (want && len != want) |
|
mandoc_msg(MANDOCERR_TBLOPT_ARGSZ, ln, *pos, |
|
"%s want %d have %d", keys[key].name, want, len); |
|
|
if (')' == p[(*pos)++]) |
*pos += len; |
return(1); |
if (p[*pos] == ')') |
|
(*pos)++; |
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos - 1); |
|
return(0); |
|
} |
} |
|
|
static void |
/* |
opt(struct tbl_node *tbl, int ln, const char *p, int *pos) |
* Parse one line of options up to the semicolon. |
|
* Each option can be preceded by blanks and/or commas, |
|
* and some options are followed by arguments. |
|
*/ |
|
void |
|
tbl_option(struct tbl_node *tbl, int ln, const char *p, int *offs) |
{ |
{ |
int i, sv; |
int i, pos, len; |
char buf[KEY_MAXNAME]; |
|
|
|
/* |
pos = *offs; |
* Parse individual options from the stream as surrounded by |
for (;;) { |
* this goto. Each pass through the routine parses out a single |
while (p[pos] == ' ' || p[pos] == '\t' || p[pos] == ',') |
* option and registers it. Option arguments are processed in |
pos++; |
* the arg() function. |
|
*/ |
|
|
|
again: /* |
if (p[pos] == ';') { |
* EBNF describing this section: |
*offs = pos + 1; |
* |
return; |
* 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])) |
/* Parse one option name. */ |
(*pos)++; |
|
|
|
/* Safe exit point. */ |
len = 0; |
|
while (isalpha((unsigned char)p[pos + len])) |
|
len++; |
|
|
if (';' == p[*pos]) |
if (len == 0) { |
return; |
mandoc_msg(MANDOCERR_TBLOPT_ALPHA, |
|
ln, pos, "%c", p[pos]); |
|
pos++; |
|
continue; |
|
} |
|
|
/* Copy up to first non-alpha character. */ |
/* Look up the option name. */ |
|
|
for (sv = *pos, i = 0; i < KEY_MAXNAME; i++, (*pos)++) { |
i = 0; |
buf[i] = tolower((unsigned char)p[*pos]); |
while (i < KEY_MAXKEYS && |
if ( ! isalpha((unsigned char)buf[i])) |
(strncasecmp(p + pos, keys[i].name, len) || |
break; |
keys[i].name[len] != '\0')) |
} |
i++; |
|
|
/* Exit if buffer is empty (or overrun). */ |
if (i == KEY_MAXKEYS) { |
|
mandoc_msg(MANDOCERR_TBLOPT_BAD, |
if (KEY_MAXNAME == i || 0 == i) { |
ln, pos, "%.*s", len, p + pos); |
TBL_MSG(tbl, MANDOCERR_TBL, ln, *pos); |
pos += len; |
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; |
continue; |
|
} |
|
|
/* |
/* Handle the option. */ |
* 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) |
pos += len; |
|
if (keys[i].key) |
tbl->opts.opts |= keys[i].key; |
tbl->opts.opts |= keys[i].key; |
else if ( ! arg(tbl, ln, p, pos, keys[i].ident)) |
else |
return; |
arg(tbl, ln, p, &pos, i); |
|
|
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); |
|
} |
} |