X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=src%2Flanguage%2Fcontrol%2Frepeat.c;h=c0fa8fe0d8dfe165deb6f55968f17ed512d9b46f;hb=9ade26c8349b4434008c46cf09bc7473ec743972;hp=796c6e9354702bd5815cd83093a8793b4cc07ac6;hpb=94de2220fa2c449bd99571639b8cabbe28824698;p=pspp-builds.git diff --git a/src/language/control/repeat.c b/src/language/control/repeat.c index 796c6e93..c0fa8fe0 100644 --- a/src/language/control/repeat.c +++ b/src/language/control/repeat.c @@ -1,467 +1,427 @@ -/* 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, 2010, 2011 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/dictionary.h" +#include "data/procedure.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/hash-functions.h" +#include "libpspp/hmap.h" +#include "libpspp/message.h" +#include "libpspp/str.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 - }; - -/* 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. */ - }; - -/* 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 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 lexer *, struct repeat_block *); -static bool parse_lines (struct lexer *, struct repeat_block *); -static void create_vars (struct repeat_block *); - -static int parse_ids (struct lexer *, const struct dictionary *dict, - struct repeat_entry *, struct pool *); - -static int parse_numbers (struct lexer *, struct repeat_entry *, - struct pool *); - -static int parse_strings (struct lexer *, struct repeat_entry *, - struct pool *); - -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 *i UNUSED); +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 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 (struct lexer *lexer, struct dataset *ds) { - struct repeat_block *block; + struct hmap dummies; + bool ok; - block = pool_create_container (struct repeat_block, pool); - block->ds = ds; + if (!parse_specification (lexer, dataset_dict (ds), &dummies)) + return CMD_CASCADING_FAILURE; - if (!parse_specification (lexer, block) || !parse_lines (lexer, block)) - goto error; - - create_vars (block); - - block->cur_line = NULL; - block->loop_idx = -1; + ok = parse_commands (lexer, &dummies); - block->parent.read = do_repeat_read; - block->parent.close = do_repeat_close; - block->parent.filter = do_repeat_filter; - block->parent.interactive = always_false; + destroy_dummies (&dummies); - getl_include_source ( (struct getl_interface *) block); + return ok ? CMD_SUCCESS : CMD_CASCADING_FAILURE; +} - return CMD_SUCCESS; +static unsigned int +hash_dummy (const char *name, size_t name_len) +{ + return hash_case_bytes (name, name_len, 0); +} - error: - pool_destroy (block->pool); - return CMD_CASCADING_FAILURE; +static const struct dummy_var * +find_dummy_var (struct hmap *hmap, const char *name, size_t name_len) +{ + const struct dummy_var *dv; + + HMAP_FOR_EACH_WITH_HASH (dv, struct dummy_var, hmap_node, + hash_dummy (name, name_len), hmap) + if (strcasecmp (dv->name, name)) + return dv; + + 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) { - 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; - 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_tokid (lexer))) - msg (SW, _("Dummy variable name \"%s\" hides dictionary " - "variable \"%s\"."), - lex_tokid (lexer), lex_tokid (lexer)); - for (iter = block->macros; iter != NULL; iter = iter->next) - if (!strcasecmp (iter->id, lex_tokid (lexer))) - { - msg (SE, _("Dummy variable name \"%s\" is given twice."), - lex_tokid (lexer)); - 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, lex_tokid (lexer)); - block->macros = e; + 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 (lexer); - if (!lex_force_match (lexer, '=')) - return false; + if (!lex_force_match (lexer, T_EQUALS)) + goto error; /* Get the details of the variable's possible values. */ - if (lex_token (lexer) == T_ID) - count = parse_ids (lexer, dict, 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)) - count = parse_numbers (lexer, e, block->pool); - else if (lex_token (lexer) == T_STRING) - count = parse_strings (lexer, e, block->pool); + ok = parse_numbers (lexer, dv); + else if (lex_is_string (lexer)) + ok = parse_strings (lexer, dv); else { lex_error (lexer, NULL); - return false; + goto error; } - if (count == 0) - return false; - if (lex_token (lexer) != '/' && lex_token (lexer) != '.') + 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; - 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, _("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, '/'); + lex_match (lexer, T_SLASH); } - while (lex_token (lexer) != '.'); - - 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: + 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 lex_syntax_mode syntax_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, syntax_mode); + + while (!ss_is_empty (s)) + { + enum segment_type type; + int n; + + n = segmenter_push (&segmenter, s.string, s.length, &type); + assert (n >= 0); + + if (type == SEG_DO_REPEAT_COMMAND) + { + for (;;) + { + int k; - cp = recognize_keyword (cp, "repeat"); - if (cp == NULL) - return false; + k = segmenter_push (&segmenter, s.string + n, s.length - n, + &type); + if (type != SEG_NEWLINE && type != SEG_DO_REPEAT_COMMAND) + break; - *print = recognize_keyword (cp, "print"); - return true; + n += k; + } + + do_parse_commands (ss_head (s, n), syntax_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 lexer *lexer, struct repeat_block *block) +parse_commands (struct lexer *lexer, struct hmap *dummies) { - char *previous_file_name; - struct line_list **last_line; - int nesting_level; + 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; - previous_file_name = NULL; - block->first_line = NULL; - last_line = &block->first_line; - nesting_level = 0; + ok = (lex_force_match_id (lexer, "END") + && lex_force_match_id (lexer, "REPEAT")); + if (ok) + lex_match_id (lexer, "PRINT"); /* XXX */ - for (;;) + lex_discard_rest_of_command (lexer); + + for (i = 0; i < n_values; i++) { - const char *cur_file_name = getl_source_name (); - int cur_line_number = getl_source_location (); - struct line_list *line; - struct string cur_line_copy; - bool dot; - - if (! lex_get_line_raw (lexer)) - return false; - - /* If the current file has changed then record the fact. */ - if (cur_file_name && - (previous_file_name == NULL - || !strcmp (cur_file_name, previous_file_name)) - ) - previous_file_name = pool_strdup (block->pool, cur_file_name); - - ds_init_string (&cur_line_copy, lex_entire_line_ds (lexer) ); - ds_rtrim (&cur_line_copy, ss_cstr (CC_SPACES)); - dot = ds_chomp (&cur_line_copy, get_endcmd ()); - - if (recognize_do_repeat (ds_cstr (&cur_line_copy))) - nesting_level++; - else if (recognize_end_repeat (ds_cstr (&cur_line_copy), &block->print)) - { - if (nesting_level-- == 0) - { - lex_discard_line (lexer); - ds_destroy (&cur_line_copy); - return true; - } - } - if (dot) - ds_put_char (&cur_line_copy, 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 (&cur_line_copy) ); - last_line = &line->next; - - ds_destroy (&cur_line_copy); + 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); - lex_discard_line (lexer); - 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 (dataset_dict (block->ds), 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 lexer *lexer, const struct dictionary *dict, - 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 (lexer, dict, 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 lexer *lexer, 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 (lexer)) - return 0; - a = lex_integer (lexer); + if (lex_next_token (lexer, 1) == T_TO) + { + long int a, b; + long int i; + + 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; - lex_get (lexer); - if (lex_token (lexer) == T_TO) - { - lex_get (lexer); - if (!lex_force_int (lexer)) - return 0; b = lex_integer (lexer); - if (b < a) + if (b < a) { msg (SE, _("%ld TO %ld is an invalid range."), a, b); - return 0; + return false; } lex_get (lexer); - } - else - b = a; - 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]; + dtoastr (s, sizeof s, 0, 0, lex_number (lexer)); + add_replacement (dv, xstrdup (s), &allocated); + lex_get (lexer); + } - lex_match (lexer, ','); + lex_match (lexer, T_COMMA); } - while (lex_token (lexer) != '/' && lex_token (lexer) != '.'); + 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_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 (lex_token (lexer) != T_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 (string, e, pool, &used, &allocated); + add_replacement (dv, token_to_string (lex_next (lexer, 0)), &allocated); lex_get (lexer); - lex_match (lexer, ','); + lex_match (lexer, T_COMMA); } - while (lex_token (lexer) != '/' && lex_token (lexer) != '.'); + while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD); - return used; + return true; } int @@ -470,105 +430,3 @@ cmd_end_repeat (struct lexer *lexer UNUSED, struct dataset *ds UNUSED) msg (SE, _("No matching 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 the - repeated lines. */ -static void -do_repeat_filter (struct getl_interface *block_, struct string *line) -{ - struct repeat_block *block = (struct repeat_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 getl_interface *b, struct string *output) -{ - struct repeat_block *block = (struct repeat_block *) b; - 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); - 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 (struct getl_interface *block_) -{ - struct repeat_block *block = (struct repeat_block *) block_; - pool_destroy (block->pool); -} - - -static bool -always_false (const struct getl_interface *i UNUSED) -{ - return false; -}