• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* xgettext Tcl backend.
2    Copyright (C) 2002-2003, 2005-2009, 2013, 2018-2020 Free Software Foundation, Inc.
3 
4    This file was written by Bruno Haible <haible@clisp.cons.org>, 2002.
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22 
23 /* Specification.  */
24 #include "x-tcl.h"
25 
26 #include <assert.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "message.h"
35 #include "xgettext.h"
36 #include "xg-pos.h"
37 #include "xg-encoding.h"
38 #include "xg-mixed-string.h"
39 #include "xg-arglist-context.h"
40 #include "xg-arglist-callshape.h"
41 #include "xg-arglist-parser.h"
42 #include "xg-message.h"
43 #include "error.h"
44 #include "xalloc.h"
45 #include "mem-hash-map.h"
46 #include "c-ctype.h"
47 #include "po-charset.h"
48 #include "unistr.h"
49 #include "gettext.h"
50 
51 #define _(s) gettext(s)
52 
53 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
54 
55 
56 /* The Tcl syntax is defined in the Tcl.n manual page.
57    Summary of Tcl syntax:
58    Like sh syntax, except that `...` is replaced with [...]. In detail:
59    - In a preprocessing pass, backslash-newline-anywhitespace is replaced
60      with single space.
61    - Input is broken into words, which are then subject to command
62      substitution [...] , variable substitution $var, backslash substitution
63      \escape.
64    - Strings are enclosed in "..."; command substitution, variable
65      substitution and backslash substitutions are performed here as well.
66    - {...} is a string without substitutions.
67    - The list of resulting words is split into commands by semicolon and
68      newline.
69    - '#' at the beginning of a command introduces a comment until end of line.
70    The parser is implemented in tcl8.3.3/generic/tclParse.c.  */
71 
72 
73 /* ====================== Keyword set customization.  ====================== */
74 
75 /* If true extract all strings.  */
76 static bool extract_all = false;
77 
78 static hash_table keywords;
79 static bool default_keywords = true;
80 
81 
82 void
x_tcl_extract_all()83 x_tcl_extract_all ()
84 {
85   extract_all = true;
86 }
87 
88 
89 void
x_tcl_keyword(const char * name)90 x_tcl_keyword (const char *name)
91 {
92   if (name == NULL)
93     default_keywords = false;
94   else
95     {
96       const char *end;
97       struct callshape shape;
98 
99       if (keywords.table == NULL)
100         hash_init (&keywords, 100);
101 
102       split_keywordspec (name, &end, &shape);
103 
104       /* The characters between name and end should form a valid Tcl
105          function name.  A leading "::" is redundant.  */
106       if (end - name >= 2 && name[0] == ':' && name[1] == ':')
107         name += 2;
108 
109       insert_keyword_callshape (&keywords, name, end - name, &shape);
110     }
111 }
112 
113 /* Finish initializing the keywords hash table.
114    Called after argument processing, before each file is processed.  */
115 static void
init_keywords()116 init_keywords ()
117 {
118   if (default_keywords)
119     {
120       /* When adding new keywords here, also update the documentation in
121          xgettext.texi!  */
122       x_tcl_keyword ("::msgcat::mc");
123       default_keywords = false;
124     }
125 }
126 
127 void
init_flag_table_tcl()128 init_flag_table_tcl ()
129 {
130   xgettext_record_flag ("::msgcat::mc:1:pass-tcl-format");
131   xgettext_record_flag ("format:1:tcl-format");
132 }
133 
134 
135 /* ======================== Reading of characters.  ======================== */
136 
137 /* The input file stream.  */
138 static FILE *fp;
139 
140 
141 /* Fetch the next character from the input file.  */
142 static int
do_getc()143 do_getc ()
144 {
145   int c = getc (fp);
146 
147   if (c == EOF)
148     {
149       if (ferror (fp))
150         error (EXIT_FAILURE, errno,
151                _("error while reading \"%s\""), real_file_name);
152     }
153   else if (c == '\n')
154    line_number++;
155 
156   return c;
157 }
158 
159 /* Put back the last fetched character, not EOF.  */
160 static void
do_ungetc(int c)161 do_ungetc (int c)
162 {
163   if (c == '\n')
164     line_number--;
165   ungetc (c, fp);
166 }
167 
168 
169 /* Combine backslash followed by newline and additional whitespace to
170    a single space.  */
171 
172 /* An int that becomes a space when casted to 'unsigned char'.  */
173 #define BS_NL (UCHAR_MAX + 1 + ' ')
174 
175 static int phase1_pushback[1];
176 static int phase1_pushback_length;
177 
178 static int
phase1_getc()179 phase1_getc ()
180 {
181   int c;
182 
183   if (phase1_pushback_length)
184     {
185       c = phase1_pushback[--phase1_pushback_length];
186       if (c == '\n' || c == BS_NL)
187         ++line_number;
188       return c;
189     }
190   c = do_getc ();
191   if (c != '\\')
192     return c;
193   c = do_getc ();
194   if (c != '\n')
195     {
196       if (c != EOF)
197         do_ungetc (c);
198       return '\\';
199     }
200   for (;;)
201     {
202       c = do_getc ();
203       if (!(c == ' ' || c == '\t'))
204         break;
205     }
206   if (c != EOF)
207     do_ungetc (c);
208   return BS_NL;
209 }
210 
211 /* Supports only one pushback character.  */
212 static void
phase1_ungetc(int c)213 phase1_ungetc (int c)
214 {
215   switch (c)
216     {
217     case EOF:
218       break;
219 
220     case '\n':
221     case BS_NL:
222       --line_number;
223       /* FALLTHROUGH */
224 
225     default:
226       if (phase1_pushback_length == SIZEOF (phase1_pushback))
227         abort ();
228       phase1_pushback[phase1_pushback_length++] = c;
229       break;
230     }
231 }
232 
233 
234 /* Keep track of brace nesting depth.
235    When a word starts with an opening brace, a character group begins that
236    ends with the corresponding closing brace.  In theory these character
237    groups are string literals, but they are used by so many Tcl primitives
238    (proc, if, ...) as representing command lists, that we treat them as
239    command lists.  */
240 
241 /* An int that becomes a closing brace when casted to 'unsigned char'.  */
242 #define CL_BRACE (UCHAR_MAX + 1 + '}')
243 
244 static int phase2_pushback[2];
245 static int phase2_pushback_length;
246 
247 /* Brace nesting depth inside the current character group.  */
248 static int brace_depth;
249 
250 static int
phase2_push()251 phase2_push ()
252 {
253   int previous_depth = brace_depth;
254   brace_depth = 1;
255   return previous_depth;
256 }
257 
258 static void
phase2_pop(int previous_depth)259 phase2_pop (int previous_depth)
260 {
261   brace_depth = previous_depth;
262 }
263 
264 static int
phase2_getc()265 phase2_getc ()
266 {
267   int c;
268 
269   if (phase2_pushback_length)
270     {
271       c = phase2_pushback[--phase2_pushback_length];
272       if (c == '\n' || c == BS_NL)
273         ++line_number;
274       else if (c == '{')
275         ++brace_depth;
276       else if (c == '}')
277         --brace_depth;
278       return c;
279     }
280   c = phase1_getc ();
281   if (c == '{')
282     ++brace_depth;
283   else if (c == '}')
284     {
285       if (--brace_depth == 0)
286         c = CL_BRACE;
287     }
288   return c;
289 }
290 
291 /* Supports 2 characters of pushback.  */
292 static void
phase2_ungetc(int c)293 phase2_ungetc (int c)
294 {
295   if (c != EOF)
296     {
297       switch (c)
298         {
299         case '\n':
300         case BS_NL:
301           --line_number;
302           break;
303 
304         case '{':
305           --brace_depth;
306           break;
307 
308         case '}':
309           ++brace_depth;
310           break;
311         }
312       if (phase2_pushback_length == SIZEOF (phase2_pushback))
313         abort ();
314       phase2_pushback[phase2_pushback_length++] = c;
315     }
316 }
317 
318 
319 /* ========================== Reading of tokens.  ========================== */
320 
321 
322 /* A token consists of a sequence of characters.  */
323 struct token
324 {
325   int allocated;                /* number of allocated 'token_char's */
326   int charcount;                /* number of used 'token_char's */
327   char *chars;                  /* the token's constituents */
328 };
329 
330 /* Initialize a 'struct token'.  */
331 static inline void
init_token(struct token * tp)332 init_token (struct token *tp)
333 {
334   tp->allocated = 10;
335   tp->chars = XNMALLOC (tp->allocated, char);
336   tp->charcount = 0;
337 }
338 
339 /* Free the memory pointed to by a 'struct token'.  */
340 static inline void
free_token(struct token * tp)341 free_token (struct token *tp)
342 {
343   free (tp->chars);
344 }
345 
346 /* Ensure there is enough room in the token for one more character.  */
347 static inline void
grow_token(struct token * tp)348 grow_token (struct token *tp)
349 {
350   if (tp->charcount == tp->allocated)
351     {
352       tp->allocated *= 2;
353       tp->chars = (char *) xrealloc (tp->chars, tp->allocated * sizeof (char));
354     }
355 }
356 
357 
358 /* ========================= Accumulating comments ========================= */
359 
360 
361 static char *buffer;
362 static size_t bufmax;
363 static size_t buflen;
364 
365 static inline void
comment_start()366 comment_start ()
367 {
368   buflen = 0;
369 }
370 
371 static inline void
comment_add(int c)372 comment_add (int c)
373 {
374   if (buflen >= bufmax)
375     {
376       bufmax = 2 * bufmax + 10;
377       buffer = xrealloc (buffer, bufmax);
378     }
379   buffer[buflen++] = c;
380 }
381 
382 static inline void
comment_line_end()383 comment_line_end ()
384 {
385   while (buflen >= 1
386          && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t'))
387     --buflen;
388   if (buflen >= bufmax)
389     {
390       bufmax = 2 * bufmax + 10;
391       buffer = xrealloc (buffer, bufmax);
392     }
393   buffer[buflen] = '\0';
394   savable_comment_add (buffer);
395 }
396 
397 
398 /* These are for tracking whether comments count as immediately before
399    keyword.  */
400 static int last_comment_line;
401 static int last_non_comment_line;
402 
403 
404 /* ========================= Accumulating messages ========================= */
405 
406 
407 static message_list_ty *mlp;
408 
409 
410 /* ========================== Reading of commands ========================== */
411 
412 
413 /* We are only interested in constant strings (e.g. "msgcat::mc" or other
414    string literals).  Other words need not to be represented precisely.  */
415 enum word_type
416 {
417   t_string,     /* constant string */
418   t_other,      /* other string */
419   t_separator,  /* command separator: semicolon or newline */
420   t_bracket,    /* ']' pseudo word */
421   t_brace,      /* '}' pseudo word */
422   t_eof         /* EOF marker */
423 };
424 
425 struct word
426 {
427   enum word_type type;
428   struct token *token;          /* for t_string */
429   int line_number_at_start;     /* for t_string */
430 };
431 
432 /* Free the memory pointed to by a 'struct word'.  */
433 static inline void
free_word(struct word * wp)434 free_word (struct word *wp)
435 {
436   if (wp->type == t_string)
437     {
438       free_token (wp->token);
439       free (wp->token);
440     }
441 }
442 
443 /* Convert a t_string token to a char*.  */
444 static char *
string_of_word(const struct word * wp)445 string_of_word (const struct word *wp)
446 {
447   char *str;
448   int n;
449 
450   if (!(wp->type == t_string))
451     abort ();
452   n = wp->token->charcount;
453   str = XNMALLOC (n + 1, char);
454   memcpy (str, wp->token->chars, n);
455   str[n] = '\0';
456   return str;
457 }
458 
459 
460 /* Context lookup table.  */
461 static flag_context_list_table_ty *flag_context_list_table;
462 
463 
464 /* Read an escape sequence.  The value is an ISO-8859-1 character (in the
465    range 0x00..0xff) or a Unicode character (in the range 0x0000..0xffff).  */
466 static int
do_getc_escaped()467 do_getc_escaped ()
468 {
469   int c;
470 
471   c = phase1_getc ();
472   switch (c)
473     {
474     case EOF:
475       return '\\';
476     case 'a':
477       return '\a';
478     case 'b':
479       return '\b';
480     case 'f':
481       return '\f';
482     case 'n':
483       return '\n';
484     case 'r':
485       return '\r';
486     case 't':
487       return '\t';
488     case 'v':
489       return '\v';
490     case 'x':
491       {
492         int n = 0;
493         unsigned int i;
494 
495         for (i = 0;; i++)
496           {
497             c = phase1_getc ();
498             if (c == EOF || !c_isxdigit ((unsigned char) c))
499               break;
500 
501             if (c >= '0' && c <= '9')
502               n = (n << 4) + (c - '0');
503             else if (c >= 'A' && c <= 'F')
504               n = (n << 4) + (c - 'A' + 10);
505             else if (c >= 'a' && c <= 'f')
506               n = (n << 4) + (c - 'a' + 10);
507           }
508         phase1_ungetc (c);
509         return (i > 0 ? (unsigned char) n : 'x');
510       }
511     case 'u':
512       {
513         int n = 0;
514         unsigned int i;
515 
516         for (i = 0; i < 4; i++)
517           {
518             c = phase1_getc ();
519             if (c == EOF || !c_isxdigit ((unsigned char) c))
520               {
521                 phase1_ungetc (c);
522                 break;
523               }
524 
525             if (c >= '0' && c <= '9')
526               n = (n << 4) + (c - '0');
527             else if (c >= 'A' && c <= 'F')
528               n = (n << 4) + (c - 'A' + 10);
529             else if (c >= 'a' && c <= 'f')
530               n = (n << 4) + (c - 'a' + 10);
531           }
532         return (i > 0 ? n : 'u');
533       }
534     case '0': case '1': case '2': case '3': case '4':
535     case '5': case '6': case '7':
536       {
537         int n = c - '0';
538 
539         c = phase1_getc ();
540         if (c != EOF)
541           {
542             if (c >= '0' && c <= '7')
543               {
544                 n = (n << 3) + (c - '0');
545                 c = phase1_getc ();
546                 if (c != EOF)
547                   {
548                     if (c >= '0' && c <= '7')
549                       n = (n << 3) + (c - '0');
550                     else
551                       phase1_ungetc (c);
552                   }
553               }
554             else
555               phase1_ungetc (c);
556           }
557         return (unsigned char) n;
558       }
559     default:
560       /* Note: If c is non-ASCII, Tcl's behaviour is undefined here.  */
561       return (unsigned char) c;
562     }
563 }
564 
565 
566 enum terminator
567 {
568   te_space_separator,           /* looking for space semicolon newline */
569   te_space_separator_bracket,   /* looking for space semicolon newline ']' */
570   te_paren,                     /* looking for ')' */
571   te_quote                      /* looking for '"' */
572 };
573 
574 /* Forward declaration of local functions.  */
575 static enum word_type read_command_list (int looking_for,
576                                          flag_context_ty outer_context);
577 
578 /* Accumulate tokens into the given word.
579    'looking_for' denotes a parse terminator combination.
580    Return the first character past the token.  */
581 static int
accumulate_word(struct word * wp,enum terminator looking_for,flag_context_ty context)582 accumulate_word (struct word *wp, enum terminator looking_for,
583                  flag_context_ty context)
584 {
585   int c;
586 
587   for (;;)
588     {
589       c = phase2_getc ();
590 
591       if (c == EOF || c == CL_BRACE)
592         return c;
593       if ((looking_for == te_space_separator
594            || looking_for == te_space_separator_bracket)
595           && (c == ' ' || c == BS_NL
596               || c == '\t' || c == '\v' || c == '\f' || c == '\r'
597               || c == ';' || c == '\n'))
598         return c;
599       if (looking_for == te_space_separator_bracket && c == ']')
600         return c;
601       if (looking_for == te_paren && c == ')')
602         return c;
603       if (looking_for == te_quote && c == '"')
604         return c;
605 
606       if (c == '$')
607         {
608           /* Distinguish $varname, ${varname} and lone $.  */
609           c = phase2_getc ();
610           if (c == '{')
611             {
612               /* ${varname} */
613               do
614                 c = phase2_getc ();
615               while (c != EOF && c != '}');
616               wp->type = t_other;
617             }
618           else
619             {
620               bool nonempty = false;
621 
622               for (; c != EOF && c != CL_BRACE; c = phase2_getc ())
623                 {
624                   if (c_isalnum ((unsigned char) c) || (c == '_'))
625                     {
626                       nonempty = true;
627                       continue;
628                     }
629                   if (c == ':')
630                     {
631                       c = phase2_getc ();
632                       if (c == ':')
633                         {
634                           do
635                             c = phase2_getc ();
636                           while (c == ':');
637 
638                           phase2_ungetc (c);
639                           nonempty = true;
640                           continue;
641                         }
642                       phase2_ungetc (c);
643                       c = ':';
644                     }
645                   break;
646                 }
647               if (c == '(')
648                 {
649                   /* $varname(index) */
650                   struct word index_word;
651 
652                   index_word.type = t_other;
653                   c = accumulate_word (&index_word, te_paren, null_context);
654                   if (c != EOF && c != ')')
655                     phase2_ungetc (c);
656                   wp->type = t_other;
657                 }
658               else
659                 {
660                   phase2_ungetc (c);
661                   if (nonempty)
662                     {
663                       /* $varname */
664                       wp->type = t_other;
665                     }
666                   else
667                     {
668                       /* lone $ */
669                       if (wp->type == t_string)
670                         {
671                           grow_token (wp->token);
672                           wp->token->chars[wp->token->charcount++] = '$';
673                         }
674                     }
675                 }
676             }
677         }
678       else if (c == '[')
679         {
680           read_command_list (']', context);
681           wp->type = t_other;
682         }
683       else if (c == '\\')
684         {
685           unsigned int uc;
686           unsigned char utf8buf[6];
687           int count;
688           int i;
689 
690           uc = do_getc_escaped ();
691           assert (uc < 0x10000);
692           count = u8_uctomb (utf8buf, uc, 6);
693           assert (count > 0);
694           if (wp->type == t_string)
695             for (i = 0; i < count; i++)
696               {
697                 grow_token (wp->token);
698                 wp->token->chars[wp->token->charcount++] = utf8buf[i];
699               }
700         }
701       else
702         {
703           if (wp->type == t_string)
704             {
705               grow_token (wp->token);
706               wp->token->chars[wp->token->charcount++] = (unsigned char) c;
707             }
708         }
709     }
710 }
711 
712 
713 /* Read the next word.
714    'looking_for' denotes a parse terminator, either ']' or '\0'.  */
715 static void
read_word(struct word * wp,int looking_for,flag_context_ty context)716 read_word (struct word *wp, int looking_for, flag_context_ty context)
717 {
718   int c;
719 
720   do
721     c = phase2_getc ();
722   while (c == ' ' || c == BS_NL
723          || c == '\t' || c == '\v' || c == '\f' || c == '\r');
724 
725   if (c == EOF)
726     {
727       wp->type = t_eof;
728       return;
729     }
730 
731   if (c == CL_BRACE)
732     {
733       wp->type = t_brace;
734       last_non_comment_line = line_number;
735       return;
736     }
737 
738   if (c == '\n')
739     {
740       /* Comments assumed to be grouped with a message must immediately
741          precede it, with no non-whitespace token on a line between both.  */
742       if (last_non_comment_line > last_comment_line)
743         savable_comment_reset ();
744       wp->type = t_separator;
745       return;
746     }
747 
748   if (c == ';')
749     {
750       wp->type = t_separator;
751       last_non_comment_line = line_number;
752       return;
753     }
754 
755   if (looking_for == ']' && c == ']')
756     {
757       wp->type = t_bracket;
758       last_non_comment_line = line_number;
759       return;
760     }
761 
762   if (c == '{')
763     {
764       int previous_depth;
765       enum word_type terminator;
766 
767       /* Start a new nested character group, which lasts until the next
768          balanced '}' (ignoring \} things).  */
769       previous_depth = phase2_push () - 1;
770 
771       /* Interpret it as a command list.  */
772       terminator = read_command_list ('\0', null_context);
773 
774       if (terminator == t_brace)
775         phase2_pop (previous_depth);
776 
777       wp->type = t_other;
778       last_non_comment_line = line_number;
779       return;
780     }
781 
782   wp->type = t_string;
783   wp->token = XMALLOC (struct token);
784   init_token (wp->token);
785   wp->line_number_at_start = line_number;
786 
787   if (c == '"')
788     {
789       c = accumulate_word (wp, te_quote, context);
790       if (c != EOF && c != '"')
791         phase2_ungetc (c);
792     }
793   else
794     {
795       phase2_ungetc (c);
796       c = accumulate_word (wp,
797                            looking_for == ']'
798                            ? te_space_separator_bracket
799                            : te_space_separator,
800                            context);
801       if (c != EOF)
802         phase2_ungetc (c);
803     }
804 
805   if (wp->type != t_string)
806     {
807       free_token (wp->token);
808       free (wp->token);
809     }
810   last_non_comment_line = line_number;
811 }
812 
813 
814 /* Read the next command.
815    'looking_for' denotes a parse terminator, either ']' or '\0'.
816    Returns the type of the word that terminated the command: t_separator or
817    t_bracket (only if looking_for is ']') or t_brace or t_eof.  */
818 static enum word_type
read_command(int looking_for,flag_context_ty outer_context)819 read_command (int looking_for, flag_context_ty outer_context)
820 {
821   int c;
822 
823   /* Skip whitespace and comments.  */
824   for (;;)
825     {
826       c = phase2_getc ();
827 
828       if (c == ' ' || c == BS_NL
829           || c == '\t' || c == '\v' || c == '\f' || c == '\r')
830         continue;
831       if (c == '#')
832         {
833           /* Skip a comment up to end of line.  */
834           last_comment_line = line_number;
835           comment_start ();
836           for (;;)
837             {
838               c = phase2_getc ();
839               if (c == EOF || c == CL_BRACE || c == '\n')
840                 break;
841               /* We skip all leading white space, but not EOLs.  */
842               if (!(buflen == 0 && (c == ' ' || c == '\t')))
843                 comment_add (c);
844             }
845           comment_line_end ();
846           continue;
847         }
848       break;
849     }
850   phase2_ungetc (c);
851 
852   /* Read the words that make up the command.  */
853   {
854     int arg = 0;                /* Current argument number.  */
855     flag_context_list_iterator_ty context_iter;
856     const struct callshapes *shapes = NULL;
857     struct arglist_parser *argparser = NULL;
858 
859     for (;; arg++)
860       {
861         struct word inner;
862         flag_context_ty inner_context;
863 
864         if (arg == 0)
865           inner_context = null_context;
866         else
867           inner_context =
868             inherited_context (outer_context,
869                                flag_context_list_iterator_advance (
870                                  &context_iter));
871 
872         read_word (&inner, looking_for, inner_context);
873 
874         /* Recognize end of command.  */
875         if (inner.type == t_separator || inner.type == t_bracket
876             || inner.type == t_brace || inner.type == t_eof)
877           {
878             if (argparser != NULL)
879               arglist_parser_done (argparser, arg);
880             return inner.type;
881           }
882 
883         if (extract_all)
884           {
885             if (inner.type == t_string)
886               {
887                 lex_pos_ty pos;
888 
889                 pos.file_name = logical_file_name;
890                 pos.line_number = inner.line_number_at_start;
891                 remember_a_message (mlp, NULL, string_of_word (&inner), false,
892                                     false, inner_context, &pos,
893                                     NULL, savable_comment, false);
894               }
895           }
896 
897         if (arg == 0)
898           {
899             /* This is the function position.  */
900             if (inner.type == t_string)
901               {
902                 char *function_name = string_of_word (&inner);
903                 char *stripped_name;
904                 void *keyword_value;
905 
906                 /* A leading "::" is redundant.  */
907                 stripped_name = function_name;
908                 if (function_name[0] == ':' && function_name[1] == ':')
909                   stripped_name += 2;
910 
911                 if (hash_find_entry (&keywords,
912                                      stripped_name, strlen (stripped_name),
913                                      &keyword_value)
914                     == 0)
915                   shapes = (const struct callshapes *) keyword_value;
916 
917                 argparser = arglist_parser_alloc (mlp, shapes);
918 
919                 context_iter =
920                   flag_context_list_iterator (
921                     flag_context_list_table_lookup (
922                       flag_context_list_table,
923                       stripped_name, strlen (stripped_name)));
924 
925                 free (function_name);
926               }
927             else
928               context_iter = null_context_list_iterator;
929           }
930         else
931           {
932             /* These are the argument positions.  */
933             if (argparser != NULL && inner.type == t_string)
934               {
935                 char *s = string_of_word (&inner);
936                 mixed_string_ty *ms =
937                   mixed_string_alloc_simple (s, lc_string,
938                                              logical_file_name,
939                                              inner.line_number_at_start);
940                 free (s);
941                 arglist_parser_remember (argparser, arg, ms,
942                                          inner_context,
943                                          logical_file_name,
944                                          inner.line_number_at_start,
945                                          savable_comment, false);
946               }
947           }
948 
949         free_word (&inner);
950       }
951   }
952 }
953 
954 
955 /* Read a list of commands.
956    'looking_for' denotes a parse terminator, either ']' or '\0'.
957    Returns the type of the word that terminated the command list:
958    t_bracket (only if looking_for is ']') or t_brace or t_eof.  */
959 static enum word_type
read_command_list(int looking_for,flag_context_ty outer_context)960 read_command_list (int looking_for, flag_context_ty outer_context)
961 {
962   for (;;)
963     {
964       enum word_type terminator;
965 
966       terminator = read_command (looking_for, outer_context);
967       if (terminator != t_separator)
968         return terminator;
969     }
970 }
971 
972 
973 void
extract_tcl(FILE * f,const char * real_filename,const char * logical_filename,flag_context_list_table_ty * flag_table,msgdomain_list_ty * mdlp)974 extract_tcl (FILE *f,
975              const char *real_filename, const char *logical_filename,
976              flag_context_list_table_ty *flag_table,
977              msgdomain_list_ty *mdlp)
978 {
979   mlp = mdlp->item[0]->messages;
980 
981   /* We convert our strings to UTF-8 encoding.  */
982   xgettext_current_source_encoding = po_charset_utf8;
983 
984   fp = f;
985   real_file_name = real_filename;
986   logical_file_name = xstrdup (logical_filename);
987   line_number = 1;
988 
989   phase1_pushback_length = 0;
990   phase2_pushback_length = 0;
991 
992   /* Initially, no brace is open.  */
993   brace_depth = 1000000;
994 
995   last_comment_line = -1;
996   last_non_comment_line = -1;
997 
998   flag_context_list_table = flag_table;
999 
1000   init_keywords ();
1001 
1002   /* Eat tokens until eof is seen.  */
1003   read_command_list ('\0', null_context);
1004 
1005   fp = NULL;
1006   real_file_name = NULL;
1007   logical_file_name = NULL;
1008   line_number = 0;
1009 }
1010