Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / x-ycp.c
1 /* xgettext YCP backend.
2    Copyright (C) 2001-2003, 2005-2009, 2011, 2015 Free Software
3    Foundation, Inc.
4
5    This file was written by Bruno Haible <haible@clisp.cons.org>, 2001.
6
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 3 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 /* Specification.  */
25 #include "x-ycp.h"
26
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "message.h"
34 #include "xgettext.h"
35 #include "error.h"
36 #include "xalloc.h"
37 #include "gettext.h"
38
39 #define _(s) gettext(s)
40
41 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
42
43
44 /* The YCP syntax is defined in libycp/doc/syntax.html.
45    See also libycp/src/scanner.ll.
46    Both are part of the yast2-core package in SuSE Linux distributions.  */
47
48
49 void
50 init_flag_table_ycp ()
51 {
52   xgettext_record_flag ("sformat:1:ycp-format");
53   xgettext_record_flag ("y2debug:1:ycp-format");
54   xgettext_record_flag ("y2milestone:1:ycp-format");
55   xgettext_record_flag ("y2warning:1:ycp-format");
56   xgettext_record_flag ("y2error:1:ycp-format");
57   xgettext_record_flag ("y2security:1:ycp-format");
58   xgettext_record_flag ("y2internal:1:ycp-format");
59 }
60
61
62 /* ======================== Reading of characters.  ======================== */
63
64
65 /* Real filename, used in error messages about the input file.  */
66 static const char *real_file_name;
67
68 /* Logical filename and line number, used to label the extracted messages.  */
69 static char *logical_file_name;
70 static int line_number;
71 static int char_in_line;
72
73 /* The input file stream.  */
74 static FILE *fp;
75
76 /* These are for tracking whether comments count as immediately before
77    keyword.  */
78 static int last_comment_line;
79 static int last_non_comment_line;
80
81
82 /* 1. line_number handling.  */
83
84 static int
85 phase1_getc ()
86 {
87   int c = getc (fp);
88
89   if (c == EOF)
90     {
91       if (ferror (fp))
92         error (EXIT_FAILURE, errno, _("error while reading \"%s\""),
93                real_file_name);
94       return EOF;
95     }
96
97   if (c == '\n')
98     {
99       line_number++;
100       char_in_line = 0;
101     }
102   else
103     char_in_line++;
104
105   return c;
106 }
107
108 /* Supports only one pushback character.  */
109 static void
110 phase1_ungetc (int c)
111 {
112   if (c != EOF)
113     {
114       if (c == '\n')
115         {
116           --line_number;
117           char_in_line = INT_MAX;
118         }
119       else
120         --char_in_line;
121
122       ungetc (c, fp);
123     }
124 }
125
126
127 /* 2. Replace each comment that is not inside a character constant or
128    string literal with a space character.  We need to remember the
129    comment for later, because it may be attached to a keyword string.
130    YCP comments can be in C comment syntax, C++ comment syntax or sh
131    comment syntax.  */
132
133 static unsigned char phase2_pushback[1];
134 static int phase2_pushback_length;
135
136 static int
137 phase2_getc ()
138 {
139   static char *buffer;
140   static size_t bufmax;
141   size_t buflen;
142   int lineno;
143   int c;
144   bool last_was_star;
145
146   if (phase2_pushback_length)
147     return phase2_pushback[--phase2_pushback_length];
148
149   if (char_in_line == 0)
150     {
151       /* Eat whitespace, to recognize ^[\t ]*# pattern.  */
152       do
153         c = phase1_getc ();
154       while (c == '\t' || c == ' ');
155
156       if (c == '#')
157         {
158           /* sh comment.  */
159           buflen = 0;
160           lineno = line_number;
161           for (;;)
162             {
163               c = phase1_getc ();
164               if (c == '\n' || c == EOF)
165                 break;
166               /* We skip all leading white space, but not EOLs.  */
167               if (!(buflen == 0 && (c == ' ' || c == '\t')))
168                 {
169                   if (buflen >= bufmax)
170                     {
171                       bufmax = 2 * bufmax + 10;
172                       buffer = xrealloc (buffer, bufmax);
173                     }
174                   buffer[buflen++] = c;
175                 }
176             }
177           if (buflen >= bufmax)
178             {
179               bufmax = 2 * bufmax + 10;
180               buffer = xrealloc (buffer, bufmax);
181             }
182           buffer[buflen] = '\0';
183           savable_comment_add (buffer);
184           last_comment_line = lineno;
185           return '\n';
186         }
187     }
188   else
189     c = phase1_getc ();
190
191   if (c == '/')
192     {
193       c = phase1_getc ();
194
195       switch (c)
196         {
197         default:
198           phase1_ungetc (c);
199           return '/';
200
201         case '*':
202           /* C comment.  */
203           buflen = 0;
204           lineno = line_number;
205           last_was_star = false;
206           for (;;)
207             {
208               c = phase1_getc ();
209               if (c == EOF)
210                 break;
211               /* We skip all leading white space, but not EOLs.  */
212               if (buflen == 0 && (c == ' ' || c == '\t'))
213                 continue;
214               if (buflen >= bufmax)
215                 {
216                   bufmax = 2 * bufmax + 10;
217                   buffer = xrealloc (buffer, bufmax);
218                 }
219               buffer[buflen++] = c;
220               switch (c)
221                 {
222                 case '\n':
223                   --buflen;
224                   while (buflen >= 1
225                          && (buffer[buflen - 1] == ' '
226                              || buffer[buflen - 1] == '\t'))
227                     --buflen;
228                   buffer[buflen] = '\0';
229                   savable_comment_add (buffer);
230                   buflen = 0;
231                   lineno = line_number;
232                   last_was_star = false;
233                   continue;
234
235                 case '*':
236                   last_was_star = true;
237                   continue;
238
239                 case '/':
240                   if (last_was_star)
241                     {
242                       buflen -= 2;
243                       while (buflen >= 1
244                              && (buffer[buflen - 1] == ' '
245                                  || buffer[buflen - 1] == '\t'))
246                         --buflen;
247                       buffer[buflen] = '\0';
248                       savable_comment_add (buffer);
249                       break;
250                     }
251                   /* FALLTHROUGH */
252
253                 default:
254                   last_was_star = false;
255                   continue;
256                 }
257               break;
258             }
259           last_comment_line = lineno;
260           return ' ';
261
262         case '/':
263           /* C++ comment.  */
264           buflen = 0;
265           lineno = line_number;
266           for (;;)
267             {
268               c = phase1_getc ();
269               if (c == '\n' || c == EOF)
270                 break;
271               /* We skip all leading white space, but not EOLs.  */
272               if (!(buflen == 0 && (c == ' ' || c == '\t')))
273                 {
274                   if (buflen >= bufmax)
275                     {
276                       bufmax = 2 * bufmax + 10;
277                       buffer = xrealloc (buffer, bufmax);
278                     }
279                   buffer[buflen++] = c;
280                 }
281             }
282           if (buflen >= bufmax)
283             {
284               bufmax = 2 * bufmax + 10;
285               buffer = xrealloc (buffer, bufmax);
286             }
287           buffer[buflen] = '\0';
288           savable_comment_add (buffer);
289           last_comment_line = lineno;
290           return '\n';
291         }
292     }
293   else
294     return c;
295 }
296
297 /* Supports only one pushback character.  */
298 static void
299 phase2_ungetc (int c)
300 {
301   if (c != EOF)
302     {
303       if (phase2_pushback_length == SIZEOF (phase2_pushback))
304         abort ();
305       phase2_pushback[phase2_pushback_length++] = c;
306     }
307 }
308
309
310 /* ========================== Reading of tokens.  ========================== */
311
312
313 enum token_type_ty
314 {
315   token_type_eof,
316   token_type_lparen,            /* ( */
317   token_type_rparen,            /* ) */
318   token_type_comma,             /* , */
319   token_type_i18n,              /* _( */
320   token_type_string_literal,    /* "abc" */
321   token_type_symbol,            /* symbol, number */
322   token_type_other              /* misc. operator */
323 };
324 typedef enum token_type_ty token_type_ty;
325
326 typedef struct token_ty token_ty;
327 struct token_ty
328 {
329   token_type_ty type;
330   char *string;         /* for token_type_string_literal, token_type_symbol */
331   refcounted_string_list_ty *comment;   /* for token_type_string_literal */
332   int line_number;
333 };
334
335
336 /* 7. Replace escape sequences within character strings with their
337    single character equivalents.  */
338
339 #define P7_QUOTES (1000 + '"')
340
341 static int
342 phase7_getc ()
343 {
344   int c;
345
346   for (;;)
347     {
348       /* Use phase 1, because phase 2 elides comments.  */
349       c = phase1_getc ();
350
351       if (c == '"')
352         return P7_QUOTES;
353       if (c != '\\')
354         return c;
355       c = phase1_getc ();
356       if (c != '\n')
357         switch (c)
358           {
359           case 'b':
360             return '\b';
361           case 'f':
362             return '\f';
363           case 'n':
364             return '\n';
365           case 'r':
366             return '\r';
367           case 't':
368             return '\t';
369
370           /* FIXME: What is the octal escape syntax?
371              syntax.html says: [0] [0-7]+
372              scanner.ll says:  [0-7] [0-7] [0-7]
373            */
374 #if 0
375           case '0': case '1': case '2': case '3':
376           case '4': case '5': case '6': case '7':
377             {
378               int n, j;
379
380               n = 0;
381               for (j = 0; j < 3; ++j)
382                 {
383                   n = n * 8 + c - '0';
384                   c = phase1_getc ();
385                   switch (c)
386                     {
387                     default:
388                       break;
389
390                     case '0': case '1': case '2': case '3':
391                     case '4': case '5': case '6': case '7':
392                       continue;
393                     }
394                   break;
395                 }
396               phase1_ungetc (c);
397               return n;
398             }
399 #endif
400
401           default:
402             return c;
403           }
404     }
405 }
406
407
408 /* Free the memory pointed to by a 'struct token_ty'.  */
409 static inline void
410 free_token (token_ty *tp)
411 {
412   if (tp->type == token_type_string_literal || tp->type == token_type_symbol)
413     free (tp->string);
414   if (tp->type == token_type_string_literal)
415     drop_reference (tp->comment);
416 }
417
418
419 /* Combine characters into tokens.  Discard whitespace.  */
420
421 static token_ty phase5_pushback[1];
422 static int phase5_pushback_length;
423
424 static void
425 phase5_get (token_ty *tp)
426 {
427   static char *buffer;
428   static int bufmax;
429   int bufpos;
430   int c;
431
432   if (phase5_pushback_length)
433     {
434       *tp = phase5_pushback[--phase5_pushback_length];
435       return;
436     }
437   for (;;)
438     {
439       tp->line_number = line_number;
440       c = phase2_getc ();
441
442       switch (c)
443         {
444         case EOF:
445           tp->type = token_type_eof;
446           return;
447
448         case '\n':
449           if (last_non_comment_line > last_comment_line)
450             savable_comment_reset ();
451           /* FALLTHROUGH */
452         case '\r':
453         case '\t':
454         case ' ':
455           /* Ignore whitespace and comments.  */
456           continue;
457         }
458
459       last_non_comment_line = tp->line_number;
460
461       switch (c)
462         {
463         case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
464         case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
465         case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
466         case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
467         case 'Y': case 'Z':
468         case '_':
469         case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
470         case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
471         case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
472         case 's': case 't': case 'u': case 'v': case 'w': case 'x':
473         case 'y': case 'z':
474         case '0': case '1': case '2': case '3': case '4':
475         case '5': case '6': case '7': case '8': case '9':
476           /* Symbol, or part of a number.  */
477           bufpos = 0;
478           for (;;)
479             {
480               if (bufpos >= bufmax)
481                 {
482                   bufmax = 2 * bufmax + 10;
483                   buffer = xrealloc (buffer, bufmax);
484                 }
485               buffer[bufpos++] = c;
486               c = phase2_getc ();
487               switch (c)
488                 {
489                 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
490                 case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
491                 case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
492                 case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
493                 case 'Y': case 'Z':
494                 case '_':
495                 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
496                 case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
497                 case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
498                 case 's': case 't': case 'u': case 'v': case 'w': case 'x':
499                 case 'y': case 'z':
500                 case '0': case '1': case '2': case '3': case '4':
501                 case '5': case '6': case '7': case '8': case '9':
502                   continue;
503                 default:
504                   if (bufpos == 1 && buffer[0] == '_' && c == '(')
505                     {
506                       tp->type = token_type_i18n;
507                       return;
508                     }
509                   phase2_ungetc (c);
510                   break;
511                 }
512               break;
513             }
514           if (bufpos >= bufmax)
515             {
516               bufmax = 2 * bufmax + 10;
517               buffer = xrealloc (buffer, bufmax);
518             }
519           buffer[bufpos] = '\0';
520           tp->string = xstrdup (buffer);
521           tp->type = token_type_symbol;
522           return;
523
524         case '"':
525           bufpos = 0;
526           for (;;)
527             {
528               c = phase7_getc ();
529               if (c == EOF || c == P7_QUOTES)
530                 break;
531               if (bufpos >= bufmax)
532                 {
533                   bufmax = 2 * bufmax + 10;
534                   buffer = xrealloc (buffer, bufmax);
535                 }
536               buffer[bufpos++] = c;
537             }
538           if (bufpos >= bufmax)
539             {
540               bufmax = 2 * bufmax + 10;
541               buffer = xrealloc (buffer, bufmax);
542             }
543           buffer[bufpos] = '\0';
544           tp->string = xstrdup (buffer);
545           tp->type = token_type_string_literal;
546           tp->comment = add_reference (savable_comment);
547           return;
548
549         case '(':
550           tp->type = token_type_lparen;
551           return;
552
553         case ')':
554           tp->type = token_type_rparen;
555           return;
556
557         case ',':
558           tp->type = token_type_comma;
559           return;
560
561         default:
562           /* We could carefully recognize each of the 2 and 3 character
563              operators, but it is not necessary, as we only need to recognize
564              gettext invocations.  Don't bother.  */
565           tp->type = token_type_other;
566           return;
567         }
568     }
569 }
570
571 /* Supports only one pushback token.  */
572 static void
573 phase5_unget (token_ty *tp)
574 {
575   if (tp->type != token_type_eof)
576     {
577       if (phase5_pushback_length == SIZEOF (phase5_pushback))
578         abort ();
579       phase5_pushback[phase5_pushback_length++] = *tp;
580     }
581 }
582
583
584 /* Concatenate adjacent string literals to form single string literals.
585    (See libycp/src/parser.yy, rule 'string' vs. terminal 'STRING'.)  */
586
587 static void
588 phase8_get (token_ty *tp)
589 {
590   phase5_get (tp);
591   if (tp->type != token_type_string_literal)
592     return;
593   for (;;)
594     {
595       token_ty tmp;
596       size_t len;
597
598       phase5_get (&tmp);
599       if (tmp.type != token_type_string_literal)
600         {
601           phase5_unget (&tmp);
602           return;
603         }
604       len = strlen (tp->string);
605       tp->string = xrealloc (tp->string, len + strlen (tmp.string) + 1);
606       strcpy (tp->string + len, tmp.string);
607       free_token (&tmp);
608     }
609 }
610
611
612 /* ========================= Extracting strings.  ========================== */
613
614
615 /* Context lookup table.  */
616 static flag_context_list_table_ty *flag_context_list_table;
617
618
619 /* The file is broken into tokens.
620
621      Normal handling: Look for
622        [A] _( [B] msgid ... )
623      Plural handling: Look for
624        [A] _( [B] msgid [C] , [D] msgid_plural ... )
625      At point [A]: state == 0.
626      At point [B]: state == 1, plural_mp == NULL.
627      At point [C]: state == 2, plural_mp != NULL.
628      At point [D]: state == 1, plural_mp != NULL.
629
630    We use recursion because we have to set the context according to the given
631    flags.  */
632
633
634 /* Extract messages until the next balanced closing parenthesis.
635    Extracted messages are added to MLP.
636    Return true upon eof, false upon closing parenthesis.  */
637 static bool
638 extract_parenthesized (message_list_ty *mlp,
639                        flag_context_ty outer_context,
640                        flag_context_list_iterator_ty context_iter,
641                        bool in_i18n)
642 {
643   int state; /* 1 or 2 inside _( ... ), otherwise 0 */
644   int plural_state = 0; /* defined only when in states 1 and 2 */
645   message_ty *plural_mp = NULL; /* defined only when in states 1 and 2 */
646   /* Context iterator that will be used if the next token is a '('.  */
647   flag_context_list_iterator_ty next_context_iter =
648     passthrough_context_list_iterator;
649   /* Current context.  */
650   flag_context_ty inner_context =
651     inherited_context (outer_context,
652                        flag_context_list_iterator_advance (&context_iter));
653
654   /* Start state is 0 or 1.  */
655   state = (in_i18n ? 1 : 0);
656
657   for (;;)
658     {
659       token_ty token;
660
661       if (in_i18n)
662         phase8_get (&token);
663       else
664         phase5_get (&token);
665
666       switch (token.type)
667         {
668         case token_type_i18n:
669           if (extract_parenthesized (mlp, inner_context, next_context_iter,
670                                      true))
671             return true;
672           next_context_iter = null_context_list_iterator;
673           state = 0;
674           continue;
675
676         case token_type_string_literal:
677           if (state == 1)
678             {
679               lex_pos_ty pos;
680               pos.file_name = logical_file_name;
681               pos.line_number = token.line_number;
682
683               if (plural_state == 0)
684                 {
685                   /* Seen an msgid.  */
686                   plural_mp = remember_a_message (mlp, NULL, token.string,
687                                                   inner_context, &pos,
688                                                   NULL, token.comment);
689                   plural_state = 1;
690                   state = 2;
691                 }
692               else
693                 {
694                   /* Seen an msgid_plural.  */
695                   if (plural_mp != NULL)
696                     remember_a_message_plural (plural_mp, token.string,
697                                                inner_context, &pos,
698                                                token.comment);
699                   state = 0;
700                 }
701               drop_reference (token.comment);
702             }
703           else
704             {
705               free_token (&token);
706               state = 0;
707             }
708           next_context_iter = null_context_list_iterator;
709           continue;
710
711         case token_type_symbol:
712           next_context_iter =
713             flag_context_list_iterator (
714               flag_context_list_table_lookup (
715                 flag_context_list_table,
716                 token.string, strlen (token.string)));
717           free_token (&token);
718           state = 0;
719           continue;
720
721         case token_type_lparen:
722           if (extract_parenthesized (mlp, inner_context, next_context_iter,
723                                      false))
724             return true;
725           next_context_iter = null_context_list_iterator;
726           state = 0;
727           continue;
728
729         case token_type_rparen:
730           return false;
731
732         case token_type_comma:
733           if (state == 2)
734             state = 1;
735           else
736             state = 0;
737           inner_context =
738             inherited_context (outer_context,
739                                flag_context_list_iterator_advance (
740                                  &context_iter));
741           next_context_iter = passthrough_context_list_iterator;
742           continue;
743
744         case token_type_other:
745           next_context_iter = null_context_list_iterator;
746           state = 0;
747           continue;
748
749         case token_type_eof:
750           return true;
751
752         default:
753           abort ();
754         }
755     }
756 }
757
758
759 void
760 extract_ycp (FILE *f,
761              const char *real_filename, const char *logical_filename,
762              flag_context_list_table_ty *flag_table,
763              msgdomain_list_ty *mdlp)
764 {
765   message_list_ty *mlp = mdlp->item[0]->messages;
766
767   fp = f;
768   real_file_name = real_filename;
769   logical_file_name = xstrdup (logical_filename);
770   line_number = 1;
771   char_in_line = 0;
772
773   last_comment_line = -1;
774   last_non_comment_line = -1;
775
776   flag_context_list_table = flag_table;
777
778   /* Eat tokens until eof is seen.  When extract_parenthesized returns
779      due to an unbalanced closing parenthesis, just restart it.  */
780   while (!extract_parenthesized (mlp, null_context, null_context_list_iterator,
781                                  false))
782     ;
783
784   fp = NULL;
785   real_file_name = NULL;
786   logical_file_name = NULL;
787   line_number = 0;
788   char_in_line = 0;
789 }