PR 78534, 83704 Handle large formatted I/O
[gcc.git] / libgfortran / io / format.c
index 0be913c59b994baac1c7eb7fa9537300bb82412a..b718faadc7606b681ad15ed082f8831eb9a0525b 100644 (file)
@@ -1,11 +1,12 @@
-/* Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
+/* Copyright (C) 2002-2018 Free Software Foundation, Inc.
    Contributed by Andy Vaught
+   F2003 I/O support contributed by Jerry DeLisle
 
-This file is part of the GNU Fortran 95 runtime library (libgfortran).
+This file is part of the GNU Fortran runtime library (libgfortran).
 
 Libgfortran is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
+the Free Software Foundation; either version 3, or (at your option)
 any later version.
 
 Libgfortran is distributed in the hope that it will be useful,
@@ -13,70 +14,189 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
-You should have received a copy of the GNU General Public License
-along with Libgfortran; see the file COPYING.  If not, write to
-the Free Software Foundation, 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA.  */
+Under Section 7 of GPL version 3, you are granted additional
+permissions described in the GCC Runtime Library Exception, version
+3.1, as published by the Free Software Foundation.
+
+You should have received a copy of the GNU General Public License and
+a copy of the GCC Runtime Library Exception along with this program;
+see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
+<http://www.gnu.org/licenses/>.  */
 
 
 /* format.c-- parse a FORMAT string into a binary format suitable for
* interpretation during I/O statements */
  interpretation during I/O statements.  */
 
-#include "config.h"
+#include "io.h"
+#include "format.h"
 #include <ctype.h>
 #include <string.h>
-#include "libgfortran.h"
-#include "io.h"
 
 
+static const fnode colon_node = { FMT_COLON, 0, NULL, NULL, {{ 0, 0, 0 }}, 0,
+                                 NULL };
 
-/* Number of format nodes that we can store statically before we have
- * to resort to dynamic allocation.  The root node is array[0]. */
+/* Error messages. */
+
+static const char posint_required[] = "Positive width required in format",
+  period_required[] = "Period required in format",
+  nonneg_required[] = "Nonnegative width required in format",
+  unexpected_element[] = "Unexpected element '%c' in format\n",
+  unexpected_end[] = "Unexpected end of format string",
+  bad_string[] = "Unterminated character constant in format",
+  bad_hollerith[] = "Hollerith constant extends past the end of the format",
+  reversion_error[] = "Exhausted data descriptors in format",
+  zero_width[] = "Zero width in format descriptor";
 
-#define FARRAY_SIZE 200
+/* The following routines support caching format data from parsed format strings
+   into a hash table.  This avoids repeatedly parsing duplicate format strings
+   or format strings in I/O statements that are repeated in loops.  */
 
-static fnode *avail, array[FARRAY_SIZE];
 
-/* Local variables for checking format strings.  The saved_token is
- * used to back up by a single format token during the parsing process. */
+/* Traverse the table and free all data.  */
 
-static char *format_string, *string;
-static const char *error;
-static format_token saved_token;
-static int value, format_string_len, reversion_ok;
+void
+free_format_hash_table (gfc_unit *u)
+{
+  size_t i;
 
-static fnode *saved_format, colon_node = { FMT_COLON };
+  /* free_format_data handles any NULL pointers.  */
+  for (i = 0; i < FORMAT_HASH_SIZE; i++)
+    {
+      if (u->format_hash_table[i].hashed_fmt != NULL)
+       {
+         free_format_data (u->format_hash_table[i].hashed_fmt);
+         free (u->format_hash_table[i].key);
+       }
+      u->format_hash_table[i].key = NULL;
+      u->format_hash_table[i].key_len = 0;
+      u->format_hash_table[i].hashed_fmt = NULL;
+    }
+}
 
-/* Error messages */
+/* Traverse the format_data structure and reset the fnode counters.  */
 
-static char posint_required[] = "Positive width required in format",
-  period_required[] = "Period required in format",
-  nonneg_required[] = "Nonnegative width required in format",
-  unexpected_element[] = "Unexpected element in format",
-  unexpected_end[] = "Unexpected end of format string",
-  bad_string[] = "Unterminated character constant in format",
-  bad_hollerith[] = "Hollerith constant extends past the end of the format",
-  reversion_error[] = "Exhausted data descriptors in format";
+static void
+reset_node (fnode *fn)
+{
+  fnode *f;
+
+  fn->count = 0;
+  fn->current = NULL;
+
+  if (fn->format != FMT_LPAREN)
+    return;
+
+  for (f = fn->u.child; f; f = f->next)
+    {
+      if (f->format == FMT_RPAREN)
+       break;
+      reset_node (f);
+    }
+}
+
+static void
+reset_fnode_counters (st_parameter_dt *dtp)
+{
+  fnode *f;
+  format_data *fmt;
+
+  fmt = dtp->u.p.fmt;
+
+  /* Clear this pointer at the head so things start at the right place.  */
+  fmt->array.array[0].current = NULL;
+
+  for (f = fmt->array.array[0].u.child; f; f = f->next)
+    reset_node (f);
+}
+
+
+/* A simple hashing function to generate an index into the hash table.  */
+
+static uint32_t
+format_hash (st_parameter_dt *dtp)
+{
+  char *key;
+  gfc_charlen_type key_len;
+  uint32_t hash = 0;
+  gfc_charlen_type i;
+
+  /* Hash the format string. Super simple, but what the heck!  */
+  key = dtp->format;
+  key_len = dtp->format_len;
+  for (i = 0; i < key_len; i++)
+    hash ^= key[i];
+  hash &= (FORMAT_HASH_SIZE - 1);
+  return hash;
+}
+
+
+static void
+save_parsed_format (st_parameter_dt *dtp)
+{
+  uint32_t hash;
+  gfc_unit *u;
+
+  hash = format_hash (dtp);
+  u = dtp->u.p.current_unit;
+
+  /* Index into the hash table.  We are simply replacing whatever is there
+     relying on probability.  */
+  if (u->format_hash_table[hash].hashed_fmt != NULL)
+    free_format_data (u->format_hash_table[hash].hashed_fmt);
+  u->format_hash_table[hash].hashed_fmt = NULL;
+
+  free (u->format_hash_table[hash].key);
+  u->format_hash_table[hash].key = dtp->format;
+
+  u->format_hash_table[hash].key_len = dtp->format_len;
+  u->format_hash_table[hash].hashed_fmt = dtp->u.p.fmt;
+}
+
+
+static format_data *
+find_parsed_format (st_parameter_dt *dtp)
+{
+  uint32_t hash;
+  gfc_unit *u;
+
+  hash = format_hash (dtp);
+  u = dtp->u.p.current_unit;
+
+  if (u->format_hash_table[hash].key != NULL)
+    {
+      /* See if it matches.  */
+      if (u->format_hash_table[hash].key_len == dtp->format_len)
+       {
+         /* So far so good.  */
+         if (strncmp (u->format_hash_table[hash].key,
+             dtp->format, dtp->format_len) == 0)
+           return u->format_hash_table[hash].hashed_fmt;
+       }
+    }
+  return NULL;
+}
 
 
 /* next_char()-- Return the next character in the format string.
* Returns -1 when the string is done.  If the literal flag is set,
* spaces are significant, otherwise they are not. */
  Returns -1 when the string is done.  If the literal flag is set,
  spaces are significant, otherwise they are not. */
 
 static int
