-/* PSPP - computes sample statistics.
- Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
- Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+ Copyright (C) 1997-9, 2000, 2007, 2009-2012 Free Software Foundation, Inc.
- This program 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 of the
- License, or (at your option) any later version.
+ This program 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 3 of the License, or
+ (at your option) any later version.
- This program is distributed in the hope that it will be useful, 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.
+ This program is distributed in the hope that it will be useful,
+ 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 this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- 02110-1301, USA. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <config.h>
-#include "repeat.h"
-
-#include <ctype.h>
-#include <math.h>
#include <stdlib.h>
-#include <data/dictionary.h>
-#include <data/procedure.h>
-#include <data/settings.h>
-#include <language/command.h>
-#include <language/lexer/lexer.h>
-#include <language/line-buffer.h>
-#include <libpspp/alloc.h>
-#include <libpspp/message.h>
-#include <libpspp/message.h>
-#include <libpspp/misc.h>
-#include <libpspp/pool.h>
-#include <libpspp/str.h>
-#include <data/variable.h>
-
-#include "intprops.h"
+#include "data/dataset.h"
+#include "data/dictionary.h"
+#include "data/settings.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/assertion.h"
+#include "libpspp/cast.h"
+#include "libpspp/hash-functions.h"
+#include "libpspp/hmap.h"
+#include "libpspp/i18n.h"
+#include "libpspp/message.h"
+#include "libpspp/str.h"
+#include "libpspp/misc.h"
+
+#include "gl/ftoastr.h"
+#include "gl/minmax.h"
+#include "gl/xalloc.h"
#include "gettext.h"
#define _(msgid) gettext (msgid)
-/* Defines a list of lines used by DO REPEAT. */
-struct line_list
+struct dummy_var
{
- struct line_list *next; /* Next line. */
- char *file_name; /* File name. */
- int line_number; /* Line number. */
- char *line; /* Contents. */
+ struct hmap_node hmap_node;
+ char *name;
+ char **values;
+ size_t n_values;
};
-/* The type of substitution made for a DO REPEAT macro. */
-enum repeat_entry_type
- {
- VAR_NAMES,
- OTHER
- };
+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);
-/* Describes one DO REPEAT macro. */
-struct repeat_entry
- {
- struct repeat_entry *next; /* Next entry. */
- enum repeat_entry_type type; /* Types of replacements. */
- char id[LONG_NAME_LEN + 1]; /* Macro identifier. */
- char **replacement; /* Macro replacement. */
- };
+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 *);
-/* A DO REPEAT...END REPEAT block. */
-struct repeat_block
- {
- struct pool *pool; /* Pool used for storage. */
- struct line_list *first_line; /* First line in line buffer. */
- struct line_list *cur_line; /* Current line in line buffer. */
- int loop_cnt; /* Number of loops. */
- int loop_idx; /* Number of loops so far. */
- struct repeat_entry *macros; /* Pointer to macro table. */
- bool print; /* Print lines as executed? */
- };
+int
+cmd_do_repeat (struct lexer *lexer, struct dataset *ds)
+{
+ struct hmap dummies;
+ bool ok;
-static bool parse_specification (struct repeat_block *);
-static bool parse_lines (struct repeat_block *);
-static void create_vars (struct repeat_block *);
+ if (!parse_specification (lexer, dataset_dict (ds), &dummies))
+ return CMD_CASCADING_FAILURE;
-static int parse_ids (struct repeat_entry *, struct pool *);
-static int parse_numbers (struct repeat_entry *, struct pool *);
-static int parse_strings (struct repeat_entry *, struct pool *);
+ ok = parse_commands (lexer, &dummies);
-static void do_repeat_filter (struct string *line, void *block);
-static bool do_repeat_read (struct string *line, char **file_name,
- int *line_number, void *block);
-static void do_repeat_close (void *block);
+ destroy_dummies (&dummies);
-int
-cmd_do_repeat (void)
-{
- struct repeat_block *block;
+ return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE;
+}
- block = pool_create_container (struct repeat_block, pool);
+static unsigned int
+hash_dummy (const char *name, size_t name_len)
+{
+ return utf8_hash_case_bytes (name, name_len, 0);
+}
- if (!parse_specification (block) || !parse_lines (block))
- goto error;
-
- create_vars (block);
-
- block->cur_line = NULL;
- block->loop_idx = -1;
- getl_include_filter (do_repeat_filter, do_repeat_close, block);
- getl_include_function (do_repeat_read, NULL, block);
+static const struct dummy_var *
+find_dummy_var (struct hmap *hmap, const char *name, size_t name_len)
+{
+ 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 (utf8_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 repeat_block *block)
+parse_specification (struct lexer *lexer, struct dictionary *dict,
+ struct hmap *dummies)
{
- char first_name[LONG_NAME_LEN + 1];
+ struct dummy_var *first_dv = NULL;
- block->loop_cnt = 0;
- block->macros = NULL;
+ hmap_init (dummies);
do
{
- struct repeat_entry *e;
- struct repeat_entry *iter;
- 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 ())
- return false;
- if (dict_lookup_var (default_dict, tokid))
- msg (SW, _("Dummy variable name \"%s\" hides dictionary "
- "variable \"%s\"."),
- tokid, tokid);
- for (iter = block->macros; iter != NULL; iter = iter->next)
- if (!strcasecmp (iter->id, tokid))
- {
- msg (SE, _("Dummy variable name \"%s\" is given twice."), tokid);
- return false;
- }
-
- /* Make a new stand-in variable entry and link it into the
- list. */
- e = pool_alloc (block->pool, sizeof *e);
- e->next = block->macros;
- strcpy (e->id, tokid);
- block->macros = e;
+ if (!lex_force_id (lexer))
+ goto error;
+ name = lex_tokcstr (lexer);
+ if (dict_lookup_var (dict, name))
+ msg (SW, _("Dummy variable name `%s' hides dictionary variable `%s'."),
+ 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. */
+ 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 ();
- if (!lex_force_match ('='))
- return false;
+ lex_get (lexer);
+ if (!lex_force_match (lexer, T_EQUALS))
+ goto error;
/* Get the details of the variable's possible values. */
- if (token == T_ID)
- count = parse_ids (e, block->pool);
- else if (lex_is_number ())
- count = parse_numbers (e, block->pool);
- else if (token == T_STRING)
- count = parse_strings (e, 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))
+ ok = parse_numbers (lexer, dv);
+ else if (lex_is_string (lexer))
+ ok = parse_strings (lexer, dv);
else
{
- lex_error (NULL);
- return false;
+ lex_error (lexer, NULL);
+ goto error;
}
- if (count == 0)
- return false;
- if (token != '/' && token != '.')
+ if (!ok)
+ goto error;
+ assert (dv->n_values > 0);
+ if (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)
{
- lex_error (NULL);
- return false;
+ lex_error (lexer, NULL);
+ 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)
- {
- block->loop_cnt = count;
- strcpy (first_name, e->id);
- }
- else if (block->loop_cnt != count)
+ /* 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)
{
- msg (SE, _("Dummy variable \"%s\" had %d "
- "substitutions, so \"%s\" must also, but %d "
- "were specified."),
- first_name, block->loop_cnt, e->id, count);
- return false;
+ msg (SE, _("Dummy variable `%s' had %zu substitutions, so `%s' must "
+ "also, but %zu were specified."),
+ first_dv->name, first_dv->n_values,
+ dv->name, dv->n_values);
+ goto error;
}
- lex_match ('/');
+ lex_match (lexer, T_SLASH);
}
- while (token != '.');
+ while (!lex_match (lexer, T_ENDCMD));
- return true;
-}
+ while (lex_match (lexer, T_ENDCMD))
+ continue;
-/* If KEYWORD appears beginning at CP, possibly preceded by white
- space, returns a pointer to the character just after the
- keyword. Otherwise, returns a null pointer. */
-static const char *
-recognize_keyword (const char *cp, const char *keyword)
-{
- const char *end;
-
- while (isspace ((unsigned char) *cp))
- cp++;
+ return true;
- end = lex_skip_identifier (cp);
- if (end != cp
- && lex_id_match_len (keyword, strlen (keyword), cp, end - cp))
- return end;
- else
- return NULL;
+error:
+ destroy_dummies (dummies);
+ return false;
}
-/* Returns CP, advanced past a '+' or '-' if present. */
-static const char *
-skip_indentor (const char *cp)
+static size_t
+count_values (struct hmap *dummies)
{
- if (*cp == '+' || *cp == '-')
- cp++;
- return cp;
+ 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 (const char *line)
+static void
+do_parse_commands (struct substring s, enum segmenter_mode mode,
+ struct hmap *dummies,
+ struct string *outputs, size_t n_outputs)
{
- const char *cp = recognize_keyword (skip_indentor (line), "do");
- return cp != NULL && recognize_keyword (cp, "repeat") != NULL;
-}
+ 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 (const char *line, bool *print)
-{
- const char *cp = recognize_keyword (skip_indentor (line), "end");
- if (cp == NULL)
- return false;
+ segmenter_init (&segmenter, mode);
- cp = recognize_keyword (cp, "repeat");
- if (cp == NULL)
- return false;
+ while (!ss_is_empty (s))
+ {
+ enum segment_type type;
+ int n;
+
+ n = segmenter_push (&segmenter, s.string, s.length, &type);
+ assert (n >= 0);
- *print = recognize_keyword (cp, "print");
- return true;
+ if (type == SEG_DO_REPEAT_COMMAND)
+ {
+ for (;;)
+ {
+ int k;
+
+ k = segmenter_push (&segmenter, s.string + n, s.length - n,
+ &type);
+ if (type != SEG_NEWLINE && type != SEG_DO_REPEAT_COMMAND)
+ break;
+
+ n += k;
+ }
+
+ do_parse_commands (ss_head (s, n), mode, dummies,
+ outputs, n_outputs);
+ }
+ else if (type != SEG_END)
+ {
+ 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));
+ }
+
+ ss_advance (&s, n);
+ }
}
-/* Read all the lines we are going to substitute, inside the DO
- REPEAT...END REPEAT block. */
static bool
-parse_lines (struct repeat_block *block)
+parse_commands (struct lexer *lexer, struct hmap *dummies)
{
- char *previous_file_name;
- struct line_list **last_line;
- int nesting_level;
+ enum lex_syntax_mode syntax_mode;
+ enum segmenter_mode mode;
+ struct string *outputs;
+ struct string input;
+ 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');
+
+ n_values = count_values (dummies);
+ outputs = xmalloc (n_values * sizeof *outputs);
+ for (i = 0; i < n_values; i++)
+ ds_init_empty (&outputs[i]);
+
+ syntax_mode = lex_get_syntax_mode (lexer);
+ if (syntax_mode == LEX_SYNTAX_AUTO)
+ mode = SEG_MODE_AUTO;
+ else if (syntax_mode == LEX_SYNTAX_INTERACTIVE)
+ mode = SEG_MODE_INTERACTIVE;
+ else if (syntax_mode == LEX_SYNTAX_BATCH)
+ mode = SEG_MODE_BATCH;
+ else
+ NOT_REACHED ();
+ do_parse_commands (ds_ss (&input), mode, 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 */
- previous_file_name = NULL;
- block->first_line = NULL;
- last_line = &block->first_line;
- nesting_level = 0;
+ lex_discard_rest_of_command (lexer);
- for (;;)
+ for (i = 0; i < n_values; i++)
{
- const char *cur_file_name;
- int cur_line_number;
- struct line_list *line;
- bool dot;
-
- if (!getl_read_line (NULL))
- return false;
-
- /* If the current file has changed then record the fact. */
- getl_location (&cur_file_name, &cur_line_number);
- if (previous_file_name == NULL
- || !strcmp (cur_file_name, previous_file_name))
- previous_file_name = pool_strdup (block->pool, cur_file_name);
-
- ds_rtrim (&getl_buf, ss_cstr (CC_SPACES));
- dot = ds_chomp (&getl_buf, get_endcmd ());
- if (recognize_do_repeat (ds_cstr (&getl_buf)))
- nesting_level++;
- else if (recognize_end_repeat (ds_cstr (&getl_buf), &block->print))
- {
- if (nesting_level-- == 0)
- {
- lex_discard_line ();
- return true;
- }
- }
- if (dot)
- ds_put_char (&getl_buf, get_endcmd ());
-
- line = *last_line = pool_alloc (block->pool, sizeof *line);
- line->next = NULL;
- line->file_name = previous_file_name;
- line->line_number = cur_line_number;
- line->line = pool_strdup (block->pool, ds_cstr (&getl_buf));
- last_line = &line->next;
+ 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);
+ free (outputs);
- lex_discard_line ();
- return true;
+ return ok;
}
-/* Creates variables for the given DO REPEAT. */
static void
-create_vars (struct repeat_block *block)
+destroy_dummies (struct hmap *dummies)
{
- struct repeat_entry *iter;
-
- for (iter = block->macros; iter; iter = iter->next)
- if (iter->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. */
- dict_create_var (default_dict, iter->replacement[i], 0);
- }
- }
+ 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
-parse_ids (struct repeat_entry *e, struct pool *pool)
+static bool
+parse_ids (struct lexer *lexer, const struct dictionary *dict,
+ struct dummy_var *dv)
{
- size_t n = 0;
- e->type = VAR_NAMES;
- return parse_mixed_vars_pool (pool, &e->replacement, &n, PV_NONE) ? n : 0;
+ return parse_mixed_vars (lexer, dict, &dv->values, &dv->n_values, PV_NONE);
}
-/* Adds STRING to E's list of replacements, which has *USED
- elements and has room for *ALLOCATED. Allocates memory from
- POOL. */
+/* 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 (char *string,
- struct repeat_entry *e, struct pool *pool,
- size_t *used, size_t *allocated)
+add_replacement (struct dummy_var *dv, char *value, size_t *allocated)
{
- if (*used == *allocated)
- e->replacement = pool_2nrealloc (pool, e->replacement, allocated,
- sizeof *e->replacement);
- e->replacement[(*used)++] = string;
+ if (dv->n_values == *allocated)
+ dv->values = x2nrealloc (dv->values, allocated, sizeof *dv->values);
+ dv->values[dv->n_values++] = value;
}
-/* Parses a list of numbers for DO REPEAT. */
-static int
-parse_numbers (struct repeat_entry *e, struct pool *pool)
+/* Parses a list or range of numbers for DO REPEAT. */
+static bool
+parse_numbers (struct lexer *lexer, struct dummy_var *dv)
{
- size_t used = 0;
size_t allocated = 0;
-
- e->type = OTHER;
- e->replacement = NULL;
do
{
- long a, b, i;
+ if (!lex_force_num (lexer))
+ return false;
- /* Parse A TO B into a, b. */
- if (!lex_force_int ())
- return 0;
- a = lex_integer ();
+ if (lex_next_token (lexer, 1) == T_TO)
+ {
+ long int a, b;
+ long int i;
- lex_get ();
- if (token == T_TO)
- {
- lex_get ();
- if (!lex_force_int ())
- return 0;
- b = lex_integer ();
- if (b < a)
+ if (!lex_is_integer (lexer))
+ {
+ msg (SE, _("Ranges may only have integer bounds."));
+ return false;
+ }
+
+ 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, _("%ld TO %ld is an invalid range."), a, b);
- return 0;
+ return false;
}
- lex_get ();
- }
- else
- b = a;
+ lex_get (lexer);
- for (i = a; i <= b; i++)
- add_replacement (pool_asprintf (pool, "%ld", i),
- e, pool, &used, &allocated);
+ for (i = a; i <= b; i++)
+ add_replacement (dv, xasprintf ("%ld", i), &allocated);
+ }
+ else
+ {
+ char s[DBL_BUFSIZE_BOUND];
+ c_dtoastr (s, sizeof s, 0, 0, lex_number (lexer));
+ add_replacement (dv, xstrdup (s), &allocated);
+ lex_get (lexer);
+ }
- lex_match (',');
+ lex_match (lexer, T_COMMA);
}
- while (token != '/' && token != '.');
+ 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 repeat_entry *e, struct pool *pool)
+static bool
+parse_strings (struct lexer *lexer, struct dummy_var *dv)
{
- size_t used = 0;
size_t allocated = 0;
-
- e->type = OTHER;
- e->replacement = NULL;
do
{
- char *string;
-
- if (token != T_STRING)
+ if (!lex_force_string (lexer))
{
- msg (SE, _("String expected."));
- return 0;
+ return false;
}
- string = lex_token_representation ();
- pool_register (pool, free, string);
- add_replacement (string, e, pool, &used, &allocated);
+ add_replacement (dv, token_to_string (lex_next (lexer, 0)), &allocated);
- lex_get ();
- lex_match (',');
+ lex_get (lexer);
+ lex_match (lexer, T_COMMA);
}
- while (token != '/' && token != '.');
+ while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD);
- return used;
+ return true;
}
\f
int
-cmd_end_repeat (void)
+cmd_end_repeat (struct lexer *lexer UNUSED, struct dataset *ds UNUSED)
{
- msg (SE, _("No matching DO REPEAT."));
+ msg (SE, _("No matching %s."), "DO REPEAT");
return CMD_CASCADING_FAILURE;
}
-\f
-/* Finds a DO REPEAT macro with name MACRO_NAME and returns the
- appropriate subsitution if found, or NULL if not. */
-static char *
-find_substitution (struct repeat_block *block, const char *name, size_t length)
-{
- struct repeat_entry *e;
-
- for (e = block->macros; e; e = e->next)
- if (!memcasecmp (e->id, name, length) && strlen (e->id) == length)
- return e->replacement[block->loop_idx];
-
- return NULL;
-}
-
-/* Makes appropriate DO REPEAT macro substitutions within getl_buf. */
-static void
-do_repeat_filter (struct string *line, void *block_)
-{
- struct repeat_block *block = block_;
- bool in_apos, in_quote;
- char *cp;
- struct string output;
- bool dot;
-
- ds_init_empty (&output);
-
- /* Strip trailing whitespace, check for & remove terminal dot. */
- while (isspace (ds_last (line)))
- ds_truncate (line, ds_length (line) - 1);
- dot = ds_chomp (line, get_endcmd ());
-
- in_apos = in_quote = false;
- for (cp = ds_cstr (line); cp < ds_end (line); )
- {
- if (*cp == '\'' && !in_quote)
- in_apos = !in_apos;
- else if (*cp == '"' && !in_apos)
- in_quote = !in_quote;
-
- if (in_quote || in_apos || !lex_is_id1 (*cp))
- ds_put_char (&output, *cp++);
- else
- {
- const char *start = cp;
- char *end = lex_skip_identifier (start);
- const char *substitution = find_substitution (block,
- start, end - start);
- if (substitution != NULL)
- ds_put_cstr (&output, substitution);
- else
- ds_put_substring (&output, ss_buffer (start, end - start));
- cp = end;
- }
- }
- if (dot)
- ds_put_char (&output, get_endcmd ());
-
- ds_swap (line, &output);
- ds_destroy (&output);
-}
-
-/* Function called by getl to read a line.
- Puts the line in OUTPUT, sets the file name in *FILE_NAME and
- line number in *LINE_NUMBER. Returns true if a line was
- obtained, false if the source is exhausted. */
-static bool
-do_repeat_read (struct string *output, char **file_name, int *line_number,
- void *block_)
-{
- struct repeat_block *block = block_;
- struct line_list *line;
-
- if (block->cur_line == NULL)
- {
- block->loop_idx++;
- if (block->loop_idx >= block->loop_cnt)
- return false;
- block->cur_line = block->first_line;
- }
- line = block->cur_line;
-
- ds_assign_cstr (output, line->line);
- *file_name = line->file_name;
- *line_number = -line->line_number;
- block->cur_line = line->next;
- return true;
-}
-
-/* Frees a DO REPEAT block.
- Called by getl to close out the DO REPEAT block. */
-static void
-do_repeat_close (void *block_)
-{
- struct repeat_block *block = block_;
- pool_destroy (block->pool);
-}