diff options
Diffstat (limited to '3rd-party/xfpt/src/dot.c')
-rw-r--r-- | 3rd-party/xfpt/src/dot.c | 906 |
1 files changed, 906 insertions, 0 deletions
diff --git a/3rd-party/xfpt/src/dot.c b/3rd-party/xfpt/src/dot.c new file mode 100644 index 000000000..6bad490bd --- /dev/null +++ b/3rd-party/xfpt/src/dot.c @@ -0,0 +1,906 @@ +/************************************************* +* xfpt - Simple ASCII->Docbook processor * +*************************************************/ + +/* Copyright (c) University of Cambridge, 2008 */ +/* Written by Philip Hazel. */ + +/* This module contains code for processing a line that starts with a dot. */ + +#include "xfpt.h" + + + +/************************************************* +* Static variables * +*************************************************/ + +static uschar *circumflexes = + US"^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"; +static uschar *spaces = + US" "; +static uschar *thisdir = NULL; + + + +/************************************************* +* Read a single number argument * +*************************************************/ + +/* Several directives take just a single number as an argument. + +Argument: pointer in the input line +Returns: the number, or -1 on error +*/ + +static int +readnumber(uschar *p) +{ +int n = 0; +if (!isdigit(*p)) { error(11, thisdir); return -1; } +while (isdigit(*p)) n = n * 10 + *p++ - '0'; +while (isspace(*p)) p++; +if (*p != 0) { error(11, thisdir); return -1; } +return n; +} + + + +/************************************************* +* Skip macro lines * +*************************************************/ + +/* This function skips to the end of the current macro or to the given +terminator line. It is called only when we know we are in a macro. The current +macro line is the conditional directive. + +Arguments: + s the conditional directive + t the terminator directive +Returns: nothing +*/ + +static void +skipto(uschar *s, uschar *t) +{ +int nest = -1; +int slength = Ustrlen(s); +int tlength = Ustrlen(t); +BOOL done = macrocurrent->nextline == NULL; +while (!done) + { + uschar *p = macrocurrent->nextline->string; + done = Ustrncmp(p, t, tlength) == 0 && + (p[tlength] == 0 || isspace(p[tlength])) && + nest-- <= 0; + if (Ustrncmp(p, s, slength) == 0 && (p[slength] == 0 || isspace(p[slength]))) + nest++; + macrocurrent->nextline = macrocurrent->nextline->next; + if (macrocurrent->nextline == NULL) + { + macroexe *temp = macrocurrent; + macrocurrent = macrocurrent->prev; + free(temp); + from_type_ptr--; + break; + } + } +} + + + +/************************************************* +* Handle .arg * +*************************************************/ + +/* The .arg directive is permitted only within a macro. It must be followed by +a positive or negative number. For a positive number, if an argument of that +number was given to the macro and is not an empty string, nothing happens. +Otherwise, the macro's input is skipped, either to .endarg or to the end of the +macro. For a negative number, the test is reversed: nothing happens if that +argument was not given or is empty. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_arg(uschar *p) +{ +BOOL mustexist = TRUE; +argstr *arg; +int i, argn; + +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".arg"); return; } + +if (*p == '-') + { + mustexist = FALSE; + p++; + } + +argn = readnumber(p); +if (argn < 0) return; + +arg = macrocurrent->args; +for (i = 1; arg != NULL && i < argn; i++) arg = arg->next; + +if (mustexist != (arg != NULL && arg->string[0] != 0)) + skipto(US".arg", US".endarg"); +} + + + +/************************************************* +* Handle .eacharg * +*************************************************/ + +/* This may be followed by a number to specify which argument to start at. The +lines between this and ".endeach" are repeated for each argument. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_eacharg(uschar *p) +{ +argstr *arg; +int argn, i; + +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".eacharg"); return; } + +argn = (*p == 0)? 1 : readnumber(p); +if (argn < 0) return; + +arg = macrocurrent->args; +for (i = 1; arg != NULL && i < argn; i++) arg = arg->next; + +/* If we did not find the starting argument, skip. Otherwise, set up the +substitution for relative arguments, and remember where to come back to. */ + +if (arg == NULL) skipto(US"eacharg", US".endeach"); else + { + macro_argbase = arg; + macro_starteach = macrocurrent->nextline; + } +} + + + +/************************************************* +* Handle .echo * +*************************************************/ + +/* This directive provides a debugging and commenting facility. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_echo(uschar *p) +{ +(void)fprintf(stderr, "%s\n", p); +} + + + +/************************************************* +* Handle .endarg * +*************************************************/ + +/* We only hit this as a stand-alone directive when the argument exists and the +previous lines have been obeyed. There is nothing to do. + +Argument: the rest of the line +Returns: nothing +*/ + +static void +do_endarg(uschar *p) +{ +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".endarg"); return; } +if (*p != 0) error(19, ".endarg", p, 8, spaces, Ustrlen(p), circumflexes); +} + + + +/************************************************* +* Handle .endeach * +*************************************************/ + +/* This marks the end of an ".eacharg" section of lines. Advance the relative +argument base pointer by the given number (default 1). If there are still some +arguments left, repeat the section. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_endeach(uschar *p) +{ +int count; + +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".endeach"); return; } + +count = (*p == 0)? 1 : readnumber(p); +if (count < 0) return; + +while (count-- > 0 && macro_argbase != NULL) + macro_argbase = macro_argbase->next; + +if (macro_argbase == NULL) macro_starteach = NULL; + else macrocurrent->nextline = macro_starteach; +} + + + + +/************************************************* +* Handle .endinliteral * +*************************************************/ + +/* We only hit this as a stand-alone directive when in a literal section and +the previous lines have been obeyed. There is nothing to do. + +Argument: the rest of the line +Returns: nothing +*/ + +static void +do_endinliteral(uschar *p) +{ +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".endinliteral"); return; } +if (*p != 0) error(19, ".endinliteral", p, 8, spaces, Ustrlen(p), circumflexes); +} + + + +/************************************************* +* Handle .flag * +*************************************************/ + +/* The .flag directive defines either a single flag (starting with &) or a pair +of flags, the first of which must start with &. We put the data into a block +that's added to the flaglist chain. We have to cope with all these (example) +possibilities: + + .flag &+ "stuff" + .flag &" "stuff" + .flag &" "& "stuff1" "stuff2" + +Argument: the rest of the command line +Returns: nothing +*/ + +static void +do_flag(uschar *p) +{ +uschar *pp, *q; +int length, term; +flagstr *f, **ff; + +f = misc_malloc(sizeof(flagstr)); + +/* Check that the flag starts with & and then get a copy of the rest of it. */ + +if (*p++ != '&') { error(9); return; } + +for (pp = p; *pp != 0 && !isspace(*pp); pp++); +length = pp - p; +if (length == 0) { error(10); return; } + +f->length1 = length; +f->flag1 = misc_copystring(p, length); + +/* Now look backwards from the end of the line and find the last quoted string +that is there. */ + +q = pp + Ustrlen(pp); +if (*(--q) != '\"' && *q != '\'') { error(11, thisdir); return; } + +term = *q; +while (--q > pp) + { + if (*q == term) { if (q[-1] == term) q--; else break; } + } + +if (q <= pp) { error(11, thisdir); return; } + +/* If there's nothing between pp and q, we have the definition of a single, +non-paired flag. */ + +while (isspace(*pp)) pp++; +if (pp == q) + { + f->rep1 = misc_readstring(q, NULL, NULL, 0); + f->length2 = 0; + f->flag2 = f->rep2 = NULL; + } + +/* Otherwise, we are dealing with a pair of flags. */ + +else + { + f->rep2 = misc_readstring(q, NULL, NULL, 0); + p = pp; + while (*pp != 0 && !isspace(*pp)) pp++; + length = pp - p; + if (length == 0) { error(10); return; } + f->length2 = length; + f->flag2 = misc_copystring(p, length); + while (isspace(*pp)) pp++; + if (*pp != '\"' && *pp != '\'') { error(11, thisdir); return; } + f->rep1 = misc_readstring(pp, &length, NULL, 0); + if (pp + length >= q) { error(11, thisdir); return; } + } + +/* Successfully defined the flag(s). Attach the block to the chain. The order +is not defined, except that the longer (initial) flag comes first. */ + +ff = &flaglist; +while (*ff != NULL && f->length1 < (*ff)->length1) ff = &((*ff)->next); +f->next = *ff; +*ff = f; +} + + +/************************************************* +* Handle .include * +*************************************************/ + +/* Add to the stack of included files and push a new input type onto the +from_type stack. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_include(uschar *p) +{ +istackstr *ist; + +ist = misc_malloc(sizeof(istackstr)); +ist->prev = istack; +istack = ist; +ist->linenumber = 0; + +if (Ustrchr(p, '/') != NULL) Ustrcpy(ist->filename, p); + else sprintf(CS ist->filename, "%s/%s", xfpt_share, p); + +ist->file = Ufopen(ist->filename, "rb"); +if (ist->file == NULL) error(0, ist->filename, strerror(errno)); /* Hard */ + +from_type[++from_type_ptr] = FROM_FILE; +} + + + +/************************************************* +* Handle .inliteral * +*************************************************/ + +/* The .inliteral directive is permitted only within a macro. If we are +handling the appropriate kind of literal text, nothing happens. Otherwise, the +macro's input is skipped, either to .endinliteral or to the end of the macro. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_inliteral(uschar *p) +{ +int state = -1; +if (from_type[from_type_ptr] != FROM_MACRO) { error(15, US".inliteral"); return; } +if (Ustrcmp(p, "layout") == 0) state = LITERAL_LAYOUT; +else if (Ustrcmp(p, "text") == 0) state = LITERAL_TEXT; +else if (Ustrcmp(p, "off") == 0) state = LITERAL_OFF; +else if (Ustrcmp(p, "xml") == 0) state = LITERAL_XML; +else error(5, p); +if (literal_state != state) skipto(US"inliteral", US".endinliteral"); +} + + + + +/************************************************* +* Handle .literal * +*************************************************/ + +/* +Argument: a single argument string +Returns: nothing +*/ + +static void +do_literal(uschar *p) +{ +if (Ustrcmp(p, "layout") == 0) literal_state = LITERAL_LAYOUT; +else if (Ustrcmp(p, "text") == 0) literal_state = LITERAL_TEXT; +else if (Ustrcmp(p, "off") == 0) literal_state = LITERAL_OFF; +else if (Ustrcmp(p, "xml") == 0) literal_state = LITERAL_XML; +else error(5, p); +} + + +/************************************************* +* Handle .macro * +*************************************************/ + +/* We set up a macro definition, whose contents are all the following lines, +uninterpreted, until we reach .endmacro. + +Argument: the rest of the .macro line +Returns: nothing +*/ + +static void +do_macro(uschar *p) +{ +int length; +int nest = 0; +argstr **pp; +macrodef *md = misc_malloc(sizeof(macrodef)); + +md->name = misc_readitem(p, NULL, &length, NULL, 0); +md->namelength = Ustrlen(md->name); +p += length; + +if (length == 0) + { + error(14); + return; + } + +md->lines = md->args = NULL; +md->next = macrolist; +macrolist = md; + +pp = &(md->args); +while (*p != 0) + { + argstr *as = misc_malloc(sizeof(argstr)); + as->next = NULL; + *pp = as; + pp = &(as->next); + as->string = misc_readitem(p, NULL, &length, NULL, 0); + p += length; + } + +pp = &(md->lines); +for (;;) + { + argstr *as; + uschar *line = read_nextline(); + if (line == NULL) { error(13, ".endmacro"); return; } + + if (Ustrncmp(line, ".macro ", 7) == 0) nest++; + else if (Ustrncmp(line, ".endmacro", 9) == 0) + { + if (isspace(line[9]) || line[9] == '\n') + if (--nest < 0) break; + } + + as = misc_malloc(sizeof(argstr)); + as->next = NULL; + *pp = as; + pp = &(as->next); + as->string = misc_copystring(line, Ustrlen(line)); + } + +/* If there aren't any replacement lines, fake up a comment so that there's +always something for a macro to generate. */ + +if (md->lines == NULL) + { + md->lines = misc_malloc(sizeof(argstr)); + md->lines->next = NULL; + md->lines->string = misc_copystring(US". Dummy line\n", 13); + } +} + + + +/************************************************* +* Handle .nest * +*************************************************/ + +/* This processing happens when .nest is encountered when not in the middle of +reading a normal paragraph. That is, it's either between paragraphs or in a +literal section. Otherwise .nest is handled in read_paragraph(). + +Argument: the rest of the line +Returns: nothing +*/ + +static void +do_nest(uschar *p) +{ +if (Ustrcmp(p, "begin") == 0) + { + if (nest_level >= MAXNEST) error(27); else + { + nest_literal_stack[nest_level++] = literal_state; + literal_state = LITERAL_OFF; + } + } +else if (Ustrcmp(p, "end") == 0) + { + if (nest_level <= 0) error(28); + else literal_state = nest_literal_stack[--nest_level]; + } +else error(26, p); +} + + + + +/************************************************* +* Handle .nonl * +*************************************************/ + +/* The argument is handled as a line without a terminating newline by putting +it into a buffer and pointing next_line at it. + +Argument: the rest of the line +Returns: nothing +*/ + +static void +do_nonl(uschar *p) +{ +static uschar nonlbuffer[INBUFFSIZE]; +int len = Ustrlen(p) + 1; +if (len > INBUFFSIZE) len = INBUFFSIZE; +Ustrncpy(nonlbuffer, p, len); +next_line = nonlbuffer; +} + + +/************************************************* +* Handle .pop * +*************************************************/ + +/* This may optionally be followed by an upper case letter identifier. This +causes a search down the stack for an item with that letter. If one is found, +we arrange for the stack to pop back to it. If not, nothing happens. If no +letter is specified, arrange to pop just one item. + +Argument: a single argument string +Returns: nothing +*/ + +static void +do_pop(uschar *p) +{ +pushstr *ps; + +if (*p == 0) + { + popto = 0; + return; + } + +if (!isupper(*p) || p[1] != 0) { error(11, thisdir); return; } + +for (ps = pushed; ps != NULL; ps = ps->next) + { if (ps->letter == *p) break; } + +if (ps != NULL) popto = *p; +} + + + +/************************************************* +* Handle .push * +*************************************************/ + +/* This directive pushes the rest of the line onto a stack. If the first thing +on the line is a single upper case letter followed by space, we set that as the +stack marker letter. Following that we either have a quoted item, or the rest +of the line unquoted. After a quoted item, the word "check" means we should +record the base file line number for a warning if the item is not popped by the +end of the input. + +Argument: the rest of the line +Returns: nothing +*/ + +static void +do_push(uschar *p) +{ +int length; +int letter = 0; +int check = 0; +pushstr *ps; +uschar *macname = NULL; +uschar *porig = p; +uschar buffer[INBUFFSIZE]; + +if (isupper(*p) && (p[1] == 0 || isspace(p[1]))) + { + letter = *p++; + while (isspace(*p)) p++; + } + +if (*p == '"') + { + uschar *s = misc_readitem(p, NULL, &length, buffer, INBUFFSIZE); + p += length; + while (isspace(*p)) p++; + if (Ustrncmp(p, "check", 5) == 0 && (p[5] == 0 || isspace(p[5]))) + { + p += 5; + while (isspace(*p)) p++; + check = istackbase->linenumber; + if (from_type[from_type_ptr] == FROM_MACRO) + macname = macrocurrent->macro->name; + } + if (*p != 0) error(19, ".push", porig, p - porig + 6, spaces, Ustrlen(p), + circumflexes); + p = s; + } + +length = Ustrlen(p); +ps = misc_malloc(sizeof(pushstr) + length); +ps->letter = letter; +ps->check = check; +ps->macname = macname; +memcpy(ps->string, p, length); +ps->string[length] = 0; +ps->next = pushed; +pushed = ps; +} + + + + +/************************************************* +* Handle .revision * +*************************************************/ + +/* Set or unset a text for <revisionflag= in many elements. If the text if +"off", treat it as empty. + +Arguments: a single argument string +Returns: nothing +*/ + +static void +do_revision(uschar *p) +{ +if (revision != NULL) + { + free(revision); + revision = NULL; + } + +if (*p != 0 && Ustrcmp(p, "off") != 0) + { + revision = misc_malloc(Ustrlen(p) + 1); + Ustrcpy(revision, p); + } +} + + + + +/************************************************* +* Handle .set * +*************************************************/ + +/* Set the value of a locally-defined entity. + +Arguments: the rest of the command line +Returns: nothing +*/ + +static void +do_set(uschar *p) +{ +int length; +tree_node *t; +uschar *porig = p; +uschar buffer[INBUFFSIZE]; +uschar *name = misc_readitem(p, NULL, &length, buffer, INBUFFSIZE); + +p += length; +while (isspace(*p)) p++; + +t = misc_malloc(sizeof(tree_node) + Ustrlen(name)); +Ustrcpy(t->name, name); + +t->data = misc_readitem(p, NULL, &length, NULL, 0); + +p += length; +while (isspace(*p)) p++; +if (*p != 0) error(19, ".set", porig, p - porig + 5, spaces, Ustrlen(p), + circumflexes); + +if (!tree_insertnode(&entities, t)) error(21, name); +} + + + +/************************************************* +* Table of directives * +*************************************************/ + +/* Quite a few directives have a single argument that can either be quoted, or +just the rest of the line. These are flagged up so that the code to read that +argument can be central. Only those directives that control macro behaviour are +permitted in macros that are called inline. */ + +typedef struct dirstr { + uschar *name; + int length; + void (*function)(uschar *); + BOOL onearg; + BOOL okinline; +} dirstr; + + +static dirstr dirs[] = { + { US".arg", 4, do_arg, TRUE, TRUE }, + { US".eacharg", 8, do_eacharg, TRUE, TRUE }, + { US".echo", 5, do_echo, TRUE, FALSE }, + { US".endarg", 7, do_endarg, FALSE, TRUE }, + { US".endeach", 8, do_endeach, TRUE, TRUE }, + { US".endinliteral", 13, do_endinliteral, FALSE, TRUE }, + { US".flag", 5, do_flag, FALSE, FALSE }, + { US".include", 8, do_include, TRUE, FALSE }, + { US".inliteral", 10, do_inliteral, TRUE, TRUE }, + { US".literal", 8, do_literal, TRUE, FALSE }, + { US".macro", 6, do_macro, FALSE, FALSE }, + { US".nest", 5, do_nest, TRUE, FALSE }, + { US".nonl", 5, do_nonl, TRUE, FALSE }, + { US".pop", 4, do_pop, TRUE, FALSE }, + { US".push", 5, do_push, FALSE, FALSE }, + { US".revision", 9, do_revision, TRUE, FALSE }, + { US".set", 4, do_set, FALSE, FALSE }, +}; + +static int cmdcount = sizeof(dirs)/sizeof(dirstr); + + +/************************************************* +* Process a line starting with a dot * +*************************************************/ + +/* There are a small number of built-in commands, but many more may be defined +as macros. When we are in literal text or literal xml states, unknown lines +starting with dot are treated as data. + +Argument: the line to be processed +Returns: nothing +*/ + +void +dot_process(uschar *p) +{ +macrodef *md; +macroexe *me; +argstr **pp; +int top, bot, length; + +thisdir = p; /* Save for error messages */ + +misc_detrail(p); + +if (p[1] == 0 || isspace(p[1])) return; /* Comment */ + +/* Seek a built-in directive by binary chop. */ + +bot = 0; +top = cmdcount; + +while (top > bot) + { + int c; + int mid = (top + bot)/2; + dirstr *dir = dirs + mid; + + length = dir->length; + c = Ustrncmp(p, dir->name, length); + + /* Found a built-in directive; if it takes a single argument, read it here + to avoid repeating the code in the individual directive functions. If there + is text after the argument, give a warning. */ + + if (c == 0 && (p[length] == 0 || isspace(p[length]))) + { + uschar buffer[INBUFFSIZE]; + p += length; + while (isspace(*p)) p++; + + if (dir->onearg) + { + int alength; + uschar *s = misc_readitem(p, NULL, &alength, buffer, INBUFFSIZE); + if (p[alength] != 0) + error(19, dir->name, p, alength + length + 1, spaces, + Ustrlen(p) - alength, circumflexes); + p = s; + } + + /* If we are in an inline macro, only certain directives are permitted */ + + if (para_inline_macro == 0 || dir->okinline) (dir->function)(p); + else error(22, dir->name); + + return; /* Dealt with this directive line */ + } + + /* No match; do the chop and continue */ + + if (c < 0) top = mid; else bot = mid + 1; + } + +/* If we can't match a built-in directive, we normally seek a macro. However, +this is not permitted if we are already expanding an inline macro call. */ + +if (para_inline_macro > 0) + { + error(22, p); + return; + } + +for (md = macrolist; md != NULL; md = md->next) + { + length = md->namelength; + if (Ustrncmp(p+1, md->name, length) == 0 && + (p[length+1] == 0 || isspace(p[length+1]))) + { + p += length + 1; + break; + } + } + +/* Could not find a macro. In literal text and xml states, treat as data. Note +that the newline has been removed. */ + +if (md == NULL) + { + switch(literal_state) + { + case LITERAL_TEXT: + case LITERAL_XML: + fprintf(outfile, "%s\n", CS p); + break; + + default: + error(2, p); + break; + } + return; + } + +/* Found a macro. Add it to the chain, and set the input type. */ + +me = misc_malloc(sizeof(macroexe)); +me->prev = macrocurrent; +macrocurrent = me; +me->macro = md; +me->nextline = md->lines; +from_type[++from_type_ptr] = FROM_MACRO; + +me->args = NULL; +pp = &(me->args); + +while (isspace(*p)) p++; +while (*p != 0) + { + argstr *as = misc_malloc(sizeof(argstr)); + as->next = NULL; + *pp = as; + pp = &(as->next); + as->string = misc_readitem(p, NULL, &length, NULL, 0); + p += length; + } +} + +/* End of dot.c */ |