-next_char (int literal)
+next_char (format_data *fmt, int literal)
 {
   int c;
 
   do
     {
-      if (format_string_len == 0)
+      if (fmt->format_string_len == 0)
        return -1;
 
-      format_string_len--;
-      c = toupper (*format_string++);
+      fmt->format_string_len--;
+      c = toupper (*fmt->format_string++);
+      fmt->error_element = c;
     }
-  while (c == ' ' && !literal);
+  while ((c == ' ' || c == '\t') && !literal);
 
   return c;
 }
@@ -84,25 +204,28 @@ next_char (int literal)
 
 /* unget_char()-- Back up one character position. */
 
-#define unget_char() { format_string--;  format_string_len++; }
+#define unget_char(fmt) \
+  { fmt->format_string--; fmt->format_string_len++; }
 
 
 /* get_fnode()-- Allocate a new format node, inserting it into the
* current singly linked list.  These are initially allocated from the
* static buffer. */
  current singly linked list.  These are initially allocated from the
  static buffer. */
 
 static fnode *
-get_fnode (fnode ** head, fnode ** tail, format_token t)
+get_fnode (format_data *fmt, fnode **head, fnode **tail, format_token t)
 {
   fnode *f;
 
-  if (avail - array >= FARRAY_SIZE)
-    f = get_mem (sizeof (fnode));
-  else
+  if (fmt->avail == &fmt->last->array[FARRAY_SIZE])
     {
-      f = avail++;
-      memset (f, '\0', sizeof (fnode));
+      fmt->last->next = xmalloc (sizeof (fnode_array));
+      fmt->last = fmt->last->next;
+      fmt->last->next = NULL;
+      fmt->avail = &fmt->last->array[0];
     }
+  f = fmt->avail++;
+  memset (f, '\0', sizeof (fnode));
 
   if (*head == NULL)
     *head = *tail = f;
@@ -114,97 +237,118 @@ get_fnode (fnode ** head, fnode ** tail, format_token t)
 
   f->format = t;
   f->repeat = -1;
-  f->source = format_string;
+  f->source = fmt->format_string;
   return f;
 }
 
 
-/* free_fnode()-- Recursive function to free the given fnode and
- * everything it points to.  We only have to actually free something
- * if it is outside of the static array. */
-
-static void
-free_fnode (fnode * f)
+/* free_format()-- Free allocated format string.  */
+void
+free_format (st_parameter_dt *dtp)
 {
-  fnode *next;
-
-  for (; f; f = next)
+  if ((dtp->common.flags & IOPARM_DT_HAS_FORMAT) && dtp->format)
     {
-      next = f->next;
-
-      if (f->format == FMT_LPAREN)
-       free_fnode (f->u.child);
-      if (f < array || f >= array + FARRAY_SIZE)
-       free_mem (f);
+      free (dtp->format);
+      dtp->format = NULL;
     }
 }
 
 
-/* free_fnodes()-- Free the current tree of fnodes.  We only have to
- * traverse the tree if some nodes were allocated dynamically. */
+/* free_format_data()-- Free all allocated format data.  */
 
 void
-free_fnodes (void)
+free_format_data (format_data *fmt)
 {
+  fnode_array *fa, *fa_next;
+  fnode *fnp;
 
-  if (avail - array >= FARRAY_SIZE)
-    free_fnode (&array[0]);
+  if (fmt == NULL)
+    return;
 
-  avail = array;
-  memset(array, 0, sizeof(avail[0]) * FARRAY_SIZE);
+  /* Free vlist descriptors in the fnode_array if one was allocated.  */
+  for (fnp = fmt->array.array; fnp < &fmt->array.array[FARRAY_SIZE] &&
+       fnp->format != FMT_NONE; fnp++)
+    if (fnp->format == FMT_DT)
+       {
+         if (GFC_DESCRIPTOR_DATA(fnp->u.udf.vlist))
+           free (GFC_DESCRIPTOR_DATA(fnp->u.udf.vlist));
+         free (fnp->u.udf.vlist);
+       }
+
+  for (fa = fmt->array.next; fa; fa = fa_next)
+    {
+      fa_next = fa->next;
+      free (fa);
+    }
+
+  free (fmt);
+  fmt = NULL;
 }
 
 
 /* format_lex()-- Simple lexical analyzer for getting the next token
* in a FORMAT string.  We support a one-level token pushback in the
saved_token variable. */
  in a FORMAT string.  We support a one-level token pushback in the
  fmt->saved_token variable. */
 
 static format_token
