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