| /* xgettext Tcl backend. |
| Copyright (C) 2002-2003, 2005-2009, 2013, 2018-2020 Free Software Foundation, Inc. |
| |
| This file was written by Bruno Haible <haible@clisp.cons.org>, 2002. |
| |
| 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. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| /* Specification. */ |
| #include "x-tcl.h" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "attribute.h" |
| #include "message.h" |
| #include "xgettext.h" |
| #include "xg-pos.h" |
| #include "xg-encoding.h" |
| #include "xg-mixed-string.h" |
| #include "xg-arglist-context.h" |
| #include "xg-arglist-callshape.h" |
| #include "xg-arglist-parser.h" |
| #include "xg-message.h" |
| #include "error.h" |
| #include "xalloc.h" |
| #include "mem-hash-map.h" |
| #include "c-ctype.h" |
| #include "po-charset.h" |
| #include "unistr.h" |
| #include "gettext.h" |
| |
| #define _(s) gettext(s) |
| |
| #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) |
| |
| |
| /* The Tcl syntax is defined in the Tcl.n manual page. |
| Summary of Tcl syntax: |
| Like sh syntax, except that `...` is replaced with [...]. In detail: |
| - In a preprocessing pass, backslash-newline-anywhitespace is replaced |
| with single space. |
| - Input is broken into words, which are then subject to command |
| substitution [...] , variable substitution $var, backslash substitution |
| \escape. |
| - Strings are enclosed in "..."; command substitution, variable |
| substitution and backslash substitutions are performed here as well. |
| - {...} is a string without substitutions. |
| - The list of resulting words is split into commands by semicolon and |
| newline. |
| - '#' at the beginning of a command introduces a comment until end of line. |
| The parser is implemented in tcl8.3.3/generic/tclParse.c. */ |
| |
| |
| /* ====================== Keyword set customization. ====================== */ |
| |
| /* If true extract all strings. */ |
| static bool extract_all = false; |
| |
| static hash_table keywords; |
| static bool default_keywords = true; |
| |
| |
| void |
| x_tcl_extract_all () |
| { |
| extract_all = true; |
| } |
| |
| |
| void |
| x_tcl_keyword (const char *name) |
| { |
| if (name == NULL) |
| default_keywords = false; |
| else |
| { |
| const char *end; |
| struct callshape shape; |
| |
| if (keywords.table == NULL) |
| hash_init (&keywords, 100); |
| |
| split_keywordspec (name, &end, &shape); |
| |
| /* The characters between name and end should form a valid Tcl |
| function name. A leading "::" is redundant. */ |
| if (end - name >= 2 && name[0] == ':' && name[1] == ':') |
| name += 2; |
| |
| insert_keyword_callshape (&keywords, name, end - name, &shape); |
| } |
| } |
| |
| /* Finish initializing the keywords hash table. |
| Called after argument processing, before each file is processed. */ |
| static void |
| init_keywords () |
| { |
| if (default_keywords) |
| { |
| /* When adding new keywords here, also update the documentation in |
| xgettext.texi! */ |
| x_tcl_keyword ("::msgcat::mc"); |
| default_keywords = false; |
| } |
| } |
| |
| void |
| init_flag_table_tcl () |
| { |
| xgettext_record_flag ("::msgcat::mc:1:pass-tcl-format"); |
| xgettext_record_flag ("format:1:tcl-format"); |
| } |
| |
| |
| /* ======================== Reading of characters. ======================== */ |
| |
| /* The input file stream. */ |
| static FILE *fp; |
| |
| |
| /* Fetch the next character from the input file. */ |
| static int |
| do_getc () |
| { |
| int c = getc (fp); |
| |
| if (c == EOF) |
| { |
| if (ferror (fp)) |
| error (EXIT_FAILURE, errno, |
| _("error while reading \"%s\""), real_file_name); |
| } |
| else if (c == '\n') |
| line_number++; |
| |
| return c; |
| } |
| |
| /* Put back the last fetched character, not EOF. */ |
| static void |
| do_ungetc (int c) |
| { |
| if (c == '\n') |
| line_number--; |
| ungetc (c, fp); |
| } |
| |
| |
| /* Combine backslash followed by newline and additional whitespace to |
| a single space. */ |
| |
| /* An int that becomes a space when casted to 'unsigned char'. */ |
| #define BS_NL (UCHAR_MAX + 1 + ' ') |
| |
| static int phase1_pushback[1]; |
| static int phase1_pushback_length; |
| |
| static int |
| phase1_getc () |
| { |
| int c; |
| |
| if (phase1_pushback_length) |
| { |
| c = phase1_pushback[--phase1_pushback_length]; |
| if (c == '\n' || c == BS_NL) |
| ++line_number; |
| return c; |
| } |
| c = do_getc (); |
| if (c != '\\') |
| return c; |
| c = do_getc (); |
| if (c != '\n') |
| { |
| if (c != EOF) |
| do_ungetc (c); |
| return '\\'; |
| } |
| for (;;) |
| { |
| c = do_getc (); |
| if (!(c == ' ' || c == '\t')) |
| break; |
| } |
| if (c != EOF) |
| do_ungetc (c); |
| return BS_NL; |
| } |
| |
| /* Supports only one pushback character. */ |
| static void |
| phase1_ungetc (int c) |
| { |
| switch (c) |
| { |
| case EOF: |
| break; |
| |
| case '\n': |
| case BS_NL: |
| --line_number; |
| FALLTHROUGH; |
| |
| default: |
| if (phase1_pushback_length == SIZEOF (phase1_pushback)) |
| abort (); |
| phase1_pushback[phase1_pushback_length++] = c; |
| break; |
| } |
| } |
| |
| |
| /* Keep track of brace nesting depth. |
| When a word starts with an opening brace, a character group begins that |
| ends with the corresponding closing brace. In theory these character |
| groups are string literals, but they are used by so many Tcl primitives |
| (proc, if, ...) as representing command lists, that we treat them as |
| command lists. */ |
| |
| /* An int that becomes a closing brace when casted to 'unsigned char'. */ |
| #define CL_BRACE (UCHAR_MAX + 1 + '}') |
| |
| static int phase2_pushback[2]; |
| static int phase2_pushback_length; |
| |
| /* Brace nesting depth inside the current character group. */ |
| static int brace_depth; |
| |
| static int |
| phase2_push () |
| { |
| int previous_depth = brace_depth; |
| brace_depth = 1; |
| return previous_depth; |
| } |
| |
| static void |
| phase2_pop (int previous_depth) |
| { |
| brace_depth = previous_depth; |
| } |
| |
| static int |
| phase2_getc () |
| { |
| int c; |
| |
| if (phase2_pushback_length) |
| { |
| c = phase2_pushback[--phase2_pushback_length]; |
| if (c == '\n' || c == BS_NL) |
| ++line_number; |
| else if (c == '{') |
| ++brace_depth; |
| else if (c == '}') |
| --brace_depth; |
| return c; |
| } |
| c = phase1_getc (); |
| if (c == '{') |
| ++brace_depth; |
| else if (c == '}') |
| { |
| if (--brace_depth == 0) |
| c = CL_BRACE; |
| } |
| return c; |
| } |
| |
| /* Supports 2 characters of pushback. */ |
| static void |
| phase2_ungetc (int c) |
| { |
| if (c != EOF) |
| { |
| switch (c) |
| { |
| case '\n': |
| case BS_NL: |
| --line_number; |
| break; |
| |
| case '{': |
| --brace_depth; |
| break; |
| |
| case '}': |
| ++brace_depth; |
| break; |
| } |
| if (phase2_pushback_length == SIZEOF (phase2_pushback)) |
| abort (); |
| phase2_pushback[phase2_pushback_length++] = c; |
| } |
| } |
| |
| |
| /* ========================== Reading of tokens. ========================== */ |
| |
| |
| /* A token consists of a sequence of characters. */ |
| struct token |
| { |
| int allocated; /* number of allocated 'token_char's */ |
| int charcount; /* number of used 'token_char's */ |
| char *chars; /* the token's constituents */ |
| }; |
| |
| /* Initialize a 'struct token'. */ |
| static inline void |
| init_token (struct token *tp) |
| { |
| tp->allocated = 10; |
| tp->chars = XNMALLOC (tp->allocated, char); |
| tp->charcount = 0; |
| } |
| |
| /* Free the memory pointed to by a 'struct token'. */ |
| static inline void |
| free_token (struct token *tp) |
| { |
| free (tp->chars); |
| } |
| |
| /* Ensure there is enough room in the token for one more character. */ |
| static inline void |
| grow_token (struct token *tp) |
| { |
| if (tp->charcount == tp->allocated) |
| { |
| tp->allocated *= 2; |
| tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char)); |
| } |
| } |
| |
| |
| /* ========================= Accumulating comments ========================= */ |
| |
| |
| static char *buffer; |
| static size_t bufmax; |
| static size_t buflen; |
| |
| static inline void |
| comment_start () |
| { |
| buflen = 0; |
| } |
| |
| static inline void |
| comment_add (int c) |
| { |
| if (buflen >= bufmax) |
| { |
| bufmax = 2 * bufmax + 10; |
| buffer = xrealloc (buffer, bufmax); |
| } |
| buffer[buflen++] = c; |
| } |
| |
| static inline void |
| comment_line_end () |
| { |
| while (buflen >= 1 |
| && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t')) |
| --buflen; |
| if (buflen >= bufmax) |
| { |
| bufmax = 2 * bufmax + 10; |
| buffer = xrealloc (buffer, bufmax); |
| } |
| buffer[buflen] = '\0'; |
| savable_comment_add (buffer); |
| } |
| |
| |
| /* These are for tracking whether comments count as immediately before |
| keyword. */ |
| static int last_comment_line; |
| static int last_non_comment_line; |
| |
| |
| /* ========================= Accumulating messages ========================= */ |
| |
| |
| static message_list_ty *mlp; |
| |
| |
| /* ========================== Reading of commands ========================== */ |
| |
| |
| /* We are only interested in constant strings (e.g. "msgcat::mc" or other |
| string literals). Other words need not to be represented precisely. */ |
| enum word_type |
| { |
| t_string, /* constant string */ |
| t_other, /* other string */ |
| t_separator, /* command separator: semicolon or newline */ |
| t_bracket, /* ']' pseudo word */ |
| t_brace, /* '}' pseudo word */ |
| t_eof /* EOF marker */ |
| }; |
| |
| struct word |
| { |
| enum word_type type; |
| struct token *token; /* for t_string */ |
| int line_number_at_start; /* for t_string */ |
| }; |
| |
| /* Free the memory pointed to by a 'struct word'. */ |
| static inline void |
| free_word (struct word *wp) |
| { |
| if (wp->type == t_string) |
| { |
| free_token (wp->token); |
| free (wp->token); |
| } |
| } |
| |
| /* Convert a t_string token to a char*. */ |
| static char * |
| string_of_word (const struct word *wp) |
| { |
| char *str; |
| int n; |
| |
| if (!(wp->type == t_string)) |
| abort (); |
| n = wp->token->charcount; |
| str = XNMALLOC (n + 1, char); |
| memcpy (str, wp->token->chars, n); |
| str[n] = '\0'; |
| return str; |
| } |
| |
| |
| /* Context lookup table. */ |
| static flag_context_list_table_ty *flag_context_list_table; |
| |
| |
| /* Read an escape sequence. The value is an ISO-8859-1 character (in the |
| range 0x00..0xff) or a Unicode character (in the range 0x0000..0xffff). */ |
| static int |
| do_getc_escaped () |
| { |
| int c; |
| |
| c = phase1_getc (); |
| switch (c) |
| { |
| case EOF: |
| return '\\'; |
| case 'a': |
| return '\a'; |
| case 'b': |
| return '\b'; |
| case 'f': |
| return '\f'; |
| case 'n': |
| return '\n'; |
| case 'r': |
| return '\r'; |
| case 't': |
| return '\t'; |
| case 'v': |
| return '\v'; |
| case 'x': |
| { |
| int n = 0; |
| unsigned int i; |
| |
| for (i = 0;; i++) |
| { |
| c = phase1_getc (); |
| if (c == EOF || !c_isxdigit ((unsigned char) c)) |
| break; |
| |
| if (c >= '0' && c <= '9') |
| n = (n << 4) + (c - '0'); |
| else if (c >= 'A' && c <= 'F') |
| n = (n << 4) + (c - 'A' + 10); |
| else if (c >= 'a' && c <= 'f') |
| n = (n << 4) + (c - 'a' + 10); |
| } |
| phase1_ungetc (c); |
| return (i > 0 ? (unsigned char) n : 'x'); |
| } |
| case 'u': |
| { |
| int n = 0; |
| unsigned int i; |
| |
| for (i = 0; i < 4; i++) |
| { |
| c = phase1_getc (); |
| if (c == EOF || !c_isxdigit ((unsigned char) c)) |
| { |
| phase1_ungetc (c); |
| break; |
| } |
| |
| if (c >= '0' && c <= '9') |
| n = (n << 4) + (c - '0'); |
| else if (c >= 'A' && c <= 'F') |
| n = (n << 4) + (c - 'A' + 10); |
| else if (c >= 'a' && c <= 'f') |
| n = (n << 4) + (c - 'a' + 10); |
| } |
| return (i > 0 ? n : 'u'); |
| } |
| case '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': |
| { |
| int n = c - '0'; |
| |
| c = phase1_getc (); |
| if (c != EOF) |
| { |
| if (c >= '0' && c <= '7') |
| { |
| n = (n << 3) + (c - '0'); |
| c = phase1_getc (); |
| if (c != EOF) |
| { |
| if (c >= '0' && c <= '7') |
| n = (n << 3) + (c - '0'); |
| else |
| phase1_ungetc (c); |
| } |
| } |
| else |
| phase1_ungetc (c); |
| } |
| return (unsigned char) n; |
| } |
| default: |
| /* Note: If c is non-ASCII, Tcl's behaviour is undefined here. */ |
| return (unsigned char) c; |
| } |
| } |
| |
| |
| enum terminator |
| { |
| te_space_separator, /* looking for space semicolon newline */ |
| te_space_separator_bracket, /* looking for space semicolon newline ']' */ |
| te_paren, /* looking for ')' */ |
| te_quote /* looking for '"' */ |
| }; |
| |
| /* Forward declaration of local functions. */ |
| static enum word_type read_command_list (int looking_for, |
| flag_context_ty outer_context); |
| |
| /* Accumulate tokens into the given word. |
| 'looking_for' denotes a parse terminator combination. |
| Return the first character past the token. */ |
| static int |
| accumulate_word (struct word *wp, enum terminator looking_for, |
| flag_context_ty context) |
| { |
| int c; |
| |
| for (;;) |
| { |
| c = phase2_getc (); |
| |
| if (c == EOF || c == CL_BRACE) |
| return c; |
| if ((looking_for == te_space_separator |
| || looking_for == te_space_separator_bracket) |
| && (c == ' ' || c == BS_NL |
| || c == '\t' || c == '\v' || c == '\f' || c == '\r' |
| || c == ';' || c == '\n')) |
| return c; |
| if (looking_for == te_space_separator_bracket && c == ']') |
| return c; |
| if (looking_for == te_paren && c == ')') |
| return c; |
| if (looking_for == te_quote && c == '"') |
| return c; |
| |
| if (c == '$') |
| { |
| /* Distinguish $varname, ${varname} and lone $. */ |
| c = phase2_getc (); |
| if (c == '{') |
| { |
| /* ${varname} */ |
| do |
| c = phase2_getc (); |
| while (c != EOF && c != '}'); |
| wp->type = t_other; |
| } |
| else |
| { |
| bool nonempty = false; |
| |
| for (; c != EOF && c != CL_BRACE; c = phase2_getc ()) |
| { |
| if (c_isalnum ((unsigned char) c) || (c == '_')) |
| { |
| nonempty = true; |
| continue; |
| } |
| if (c == ':') |
| { |
| c = phase2_getc (); |
| if (c == ':') |
| { |
| do |
| c = phase2_getc (); |
| while (c == ':'); |
| |
| phase2_ungetc (c); |
| nonempty = true; |
| continue; |
| } |
| phase2_ungetc (c); |
| c = ':'; |
| } |
| break; |
| } |
| if (c == '(') |
| { |
| /* $varname(index) */ |
| struct word index_word; |
| |
| index_word.type = t_other; |
| c = accumulate_word (&index_word, te_paren, null_context); |
| if (c != EOF && c != ')') |
| phase2_ungetc (c); |
| wp->type = t_other; |
| } |
| else |
| { |
| phase2_ungetc (c); |
| if (nonempty) |
| { |
| /* $varname */ |
| wp->type = t_other; |
| } |
| else |
| { |
| /* lone $ */ |
| if (wp->type == t_string) |
| { |
| grow_token (wp->token); |
| wp->token->chars[wp->token->charcount++] = '$'; |
| } |
| } |
| } |
| } |
| } |
| else if (c == '[') |
| { |
| read_command_list (']', context); |
| wp->type = t_other; |
| } |
| else if (c == '\\') |
| { |
| unsigned int uc; |
| unsigned char utf8buf[6]; |
| int count; |
| int i; |
| |
| uc = do_getc_escaped (); |
| assert (uc < 0x10000); |
| count = u8_uctomb (utf8buf, uc, 6); |
| assert (count > 0); |
| if (wp->type == t_string) |
| for (i = 0; i < count; i++) |
| { |
| grow_token (wp->token); |
| wp->token->chars[wp->token->charcount++] = utf8buf[i]; |
| } |
| } |
| else |
| { |
| if (wp->type == t_string) |
| { |
| grow_token (wp->token); |
| wp->token->chars[wp->token->charcount++] = (unsigned char) c; |
| } |
| } |
| } |
| } |
| |
| |
| /* Read the next word. |
| 'looking_for' denotes a parse terminator, either ']' or '\0'. */ |
| static void |
| read_word (struct word *wp, int looking_for, flag_context_ty context) |
| { |
| int c; |
| |
| do |
| c = phase2_getc (); |
| while (c == ' ' || c == BS_NL |
| || c == '\t' || c == '\v' || c == '\f' || c == '\r'); |
| |
| if (c == EOF) |
| { |
| wp->type = t_eof; |
| return; |
| } |
| |
| if (c == CL_BRACE) |
| { |
| wp->type = t_brace; |
| last_non_comment_line = line_number; |
| return; |
| } |
| |
| if (c == '\n') |
| { |
| /* Comments assumed to be grouped with a message must immediately |
| precede it, with no non-whitespace token on a line between both. */ |
| if (last_non_comment_line > last_comment_line) |
| savable_comment_reset (); |
| wp->type = t_separator; |
| return; |
| } |
| |
| if (c == ';') |
| { |
| wp->type = t_separator; |
| last_non_comment_line = line_number; |
| return; |
| } |
| |
| if (looking_for == ']' && c == ']') |
| { |
| wp->type = t_bracket; |
| last_non_comment_line = line_number; |
| return; |
| } |
| |
| if (c == '{') |
| { |
| int previous_depth; |
| enum word_type terminator; |
| |
| /* Start a new nested character group, which lasts until the next |
| balanced '}' (ignoring \} things). */ |
| previous_depth = phase2_push () - 1; |
| |
| /* Interpret it as a command list. */ |
| terminator = read_command_list ('\0', null_context); |
| |
| if (terminator == t_brace) |
| phase2_pop (previous_depth); |
| |
| wp->type = t_other; |
| last_non_comment_line = line_number; |
| return; |
| } |
| |
| wp->type = t_string; |
| wp->token = XMALLOC (struct token); |
| init_token (wp->token); |
| wp->line_number_at_start = line_number; |
| |
| if (c == '"') |
| { |
| c = accumulate_word (wp, te_quote, context); |
| if (c != EOF && c != '"') |
| phase2_ungetc (c); |
| } |
| else |
| { |
| phase2_ungetc (c); |
| c = accumulate_word (wp, |
| looking_for == ']' |
| ? te_space_separator_bracket |
| : te_space_separator, |
| context); |
| if (c != EOF) |
| phase2_ungetc (c); |
| } |
| |
| if (wp->type != t_string) |
| { |
| free_token (wp->token); |
| free (wp->token); |
| } |
| last_non_comment_line = line_number; |
| } |
| |
| |
| /* Read the next command. |
| 'looking_for' denotes a parse terminator, either ']' or '\0'. |
| Returns the type of the word that terminated the command: t_separator or |
| t_bracket (only if looking_for is ']') or t_brace or t_eof. */ |
| static enum word_type |
| read_command (int looking_for, flag_context_ty outer_context) |
| { |
| int c; |
| |
| /* Skip whitespace and comments. */ |
| for (;;) |
| { |
| c = phase2_getc (); |
| |
| if (c == ' ' || c == BS_NL |
| || c == '\t' || c == '\v' || c == '\f' || c == '\r') |
| continue; |
| if (c == '#') |
| { |
| /* Skip a comment up to end of line. */ |
| last_comment_line = line_number; |
| comment_start (); |
| for (;;) |
| { |
| c = phase2_getc (); |
| if (c == EOF || c == CL_BRACE || c == '\n') |
| break; |
| /* We skip all leading white space, but not EOLs. */ |
| if (!(buflen == 0 && (c == ' ' || c == '\t'))) |
| comment_add (c); |
| } |
| comment_line_end (); |
| continue; |
| } |
| break; |
| } |
| phase2_ungetc (c); |
| |
| /* Read the words that make up the command. */ |
| { |
| int arg = 0; /* Current argument number. */ |
| flag_context_list_iterator_ty context_iter; |
| const struct callshapes *shapes = NULL; |
| struct arglist_parser *argparser = NULL; |
| |
| for (;; arg++) |
| { |
| struct word inner; |
| flag_context_ty inner_context; |
| |
| if (arg == 0) |
| inner_context = null_context; |
| else |
| inner_context = |
| inherited_context (outer_context, |
| flag_context_list_iterator_advance ( |
| &context_iter)); |
| |
| read_word (&inner, looking_for, inner_context); |
| |
| /* Recognize end of command. */ |
| if (inner.type == t_separator || inner.type == t_bracket |
| || inner.type == t_brace || inner.type == t_eof) |
| { |
| if (argparser != NULL) |
| arglist_parser_done (argparser, arg); |
| return inner.type; |
| } |
| |
| if (extract_all) |
| { |
| if (inner.type == t_string) |
| { |
| lex_pos_ty pos; |
| |
| pos.file_name = logical_file_name; |
| pos.line_number = inner.line_number_at_start; |
| remember_a_message (mlp, NULL, string_of_word (&inner), false, |
| false, inner_context, &pos, |
| NULL, savable_comment, false); |
| } |
| } |
| |
| if (arg == 0) |
| { |
| /* This is the function position. */ |
| if (inner.type == t_string) |
| { |
| char *function_name = string_of_word (&inner); |
| char *stripped_name; |
| void *keyword_value; |
| |
| /* A leading "::" is redundant. */ |
| stripped_name = function_name; |
| if (function_name[0] == ':' && function_name[1] == ':') |
| stripped_name += 2; |
| |
| if (hash_find_entry (&keywords, |
| stripped_name, strlen (stripped_name), |
| &keyword_value) |
| == 0) |
| shapes = (const struct callshapes *) keyword_value; |
| |
| argparser = arglist_parser_alloc (mlp, shapes); |
| |
| context_iter = |
| flag_context_list_iterator ( |
| flag_context_list_table_lookup ( |
| flag_context_list_table, |
| stripped_name, strlen (stripped_name))); |
| |
| free (function_name); |
| } |
| else |
| context_iter = null_context_list_iterator; |
| } |
| else |
| { |
| /* These are the argument positions. */ |
| if (argparser != NULL && inner.type == t_string) |
| { |
| char *s = string_of_word (&inner); |
| mixed_string_ty *ms = |
| mixed_string_alloc_simple (s, lc_string, |
| logical_file_name, |
| inner.line_number_at_start); |
| free (s); |
| arglist_parser_remember (argparser, arg, ms, |
| inner_context, |
| logical_file_name, |
| inner.line_number_at_start, |
| savable_comment, false); |
| } |
| } |
| |
| free_word (&inner); |
| } |
| } |
| } |
| |
| |
| /* Read a list of commands. |
| 'looking_for' denotes a parse terminator, either ']' or '\0'. |
| Returns the type of the word that terminated the command list: |
| t_bracket (only if looking_for is ']') or t_brace or t_eof. */ |
| static enum word_type |
| read_command_list (int looking_for, flag_context_ty outer_context) |
| { |
| for (;;) |
| { |
| enum word_type terminator; |
| |
| terminator = read_command (looking_for, outer_context); |
| if (terminator != t_separator) |
| return terminator; |
| } |
| } |
| |
| |
| void |
| extract_tcl (FILE *f, |
| const char *real_filename, const char *logical_filename, |
| flag_context_list_table_ty *flag_table, |
| msgdomain_list_ty *mdlp) |
| { |
| mlp = mdlp->item[0]->messages; |
| |
| /* We convert our strings to UTF-8 encoding. */ |
| xgettext_current_source_encoding = po_charset_utf8; |
| |
| fp = f; |
| real_file_name = real_filename; |
| logical_file_name = xstrdup (logical_filename); |
| line_number = 1; |
| |
| phase1_pushback_length = 0; |
| phase2_pushback_length = 0; |
| |
| /* Initially, no brace is open. */ |
| brace_depth = 1000000; |
| |
| last_comment_line = -1; |
| last_non_comment_line = -1; |
| |
| flag_context_list_table = flag_table; |
| |
| init_keywords (); |
| |
| /* Eat tokens until eof is seen. */ |
| read_command_list ('\0', null_context); |
| |
| fp = NULL; |
| real_file_name = NULL; |
| logical_file_name = NULL; |
| line_number = 0; |
| } |