lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / language / control / repeat.c
index ecff0577bf3ca64624e139cbd31b633ffe4e8ab1..c0fa8fe0d8dfe165deb6f55968f17ed512d9b46f 100644 (file)
 
 #include <config.h>
 
-#include "language/control/repeat.h"
-
-#include <ctype.h>
-#include <math.h>
 #include <stdlib.h>
 
 #include "data/dictionary.h"
 #include "data/procedure.h"
-#include "data/settings.h"
-#include "libpspp/getl.h"
 #include "language/command.h"
 #include "language/lexer/lexer.h"
+#include "language/lexer/segment.h"
+#include "language/lexer/token.h"
 #include "language/lexer/variable-parser.h"
 #include "libpspp/cast.h"
-#include "libpspp/ll.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/hmap.h"
 #include "libpspp/message.h"
-#include "libpspp/misc.h"
-#include "libpspp/pool.h"
 #include "libpspp/str.h"
-#include "data/variable.h"
 
-#include "gl/intprops.h"
+#include "gl/ftoastr.h"
+#include "gl/minmax.h"
 #include "gl/xalloc.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-/* A line repeated by DO REPEAT. */
-struct repeat_line
-  {
-    struct ll ll;               /* In struct repeat_block line_list. */
-    const char *file_name;      /* File name. */
-    int line_number;            /* Line number. */
-    struct substring text;     /* Contents. */
-  };
-
-/* The type of substitution made for a DO REPEAT macro. */
-enum repeat_macro_type
-  {
-    VAR_NAMES,
-    OTHER
-  };
-
-/* Describes one DO REPEAT macro. */
-struct repeat_macro
+struct dummy_var
   {
-    struct ll ll;                       /* In struct repeat_block macros. */
-    enum repeat_macro_type type;        /* Types of replacements. */
-    struct substring name;              /* Macro name. */
-    struct substring *replacements;     /* Macro replacement. */
+    struct hmap_node hmap_node;
+    char *name;
+    char **values;
+    size_t n_values;
   };
 
