X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flanguage%2Fcontrol%2Frepeat.c;h=f71409147cd5f167deb680a523f75b6ae7d811d7;hb=5fa244a7b0fcbd971e760cb67644514fe22aa4a3;hp=b5efdc99f6c797c1eb02ceee15395abed643955c;hpb=7a2039fb1ebfd48013ab259b28091e74e7f50588;p=pspp diff --git a/src/language/control/repeat.c b/src/language/control/repeat.c index b5efdc99f6..f71409147c 100644 --- a/src/language/control/repeat.c +++ b/src/language/control/repeat.c @@ -1,543 +1,418 @@ -/* PSPP - computes sample statistics. - Copyright (C) 1997-9, 2000 Free Software Foundation, Inc. - Written by Ben Pfaff . +/* 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 . */ #include -#include "repeat.h" - -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "output/output-item.h" + +#include "gl/ftoastr.h" +#include "gl/minmax.h" +#include "gl/xalloc.h" +#include "gl/xmemdup0.h" #include "gettext.h" #define _(msgid) gettext (msgid) -/* Defines a list of lines used by DO REPEAT. */ -struct line_list - { - struct line_list *next; /* Next line. */ - char *file_name; /* File name. */ - int line_number; /* Line number. */ - char *line; /* Contents. */ - }; - -/* The type of substitution made for a DO REPEAT macro. */ -enum repeat_entry_type - { - VAR_NAMES, - OTHER - }; - -/* Describes one DO REPEAT macro. */ -struct repeat_entry +struct dummy_var { - 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. */ + struct hmap_node hmap_node; + struct substring name; + char **values; + size_t n_values; + int start_ofs, end_ofs; }; -/* 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? */ - }; - -static bool parse_specification (struct repeat_block *); -static bool parse_lines (struct repeat_block *); -static void create_vars (struct repeat_block *); - -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 *); +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); -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); +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 *); int -cmd_do_repeat (void) +cmd_do_repeat (struct lexer *lexer, struct dataset *ds) { - struct repeat_block *block; + struct hmap dummies = HMAP_INITIALIZER (dummies); + bool ok = parse_specification (lexer, dataset_dict (ds), &dummies); + ok = parse_commands (lexer, &dummies) && ok; + destroy_dummies (&dummies); - block = pool_create_container (struct repeat_block, pool); + return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE; +} - 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, struct substring name) +{ + const struct dummy_var *dv; - return CMD_SUCCESS; + HMAP_FOR_EACH_WITH_HASH (dv, struct dummy_var, hmap_node, + utf8_hash_case_substring (name, 0), hmap) + if (!utf8_sscasecmp (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; do { - struct repeat_entry *e; - struct repeat_entry *iter; - int count; + int start_ofs = lex_ofs (lexer); /* 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; + struct substring name = lex_tokss (lexer); + if (dict_lookup_var (dict, name.string)) + lex_msg (lexer, SW, + _("Dummy variable name `%s' hides dictionary variable `%s'."), + name.string, name.string); + if (find_dummy_var (dummies, name)) + { + lex_error (lexer, _("Dummy variable name `%s' is given twice."), + name.string); + goto error; + } + + /* Make a new macro. */ + struct dummy_var *dv = xmalloc (sizeof *dv); + *dv = (struct dummy_var) { + .name = ss_clone (name), + .start_ofs = start_ofs, + }; + hmap_insert (dummies, &dv->hmap_node, utf8_hash_case_substring (name, 0)); /* 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); + bool ok; + 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, _("Syntax error expecting substitution values.")); + 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, _("Syntax error expecting `/' or end of command.")); + goto error; } + dv->end_ofs = lex_ofs (lexer) - 1; - /* 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; - strcpy (first_name, e->id); - } - else if (block->loop_cnt != count) - { - 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, _("Each dummy variable must have the same number of " + "substitutions.")); + + lex_ofs_msg (lexer, SN, first_dv->start_ofs, first_dv->end_ofs, + ngettext ("Dummy variable %s had %zu substitution.", + "Dummy variable %s had %zu substitutions.", + first_dv->n_values), + first_dv->name.string, first_dv->n_values); + lex_ofs_msg (lexer, SN, dv->start_ofs, dv->end_ofs, + ngettext ("Dummy variable %s had %zu substitution.", + "Dummy variable %s had %zu substitutions.", + dv->n_values), + dv->name.string, dv->n_values); + goto error; } - lex_match ('/'); + lex_match (lexer, T_SLASH); } - while (token != '.'); - - return true; -} + while (!lex_match (lexer, T_ENDCMD)); -/* 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 (lex_match (lexer, T_ENDCMD)) + continue; - 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: + lex_discard_rest_of_command (lexer); + while (lex_match (lexer, T_ENDCMD)) + continue; + destroy_dummies (dummies); + hmap_init (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; + if (hmap_is_empty (dummies)) + return 0; + const struct dummy_var *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 = segmenter_init (mode, false); + while (!ss_is_empty (s)) + { + enum segment_type type; + int n = segmenter_push (&segmenter, s.string, s.length, true, &type); + assert (n >= 0); -/* 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; + if (type == SEG_DO_REPEAT_COMMAND) + { + for (;;) + { + int k = segmenter_push (&segmenter, s.string + n, s.length - n, + true, &type); + if (type != SEG_NEWLINE && type != SEG_DO_REPEAT_COMMAND) + break; + + n += k; + } - cp = recognize_keyword (cp, "repeat"); - if (cp == NULL) - return false; + do_parse_commands (ss_head (s, n), mode, dummies, + outputs, n_outputs); + } + else if (type != SEG_END) + { + const struct dummy_var *dv + = (type == SEG_IDENTIFIER ? find_dummy_var (dummies, ss_head (s, n)) + : NULL); + for (size_t 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)); + } - *print = recognize_keyword (cp, "print"); - return true; + 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; + char *file_name = xstrdup_if_nonnull (lex_get_file_name (lexer)); + int line_number = lex_ofs_start_point (lexer, lex_ofs (lexer)).line; - previous_file_name = NULL; - block->first_line = NULL; - last_line = &block->first_line; - nesting_level = 0; - - for (;;) + struct string input = DS_EMPTY_INITIALIZER; + while (lex_is_string (lexer)) { - const char *cur_file_name; - int cur_line_number; - struct line_list *line; - bool dot; + ds_put_substring (&input, lex_tokss (lexer)); + ds_put_byte (&input, '\n'); + lex_get (lexer); + } - if (!getl_read_line (NULL)) - return false; + size_t n_values = count_values (dummies); + struct string *outputs = xmalloc (n_values * sizeof *outputs); + for (size_t 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); - /* 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_spaces (&getl_buf); - dot = ds_chomp (&getl_buf, get_endcmd ()); - if (recognize_do_repeat (ds_c_str (&getl_buf))) - nesting_level++; - else if (recognize_end_repeat (ds_c_str (&getl_buf), &block->print)) + while (lex_match (lexer, T_ENDCMD)) + continue; + + bool ok = lex_match_phrase (lexer, "END REPEAT"); + if (!ok) + lex_error (lexer, _("Syntax error expecting END REPEAT.")); + bool print = ok && lex_match_id (lexer, "PRINT"); + lex_discard_rest_of_command (lexer); + + for (size_t i = 0; i < n_values; i++) + { + struct string *output = &outputs[n_values - i - 1]; + if (print) { - if (nesting_level-- == 0) - { - lex_discard_line (); - return true; - } + struct substring s = output->ss; + ss_chomp_byte (&s, '\n'); + char *label = xasprintf (_("Expansion %zu of %zu"), i + 1, n_values); + output_item_submit ( + text_item_create_nocopy (TEXT_ITEM_LOG, ss_xstrdup (s), label)); } - if (dot) - ds_putc (&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_c_str (&getl_buf)); - last_line = &line->next; + const char *encoding = lex_get_encoding (lexer); + struct lex_reader *reader = lex_reader_for_substring_nocopy (ds_ss (output), encoding); + 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) + { + hmap_delete (dummies, &dv->hmap_node); + + ss_dealloc (&dv->name); + for (size_t 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) + { + if (!lex_is_integer (lexer)) + { + lex_error (lexer, _("Ranges may only have integer bounds.")); + return false; + } - lex_get (); - if (token == T_TO) - { - lex_get (); - if (!lex_force_int ()) - return 0; - b = lex_integer (); - if (b < a) + long a = lex_integer (lexer); + lex_get (lexer); + lex_get (lexer); + + if (!lex_force_int_range (lexer, NULL, a, LONG_MAX)) + return false; + + long b = lex_integer (lexer); + if (b < a) { - msg (SE, _("%ld TO %ld is an invalid range."), a, b); - return 0; + lex_next_error (lexer, -2, 0, + _("%ld TO %ld is an invalid range."), a, b); + 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 (long 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) - { - msg (SE, _("String expected.")); - return 0; - } + if (!lex_force_string (lexer)) + 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; } int -cmd_end_repeat (void) +cmd_end_repeat (struct lexer *lexer, struct dataset *ds UNUSED) { - msg (SE, _("No matching DO REPEAT.")); + lex_ofs_error (lexer, 0, 1, _("No matching %s."), "DO REPEAT"); return CMD_CASCADING_FAILURE; } - -/* 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 (&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_c_str (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_putc (&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_puts (&output, substitution); - else - ds_concat (&output, start, end - start); - cp = end; - } - } - if (dot) - ds_putc (&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_c_str (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); -}