Add '3rd-party/xfpt/' from commit '24eaa721effcf2f56d1da62344ee27ac9721d3ec'
[exim.git] / 3rd-party / xfpt / src / read.c
diff --git a/3rd-party/xfpt/src/read.c b/3rd-party/xfpt/src/read.c
new file mode 100644 (file)
index 0000000..4aa8757
--- /dev/null
@@ -0,0 +1,388 @@
+/*************************************************
+*     xfpt - Simple ASCII->Docbook processor     *
+*************************************************/
+
+/* Copyright (c) University of Cambridge, 2010 */
+/* Written by Philip Hazel. */
+
+/* This module contains code for reading the input. */
+
+#include "xfpt.h"
+
+
+
+
+/*************************************************
+*             Process macro line                 *
+*************************************************/
+
+/* This is the place where macro arguments are substituted. In a section
+delimited by .eacharg/.endeach, the variable macro_argbase is set to the first
+of the relative arguments. This function is also called from para.c in order to
+handle inline macro calls.
+
+Arguments:
+  p         the macro input line
+  b         where to put the result
+
+Returns:    nothing
+*/
+
+void
+read_process_macroline(uschar *p, uschar *b)
+{
+int optend = 0;
+
+while (*p != 0)
+  {
+  int i;
+  int argn = 0;
+  argstr *argbase, *arg;
+
+  /* If we are including an optional substring, when we get to the terminator,
+  just skip it. */
+
+  if (*p == optend)
+    {
+    optend = 0;
+    p++;
+    continue;
+    }
+
+  /* Until we hit a dollar, just copy verbatim */
+
+  if (*p != '$') { *b++ = *p++; continue; }
+
+  /* If the character after $ is another $, insert a literal $. */
+
+  if (p[1] == '$') { p++; *b++ = *p++; continue; }
+
+  /* If the character after $ is +, we are dealing with arguments
+  relative to macro_arg0 in a ".eacharg" section. Otherwise, we are dealing
+  with an absolute argument number. */
+
+  if (p[1] == '+')
+    {
+    p++;
+    if (macro_argbase == NULL)       /* Not in a .eacharg section */
+      {
+      error(18);
+      *b++ = '+';
+      *b++ = *p++;
+      continue;
+      }
+    argbase = macro_argbase;
+    }
+  else argbase = macrocurrent->args;
+
+  /* $= introduces an optional substring */
+
+  if (p[1] == '=')
+    {
+    p++;
+    if (!isdigit(p[1]))
+      {
+      error(17, p[1], "$=");
+      *b++ = '$';
+      *b++ = *p++;
+      continue;
+      }
+    while (isdigit(*(++p))) argn = argn * 10 + *p - '0';
+
+    optend = *p++;
+
+    arg = argbase;
+    for (i = 1; i < argn; i++)
+      {
+      if (arg == NULL) break;
+      arg = arg->next;
+      }
+
+    if (arg == NULL || arg->string[0] == 0)
+      {
+      while (*p != 0 && *p != optend) p++;
+      if (*p == optend) p++;
+      }
+
+    continue;
+    }
+
+  /* Not '=' after $; this is an argument substitution */
+
+  if (!isdigit(p[1]))
+    {
+    error(17, p[1], "$");
+    *b++ = *p++;
+    continue;
+    }
+
+  while (isdigit(*(++p))) argn = argn * 10 + *p - '0';
+
+  /* Handle $0 - currently no meaning */
+
+  if (argn == 0)
+    {
+    continue;
+    }
+
+  /* Seek an argument in this invocation */
+
+  arg = argbase;
+  for (i = 1; i < argn; i++)
+    {
+    if (arg == NULL) break;
+    arg = arg->next;
+    }
+
+  /* If not found, seek a default argument for an absolute substitution, but
+  not for a relative one. */
+
+  if (arg == NULL && argbase == macrocurrent->args)
+    {
+    arg = macrocurrent->macro->args;
+    for (i = 1; i < argn; i++)
+      {
+      if (arg == NULL) break;
+      arg = arg->next;
+      }
+    }
+
+  /* If we have found an argument, substitute it. */
+
+  if (arg != NULL) b += sprintf(CS b, "%s", arg->string);
+  }
+
+*b = 0;
+}
+
+
+
+/*************************************************
+*         Get the next line of input             *
+*************************************************/
+
+/* There may be a saved line already in the buffer, following the reading of a
+paragraph or a .nonl directive. Otherwise, take the next line from one of three
+sources:
+
+  (1) If popto is not negative, get an appropropriate line off the stack.
+  (2) If we are in a macro, get the next macro line.
+  (3) If we are in a file, read a new line from a file and handle any
+      continuations.
+
+There can be arbitrary nesting of macros and files, because a .include
+directive may appear inside a macro. The current from_type vector is used to
+keep track of what is current.
+
+Arguments:  none
+Returns:    pointer to the next line or NULL
+*/
+
+uschar *
+read_nextline(void)
+{
+int len;
+uschar *p, *q;
+
+/* Handle a dot line that terminated a paragraph, or a .nonl line */
+
+if (next_line != NULL)
+  {
+  uschar *yield = next_line;
+  next_line = NULL;
+  return yield;
+  }
+
+/* Handle a line off the stack */
+
+if (popto == 0)
+  {
+  pushstr *ps = pushed;
+  if (ps == NULL) error(12); else
+    {
+    popto = -1;
+    (void)sprintf(CS inbuffer, "%s\n", ps->string);
+    pushed = ps->next;
+    free(ps);
+    return inbuffer;
+    }
+  }
+
+/* Handle a line off the stack when there is a matching line at the top or
+below for the given letter. When we reach the matching line, stop popping. The
+value of popto is set greater than zero only when it is known that there's a
+matching line. */
+
+if (popto > 0)
+  {
+  pushstr *ps = pushed;
+  if (ps->letter == popto) popto = -1;
+  (void)sprintf(CS inbuffer, "%s\n", ps->string);
+  pushed = ps->next;
+  free(ps);
+  return inbuffer;
+  }
+
+/* Get the next line from the current macro or the current file. We need a loop
+for handling the ends of macros and files. First check for having previously
+reached the end of the input. */
+
+if (from_type_ptr < 0) return NULL;
+
+for (;;)
+  {
+  if (from_type[from_type_ptr] == FROM_MACRO)
+    {
+    if (macrocurrent->nextline == NULL)
+      {
+      macroexe *temp = macrocurrent;
+      macrocurrent = macrocurrent->prev;
+      free(temp);
+      }
+    else
+      {
+      read_process_macroline(macrocurrent->nextline->string, inbuffer);
+      macrocurrent->nextline = macrocurrent->nextline->next;
+      break;
+      }
+    }
+
+  /* When reading from a file, handle continuation lines, but only within the
+  single file. */
+
+  else
+    {
+    if (Ufgets(inbuffer, INBUFFSIZE, istack->file) == NULL)
+      {
+      istackstr *prev = istack->prev;
+      fclose(istack->file);
+      free(istack);
+      istack = prev;
+      }
+    else
+      {
+      istack->linenumber++;
+
+      q = inbuffer;
+      len = Ustrlen(q);
+
+      for (;;)
+        {
+        p = q + len;
+        while (p > q && isspace(p[-1])) p--;
+
+        if (p - q < 3 || Ustrncmp(p - 3, "&&&", 3) != 0) break;
+
+        q = p - 3;
+        *q = 0;
+
+        if (istack == NULL ||
+            Ufgets(q, INBUFFSIZE - (q - inbuffer), istack->file) == NULL)
+          break;
+
+        istack->linenumber++;
+        p = q;
+        while (*p == ' ' || *p == '\t') p++;
+        len = Ustrlen(p);
+        if (p > q) memmove(q, p, len + 1);
+        }
+
+      break;
+      }
+    }
+
+  /* We get here if the end of a macro or a file was reached. The appropriate
+  chain has been popped. Back up the stack of input types before the loop
+  repeats. When we reach the end of the stack, we have reached the end of all
+  the input. */
+
+  if (--from_type_ptr < 0) return NULL;
+  }
+
+return inbuffer;
+}
+
+
+
+/*************************************************
+*        Complete the reading of a paragraph     *
+*************************************************/
+
+/* This function is called after a line has been identified as the start of a
+paragraph. We need to read the rest so that flags can be matched across the
+entire paragraph. (If there is nested material such as a footnote, this applies
+only to the separate parts, not across the nesting.) The text is copied into
+the paragraph buffer. Directives that are encountered in the paragraph are
+processed, with two exceptions.
+
+(1) For .literal, we set next_line so it is processed next, and exit. This is
+the end of the paragraph.
+
+(2) For .nest, we set *nest_info, according to whether it is the start or
+end of a nested section, and exit.
+
+Arguments:
+  p           the first line
+  nest_info   returns NEST_NO, NEST_START, or NEST_END
+
+Returns:      the paragraph
+*/
+
+
+uschar *
+read_paragraph(uschar *p, int *nest_info)
+{
+uschar *q = parabuffer;
+int length = Ustrlen(p);
+
+memcpy(q, p, length);
+q += length;
+
+*nest_info = NEST_NO;    /* Not hit .nest */
+
+for (;;)
+  {
+  uschar *s;
+
+  if ((p = read_nextline()) == NULL) break;
+
+  if (Ustrncmp(p, ".literal ", 9) == 0)
+    {
+    next_line = p;
+    break;
+    }
+
+  if (Ustrncmp(p, ".nest ", 6) == 0)
+    {
+    p += 6;
+    while (isspace(*p)) p++;
+    s = p + Ustrlen(p);
+    while (s > p && isspace(s[-1])) s--;
+    *s = 0;
+    if (Ustrcmp(p, "begin") == 0) *nest_info = NEST_BEGIN;
+    else if (Ustrcmp(p, "end") == 0) *nest_info = NEST_END;
+    else error(26, p);
+    break;
+    }
+
+  else if (*p == '.')
+    {
+    dot_process(p);
+    continue;
+    }
+
+  /* End paragraph on encountering a completely blank line */
+
+  for (s = p;  *s == ' ' || *s == '\t'; s++);
+  if (*s == '\n') break;
+
+  length = Ustrlen(p);
+  memcpy(q, p, length);
+  q += length;
+  }
+
+*q = 0;
+return parabuffer;
+}
+
+/* End of read.c */