-/* A DO REPEAT...END REPEAT block. */
-struct repeat_block
-  {
-    struct getl_interface parent;
-
-    struct pool *pool;                  /* Pool used for storage. */
-    struct dataset *ds;                 /* The dataset for this block */
-
-    struct ll_list lines;               /* Lines in buffer. */
-    struct ll *cur_line;                /* Last line output. */
-    int loop_cnt;                       /* Number of loops. */
-    int loop_idx;                       /* Number of loops so far. */
+static bool parse_specification (struct lexer *, struct dictionary *,
+                                 struct hmap *dummies);
+static bool parse_commands (struct lexer *, struct hmap *dummies);
+static void destroy_dummies (struct hmap *dummies);
 
-    struct ll_list macros;              /* Table of macros. */
+static bool parse_ids (struct lexer *, const struct dictionary *,
+                       struct dummy_var *);
+static bool parse_numbers (struct lexer *, struct dummy_var *);
+static bool parse_strings (struct lexer *, struct dummy_var *);
 
-    bool print;                         /* Print lines as executed? */
-  };
-
-static bool parse_specification (struct lexer *, struct repeat_block *);
-static bool parse_lines (struct lexer *, struct repeat_block *);
-static void create_vars (struct repeat_block *);
+int
+cmd_do_repeat (struct lexer *lexer, struct dataset *ds)
+{
+  struct hmap dummies;
+  bool ok;
 
-static struct repeat_macro *find_macro (struct repeat_block *,
-                                        struct substring name);
+  if (!parse_specification (lexer, dataset_dict (ds), &dummies))
+    return CMD_CASCADING_FAILURE;
 
-static int parse_ids (struct lexer *, const struct dictionary *dict,
-                     struct repeat_macro *, struct pool *);
+  ok = parse_commands (lexer, &dummies);
 
-static int parse_numbers (struct lexer *, struct repeat_macro *,
-                         struct pool *);
+  destroy_dummies (&dummies);
 
-static int parse_strings (struct lexer *, struct repeat_macro *,
-                         struct pool *);
+  return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
 
-static void do_repeat_filter (struct getl_interface *,
-                              struct string *);
-static bool do_repeat_read (struct getl_interface *,
-                            struct string *);
-static void do_repeat_close (struct getl_interface *);
-static bool always_false (const struct getl_interface *);
-static const char *do_repeat_name (const struct getl_interface *);
-static int do_repeat_location (const struct getl_interface *);
+static unsigned int
+hash_dummy (const char *name, size_t name_len)
+{
+  return hash_case_bytes (name, name_len, 0);
+}
 
-int
-cmd_do_repeat (struct lexer *lexer, struct dataset *ds)
+static const struct dummy_var *
+find_dummy_var (struct hmap *hmap, const char *name, size_t name_len)
 {
-  struct repeat_block *block;
-
-  block = pool_create_container (struct repeat_block, pool);
-  block->ds = ds;
-  ll_init (&block->lines);
-  block->cur_line = ll_null (&block->lines);
-  block->loop_idx = 0;
-  ll_init (&block->macros);
-
-  if (!parse_specification (lexer, block) || !parse_lines (lexer, block))
-    goto error;
-
-  create_vars (block);
-
-  block->parent.read = do_repeat_read;
-  block->parent.close = do_repeat_close;
-  block->parent.filter = do_repeat_filter;
-  block->parent.interactive = always_false;
-  block->parent.name = do_repeat_name;
-  block->parent.location = do_repeat_location;
-
-  if (!ll_is_empty (&block->lines))
-    getl_include_source (lex_get_source_stream (lexer),
-                        &block->parent,
-                        lex_current_syntax_mode (lexer),
-                        lex_current_error_mode (lexer)
-                        );
-  else
-    pool_destroy (block->pool);
+  const struct dummy_var *dv;
 
-  return CMD_SUCCESS;
+  HMAP_FOR_EACH_WITH_HASH (dv, struct dummy_var, hmap_node,
+                           hash_dummy (name, name_len), hmap)
+    if (strcasecmp (dv->name, name))
+      return dv;
 
- error:
-  pool_destroy (block->pool);
-  return CMD_CASCADING_FAILURE;
+  return NULL;
 }
 
 /* Parses the whole DO REPEAT command specification.
    Returns success. */
 static bool
-parse_specification (struct lexer *lexer, struct repeat_block *block)
+parse_specification (struct lexer *lexer, struct dictionary *dict,
+                     struct hmap *dummies)
 {
-  struct substring first_name;
+  struct dummy_var *first_dv = NULL;
 
-  block->loop_cnt = 0;
+  hmap_init (dummies);
   do
     {
-      struct repeat_macro *macro;
-      struct dictionary *dict = dataset_dict (block->ds);
-      int count;
+      struct dummy_var *dv;
+      const char *name;
+      bool ok;
 
       /* Get a stand-in variable name and make sure it's unique. */
       if (!lex_force_id (lexer))
-       return false;
-      if (dict_lookup_var (dict, lex_tokcstr (lexer)))
+       goto error;
+      name = lex_tokcstr (lexer);
+      if (dict_lookup_var (dict, name))
         msg (SW, _("Dummy variable name `%s' hides dictionary variable `%s'."),
-             lex_tokcstr (lexer), lex_tokcstr (lexer));
-      if (find_macro (block, lex_tokss (lexer)))
-         {
-           msg (SE, _("Dummy variable name `%s' is given twice."),
-                lex_tokcstr (lexer));
-           return false;
-         }
+             name, name);
+      if (find_dummy_var (dummies, name, strlen (name)))
+        {
+          msg (SE, _("Dummy variable name `%s' is given twice."), name);
+          goto error;
+        }
 
       /* Make a new macro. */
-      macro = pool_alloc (block->pool, sizeof *macro);
-      ss_alloc_substring_pool (&macro->name, lex_tokss (lexer), block->pool);
-      ll_push_tail (&block->macros, &macro->ll);
+      dv = xmalloc (sizeof *dv);
+      dv->name = xstrdup (name);
+      dv->values = NULL;
+      dv->n_values = 0;
+      hmap_insert (dummies, &dv->hmap_node, hash_dummy (name, strlen (name)));
 
       /* Skip equals sign. */
       lex_get (lexer);
       if (!lex_force_match (lexer, T_EQUALS))
-       return false;
+       goto error;
 
       /* Get the details of the variable's possible values. */
-      if (lex_token (lexer) == T_ID)
-       count = parse_ids (lexer, dict, macro, block->pool);
+      if (lex_token (lexer) == T_ID || lex_token (lexer) == T_ALL)
+       ok = parse_ids (lexer, dict, dv);
       else if (lex_is_number (lexer))
-       count = parse_numbers (lexer, macro, block->pool);
+       ok = parse_numbers (lexer, dv);
       else if (lex_is_string (lexer))
-       count = parse_strings (lexer, macro, block->pool);
+       ok = parse_strings (lexer, dv);
       else
        {
          lex_error (lexer, NULL);
-         return false;
+         goto error;
        }
-      if (count == 0)
-       return false;
+      if (!ok)
+       goto error;
+      assert (dv->n_values > 0);
       if (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
         {
           lex_error (lexer, NULL);
-          return false;
+          goto error;
         }
 
-      /* If this is the first variable then it defines how many
-        replacements there must be; otherwise enforce this number of
-        replacements. */
-      if (block->loop_cnt == 0)
+      /* If this is the first variable then it defines how many replacements
+        there must be; otherwise enforce this number of replacements. */
+      if (first_dv == NULL)
+        first_dv = dv;
+      else if (first_dv->n_values != dv->n_values)
        {
-         block->loop_cnt = count;
-         first_name = macro->name;
-       }
-      else if (block->loop_cnt != count)
-       {
-         msg (SE, _("Dummy variable `%.*s' had %d "
-                     "substitutions, so `%.*s' must also, but %d "
-                     "were specified."),
-              (int) ss_length (first_name), ss_data (first_name),
-               block->loop_cnt,
-               (int) ss_length (macro->name), ss_data (macro->name),
-               count);
-         return false;
+         msg (SE, _("Dummy variable `%s' had %d substitutions, so `%s' must "
+                     "also, but %d were specified."),
+               first_dv->name, first_dv->n_values,
+               dv->name, dv->n_values);
+         goto error;
        }
 
       lex_match (lexer, T_SLASH);
     }
-  while (lex_token (lexer) != T_ENDCMD);
+  while (!lex_match (lexer, T_ENDCMD));
 
-  return true;
-}
+  while (lex_match (lexer, T_ENDCMD))
+    continue;
 
-/* Finds and returns a DO REPEAT macro with the given NAME, or
-   NULL if there is none */
-static struct repeat_macro *
-find_macro (struct repeat_block *block, struct substring name)
-{
-  struct repeat_macro *macro;
-
-  ll_for_each (macro, struct repeat_macro, ll, &block->macros)
-    if (ss_equals (macro->name, name))
-      return macro;
+  return true;
 
-  return NULL;
+error:
+  destroy_dummies (dummies);
+  return false;
 }
 
-/* Advances LINE past white space and an identifier, if present.
-   Returns true if KEYWORD matches the identifer, false
-   otherwise. */
-static bool
-recognize_keyword (struct substring *line, const char *keyword)
+static size_t
+count_values (struct hmap *dummies)
 {
-  struct substring id;
-  ss_ltrim (line, ss_cstr (CC_SPACES));
-  ss_get_bytes (line, lex_id_get_length (*line), &id);
-  return lex_id_match (ss_cstr (keyword), id);
+  const struct dummy_var *dv;
+  dv = HMAP_FIRST (struct dummy_var, hmap_node, dummies);
+  return dv->n_values;
 }
 
-/* Returns true if LINE contains a DO REPEAT command, false
-   otherwise. */
-static bool
-recognize_do_repeat (struct substring line)
+static void
+do_parse_commands (struct substring s, enum lex_syntax_mode syntax_mode,
+                   struct hmap *dummies,
+                   struct string *outputs, size_t n_outputs)
 {
-  return (recognize_keyword (&line, "do")
-          && recognize_keyword (&line, "repeat"));
-}
+  struct segmenter segmenter;
 
-/* Returns true if LINE contains an END REPEAT command, false
-   otherwise.  Sets *PRINT to true for END REPEAT PRINT, false
-   otherwise. */
-static bool
-recognize_end_repeat (struct substring line, bool *print)
-{
-  if (!recognize_keyword (&line, "end")
-      || !recognize_keyword (&line, "repeat"))
-    return false;
+  segmenter_init (&segmenter, syntax_mode);
 
-  *print = recognize_keyword (&line, "print");
-  return true;
-}
+  while (!ss_is_empty (s))
+    {
+      enum segment_type type;
+      int n;
 
-/* Read all the lines we are going to substitute, inside the DO
-   REPEAT...END REPEAT block. */
-static bool
-parse_lines (struct lexer *lexer, struct repeat_block *block)
-{
-  char *previous_file_name;
-  int nesting_level;
+      n = segmenter_push (&segmenter, s.string, s.length, &type);
+      assert (n >= 0);
 
-  previous_file_name = NULL;
-  nesting_level = 0;
+      if (type == SEG_DO_REPEAT_COMMAND)
+        {
+          for (;;)
+            {
+              int k;
 
-  for (;;)
-    {
-      const char *cur_file_name;
-      struct repeat_line *line;
-      struct string text;
-      bool command_ends_before_line, command_ends_after_line;
+              k = segmenter_push (&segmenter, s.string + n, s.length - n,
+                                  &type);
+              if (type != SEG_NEWLINE && type != SEG_DO_REPEAT_COMMAND)
+                break;
 
-      /* Retrieve an input line and make a copy of it. */
-      if (!lex_get_line_raw (lexer))
-        {
-          msg (SE, _("DO REPEAT without END REPEAT."));
-          return false;
-        }
-      ds_init_string (&text, lex_entire_line_ds (lexer));
-
-      /* Record file name. */
-      cur_file_name = getl_source_name (lex_get_source_stream (lexer));
-      if (cur_file_name != NULL &&
-         (previous_file_name == NULL
-           || !strcmp (cur_file_name, previous_file_name)))
-        previous_file_name = pool_strdup (block->pool, cur_file_name);
-
-      /* Create a line structure. */
-      line = pool_alloc (block->pool, sizeof *line);
-      line->file_name = previous_file_name;
-      line->line_number = getl_source_location (lex_get_source_stream (lexer));
-      ss_alloc_substring_pool (&line->text, ds_ss (&text), block->pool);
-
-
-      /* Check whether the line contains a DO REPEAT or END
-         REPEAT command. */
-      lex_preprocess_line (&text,
-                          lex_current_syntax_mode (lexer),
-                           &command_ends_before_line,
-                           &command_ends_after_line);
-      if (recognize_do_repeat (ds_ss (&text)))
-        {
-          if (settings_get_syntax () == COMPATIBLE)
-            msg (SE, _("DO REPEAT may not nest in compatibility mode."));
-          else
-            nesting_level++;
+              n += k;
+            }
+
+          do_parse_commands (ss_head (s, n), syntax_mode, dummies,
+                             outputs, n_outputs);
         }
-      else if (recognize_end_repeat (ds_ss (&text), &block->print)
-               && nesting_level-- == 0)
+      else if (type != SEG_END)
         {
-          lex_discard_line (lexer);
-         ds_destroy (&text);
-          return true;
+          const struct dummy_var *dv;
+          size_t i;
+
+          dv = (type == SEG_IDENTIFIER
+                ? find_dummy_var (dummies, s.string, n)
+                : NULL);
+          for (i = 0; i < n_outputs; i++)
+            if (dv != NULL)
+              ds_put_cstr (&outputs[i], dv->values[i]);
+            else
+              ds_put_substring (&outputs[i], ss_head (s, n));
         }
-      ds_destroy (&text);
 
-      /* Add the line to the list. */
-      ll_push_tail (&block->lines, &line->ll);
+      ss_advance (&s, n);
     }
 }
 
-/* Creates variables for the given DO REPEAT. */
+static bool
+parse_commands (struct lexer *lexer, struct hmap *dummies)
+{
+  struct string *outputs;
+  struct string input;
+  size_t input_len;
+  size_t n_values;
+  char *file_name;
+  int line_number;
+  bool ok;
+  size_t i;
+
+  if (lex_get_file_name (lexer) != NULL)
+    file_name = xstrdup (lex_get_file_name (lexer));
+  else
+    file_name = NULL;
+  line_number = lex_get_first_line_number (lexer, 0);
+
+  ds_init_empty (&input);
+  while (lex_is_string (lexer))
+    {
+      ds_put_substring (&input, lex_tokss (lexer));
+      ds_put_byte (&input, '\n');
+      lex_get (lexer);
+    }
+  if (ds_is_empty (&input))
+    ds_put_byte (&input, '\n');
+  ds_put_byte (&input, '\0');
+  input_len = ds_length (&input);
+
+  n_values = count_values (dummies);
+  outputs = xmalloc (n_values * sizeof *outputs);
+  for (i = 0; i < n_values; i++)
+    ds_init_empty (&outputs[i]);
+
+  do_parse_commands (ds_ss (&input), lex_get_syntax_mode (lexer),
+                     dummies, outputs, n_values);
+
+  ds_destroy (&input);
+
+  while (lex_match (lexer, T_ENDCMD))
+    continue;
+
+  ok = (lex_force_match_id (lexer, "END")
+        && lex_force_match_id (lexer, "REPEAT"));
+  if (ok)
+    lex_match_id (lexer, "PRINT"); /* XXX */
+
+  lex_discard_rest_of_command (lexer);
+
+  for (i = 0; i < n_values; i++)
+    {
+      struct string *output = &outputs[n_values - i - 1];
+      struct lex_reader *reader;
+
+      reader = lex_reader_for_substring_nocopy (ds_ss (output));
+      lex_reader_set_file_name (reader, file_name);
+      reader->line_number = line_number;
+      lex_include (lexer, reader);
+    }
+  free (file_name);
+
+  return ok;
+}
+
 static void
-create_vars (struct repeat_block *block)
+destroy_dummies (struct hmap *dummies)
 {
-  struct repeat_macro *macro;
-
-  ll_for_each (macro, struct repeat_macro, ll, &block->macros)
-    if (macro->type == VAR_NAMES)
-      {
-        int i;
-
-        for (i = 0; i < block->loop_cnt; i++)
-          {
-            /* Ignore return value: if the variable already
-               exists there is no harm done. */
-            char *var_name = ss_xstrdup (macro->replacements[i]);
-            dict_create_var (dataset_dict (block->ds), var_name, 0);
-            free (var_name);
-          }
-      }
+  struct dummy_var *dv, *next;
+
+  HMAP_FOR_EACH_SAFE (dv, next, struct dummy_var, hmap_node, dummies)
+    {
+      size_t i;
+
+      hmap_delete (dummies, &dv->hmap_node);
+
+      free (dv->name);
+      for (i = 0; i < dv->n_values; i++)
+        free (dv->values[i]);
+      free (dv->values);
+      free (dv);
+    }
+  hmap_destroy (dummies);
 }
 
 /* Parses a set of ids for DO REPEAT. */
-static int
+static bool
 parse_ids (struct lexer *lexer, const struct dictionary *dict,
-          struct repeat_macro *macro, struct pool *pool)
+          struct dummy_var *dv)
 {
-  char **replacements;
-  size_t n, i;
-
-  macro->type = VAR_NAMES;
-  if (!parse_mixed_vars_pool (lexer, dict, pool, &replacements, &n, PV_NONE))
-    return 0;
-
-  macro->replacements = pool_nalloc (pool, n, sizeof *macro->replacements);
-  for (i = 0; i < n; i++)
-    macro->replacements[i] = ss_cstr (replacements[i]);
-  return n;
+  return parse_mixed_vars (lexer, dict, &dv->values, &dv->n_values, PV_NONE);
 }
 
 /* Adds REPLACEMENT to MACRO's list of replacements, which has
    *USED elements and has room for *ALLOCATED.  Allocates memory
    from POOL. */
 static void
-add_replacement (struct substring replacement,
-                 struct repeat_macro *macro, struct pool *pool,
-                 size_t *used, size_t *allocated)
+add_replacement (struct dummy_var *dv, char *value, size_t *allocated)
 {
-  if (*used == *allocated)
-    macro->replacements = pool_2nrealloc (pool, macro->replacements, allocated,
-                                          sizeof *macro->replacements);
-  macro->replacements[(*used)++] = replacement;
+  if (dv->n_values == *allocated)
+    dv->values = x2nrealloc (dv->values, allocated, sizeof *dv->values);
+  dv->values[dv->n_values++] = value;
 }
 
 /* Parses a list or range of numbers for DO REPEAT. */
-static int
-parse_numbers (struct lexer *lexer, struct repeat_macro *macro,
-              struct pool *pool)
+static bool
+parse_numbers (struct lexer *lexer, struct dummy_var *dv)
 {
-  size_t used = 0;
   size_t allocated = 0;
 
-  macro->type = OTHER;
-  macro->replacements = NULL;
-
   do
     {
-      bool integer_value_seen;
-      double a, b, i;
-
-      /* Parse A TO B into a, b. */
       if (!lex_force_num (lexer))
-       return 0;
+       return false;
 
-      if ( (integer_value_seen = lex_is_integer (lexer) ) )
-       a = lex_integer (lexer);
-      else
-       a = lex_number (lexer);
+      if (lex_next_token (lexer, 1) == T_TO)
+        {
+          long int a, b;
+          long int i;
 
-      lex_get (lexer);
-      if (lex_token (lexer) == T_TO)
-       {
-         if ( !integer_value_seen )
+          if (!lex_is_integer (lexer))
            {
-             msg (SE, _("Ranges may only have integer bounds"));
-             return 0;
+             msg (SE, _("Ranges may only have integer bounds."));
+             return false;
            }
-         lex_get (lexer);
-         if (!lex_force_int (lexer))
-           return 0;
+
+          a = lex_integer (lexer);
+          lex_get (lexer);
+          lex_get (lexer);
+
+          if (!lex_force_int (lexer))
+            return false;
+
          b = lex_integer (lexer);
           if (b < a)
             {
-              msg (SE, _("%g TO %g is an invalid range."), a, b);
-              return 0;
+              msg (SE, _("%ld TO %ld is an invalid range."), a, b);
+              return false;
             }
          lex_get (lexer);
-       }
+
+          for (i = a; i <= b; i++)
+            add_replacement (dv, xasprintf ("%ld", i), &allocated);
+        }
       else
-        b = a;
+        {
+          char s[DBL_BUFSIZE_BOUND];
 
-      for (i = a; i <= b; i++)
-        add_replacement (ss_cstr (pool_asprintf (pool, "%g", i)),
-                         macro, pool, &used, &allocated);
+          dtoastr (s, sizeof s, 0, 0, lex_number (lexer));
+          add_replacement (dv, xstrdup (s), &allocated);
+          lex_get (lexer);
+        }
 
       lex_match (lexer, T_COMMA);
     }
   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD);
 
-  return used;
+  return true;
 }
 
 /* Parses a list of strings for DO REPEAT. */
-int
-parse_strings (struct lexer *lexer, struct repeat_macro *macro, struct pool *pool)
+static bool
+parse_strings (struct lexer *lexer, struct dummy_var *dv)
 {
-  size_t used = 0;
   size_t allocated = 0;
 
-  macro->type = OTHER;
-  macro->replacements = NULL;
-
   do
     {
-      char *string;
-
       if (!lex_force_string (lexer))
        {
          msg (SE, _("String expected."));
-         return 0;
+         return false;
        }
 
-      string = lex_token_representation (lexer);
-      pool_register (pool, free, string);
-      add_replacement (ss_cstr (string), macro, pool, &used, &allocated);
+      add_replacement (dv, token_to_string (lex_next (lexer, 0)), &allocated);
 
       lex_get (lexer);
       lex_match (lexer, T_COMMA);
     }
   while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD);
 
-  return used;
+  return true;
 }
 \f
 int