-format_lex (void)
+format_lex (format_data *fmt)
 {
   format_token token;
   int negative_flag;
-  char c, delim;
+  int c;
+  char delim;
 
-  if (saved_token != FMT_NONE)
+  if (fmt->saved_token != FMT_NONE)
     {
-      token = saved_token;
-      saved_token = FMT_NONE;
+      token = fmt->saved_token;
+      fmt->saved_token = FMT_NONE;
       return token;
     }
 
   negative_flag = 0;
-  c = next_char (0);
+  c = next_char (fmt, 0);
 
   switch (c)
     {
+    case '*':
+       token = FMT_STAR;
+       break;
+
+    case '(':
+      token = FMT_LPAREN;
+      break;
+
+    case ')':
+      token = FMT_RPAREN;
+      break;
+
     case '-':
       negative_flag = 1;
       /* Fall Through */
 
     case '+':
-      c = next_char (0);
+      c = next_char (fmt, 0);
       if (!isdigit (c))
        {
          token = FMT_UNKNOWN;
          break;
        }
 
-      value = c - '0';
+      fmt->value = c - '0';
 
       for (;;)
        {
-         c = next_char (0);
+         c = next_char (fmt, 0);
          if (!isdigit (c))
            break;
 
-         value = 10 * value + c - '0';
+         fmt->value = 10 * fmt->value + c - '0';
        }
 
-      unget_char ();
+      unget_char (fmt);
 
       if (negative_flag)
-       value = -value;
+       fmt->value = -fmt->value;
       token = FMT_SIGNED_INT;
       break;
 
@@ -218,19 +362,19 @@ format_lex (void)
     case '7':
     case '8':
     case '9':
-      value = c - '0';
+      fmt->value = c - '0';
 
       for (;;)
        {
-         c = next_char (0);
+         c = next_char (fmt, 0);
          if (!isdigit (c))
            break;
 
-         value = 10 * value + c - '0';
+         fmt->value = 10 * fmt->value + c - '0';
        }
 
-      unget_char ();
-      token = (value == 0) ? FMT_ZERO : FMT_POSINT;
+      unget_char (fmt);
+      token = (fmt->value == 0) ? FMT_ZERO : FMT_POSINT;
       break;
 
     case '.':
@@ -254,7 +398,7 @@ format_lex (void)
       break;
 
     case 'T':
-      switch (next_char (0))
+      switch (next_char (fmt, 0))
        {
        case 'L':
          token = FMT_TL;
@@ -264,26 +408,18 @@ format_lex (void)
          break;
        default:
          token = FMT_T;
-         unget_char ();
+         unget_char (fmt);
          break;
        }
 
       break;
 
-    case '(':
-      token = FMT_LPAREN;
-      break;
-
-    case ')':
-      token = FMT_RPAREN;
-      break;
-
     case 'X':
       token = FMT_X;
       break;
 
     case 'S':
-      switch (next_char (0))
+      switch (next_char (fmt, 0))
        {
        case 'S':
          token = FMT_SS;
@@ -293,14 +429,14 @@ format_lex (void)
          break;
        default:
          token = FMT_S;
-         unget_char ();
+         unget_char (fmt);
          break;
        }
 
       break;
 
     case 'B':
-      switch (next_char (0))
+      switch (next_char (fmt, 0))
        {
        case 'N':
          token = FMT_BN;
@@ -310,7 +446,7 @@ format_lex (void)
          break;
        default:
          token = FMT_B;
-         unget_char ();
+         unget_char (fmt);
          break;
        }
 
@@ -320,39 +456,39 @@ format_lex (void)
     case '"':
       delim = c;
 
-      string = format_string;
-      value = 0;               /* This is the length of the string */
+      fmt->string = fmt->format_string;
+      fmt->value = 0;          /* This is the length of the string */
 
       for (;;)
        {
-         c = next_char (1);
+         c = next_char (fmt, 1);
          if (c == -1)
            {
              token = FMT_BADSTRING;
-             error = bad_string;
+             fmt->error = bad_string;
              break;
            }
 
          if (c == delim)
            {
-             c = next_char (1);
+             c = next_char (fmt, 1);
 
              if (c == -1)
                {
                  token = FMT_BADSTRING;
-                 error = bad_string;
+                 fmt->error = bad_string;
                  break;
                }
 
              if (c != delim)
                {
-                 unget_char ();
+                 unget_char (fmt);
                  token = FMT_STRING;
                  break;
                }
            }
 
-         value++;
+         fmt->value++;
        }
 
       break;
@@ -378,7 +514,7 @@ format_lex (void)
       break;
 
     case 'E':
-      switch (next_char (0))
+      switch (next_char (fmt, 0))
        {
        case 'N':
          token = FMT_EN;
@@ -388,10 +524,9 @@ format_lex (void)
          break;
        default:
          token = FMT_E;
-         unget_char ();
+         unget_char (fmt);
          break;
        }
-
       break;
 
     case 'G':
@@ -411,7 +546,50 @@ format_lex (void)
       break;
 
     case 'D':
-      token = FMT_D;
+      switch (next_char (fmt, 0))
+       {
+       case 'P':
+         token = FMT_DP;
+         break;
+       case 'C':
+         token = FMT_DC;
+         break;
+       case 'T':
+         token = FMT_DT;
+         break;
+       default:
+         token = FMT_D;
+         unget_char (fmt);
+         break;
+       }
+      break;
+
+    case 'R':
+      switch (next_char (fmt, 0))
+       {
+       case 'C':
+         token = FMT_RC;
+         break;
+       case 'D':
+         token = FMT_RD;
+         break;
+       case 'N':
+         token = FMT_RN;
+         break;
+       case 'P':
+         token = FMT_RP;
+         break;
+       case 'U':
+         token = FMT_RU;
+         break;
+       case 'Z':
+         token = FMT_RZ;
+         break;
+       default:
+         unget_char (fmt);
+         token = FMT_UNKNOWN;
+         break;
+       }
       break;
 
     case -1:
@@ -428,48 +606,71 @@ format_lex (void)
 
 
 /* parse_format_list()-- Parse a format list.  Assumes that a left
* paren has already been seen.  Returns a list representing the
* parenthesis node which contains the rest of the list. */
  paren has already been seen.  Returns a list representing the
  parenthesis node which contains the rest of the list. */
 
 static fnode *
-parse_format_list (void)
+parse_format_list (st_parameter_dt *dtp, bool *seen_dd)
 {
   fnode *head, *tail;
   format_token t, u, t2;
   int repeat;
+  format_data *fmt = dtp->u.p.fmt;
+  bool seen_data_desc = false;
 
   head = tail = NULL;
 
-/* Get the next format item */
-
-format_item:
-  t = format_lex ();
+  /* Get the next format item */
+ format_item:
+  t = format_lex (fmt);
+ format_item_1:
   switch (t)
     {
+    case FMT_STAR:
+      t = format_lex (fmt);
+      if (t != FMT_LPAREN)
+       {
+         fmt->error = "Left parenthesis required after '*'";
+         goto finished;
+       }
+      get_fnode (fmt, &head, &tail, FMT_LPAREN);
+      tail->repeat = -2;  /* Signifies unlimited format.  */
+      tail->u.child = parse_format_list (dtp, &seen_data_desc);
+      *seen_dd = seen_data_desc;
+      if (fmt->error != NULL)
+       goto finished;
+      if (!seen_data_desc)
+       {
+         fmt->error = "'*' requires at least one associated data descriptor";
+         goto finished;
+       }
+      goto between_desc;
+
     case FMT_POSINT:
-      repeat = value;
+      repeat = fmt->value;
 
-      t = format_lex ();
+      t = format_lex (fmt);
       switch (t)
        {
        case FMT_LPAREN:
-         get_fnode (&head, &tail, FMT_LPAREN);
+         get_fnode (fmt, &head, &tail, FMT_LPAREN);
          tail->repeat = repeat;
-         tail->u.child = parse_format_list ();
-         if (error != NULL)
+         tail->u.child = parse_format_list (dtp, &seen_data_desc);
+         *seen_dd = seen_data_desc;
+         if (fmt->error != NULL)
            goto finished;
 
          goto between_desc;
 
        case FMT_SLASH:
-         get_fnode (&head, &tail, FMT_SLASH);
+         get_fnode (fmt, &head, &tail, FMT_SLASH);
          tail->repeat = repeat;
          goto optional_comma;
 
        case FMT_X:
-         get_fnode (&head, &tail, FMT_X);
+         get_fnode (fmt, &head, &tail, FMT_X);
          tail->repeat = 1;
-         tail->u.k = value;
+         tail->u.k = fmt->value;
          goto between_desc;
 
        case FMT_P:
@@ -480,28 +681,30 @@ format_item:
        }
 
     case FMT_LPAREN:
-      get_fnode (&head, &tail, FMT_LPAREN);
+      get_fnode (fmt, &head, &tail, FMT_LPAREN);
       tail->repeat = 1;
-      tail->u.child = parse_format_list ();
-      if (error != NULL)
+      tail->u.child = parse_format_list (dtp, &seen_data_desc);
+      *seen_dd = seen_data_desc;
+      if (fmt->error != NULL)
        goto finished;
 
       goto between_desc;
 
     case FMT_SIGNED_INT:       /* Signed integer can only precede a P format.  */
     case FMT_ZERO:             /* Same for zero.  */
-      t = format_lex ();
+      t = format_lex (fmt);
       if (t != FMT_P)
        {
-         error = "Expected P edit descriptor in format";
+         fmt->error = "Expected P edit descriptor in format";
          goto finished;
        }
 
     p_descriptor:
-      get_fnode (&head, &tail, FMT_P);
-      tail->u.k = value;
+      get_fnode (fmt, &head, &tail, FMT_P);
+      tail->u.k = fmt->value;
+      tail->repeat = 1;
 
-      t = format_lex ();
+      t = format_lex (fmt);
       if (t == FMT_F || t == FMT_EN || t == FMT_ES || t == FMT_D
          || t == FMT_G || t == FMT_E)
        {
@@ -509,11 +712,18 @@ format_item:
          goto data_desc;
        }
 
-      saved_token = t;
+      if (t != FMT_COMMA && t != FMT_RPAREN && t != FMT_SLASH
+         && t != FMT_POSINT)
+       {
+         fmt->error = "Comma required after P descriptor";
+         goto finished;
+       }
+
+      fmt->saved_token = t;
       goto optional_comma;
 
     case FMT_P:                /* P and X require a prior number */
-      error = "P descriptor requires leading scale factor";
+      fmt->error = "P descriptor requires leading scale factor";
       goto finished;
 
     case FMT_X:
@@ -523,7 +733,7 @@ format_item:
    If we would be pedantic in the library, we would have to reject
    an X descriptor without an integer prefix:
 
-      error = "X descriptor requires leading space count";
+      fmt->error = "X descriptor requires leading space count";
       goto finished;
 
    However, this is an extension supported by many Fortran compilers,
@@ -531,52 +741,72 @@ format_item:
    runtime library, and make the front end reject it if the compiler
    is in pedantic mode.  The interpretation of 'X' is '1X'.
 */
-      get_fnode (&head, &tail, FMT_X);
+      get_fnode (fmt, &head, &tail, FMT_X);
       tail->repeat = 1;
       tail->u.k = 1;
       goto between_desc;
 
     case FMT_STRING:
-      get_fnode (&head, &tail, FMT_STRING);
+      get_fnode (fmt, &head, &tail, FMT_STRING);
+      tail->u.string.p = fmt->string;
+      tail->u.string.length = fmt->value;
+      tail->repeat = 1;
+      goto optional_comma;
 
-      tail->u.string.p = string;
-      tail->u.string.length = value;
+    case FMT_RC:
+    case FMT_RD:
+    case FMT_RN:
+    case FMT_RP:
+    case FMT_RU:
+    case FMT_RZ:
+      notify_std (&dtp->common, GFC_STD_F2003, "Fortran 2003: Round "
+                 "descriptor not allowed");
+      get_fnode (fmt, &head, &tail, t);
       tail->repeat = 1;
       goto between_desc;
 
+    case FMT_DC:
+    case FMT_DP:
+      notify_std (&dtp->common, GFC_STD_F2003, "Fortran 2003: DC or DP "
+                 "descriptor not allowed");
+    /* Fall through.  */
     case FMT_S:
     case FMT_SS:
     case FMT_SP:
     case FMT_BN:
     case FMT_BZ:
-      get_fnode (&head, &tail, t);
+      get_fnode (fmt, &head, &tail, t);
+      tail->repeat = 1;
       goto between_desc;
 
     case FMT_COLON:
-      get_fnode (&head, &tail, FMT_COLON);
+      get_fnode (fmt, &head, &tail, FMT_COLON);
+      tail->repeat = 1;
       goto optional_comma;
 
     case FMT_SLASH:
-      get_fnode (&head, &tail, FMT_SLASH);
+      get_fnode (fmt, &head, &tail, FMT_SLASH);
       tail->repeat = 1;
       tail->u.r = 1;
       goto optional_comma;
 
     case FMT_DOLLAR:
-      get_fnode (&head, &tail, FMT_DOLLAR);
+      get_fnode (fmt, &head, &tail, FMT_DOLLAR);
+      tail->repeat = 1;
+      notify_std (&dtp->common, GFC_STD_GNU, "Extension: $ descriptor");
       goto between_desc;
 
     case FMT_T:
     case FMT_TL:
     case FMT_TR:
-      t2 = format_lex ();
+      t2 = format_lex (fmt);
       if (t2 != FMT_POSINT)
        {
-         error = posint_required;
+         fmt->error = posint_required;
          goto finished;
        }
-      get_fnode (&head, &tail, t);
-      tail->u.n = value;
+      get_fnode (fmt, &head, &tail, t);
+      tail->u.n = fmt->value;
       tail->repeat = 1;
       goto between_desc;
 
@@ -588,33 +818,34 @@ format_item:
     case FMT_EN:
     case FMT_ES:
     case FMT_D:
+    case FMT_DT:
     case FMT_L:
     case FMT_A:
     case FMT_F:
     case FMT_G:
       repeat = 1;
+      *seen_dd = true;
       goto data_desc;
 
     case FMT_H:
-      get_fnode (&head, &tail, FMT_STRING);
-
-      if (format_string_len < 1)
+      get_fnode (fmt, &head, &tail, FMT_STRING);
+      if (fmt->format_string_len < 1)
        {
-         error = bad_hollerith;
+         fmt->error = bad_hollerith;
          goto finished;
        }
 
-      tail->u.string.p = format_string;
+      tail->u.string.p = fmt->format_string;
       tail->u.string.length = 1;
       tail->repeat = 1;
 
-      format_string++;
-      format_string_len--;
+      fmt->format_string++;
+      fmt->format_string_len--;
 
       goto between_desc;
 
     case FMT_END:
-      error = unexpected_end;
+      fmt->error = unexpected_end;
       goto finished;
 
     case FMT_BADSTRING:
@@ -624,53 +855,63 @@ format_item:
       goto finished;
 
     default:
-      error = unexpected_element;
+      fmt->error = unexpected_element;
       goto finished;
     }
 
-/* In this state, t must currently be a data descriptor.  Deal with
- * things that can/must follow the descriptor */
+  /* In this state, t must currently be a data descriptor.  Deal with
+     things that can/must follow the descriptor */
+ data_desc:
 
-data_desc:
   switch (t)
     {
-    case FMT_P:
-      t = format_lex ();
-      if (t == FMT_POSINT)
-       {
-         error = "Repeat count cannot follow P descriptor";
-         goto finished;
-       }
-
-      saved_token = t;
-      get_fnode (&head, &tail, FMT_P);
-
-      goto optional_comma;
-
     case FMT_L:
-      t = format_lex ();
+      *seen_dd = true;
+      t = format_lex (fmt);
       if (t != FMT_POSINT)
        {
-         error = posint_required;
-         goto finished;
+         if (t == FMT_ZERO)
+           {
+             if (notification_std(GFC_STD_GNU) == NOTIFICATION_ERROR)
+               {
+                 fmt->error = "Extension: Zero width after L descriptor";
+                 goto finished;
+               }
+             else
+               notify_std (&dtp->common, GFC_STD_GNU,
+                           "Zero width after L descriptor");
+           }
+         else
+           {
+             fmt->saved_token = t;
+             notify_std (&dtp->common, GFC_STD_GNU,
+                         "Positive width required with L descriptor");
+           }
+         fmt->value = 1;       /* Default width */
        }
-
-      get_fnode (&head, &tail, FMT_L);
-      tail->u.n = value;
+      get_fnode (fmt, &head, &tail, FMT_L);
+      tail->u.n = fmt->value;
       tail->repeat = repeat;
       break;
 
     case FMT_A:
-      t = format_lex ();
+      *seen_dd = true;
+      t = format_lex (fmt);
+      if (t == FMT_ZERO)
+       {
+         fmt->error = zero_width;
+         goto finished;
+       }
+
       if (t != FMT_POSINT)
        {
-         saved_token = t;
-         value = -1;           /* Width not present */
+         fmt->saved_token = t;
+         fmt->value = -1;              /* Width not present */
        }
 
-      get_fnode (&head, &tail, FMT_A);
+      get_fnode (fmt, &head, &tail, FMT_A);
       tail->repeat = repeat;
-      tail->u.n = value;
+      tail->u.n = fmt->value;
       break;
 
     case FMT_D:
@@ -679,84 +920,168 @@ data_desc:
     case FMT_G:
     case FMT_EN:
     case FMT_ES:
-      get_fnode (&head, &tail, t);
+      *seen_dd = true;
+      get_fnode (fmt, &head, &tail, t);
       tail->repeat = repeat;
 
-      u = format_lex ();
-      if (t == FMT_F || g.mode == WRITING)
+      u = format_lex (fmt);
+      if (t == FMT_G && u == FMT_ZERO)
        {
-         if (u != FMT_POSINT && u != FMT_ZERO)
+         *seen_dd = true;
+         if (notification_std (GFC_STD_F2008) == NOTIFICATION_ERROR
+             || dtp->u.p.mode == READING)
            {
-             error = nonneg_required;
+             fmt->error = zero_width;
              goto finished;
            }
+         tail->u.real.w = 0;
+         u = format_lex (fmt);
+         if (u != FMT_PERIOD)
+           {
+             fmt->saved_token = u;
+             break;
+           }
+
+         u = format_lex (fmt);
+         if (u != FMT_POSINT)
+           {
+             fmt->error = posint_required;
+             goto finished;
+           }
+         tail->u.real.d = fmt->value;
+         break;
        }
-      else
+      if (t == FMT_F && dtp->u.p.mode == WRITING)
        {
-         if (u != FMT_POSINT)
+         *seen_dd = true;
+         if (u != FMT_POSINT && u != FMT_ZERO)
            {
-             error = posint_required;
+             fmt->error = nonneg_required;
              goto finished;
            }
        }
+      else if (u != FMT_POSINT)
+       {
+         fmt->error = posint_required;
+         goto finished;
+       }
 
-      tail->u.real.w = value;
+      tail->u.real.w = fmt->value;
       t2 = t;
-      t = format_lex ();
+      t = format_lex (fmt);
       if (t != FMT_PERIOD)
        {
-         error = period_required;
-         goto finished;
+         /* We treat a missing decimal descriptor as 0.  Note: This is only
+            allowed if -std=legacy, otherwise an error occurs.  */
+         if (compile_options.warn_std != 0)
+           {
+             fmt->error = period_required;
+             goto finished;
+           }
+         fmt->saved_token = t;
+         tail->u.real.d = 0;
+         tail->u.real.e = -1;
+         break;
        }
 
-      t = format_lex ();
+      t = format_lex (fmt);
       if (t != FMT_ZERO && t != FMT_POSINT)
        {
-         error = nonneg_required;
+         fmt->error = nonneg_required;
          goto finished;
        }
 
-      tail->u.real.d = value;
-
-      if (t == FMT_D || t == FMT_F)
-       break;
-
+      tail->u.real.d = fmt->value;
       tail->u.real.e = -1;
 
-/* Look for optional exponent */
+      if (t2 == FMT_D || t2 == FMT_F)
+       {
+         *seen_dd = true;
+         break;
+       }
 
-      t = format_lex ();
+      /* Look for optional exponent */
+      t = format_lex (fmt);
       if (t != FMT_E)
-       saved_token = t;
+       fmt->saved_token = t;
       else
        {
-         t = format_lex ();
+         t = format_lex (fmt);
          if (t != FMT_POSINT)
            {
-             error = "Positive exponent width required in format";
+             fmt->error = "Positive exponent width required in format";
              goto finished;
            }
 
-         tail->u.real.e = value;
+         tail->u.real.e = fmt->value;
        }
 
       break;
+    case FMT_DT:
+      *seen_dd = true;
+      get_fnode (fmt, &head, &tail, t);
+      tail->repeat = repeat;
+
+      t = format_lex (fmt);
+
+      /* Initialize the vlist to a zero size array.  */
+      tail->u.udf.vlist= xmalloc (sizeof(gfc_array_i4));
+      GFC_DESCRIPTOR_DATA(tail->u.udf.vlist) = NULL;
+      GFC_DIMENSION_SET(tail->u.udf.vlist->dim[0],1, 0, 0);
 
+      if (t == FMT_STRING)
+        {
+         /* Get pointer to the optional format string.  */
+         tail->u.udf.string = fmt->string;
+         tail->u.udf.string_len = fmt->value;
+         t = format_lex (fmt);
+       }
+      if (t == FMT_LPAREN)
+        {
+         /* Temporary buffer to hold the vlist values.  */
+         GFC_INTEGER_4 temp[FARRAY_SIZE];
+         int i = 0;
+       loop:
+         t = format_lex (fmt);
+         if (t != FMT_POSINT)
+           {
+             fmt->error = posint_required;
+             goto finished;
+           }
+         /* Save the positive integer value.  */
+         temp[i++] = fmt->value;
+         t = format_lex (fmt);
+         if (t == FMT_COMMA)
+           goto loop;
+         if (t == FMT_RPAREN)
+           {
+             /* We have parsed the complete vlist so initialize the
+                array descriptor and save it in the format node.  */
+             gfc_array_i4 *vp = tail->u.udf.vlist;
+             GFC_DESCRIPTOR_DATA(vp) = xmalloc (i * sizeof(GFC_INTEGER_4));
+             GFC_DIMENSION_SET(vp->dim[0],1, i, 1);
+             memcpy (GFC_DESCRIPTOR_DATA(vp), temp, i * sizeof(GFC_INTEGER_4));
+             break;
+           }
+         fmt->error = unexpected_element;
+         goto finished;
+       }
+      fmt->saved_token = t;
+      break;
     case FMT_H:
-      if (repeat > format_string_len)
+      if (repeat > fmt->format_string_len)
        {
-         error = bad_hollerith;
+         fmt->error = bad_hollerith;
          goto finished;
        }
 
-      get_fnode (&head, &tail, FMT_STRING);
-
-      tail->u.string.p = format_string;
+      get_fnode (fmt, &head, &tail, FMT_STRING);
+      tail->u.string.p = fmt->format_string;
       tail->u.string.length = repeat;
       tail->repeat = 1;
 
-      format_string += value;
-      format_string_len -= repeat;
+      fmt->format_string += fmt->value;
+      fmt->format_string_len -= repeat;
 
       break;
 
@@ -764,16 +1089,17 @@ data_desc:
     case FMT_B:
     case FMT_O:
     case FMT_Z:
-      get_fnode (&head, &tail, t);
+      *seen_dd = true;
+      get_fnode (fmt, &head, &tail, t);
       tail->repeat = repeat;
 
-      t = format_lex ();
+      t = format_lex (fmt);
 
-      if (g.mode == READING)
+      if (dtp->u.p.mode == READING)
        {
          if (t != FMT_POSINT)
            {
-             error = posint_required;
+             fmt->error = posint_required;
              goto finished;
            }
        }
@@ -781,47 +1107,47 @@ data_desc:
        {
          if (t != FMT_ZERO && t != FMT_POSINT)
            {
-             error = nonneg_required;
+             fmt->error = nonneg_required;
              goto finished;
            }
        }
 
-      tail->u.integer.w = value;
+      tail->u.integer.w = fmt->value;
       tail->u.integer.m = -1;
 
-      t = format_lex ();
+      t = format_lex (fmt);
       if (t != FMT_PERIOD)
        {
-         saved_token = t;
+         fmt->saved_token = t;
        }
       else
        {
-         t = format_lex ();
+         t = format_lex (fmt);
          if (t != FMT_ZERO && t != FMT_POSINT)
            {
-             error = nonneg_required;
+             fmt->error = nonneg_required;
              goto finished;
            }
 
-         tail->u.integer.m = value;
+         tail->u.integer.m = fmt->value;
        }
 
       if (tail->u.integer.w != 0 && tail->u.integer.m > tail->u.integer.w)
        {
-         error = "Minimum digits exceeds field width";
+         fmt->error = "Minimum digits exceeds field width";
          goto finished;
        }
 
       break;
 
     default:
-      error = unexpected_element;
+      fmt->error = unexpected_element;
       goto finished;
     }
 
-/* Between a descriptor and what comes next */
-between_desc:
-  t = format_lex ();
+  /* Between a descriptor and what comes next */
+ between_desc:
+  t = format_lex (fmt);
   switch (t)
     {
     case FMT_COMMA:
@@ -831,28 +1157,24 @@ between_desc:
       goto finished;
 
     case FMT_SLASH:
-      get_fnode (&head, &tail, FMT_SLASH);
-      tail->repeat = 1;
-
-      /* Fall Through */
-
     case FMT_COLON:
+      get_fnode (fmt, &head, &tail, t);
+      tail->repeat = 1;
       goto optional_comma;
 
     case FMT_END:
-      error = unexpected_end;
+      fmt->error = unexpected_end;
       goto finished;
 
     default:
-      error = "Missing comma in format";
-      goto finished;
+      /* Assume a missing comma, this is a GNU extension */
+      goto format_item_1;
     }
 
-/* Optional comma is a weird between state where we've just finished
- * reading a colon, slash or P descriptor. */
-
-optional_comma:
-  t = format_lex ();
+  /* Optional comma is a weird between state where we've just finished
+     reading a colon, slash or P descriptor. */
+ optional_comma:
+  t = format_lex (fmt);
   switch (t)
     {
     case FMT_COMMA:
@@ -862,46 +1184,50 @@ optional_comma:
       goto finished;
 
     default:                   /* Assume that we have another format item */
-      saved_token = t;
+      fmt->saved_token = t;
       break;
     }
 
   goto format_item;
 
-finished:
+ finished:
+
   return head;
 }
 
 
 /* format_error()-- Generate an error message for a format statement.
- * If the node that gives the location of the error is NULL, the error
- * is assumed to happen at parse time, and the current location of the
- * parser is shown.
- *
- * After freeing any dynamically allocated fnodes, generate a message
- * showing where the problem is.  We take extra care to print only the
- * relevant part of the format if it is longer than a standard 80
- * column display. */
+   If the node that gives the location of the error is NULL, the error
+   is assumed to happen at parse time, and the current location of the
+   parser is shown.
+
+   We generate a message showing where the problem is.  We take extra
+   care to print only the relevant part of the format if it is longer
+   than a standard 80 column display. */
 
 void
-format_error (fnode * f, const char *message)
+format_error (st_parameter_dt *dtp, const fnode *f, const char *message)
 {
-  int width, i, j, offset;
-  char *p, buffer[300];
+  int width, i, offset;
+#define BUFLEN 300
+  char *p, buffer[BUFLEN];
+  format_data *fmt = dtp->u.p.fmt;
 
   if (f != NULL)
-    format_string = f->source;
+    p = f->source;
+  else                /* This should not happen.  */
+    p = dtp->format;
 
-  free_fnodes ();
-
-  st_sprintf (buffer, "%s\n", message);
-
-  j = format_string - ioparm.format;
+  if (message == unexpected_element)
+    snprintf (buffer, BUFLEN, message, fmt->error_element);
+  else
+    snprintf (buffer, BUFLEN, "%s\n", message);
 
-  offset = (j > 60) ? j - 40 : 0;
+  /* Get the offset into the format string where the error occurred.  */
+  offset = dtp->format_len - (fmt->reversion_ok ?
+                             (int) strlen(p) : fmt->format_string_len);
 
-  j -= offset;
-  width = ioparm.format_len - offset;
+  width = dtp->format_len;
 
   if (width > 80)
     width = 80;
@@ -910,95 +1236,139 @@ format_error (fnode * f, const char *message)
 
   p = strchr (buffer, '\0');
 
-  memcpy (p, ioparm.format + offset, width);
+  if (dtp->format)
+    memcpy (p, dtp->format, width);
 
   p += width;
   *p++ = '\n';
 
   /* Show where the problem is */
 
-  for (i = 1; i < j; i++)
+  for (i = 1; i < offset; i++)
     *p++ = ' ';
 
   *p++ = '^';
   *p = '\0';
 
-  generate_error (ERROR_FORMAT, buffer);
+  generate_error (&dtp->common, LIBERROR_FORMAT, buffer);
 }
 
 
-/* parse_format()-- Parse a format string.  */
+/* revert()-- Do reversion of the format.  Control reverts to the left
+   parenthesis that matches the rightmost right parenthesis.  From our
+   tree structure, we are looking for the rightmost parenthesis node
+   at the second level, the first level always being a single
+   parenthesis node.  If this node doesn't exit, we use the top
+   level. */
 
-void
-parse_format (void)
+static void
+revert (st_parameter_dt *dtp)
 {
+  fnode *f, *r;
+  format_data *fmt = dtp->u.p.fmt;
 
-  format_string = ioparm.format;
-  format_string_len = ioparm.format_len;
+  dtp->u.p.reversion_flag = 1;
 
-  saved_token = FMT_NONE;
-  error = NULL;
+  r = NULL;
 
-/* Initialize variables used during traversal of the tree */
+  for (f = fmt->array.array[0].u.child; f; f = f->next)
+    if (f->format == FMT_LPAREN)
+      r = f;
 
-  reversion_ok = 0;
-  g.reversion_flag = 0;
-  saved_format = NULL;
+  /* If r is NULL because no node was found, the whole tree will be used */
 
-/* Allocate the first format node as the root of the tree */
+  fmt->array.array[0].current = r;
+  fmt->array.array[0].count = 0;
+}
 
-  avail = array;
+/* parse_format()-- Parse a format string.  */
 
-  avail->format = FMT_LPAREN;
-  avail->repeat = 1;
-  avail++;
+void
+parse_format (st_parameter_dt *dtp)
+{
+  format_data *fmt;
+  bool format_cache_ok, seen_data_desc = false;
+
+  /* Don't cache for internal units and set an arbitrary limit on the
+     size of format strings we will cache.  (Avoids memory issues.)
+     Also, the format_hash_table resides in the current_unit, so
+     child_dtio procedures would overwrite the parent table  */
+  format_cache_ok = !is_internal_unit (dtp)
+                   && (dtp->u.p.current_unit->child_dtio == 0);
+
+  /* Lookup format string to see if it has already been parsed.  */
+  if (format_cache_ok)
+    {
+      dtp->u.p.fmt = find_parsed_format (dtp);
 
-  if (format_lex () == FMT_LPAREN)
-    array[0].u.child = parse_format_list ();
-  else
-    error = "Missing initial left parenthesis in format";
+      if (dtp->u.p.fmt != NULL)
+       {
+         dtp->u.p.fmt->reversion_ok = 0;
+         dtp->u.p.fmt->saved_token = FMT_NONE;
+         dtp->u.p.fmt->saved_format = NULL;
+         reset_fnode_counters (dtp);
+         return;
+       }
+    }
 
-  if (error)
-    format_error (NULL, error);
-}
+  /* Not found so proceed as follows.  */
 
+  char *fmt_string = fc_strdup_notrim (dtp->format, dtp->format_len);
+  dtp->format = fmt_string;
 
-/* revert()-- Do reversion of the format.  Control reverts to the left
- * parenthesis that matches the rightmost right parenthesis.  From our
- * tree structure, we are looking for the rightmost parenthesis node
- * at the second level, the first level always being a single
- * parenthesis node.  If this node doesn't exit, we use the top
- * level. */
+  dtp->u.p.fmt = fmt = xmalloc (sizeof (format_data));
+  fmt->format_string = dtp->format;
+  fmt->format_string_len = dtp->format_len;
 
-static void
-revert (void)
-{
-  fnode *f, *r;
+  fmt->string = NULL;
+  fmt->saved_token = FMT_NONE;
+  fmt->error = NULL;
+  fmt->value = 0;
 
-  g.reversion_flag = 1;
+  /* Initialize variables used during traversal of the tree.  */
 
-  r = NULL;
+  fmt->reversion_ok = 0;
+  fmt->saved_format = NULL;
 
-  for (f = array[0].u.child; f; f = f->next)
-    if (f->format == FMT_LPAREN)
-      r = f;
+  /* Initialize the fnode_array.  */
 
-  /* If r is NULL because no node was found, the whole tree will be used */
+  memset (&(fmt->array), 0, sizeof(fmt->array));
+
+  /* Allocate the first format node as the root of the tree.  */
+
+  fmt->last = &fmt->array;
+  fmt->last->next = NULL;
+  fmt->avail = &fmt->array.array[0];
+
+  memset (fmt->avail, 0, sizeof (*fmt->avail));
+  fmt->avail->format = FMT_LPAREN;
+  fmt->avail->repeat = 1;
+  fmt->avail++;
 
-  array[0].current = r;
-  array[0].count = 0;
+  if (format_lex (fmt) == FMT_LPAREN)
+    fmt->array.array[0].u.child = parse_format_list (dtp, &seen_data_desc);
+  else
+    fmt->error = "Missing initial left parenthesis in format";
+
+  if (format_cache_ok)
+    save_parsed_format (dtp);
+  else
+    dtp->u.p.format_not_saved = 1;
+
+  if (fmt->error)
+    format_error (dtp, NULL, fmt->error);
 }
 
 
 /* next_format0()-- Get the next format node without worrying about
* reversion.  Returns NULL when we hit the end of the list.
* Parenthesis nodes are incremented after the list has been
* exhausted, other nodes are incremented before they are returned. */
  reversion.  Returns NULL when we hit the end of the list.
  Parenthesis nodes are incremented after the list has been
  exhausted, other nodes are incremented before they are returned. */
 
-static fnode *
-next_format0 (fnode * f)
+static const fnode *
+next_format0 (fnode *f)
 {
-  fnode *r;
+  const fnode *r;
 
   if (f == NULL)
     return NULL;
@@ -1013,8 +1383,23 @@ next_format0 (fnode * f)
       return NULL;
     }
 
-  /* Deal with a parenthesis node */
+  /* Deal with a parenthesis node with unlimited format.  */
+
+  if (f->repeat == -2)  /* -2 signifies unlimited.  */
+  for (;;)
+    {
+      if (f->current == NULL)
+       f->current = f->u.child;
+
+      for (; f->current != NULL; f->current = f->current->next)
+       {
+         r = next_format0 (f->current);
+         if (r != NULL)
+           return r;
+       }
+    }
 
+  /* Deal with a parenthesis node with specific repeat count.  */
   for (; f->count < f->repeat; f->count++)
     {
       if (f->current == NULL)
@@ -1034,252 +1419,72 @@ next_format0 (fnode * f)
 
 
 /* next_format()-- Return the next format node.  If the format list
* ends up being exhausted, we do reversion.  Reversion is only
* allowed if the we've seen a data descriptor since the
* initialization or the last reversion.  We return NULL if the there
* are no more data descriptors to return (which is an error
* condition). */
-
-fnode *
-next_format (void)
  ends up being exhausted, we do reversion.  Reversion is only
  allowed if we've seen a data descriptor since the
  initialization or the last reversion.  We return NULL if there
  are no more data descriptors to return (which is an error
  condition).  */
+
+const fnode *
+next_format (st_parameter_dt *dtp)
 {
   format_token t;
-  fnode *f;
+  const fnode *f;
+  format_data *fmt = dtp->u.p.fmt;
 
-  if (saved_format != NULL)
+  if (fmt->saved_format != NULL)
     {                          /* Deal with a pushed-back format node */
-      f = saved_format;
-      saved_format = NULL;
+      f = fmt->saved_format;
+      fmt->saved_format = NULL;
       goto done;
     }
 
-  f = next_format0 (&array[0]);
+  f = next_format0 (&fmt->array.array[0]);
   if (f == NULL)
     {
-      if (!reversion_ok)
-       {
-         return NULL;
-       }
+      if (!fmt->reversion_ok)
+       return NULL;
 
-      reversion_ok = 0;
-      revert ();
+      fmt->reversion_ok = 0;
+      revert (dtp);
 
-      f = next_format0 (&array[0]);
+      f = next_format0 (&fmt->array.array[0]);
       if (f == NULL)
        {
-         format_error (NULL, reversion_error);
+         format_error (dtp, NULL, reversion_error);
          return NULL;
        }
 
       /* Push the first reverted token and return a colon node in case
-       * there are no more data items. */
+        there are no more data items.  */
 
-      saved_format = f;
+      fmt->saved_format = f;
       return &colon_node;
     }
 
   /* If this is a data edit descriptor, then reversion has become OK. */
-
-done:
+ done:
   t = f->format;
 
-  if (!reversion_ok &&
+  if (!fmt->reversion_ok &&
       (t == FMT_I || t == FMT_B || t == FMT_O || t == FMT_Z || t == FMT_F ||
        t == FMT_E || t == FMT_EN || t == FMT_ES || t == FMT_G || t == FMT_L ||
-       t == FMT_A || t == FMT_D))
-    reversion_ok = 1;
+       t == FMT_A || t == FMT_D || t == FMT_DT))
+    fmt->reversion_ok = 1;
   return f;
 }
 
 
 /* unget_format()-- Push the given format back so that it will be
* returned on the next call to next_format() without affecting
* counts.  This is necessary when we've encountered a data
* descriptor, but don't know what the data item is yet.  The format
* node is pushed back, and we return control to the main program,
* which calls the library back with the data item (or not). */
  returned on the next call to next_format() without affecting
  counts.  This is necessary when we've encountered a data
  descriptor, but don't know what the data item is yet.  The format
  node is pushed back, and we return control to the main program,
  which calls the library back with the data item (or not). */
 
 void
-unget_format (fnode * f)
+unget_format (st_parameter_dt *dtp, const fnode *f)
 {
-
-  saved_format = f;
-}
-
-
-
-
-#if 0
-
-static void dump_format1 (fnode * f);
-
-/* dump_format0()-- Dump a single format node */
-
-void
-dump_format0 (fnode * f)
-{
-  char *p;
-  int i;
-
-  switch (f->format)
-    {
-    case FMT_COLON:
-      st_printf (" :");
-      break;
-    case FMT_SLASH:
-      st_printf (" %d/", f->u.r);
-      break;
-    case FMT_DOLLAR:
-      st_printf (" $");
-      break;
-    case FMT_T:
-      st_printf (" T%d", f->u.n);
-      break;
-    case FMT_TR:
-      st_printf (" TR%d", f->u.n);
-      break;
-    case FMT_TL:
-      st_printf (" TL%d", f->u.n);
-      break;
-    case FMT_X:
-      st_printf (" %dX", f->u.n);
-      break;
-    case FMT_S:
-      st_printf (" S");
-      break;
-    case FMT_SS:
-      st_printf (" SS");
-      break;
-    case FMT_SP:
-      st_printf (" SP");
-      break;
-
-    case FMT_LPAREN:
-      if (f->repeat == 1)
-       st_printf (" (");
-      else
-       st_printf (" %d(", f->repeat);
-
-      dump_format1 (f->u.child);
-      st_printf (" )");
-      break;
-
-    case FMT_STRING:
-      st_printf (" '");
-      p = f->u.string.p;
-      for (i = f->u.string.length; i > 0; i--)
-       st_printf ("%c", *p++);
-
-      st_printf ("'");
-      break;
-
-    case FMT_P:
-      st_printf (" %dP", f->u.k);
-      break;
-    case FMT_I:
-      st_printf (" %dI%d.%d", f->repeat, f->u.integer.w, f->u.integer.m);
-      break;
-
-    case FMT_B:
-      st_printf (" %dB%d.%d", f->repeat, f->u.integer.w, f->u.integer.m);
-      break;
-
-    case FMT_O:
-      st_printf (" %dO%d.%d", f->repeat, f->u.integer.w, f->u.integer.m);
-      break;
-
-    case FMT_Z:
-      st_printf (" %dZ%d.%d", f->repeat, f->u.integer.w, f->u.integer.m);
-      break;
-
-    case FMT_BN:
-      st_printf (" BN");
-      break;
-    case FMT_BZ:
-      st_printf (" BZ");
-      break;
-    case FMT_D:
-      st_printf (" %dD%d.%d", f->repeat, f->u.real.w, f->u.real.d);
-      break;
-
-    case FMT_EN:
-      st_printf (" %dEN%d.%dE%d", f->repeat, f->u.real.w, f->u.real.d,
-                f->u.real.e);
-      break;
-
-    case FMT_ES:
-      st_printf (" %dES%d.%dE%d", f->repeat, f->u.real.w, f->u.real.d,
-                f->u.real.e);
-      break;
-
-    case FMT_F:
-      st_printf (" %dF%d.%d", f->repeat, f->u.real.w, f->u.real.d);
-      break;
-
-    case FMT_E:
-      st_printf (" %dE%d.%dE%d", f->repeat, f->u.real.w, f->u.real.d,
-                f->u.real.e);
-      break;
-
-    case FMT_G:
-      st_printf (" %dG%d.%dE%d", f->repeat, f->u.real.w, f->u.real.d,
-                f->u.real.e);
-      break;
-
-    case FMT_L:
-      st_printf (" %dL%d", f->repeat, f->u.w);
-      break;
-    case FMT_A:
-      st_printf (" %dA%d", f->repeat, f->u.w);
-      break;
-
-    default:
-      st_printf (" ???");
-      break;
-    }
-}
-
-
-/* dump_format1()-- Dump a string of format nodes */
-
-static void
-dump_format1 (fnode * f)
-{
-
-  for (; f; f = f->next)
-    dump_format1 (f);
-}
-
-/* dump_format()-- Dump the whole format node tree */
-
-void
-dump_format (void)
-{
-
-  st_printf ("format = ");
-  dump_format0 (&array[0]);
-  st_printf ("\n");
-}
-
-
-void
-next_test (void)
-{
-  fnode *f;
-  int i;
-
-  for (i = 0; i < 20; i++)
-    {
-      f = next_format ();
-      if (f == NULL)
-       {
-         st_printf ("No format!\n");
-         break;
-       }
-
-      dump_format1 (f);
-      st_printf ("\n");
-    }
+  dtp->u.p.fmt->saved_format = f;
 }
 
-#endif