Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / x-vala.c
1 /* xgettext Vala backend.
2    Copyright (C) 2013 Free Software Foundation, Inc.
3
4    This file was written by Daiki Ueno <ueno@gnu.org>, 2013.
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 <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 /* Specification.  */
24 #include "x-vala.h"
25
26 #include <errno.h>
27 #include <stdbool.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "message.h"
33 #include "xgettext.h"
34 #include "error.h"
35 #include "error-progname.h"
36 #include "xalloc.h"
37 #include "xvasprintf.h"
38 #include "hash.h"
39 #include "gettext.h"
40
41 #define _(s) gettext(s)
42
43 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
44
45 /* The Vala syntax is defined in the Vala Reference Manual
46    http://www.vala-project.org/doc/vala/.
47    See also vala/valascanner.vala.  */
48
49 /* ====================== Keyword set customization.  ====================== */
50
51 /* If true extract all strings.  */
52 static bool extract_all = false;
53
54 static hash_table keywords;
55 static bool default_keywords = true;
56
57
58 void
59 x_vala_extract_all ()
60 {
61   extract_all = true;
62 }
63
64
65 static void
66 add_keyword (const char *name, hash_table *keywords)
67 {
68   if (name == NULL)
69     default_keywords = false;
70   else
71     {
72       const char *end;
73       struct callshape shape;
74       const char *colon;
75
76       if (keywords->table == NULL)
77         hash_init (keywords, 100);
78
79       split_keywordspec (name, &end, &shape);
80
81       /* The characters between name and end should form a valid C identifier.
82          A colon means an invalid parse in split_keywordspec().  */
83       colon = strchr (name, ':');
84       if (colon == NULL || colon >= end)
85         insert_keyword_callshape (keywords, name, end - name, &shape);
86     }
87 }
88
89 void
90 x_vala_keyword (const char *name)
91 {
92   add_keyword (name, &keywords);
93 }
94
95 static void
96 init_keywords ()
97 {
98   if (default_keywords)
99     {
100       /* When adding new keywords here, also update the documentation in
101          xgettext.texi!  */
102       x_vala_keyword ("dgettext:2");
103       x_vala_keyword ("dcgettext:2");
104       x_vala_keyword ("ngettext:1,2");
105       x_vala_keyword ("dngettext:2,3");
106       x_vala_keyword ("dpgettext:2g");
107       x_vala_keyword ("dpgettext2:2c,3");
108       x_vala_keyword ("_");
109       x_vala_keyword ("Q_");
110       x_vala_keyword ("N_");
111       x_vala_keyword ("NC_:1c,2");
112
113       default_keywords = false;
114     }
115 }
116
117 void
118 init_flag_table_vala ()
119 {
120   xgettext_record_flag ("dgettext:2:pass-c-format");
121   xgettext_record_flag ("dcgettext:2:pass-c-format");
122   xgettext_record_flag ("ngettext:1:pass-c-format");
123   xgettext_record_flag ("ngettext:2:pass-c-format");
124   xgettext_record_flag ("dngettext:2:pass-c-format");
125   xgettext_record_flag ("dngettext:3:pass-c-format");
126   xgettext_record_flag ("dpgettext:2:pass-c-format");
127   xgettext_record_flag ("dpgettext2:3:pass-c-format");
128   xgettext_record_flag ("_:1:pass-c-format");
129   xgettext_record_flag ("Q_:1:pass-c-format");
130   xgettext_record_flag ("N_:1:pass-c-format");
131   xgettext_record_flag ("NC_:2:pass-c-format");
132
133   /* Vala leaves string formatting to Glib functions and thus the
134      format string is exactly same as C.  See also
135      vapi/glib-2.0.vapi.  */
136   xgettext_record_flag ("printf:1:c-format");
137   xgettext_record_flag ("vprintf:1:c-format");
138 }
139
140
141 /* ======================== Reading of characters.  ======================== */
142
143 /* Real filename, used in error messages about the input file.  */
144 static const char *real_file_name;
145
146 /* Logical filename and line number, used to label the extracted messages.  */
147 static char *logical_file_name;
148 static int line_number;
149
150 /* The input file stream.  */
151 static FILE *fp;
152
153
154 /* 1. line_number handling.  */
155
156 #define MAX_PHASE1_PUSHBACK 16
157 static unsigned char phase1_pushback[MAX_PHASE1_PUSHBACK];
158 static int phase1_pushback_length;
159
160
161 static int
162 phase1_getc ()
163 {
164   int c;
165
166   if (phase1_pushback_length)
167     c = phase1_pushback[--phase1_pushback_length];
168   else
169     {
170       c = getc (fp);
171       if (c == EOF)
172         {
173           if (ferror (fp))
174             error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
175                    real_file_name);
176           return EOF;
177         }
178     }
179
180   if (c == '\n')
181     ++line_number;
182   return c;
183 }
184
185
186 /* Supports 2 characters of pushback.  */
187 static void
188 phase1_ungetc (int c)
189 {
190   if (c != EOF)
191     {
192       if (c == '\n')
193         --line_number;
194
195       if (phase1_pushback_length == SIZEOF (phase1_pushback))
196         abort ();
197       phase1_pushback[phase1_pushback_length++] = c;
198     }
199 }
200
201
202 /* These are for tracking whether comments count as immediately before
203    keyword.  */
204 static int last_comment_line;
205 static int last_non_comment_line;
206
207 /* Accumulating comments.  */
208
209 static char *buffer;
210 static size_t bufmax;
211 static size_t buflen;
212
213 static inline void
214 comment_start ()
215 {
216   buflen = 0;
217 }
218
219 static inline void
220 comment_add (int c)
221 {
222   if (buflen >= bufmax)
223     {
224       bufmax = 2 * bufmax + 10;
225       buffer = xrealloc (buffer, bufmax);
226     }
227   buffer[buflen++] = c;
228 }
229
230 static inline void
231 comment_line_end (size_t chars_to_remove)
232 {
233   buflen -= chars_to_remove;
234   while (buflen >= 1
235          && (buffer[buflen - 1] == ' ' || buffer[buflen - 1] == '\t'))
236     --buflen;
237   if (chars_to_remove == 0 && buflen >= bufmax)
238     {
239       bufmax = 2 * bufmax + 10;
240       buffer = xrealloc (buffer, bufmax);
241     }
242   buffer[buflen] = '\0';
243   savable_comment_add (buffer);
244 }
245
246
247 /* 2. Replace each comment that is not inside a character constant or
248    string literal with a space character.  */
249
250 static int
251 phase2_getc ()
252 {
253   int c;
254   bool last_was_star;
255
256   c = phase1_getc ();
257   if (c != '/')
258     return c;
259   c = phase1_getc ();
260   switch (c)
261     {
262     default:
263       phase1_ungetc (c);
264       return '/';
265
266     case '*':
267       /* C comment.  */
268       comment_start ();
269       last_was_star = false;
270       for (;;)
271         {
272           c = phase1_getc ();
273           if (c == EOF)
274             break;
275           /* We skip all leading white space, but not EOLs.  */
276           if (!(buflen == 0 && (c == ' ' || c == '\t')))
277             comment_add (c);
278           switch (c)
279             {
280             case '\n':
281               comment_line_end (1);
282               comment_start ();
283               last_was_star = false;
284               continue;
285
286             case '*':
287               last_was_star = true;
288               continue;
289
290             case '/':
291               if (last_was_star)
292                 {
293                   comment_line_end (2);
294                   break;
295                 }
296               /* FALLTHROUGH */
297
298             default:
299               last_was_star = false;
300               continue;
301             }
302           break;
303         }
304       last_comment_line = line_number;
305       return ' ';
306
307     case '/':
308       /* C++ or ISO C 99 comment.  */
309       comment_start ();
310       for (;;)
311         {
312           c = phase1_getc ();
313           if (c == '\n' || c == EOF)
314             break;
315           /* We skip all leading white space, but not EOLs.  */
316           if (!(buflen == 0 && (c == ' ' || c == '\t')))
317             comment_add (c);
318         }
319       comment_line_end (0);
320       last_comment_line = line_number;
321       return '\n';
322     }
323 }
324
325
326 static void
327 phase2_ungetc (int c)
328 {
329   phase1_ungetc (c);
330 }
331
332
333 /* ========================== Reading of tokens.  ========================== */
334
335 enum token_type_ty
336 {
337   token_type_character_constant,        /* 'x' */
338   token_type_eof,
339   token_type_lparen,                    /* ( */
340   token_type_rparen,                    /* ) */
341   token_type_lbrace,                    /* { */
342   token_type_rbrace,                    /* } */
343   token_type_assign,                    /* = */
344   token_type_return,                    /* return */
345   token_type_plus,                      /* + */
346   token_type_minus,                     /* - */
347   token_type_equality_test_operator,    /* == < > >= <= != */
348   token_type_logic_operator,            /* ! && || */
349   token_type_comma,                     /* , */
350   token_type_colon,                     /* : */
351   token_type_number,                    /* 2.7 */
352   token_type_string_literal,            /* "abc" */
353   token_type_string_template,           /* @"abc" */
354   token_type_regex_literal,             /* /.../ */
355   token_type_symbol,                    /* if else etc. */
356   token_type_other
357 };
358 typedef enum token_type_ty token_type_ty;
359
360 typedef struct token_ty token_ty;
361 struct token_ty
362 {
363   token_type_ty type;
364   char *string;         /* for token_type_symbol, token_type_string_literal */
365   refcounted_string_list_ty *comment;   /* for token_type_string_literal */
366   int line_number;
367 };
368
369 /* Free the memory pointed to by a 'struct token_ty'.  */
370 static inline void
371 free_token (token_ty *tp)
372 {
373   if (tp->type == token_type_string_literal || tp->type == token_type_symbol)
374     free (tp->string);
375   if (tp->type == token_type_string_literal)
376     drop_reference (tp->comment);
377 }
378
379
380 /* Replace escape sequences within character strings with their single
381    character equivalents.  */
382
383 #define P7_QUOTES (1000 + '"')
384 #define P7_QUOTE (1000 + '\'')
385 #define P7_NEWLINE (1000 + '\n')
386
387 static int
388 phase7_getc ()
389 {
390   int c, n, j;
391
392   /* Use phase 1, because phase 2 elides comments.  */
393   c = phase1_getc ();
394
395   /* Return a magic newline indicator, so that we can distinguish
396      between the user requesting a newline in the string (e.g. using
397      "\n" or "\012") from the user failing to terminate the string or
398      character constant.  The ANSI C standard says: 3.1.3.4 Character
399      Constants contain "any character except single quote, backslash or
400      newline; or an escape sequence" and 3.1.4 String Literals contain
401      "any character except double quote, backslash or newline; or an
402      escape sequence".
403
404      Most compilers give a fatal error in this case, however gcc is
405      stupidly silent, even though this is a very common typo.  OK, so
406      "gcc --pedantic" will tell me, but that gripes about too much other
407      stuff.  Could I have a "gcc -Wnewline-in-string" option, or
408      better yet a "gcc -fno-newline-in-string" option, please?  Gcc is
409      also inconsistent between string literals and character constants:
410      you may not embed newlines in character constants; try it, you get
411      a useful diagnostic.  --PMiller  */
412   if (c == '\n')
413     return P7_NEWLINE;
414
415   if (c == '"')
416     return P7_QUOTES;
417   if (c == '\'')
418     return P7_QUOTE;
419   if (c != '\\')
420     return c;
421   c = phase1_getc ();
422   switch (c)
423     {
424     default:
425       /* Unknown escape sequences really should be an error, but just
426          ignore them, and let the real compiler complain.  */
427       phase1_ungetc (c);
428       return '\\';
429
430     case '"':
431     case '\'':
432     case '?':
433     case '\\':
434       return c;
435
436     case 'a':
437       return '\a';
438     case 'b':
439       return '\b';
440
441       /* The \e escape is preculiar to gcc, and assumes an ASCII
442          character set (or superset).  We don't provide support for it
443          here.  */
444
445     case 'f':
446       return '\f';
447     case 'n':
448       return '\n';
449     case 'r':
450       return '\r';
451     case 't':
452       return '\t';
453     case 'v':
454       return '\v';
455
456     case 'x':
457       c = phase1_getc ();
458       switch (c)
459         {
460         default:
461           phase1_ungetc (c);
462           phase1_ungetc ('x');
463           return '\\';
464
465         case '0': case '1': case '2': case '3': case '4':
466         case '5': case '6': case '7': case '8': case '9':
467         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
468         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
469           break;
470         }
471       n = 0;
472       for (;;)
473         {
474           switch (c)
475             {
476             default:
477               phase1_ungetc (c);
478               return n;
479
480             case '0': case '1': case '2': case '3': case '4':
481             case '5': case '6': case '7': case '8': case '9':
482               n = n * 16 + c - '0';
483               break;
484
485             case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
486               n = n * 16 + 10 + c - 'A';
487               break;
488
489             case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
490               n = n * 16 + 10 + c - 'a';
491               break;
492             }
493           c = phase1_getc ();
494         }
495       return n;
496
497     case '0': case '1': case '2': case '3':
498     case '4': case '5': case '6': case '7':
499       n = 0;
500       for (j = 0; j < 3; ++j)
501         {
502           n = n * 8 + c - '0';
503           c = phase1_getc ();
504           switch (c)
505             {
506             default:
507               break;
508
509             case '0': case '1': case '2': case '3':
510             case '4': case '5': case '6': case '7':
511               continue;
512             }
513           break;
514         }
515       phase1_ungetc (c);
516       return n;
517     }
518 }
519
520
521 static void
522 phase7_ungetc (int c)
523 {
524   phase1_ungetc (c);
525 }
526
527
528 /* 3. Parse each resulting logical line as preprocessing tokens and
529    white space.  Preprocessing tokens and Vala tokens don't always
530    match.  */
531
532 static token_ty phase3_pushback[2];
533 static int phase3_pushback_length;
534
535
536 static token_type_ty last_token_type = token_type_other;
537
538 static void
539 phase3_scan_regex ()
540 {
541     int c;
542
543     for (;;)
544       {
545         c = phase1_getc ();
546         if (c == '/')
547           break;
548         if (c == '\\')
549           {
550             c = phase1_getc ();
551             if (c != EOF)
552               continue;
553           }
554         if (c == EOF)
555           {
556             error_with_progname = false;
557             error (0, 0,
558                    _("%s:%d: warning: regular expression literal terminated too early"),
559                    logical_file_name, line_number);
560             error_with_progname = true;
561             return;
562           }
563       }
564
565     c = phase2_getc ();
566     if (!(c == 'i' || c == 's' || c == 'm' || c == 'x'))
567       phase2_ungetc (c);
568 }
569
570 static void
571 phase3_get (token_ty *tp)
572 {
573   static char *buffer;
574   static int bufmax;
575   int bufpos;
576
577   if (phase3_pushback_length)
578     {
579       *tp = phase3_pushback[--phase3_pushback_length];
580       last_token_type = tp->type;
581       return;
582     }
583
584   for (;;)
585     {
586       bool template;
587       bool verbatim;
588       int c;
589
590       tp->line_number = line_number;
591       c = phase2_getc ();
592
593       switch (c)
594         {
595         case EOF:
596           tp->type = last_token_type = token_type_eof;
597           return;
598
599         case '\n':
600           if (last_non_comment_line > last_comment_line)
601             savable_comment_reset ();
602           /* FALLTHROUGH */
603         case ' ':
604         case '\f':
605         case '\t':
606           /* Ignore whitespace and comments.  */
607           continue;
608         default:
609           break;
610         }
611
612       last_non_comment_line = tp->line_number;
613       template = false;
614       verbatim = false;
615
616       switch (c)
617         {
618         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
619         case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
620         case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
621         case 'V': case 'W': case 'X': case 'Y': case 'Z':
622         case '_':
623         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g':
624         case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n':
625         case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u':
626         case 'v': case 'w': case 'x': case 'y': case 'z':
627           bufpos = 0;
628           for (;;)
629             {
630               if (bufpos >= bufmax)
631                 {
632                   bufmax = 2 * bufmax + 10;
633                   buffer = xrealloc (buffer, bufmax);
634                 }
635               buffer[bufpos++] = c;
636               c = phase2_getc ();
637               switch (c)
638                 {
639                 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
640                 case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
641                 case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
642                 case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
643                 case 'Y': case 'Z':
644                 case '_':
645                 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
646                 case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
647                 case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
648                 case 's': case 't': case 'u': case 'v': case 'w': case 'x':
649                 case 'y': case 'z':
650                 case '0': case '1': case '2': case '3': case '4':
651                 case '5': case '6': case '7': case '8': case '9':
652                   continue;
653
654                 default:
655                   phase2_ungetc (c);
656                   break;
657                 }
658               break;
659             }
660           if (bufpos >= bufmax)
661             {
662               bufmax = 2 * bufmax + 10;
663               buffer = xrealloc (buffer, bufmax);
664             }
665           buffer[bufpos] = 0;
666           if (strcmp (buffer, "return") == 0)
667             tp->type = last_token_type = token_type_return;
668           else
669             {
670               tp->string = xstrdup (buffer);
671               tp->type = last_token_type = token_type_symbol;
672             }
673           return;
674
675         case '.':
676           c = phase2_getc ();
677           phase2_ungetc (c);
678           switch (c)
679             {
680             default:
681               tp->string = xstrdup (".");
682               tp->type = last_token_type = token_type_symbol;
683               return;
684
685             case '0': case '1': case '2': case '3': case '4':
686             case '5': case '6': case '7': case '8': case '9':
687               c = '.';
688               break;
689             }
690           /* FALLTHROUGH */
691
692         case '0': case '1': case '2': case '3': case '4':
693         case '5': case '6': case '7': case '8': case '9':
694           /* The preprocessing number token is more "generous" than the C
695              number tokens.  This is mostly due to token pasting (another
696              thing we can ignore here).  */
697           bufpos = 0;
698           for (;;)
699             {
700               if (bufpos >= bufmax)
701                 {
702                   bufmax = 2 * bufmax + 10;
703                   buffer = xrealloc (buffer, bufmax);
704                 }
705               buffer[bufpos++] = c;
706               c = phase2_getc ();
707               switch (c)
708                 {
709                 case 'e':
710                 case 'E':
711                   if (bufpos >= bufmax)
712                     {
713                       bufmax = 2 * bufmax + 10;
714                       buffer = xrealloc (buffer, bufmax);
715                     }
716                   buffer[bufpos++] = c;
717                   c = phase2_getc ();
718                   if (c != '+' && c != '-')
719                     {
720                       phase2_ungetc (c);
721                       break;
722                     }
723                   continue;
724
725                 case 'A': case 'B': case 'C': case 'D':           case 'F':
726                 case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
727                 case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
728                 case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
729                 case 'Y': case 'Z':
730                 case 'a': case 'b': case 'c': case 'd':           case 'f':
731                 case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
732                 case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
733                 case 's': case 't': case 'u': case 'v': case 'w': case 'x':
734                 case 'y': case 'z':
735                 case '0': case '1': case '2': case '3': case '4':
736                 case '5': case '6': case '7': case '8': case '9':
737                 case '.':
738                   continue;
739
740                 default:
741                   phase2_ungetc (c);
742                   break;
743                 }
744               break;
745             }
746           if (bufpos >= bufmax)
747             {
748               bufmax = 2 * bufmax + 10;
749               buffer = xrealloc (buffer, bufmax);
750             }
751           buffer[bufpos] = 0;
752           tp->type = last_token_type = token_type_number;
753           return;
754
755         case '\'':
756           for (;;)
757             {
758               c = phase7_getc ();
759               if (c == P7_NEWLINE)
760                 {
761                   error_with_progname = false;
762                   error (0, 0, _("%s:%d: warning: unterminated character constant"),
763                          logical_file_name, line_number - 1);
764                   error_with_progname = true;
765                   phase7_ungetc ('\n');
766                   break;
767                 }
768               if (c == EOF || c == P7_QUOTE)
769                 break;
770             }
771           tp->type = last_token_type = token_type_character_constant;
772           return;
773
774           /* Vala provides strings in three different formats.
775
776              Usual string literals:
777                "..."
778              Verbatim string literals:
779                """...""" (where ... can include newlines and double quotes)
780              String templates.
781                @"...", @"""..."""
782           
783              Note that, with the current implementation string
784              templates are not subject to translation, because they are
785              inspected at compile time.  For example, the following code
786
787                string bar = "bar";
788                string foo = _(@"foo $bar");
789
790              will be translated into the C code, like:
791
792                _(g_strconcat ("foo ", "bar", NULL));  */
793         case '@':
794           c = phase2_getc ();
795           if (c != '"')
796             {
797               phase2_ungetc (c);
798               tp->type = last_token_type = token_type_other;
799               return;
800             }
801           template = true;
802           /* FALLTHROUGH */
803         case '"':
804           {
805             int c2 = phase2_getc ();
806             if (c2 == '"')
807               {
808                 int c3 = phase2_getc ();
809                 if (c3 == '"')
810                   verbatim = true;
811                 else
812                   {
813                     phase2_ungetc (c3);
814                     phase2_ungetc (c2);
815                   }
816               }
817             else
818               phase2_ungetc (c2);
819           }
820
821           bufpos = 0;
822           for (;;)
823             {
824               c = phase7_getc ();
825               if (c == P7_NEWLINE)
826                 {
827                   if (verbatim)
828                     c = '\n';
829                   else
830                     {
831                       error_with_progname = false;
832                       error (0, 0, _("%s:%d: warning: unterminated string literal"),
833                              logical_file_name, line_number - 1);
834                       error_with_progname = true;
835                       phase7_ungetc ('\n');
836                       break;
837                     }
838                 }
839               if (c == P7_QUOTES)
840                 {
841                   if (verbatim)
842                     {
843                       int c2 = phase2_getc ();
844                       if (c2 == '"')
845                         {
846                           int c3 = phase2_getc ();
847                           if (c3 == '"')
848                             break;
849                           phase2_ungetc (c3);
850                         }
851                       phase2_ungetc (c2);
852                       c = '"';
853                     }
854                   else
855                     break;
856                 }
857               if (c == EOF)
858                 break;
859               if (c == P7_QUOTE)
860                 c = '\'';
861               if (bufpos >= bufmax)
862                 {
863                   bufmax = 2 * bufmax + 10;
864                   buffer = xrealloc (buffer, bufmax);
865                 }
866               buffer[bufpos++] = c;
867             }
868           if (bufpos >= bufmax)
869             {
870               bufmax = 2 * bufmax + 10;
871               buffer = xrealloc (buffer, bufmax);
872             }
873           buffer[bufpos] = 0;
874           tp->type = last_token_type = template ? token_type_string_template : token_type_string_literal;
875           tp->string = xstrdup (buffer);
876           tp->comment = add_reference (savable_comment);
877           return;
878
879         case '/':
880           switch (last_token_type)
881             {
882             case token_type_lparen:
883             case token_type_lbrace:
884             case token_type_assign:
885             case token_type_return:
886             case token_type_plus:
887             case token_type_minus:
888             case token_type_equality_test_operator:
889             case token_type_logic_operator:
890             case token_type_comma:
891               phase3_scan_regex ();
892               tp->type = last_token_type = token_type_regex_literal;
893               break;
894             default:
895               {
896                 int c2 = phase2_getc ();
897                 if (c2 == '=')
898                   {
899                     /* /= */
900                     phase2_ungetc (c2);
901                   }
902                 tp->type = last_token_type = token_type_other;
903                 break;
904               }
905             }
906           return;
907
908         case '(':
909           tp->type = last_token_type = token_type_lparen;
910           return;
911
912         case ')':
913           tp->type = last_token_type = token_type_rparen;
914           return;
915
916         case '{':
917           tp->type = last_token_type = token_type_lbrace;
918           return;
919
920         case '}':
921           tp->type = last_token_type = token_type_rbrace;
922           return;
923
924         case '+':
925           {
926             int c2 = phase2_getc ();
927             switch (c2)
928               {
929               case '=': case '+':
930                 tp->type = last_token_type = token_type_other;
931                 break;
932               default:
933                 phase2_ungetc (c2);
934                 tp->type = last_token_type = token_type_plus;
935                 break;
936               }
937             return;
938           }
939
940         case '-':
941           {
942             int c2 = phase2_getc ();
943             switch (c2)
944               {
945               case '=': case '-':
946                 tp->type = last_token_type = token_type_other;
947                 break;
948               default:
949                 phase2_ungetc (c2);
950                 tp->type = last_token_type = token_type_minus;
951                 break;
952               }
953             return;
954           }
955
956         case '=':
957           {
958             int c2 = phase2_getc ();
959             switch (c2)
960               {
961               case '=':
962                 tp->type = last_token_type = token_type_equality_test_operator;
963                 break;
964               case '>':
965                 tp->type = last_token_type = token_type_other;
966                 break;
967               default:
968                 phase2_ungetc (c2);
969                 tp->type = last_token_type = token_type_assign;
970                 break;
971               }
972             return;
973           }
974
975         case '!':
976           {
977             int c2 = phase2_getc ();
978             if (c2 == '=')
979               {
980                 tp->type = last_token_type = token_type_equality_test_operator;
981                 return;
982               }
983             phase2_ungetc (c2);
984             tp->type = last_token_type = token_type_logic_operator;
985             return;
986           }
987           
988         case '>':
989         case '<':
990           {
991             int c2 = phase2_getc ();
992             if (c2 == '=')
993               tp->type = last_token_type = token_type_equality_test_operator;
994             else if (c2 == c)
995               {
996                 int c3 = phase2_getc ();
997                 if (c3 != '=')
998                   phase2_ungetc (c3);
999                 tp->type = last_token_type = token_type_other;
1000               }
1001             else
1002               {
1003                 phase2_ungetc (c2);
1004                 tp->type = last_token_type = token_type_equality_test_operator;
1005               }
1006           }
1007           return;
1008           
1009         case ',':
1010           tp->type = last_token_type = token_type_comma;
1011           return;
1012
1013         case ':':
1014           tp->type = last_token_type = token_type_colon;
1015           return;
1016
1017         case '&':
1018         case '|':
1019           {
1020             int c2 = phase2_getc ();
1021             if (c2 == c)
1022               tp->type = last_token_type = token_type_logic_operator;
1023             else if (c2 == '=')
1024               tp->type = last_token_type = token_type_other;
1025             else
1026               {
1027                 phase2_ungetc (c2);
1028                 tp->type = last_token_type = token_type_other;
1029               }
1030           }
1031           return;
1032
1033         case '?':
1034           {
1035             int c2 = phase2_getc ();
1036             if (c2 == '?')
1037               {
1038                 tp->type = last_token_type = token_type_logic_operator;
1039                 return;
1040               }
1041             phase2_ungetc (c2);
1042             tp->type = last_token_type = token_type_other;
1043             return;
1044           }
1045
1046         default:
1047           tp->type = last_token_type = token_type_other;
1048           return;
1049         }
1050     }
1051 }
1052
1053 static void
1054 phase3_unget (token_ty *tp)
1055 {
1056   if (tp->type != token_type_eof)
1057     {
1058       if (phase3_pushback_length == SIZEOF (phase3_pushback))
1059         abort ();
1060       phase3_pushback[phase3_pushback_length++] = *tp;
1061     }
1062 }
1063
1064
1065 /* String concatenation with '+'.  */
1066
1067 static void
1068 x_vala_lex (token_ty *tp)
1069 {
1070   phase3_get (tp);
1071   if (tp->type == token_type_string_literal)
1072     {
1073       char *sum = tp->string;
1074       size_t sum_len = strlen (sum);
1075
1076       for (;;)
1077         {
1078           token_ty token2;
1079
1080           phase3_get (&token2);
1081           if (token2.type == token_type_plus)
1082             {
1083               token_ty token3;
1084
1085               phase3_get (&token3);
1086               if (token3.type == token_type_string_literal)
1087                 {
1088                   char *addend = token3.string;
1089                   size_t addend_len = strlen (addend);
1090
1091                   sum = (char *) xrealloc (sum, sum_len + addend_len + 1);
1092                   memcpy (sum + sum_len, addend, addend_len + 1);
1093                   sum_len += addend_len;
1094
1095                   free_token (&token3);
1096                   free_token (&token2);
1097                   continue;
1098                 }
1099               phase3_unget (&token3);
1100             }
1101           phase3_unget (&token2);
1102           break;
1103         }
1104       tp->string = sum;
1105     }
1106 }
1107
1108
1109 /* ========================= Extracting strings.  ========================== */
1110
1111
1112 /* Context lookup table.  */
1113 static flag_context_list_table_ty *flag_context_list_table;
1114
1115
1116 /* The file is broken into tokens.  Scan the token stream, looking for
1117    a keyword, followed by a left paren, followed by a string.  When we
1118    see this sequence, we have something to remember.  We assume we are
1119    looking at a valid Vala program, and leave the complaints about the
1120    grammar to the compiler.
1121
1122      Normal handling: Look for
1123        keyword ( ... msgid ... )
1124        keyword msgid
1125      Plural handling: Look for
1126        keyword ( ... msgid ... msgid_plural ... )
1127
1128    We use recursion because the arguments before msgid or between msgid
1129    and msgid_plural can contain subexpressions of the same form.  */
1130
1131 /* Extract messages until the next balanced closing parenthesis or bracket.
1132    Extracted messages are added to MLP.
1133    DELIM can be either token_type_rparen or token_type_rbracket, or
1134    token_type_eof to accept both.
1135    Return true upon eof, false upon closing parenthesis or bracket.  */
1136 static bool
1137 extract_balanced (message_list_ty *mlp, token_type_ty delim,
1138                   flag_context_ty outer_context,
1139                   flag_context_list_iterator_ty context_iter,
1140                   struct arglist_parser *argparser)
1141 {
1142   /* Current argument number.  */
1143   int arg = 1;
1144   /* 0 when no keyword has been seen.  1 right after a keyword is seen.  */
1145   int state;
1146   /* Parameters of the keyword just seen.  Defined only in state 1.  */
1147   const struct callshapes *next_shapes = NULL;
1148   /* Context iterator that will be used if the next token is a '('.  */
1149   flag_context_list_iterator_ty next_context_iter =
1150     passthrough_context_list_iterator;
1151   /* Current context.  */
1152   flag_context_ty inner_context =
1153     inherited_context (outer_context,
1154                        flag_context_list_iterator_advance (&context_iter));
1155
1156   /* Start state is 0.  */
1157   state = 0;
1158
1159   for (;;)
1160     {
1161       token_ty token;
1162
1163       x_vala_lex (&token);
1164
1165       switch (token.type)
1166         {
1167         case token_type_symbol:
1168           {
1169             void *keyword_value;
1170
1171             if (hash_find_entry (&keywords, token.string, strlen (token.string),
1172                                  &keyword_value)
1173                 == 0)
1174               {
1175                 next_shapes = (const struct callshapes *) keyword_value;
1176                 state = 1;
1177               }
1178             else
1179               state = 0;
1180           }
1181           next_context_iter =
1182             flag_context_list_iterator (
1183               flag_context_list_table_lookup (
1184                 flag_context_list_table,
1185                 token.string, strlen (token.string)));
1186           free (token.string);
1187           continue;
1188
1189         case token_type_lparen:
1190           if (extract_balanced (mlp, token_type_rparen,
1191                                 inner_context, next_context_iter,
1192                                 arglist_parser_alloc (mlp,
1193                                                       state ? next_shapes : NULL)))
1194             {
1195               arglist_parser_done (argparser, arg);
1196               return true;
1197             }
1198           next_context_iter = null_context_list_iterator;
1199           state = 0;
1200           break;
1201
1202         case token_type_rparen:
1203           if (delim == token_type_rparen || delim == token_type_eof)
1204             {
1205               arglist_parser_done (argparser, arg);
1206               return false;
1207             }
1208
1209           next_context_iter = null_context_list_iterator;
1210           state = 0;
1211           continue;
1212
1213         case token_type_comma:
1214           arg++;
1215           inner_context =
1216             inherited_context (outer_context,
1217                                flag_context_list_iterator_advance (
1218                                  &context_iter));
1219           next_context_iter = passthrough_context_list_iterator;
1220           state = 0;
1221           continue;
1222
1223         case token_type_eof:
1224           arglist_parser_done (argparser, arg);
1225           return true;
1226
1227         case token_type_string_literal:
1228           {
1229             lex_pos_ty pos;
1230             pos.file_name = logical_file_name;
1231             pos.line_number = token.line_number;
1232
1233             if (extract_all)
1234               remember_a_message (mlp, NULL, token.string, inner_context,
1235                                   &pos, NULL, token.comment);
1236             else
1237               {
1238                 /* A string immediately after a symbol means a function call.  */
1239                 if (state)
1240                   {
1241                     struct arglist_parser *tmp_argparser;
1242                     tmp_argparser = arglist_parser_alloc (mlp, next_shapes);
1243
1244                     arglist_parser_remember (tmp_argparser, 1, token.string,
1245                                              inner_context, pos.file_name,
1246                                              pos.line_number, token.comment);
1247                     arglist_parser_done (tmp_argparser, 1);
1248                   }
1249                 else
1250                   arglist_parser_remember (argparser, arg, token.string,
1251                                            inner_context, pos.file_name,
1252                                            pos.line_number, token.comment);
1253               }
1254           }
1255           drop_reference (token.comment);
1256           next_context_iter = null_context_list_iterator;
1257           state = 0;
1258           continue;
1259
1260         case token_type_character_constant:
1261         case token_type_lbrace:
1262         case token_type_rbrace:
1263         case token_type_assign:
1264         case token_type_return:
1265         case token_type_plus:
1266         case token_type_minus:
1267         case token_type_equality_test_operator:
1268         case token_type_logic_operator:
1269         case token_type_colon:
1270         case token_type_number:
1271         case token_type_string_template:
1272         case token_type_regex_literal:
1273         case token_type_other:
1274           next_context_iter = null_context_list_iterator;
1275           state = 0;
1276           continue;
1277
1278         default:
1279           abort ();
1280         }
1281     }
1282 }
1283
1284 void
1285 extract_vala (FILE *f,
1286               const char *real_filename, const char *logical_filename,
1287               flag_context_list_table_ty *flag_table,
1288               msgdomain_list_ty *mdlp)
1289 {
1290   message_list_ty *mlp = mdlp->item[0]->messages;
1291
1292   fp = f;
1293   real_file_name = real_filename;
1294   logical_file_name = xstrdup (logical_filename);
1295   line_number = 1;
1296
1297   last_comment_line = -1;
1298   last_non_comment_line = -1;
1299
1300   flag_context_list_table = flag_table;
1301
1302   init_keywords ();
1303
1304   /* Eat tokens until eof is seen.  When extract_parenthesized returns
1305      due to an unbalanced closing parenthesis, just restart it.  */
1306   while (!extract_balanced (mlp, token_type_eof,
1307                             null_context, null_context_list_iterator,
1308                             arglist_parser_alloc (mlp, NULL)))
1309     ;
1310
1311   fp = NULL;
1312   real_file_name = NULL;
1313   logical_file_name = NULL;
1314   line_number = 0;
1315 }