@@ -501,128 +430,3 @@ cmd_end_repeat (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
   msg (SE, _("No matching DO REPEAT."));
   return CMD_CASCADING_FAILURE;
 }
-\f
-/* Finds a DO REPEAT macro with the given NAME and returns the
-   appropriate substitution if found, or NAME otherwise. */
-static struct substring
-find_substitution (struct repeat_block *block, struct substring name)
-{
-  struct repeat_macro *macro = find_macro (block, name);
-  return macro ? macro->replacements[block->loop_idx] : name;
-}
-
-/* Makes appropriate DO REPEAT macro substitutions within the
-   repeated lines. */
-static void
-do_repeat_filter (struct getl_interface *interface, struct string *line)
-{
-  struct repeat_block *block
-    = UP_CAST (interface, struct repeat_block, parent);
-  bool in_apos, in_quote, dot;
-  struct substring input;
-  struct string output;
-  int c;
-
-  ds_init_empty (&output);
-
-  /* Strip trailing whitespace, check for & remove terminal dot. */
-  ds_rtrim (line, ss_cstr (CC_SPACES));
-  dot = ds_chomp_byte (line, '.');
-  input = ds_ss (line);
-  in_apos = in_quote = false;
-  while ((c = ss_first (input)) != EOF)
-    {
-      if (c == '\'' && !in_quote)
-       in_apos = !in_apos;
-      else if (c == '"' && !in_apos)
-       in_quote = !in_quote;
-
-      if (in_quote || in_apos || !lex_is_id1 (c))
-        {
-          ds_put_byte (&output, c);
-          ss_advance (&input, 1);
-        }
-      else
-        {
-          struct substring id;
-          ss_get_bytes (&input, lex_id_get_length (input), &id);
-          ds_put_substring (&output, find_substitution (block, id));
-        }
-    }
-  if (dot)
-    ds_put_byte (&output, '.');
-
-  ds_swap (line, &output);
-  ds_destroy (&output);
-}
-
-static struct repeat_line *
-current_line (const struct getl_interface *interface)
-{
-  struct repeat_block *block
-    = UP_CAST (interface, struct repeat_block, parent);
-  return (block->cur_line != ll_null (&block->lines)
-          ? ll_data (block->cur_line, struct repeat_line, ll)
-          : NULL);
-}
-
-/* Function called by getl to read a line.  Puts the line in
-   OUTPUT and its syntax mode in *SYNTAX.  Returns true if a line
-   was obtained, false if the source is exhausted. */
-static bool
-do_repeat_read  (struct getl_interface *interface,
-                 struct string *output)
-{
-  struct repeat_block *block
-    = UP_CAST (interface, struct repeat_block, parent);
-  struct repeat_line *line;
-
-  block->cur_line = ll_next (block->cur_line);
-  if (block->cur_line == ll_null (&block->lines))
-    {
-      block->loop_idx++;
-      if (block->loop_idx >= block->loop_cnt)
-        return false;
-
-      block->cur_line = ll_head (&block->lines);
-    }
-
-  line = current_line (interface);
-  ds_assign_substring (output, line->text);
-  return true;
-}
-
-/* Frees a DO REPEAT block.
-   Called by getl to close out the DO REPEAT block. */
-static void
-do_repeat_close (struct getl_interface *interface)
-{
-  struct repeat_block *block
-    = UP_CAST (interface, struct repeat_block, parent);
-  pool_destroy (block->pool);
-}
-
-
-static bool
-always_false (const struct getl_interface *i UNUSED)
-{
-  return false;
-}
-
-/* Returns the name of the source file from which the previous
-   line was originally obtained, or a null pointer if none. */
-static const char *
-do_repeat_name (const struct getl_interface *interface)
-{
-  struct repeat_line *line = current_line (interface);
-  return line ? line->file_name : NULL;
-}
-
-/* Returns the line number in the source file from which the
-   previous line was originally obtained, or 0 if none. */
-static int
-do_repeat_location (const struct getl_interface *interface)
-{
-  struct repeat_line *line = current_line (interface);
-  return line ? line->line_number : 0;
-}