Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / format-boost.c
1 /* Boost format strings.
2    Copyright (C) 2001-2004, 2006-2007, 2009, 2015 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2006.
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 #include <stdbool.h>
24 #include <stdlib.h>
25
26 #include "format.h"
27 #include "c-ctype.h"
28 #include "xalloc.h"
29 #include "xvasprintf.h"
30 #include "format-invalid.h"
31 #include "gettext.h"
32
33 #define _(str) gettext (str)
34
35 /* Boost format strings are described in
36      boost_1_33_1/libs/format/doc/format.html
37    and implemented in
38      boost_1_33_1/boost/format/parsing.hpp.
39    A directive (other than '%%')
40    - starts with '%' or '%|'; in the latter case it must end in '|',
41    - is continued either by
42        - 'm%' where m is a positive integer, starting with a nonzero digit;
43          in this case the directive must not have started with '%|'; or
44        - the following:
45            - optional: 'm$' where m is a positive integer, starting with a
46              nonzero digit,
47            - optional: any of the characters '#', '0', '-', ' ', '+', "'",
48              '_', '=', 'h', 'l',
49            - optional: a width specification: '*' (reads an argument) or '*m$'
50              or a nonempty digit sequence,
51            - optional: a '.' and a precision specification: '*' (reads an
52              argument) or '*m$' or a nonempty digit sequence,
53            - optional: any of the characters 'h', 'l', 'L',
54            - if the directive started with '%|':
55                an optional specifier and a final '|',
56              otherwise
57                a mandatory specifier.
58              If no specifier is given, it needs an argument of any type.
59              The possible specifiers are:
60                - 'c', 'C', that need a character argument,
61                - 's', 'S', that need an argument of any type,
62                - 'i', 'd', 'o', 'u', 'x', 'X', that need an integer argument,
63                - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument,
64                - 'p', that needs a 'void *' argument,
65                - 't', that doesn't need an argument,
66                - 'TX', where X is any character, that doesn't need an argument,
67                - 'n', that needs a pointer to integer.
68              The Boost format string interpreter doesn't actually care about
69              the argument types, but we do, because it increases the likelihood
70              of detecting translator mistakes.
71    Numbered ('%m%' or '%m$' or '*m$') and unnumbered argument specifications
72    cannot be used in the same string.
73  */
74
75 enum format_arg_type
76 {
77   FAT_NONE              = 0,
78   /* Basic types */
79   FAT_INTEGER           = 1,
80   FAT_DOUBLE            = 2,
81   FAT_CHAR              = 3,
82   FAT_POINTER           = 4,
83   FAT_ANY               = 5
84 };
85
86 struct numbered_arg
87 {
88   unsigned int number;
89   enum format_arg_type type;
90 };
91
92 struct spec
93 {
94   unsigned int directives;
95   unsigned int numbered_arg_count;
96   unsigned int allocated;
97   struct numbered_arg *numbered;
98 };
99
100 /* Locale independent test for a decimal digit.
101    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
102    <ctype.h> isdigit must be an 'unsigned char'.)  */
103 #undef isdigit
104 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
105
106
107 static int
108 numbered_arg_compare (const void *p1, const void *p2)
109 {
110   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
111   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
112
113   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
114 }
115
116 static void *
117 format_parse (const char *format, bool translated, char *fdi,
118               char **invalid_reason)
119 {
120   const char *const format_start = format;
121   struct spec spec;
122   unsigned int unnumbered_arg_count;
123   struct spec *result;
124
125   spec.directives = 0;
126   spec.numbered_arg_count = 0;
127   spec.allocated = 0;
128   spec.numbered = NULL;
129   unnumbered_arg_count = 0;
130
131   for (; *format != '\0';)
132     if (*format++ == '%')
133       {
134         /* A directive.  */
135         FDI_SET (format - 1, FMTDIR_START);
136         spec.directives++;
137
138         if (*format == '%')
139           format++;
140         else
141           {
142             bool brackets = false;
143             bool done = false;
144             unsigned int number = 0;
145             enum format_arg_type type = FAT_NONE;
146
147             if (*format == '|')
148               {
149                 format++;
150                 brackets = true;
151               }
152
153             if (isdigit (*format) && *format != '0')
154               {
155                 const char *f = format;
156                 unsigned int m = 0;
157
158                 do
159                   {
160                     m = 10 * m + (*f - '0');
161                     f++;
162                   }
163                 while (isdigit (*f));
164
165                 if ((!brackets && *f == '%') || *f == '$')
166                   {
167                     if (m == 0) /* can happen if m overflows */
168                       {
169                         *invalid_reason = INVALID_ARGNO_0 (spec.directives);
170                         FDI_SET (f, FMTDIR_ERROR);
171                         goto bad_format;
172                       }
173                     number = m;
174                     if (*f == '%')
175                       {
176                         type = FAT_ANY;
177                         done = true;
178                       }
179                     format = ++f;
180                   }
181               }
182
183             if (!done)
184               {
185                 /* Parse flags.  */
186                 for (;;)
187                   {
188                     if (*format == ' ' || *format == '+' || *format == '-'
189                         || *format == '#' || *format == '0' || *format == '\''
190                         || *format == '_' || *format == '=' || *format == 'h'
191                         || *format == 'l')
192                       format++;
193                     else
194                       break;
195                   }
196
197                 /* Parse width.  */
198                 if (*format == '*')
199                   {
200                     unsigned int width_number = 0;
201
202                     format++;
203
204                     if (isdigit (*format))
205                       {
206                         const char *f = format;
207                         unsigned int m = 0;
208
209                         do
210                           {
211                             m = 10 * m + (*f - '0');
212                             f++;
213                           }
214                         while (isdigit (*f));
215
216                         if (*f == '$')
217                           {
218                             if (m == 0)
219                               {
220                                 *invalid_reason =
221                                   INVALID_WIDTH_ARGNO_0 (spec.directives);
222                                 FDI_SET (f, FMTDIR_ERROR);
223                                 goto bad_format;
224                               }
225                             width_number = m;
226                             format = ++f;
227                           }
228                       }
229
230                     if (width_number)
231                       {
232                         /* Numbered argument.  */
233
234                         /* Numbered and unnumbered specifications are
235                            exclusive.  */
236                         if (unnumbered_arg_count > 0)
237                           {
238                             *invalid_reason =
239                               INVALID_MIXES_NUMBERED_UNNUMBERED ();
240                             FDI_SET (format - 1, FMTDIR_ERROR);
241                             goto bad_format;
242                           }
243
244                         if (spec.allocated == spec.numbered_arg_count)
245                           {
246                             spec.allocated = 2 * spec.allocated + 1;
247                             spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
248                           }
249                         spec.numbered[spec.numbered_arg_count].number = width_number;
250                         spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
251                         spec.numbered_arg_count++;
252                       }
253                     else
254                       {
255                         /* Unnumbered argument.  */
256
257                         /* Numbered and unnumbered specifications are
258                            exclusive.  */
259                         if (spec.numbered_arg_count > 0)
260                           {
261                             *invalid_reason =
262                               INVALID_MIXES_NUMBERED_UNNUMBERED ();
263                             FDI_SET (format - 1, FMTDIR_ERROR);
264                             goto bad_format;
265                           }
266
267                         if (spec.allocated == unnumbered_arg_count)
268                           {
269                             spec.allocated = 2 * spec.allocated + 1;
270                             spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
271                           }
272                         spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
273                         spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
274                         unnumbered_arg_count++;
275                       }
276                   }
277                 else if (isdigit (*format))
278                   {
279                     do format++; while (isdigit (*format));
280                   }
281
282                 /* Parse precision.  */
283                 if (*format == '.')
284                   {
285                     format++;
286
287                     if (*format == '*')
288                       {
289                         unsigned int precision_number = 0;
290
291                         format++;
292
293                         if (isdigit (*format))
294                           {
295                             const char *f = format;
296                             unsigned int m = 0;
297
298                             do
299                               {
300                                 m = 10 * m + (*f - '0');
301                                 f++;
302                               }
303                             while (isdigit (*f));
304
305                             if (*f == '$')
306                               {
307                                 if (m == 0)
308                                   {
309                                     *invalid_reason =
310                                       INVALID_PRECISION_ARGNO_0 (spec.directives);
311                                     FDI_SET (f, FMTDIR_ERROR);
312                                     goto bad_format;
313                                   }
314                                 precision_number = m;
315                                 format = ++f;
316                               }
317                           }
318
319                         if (precision_number)
320                           {
321                             /* Numbered argument.  */
322
323                             /* Numbered and unnumbered specifications are
324                                exclusive.  */
325                             if (unnumbered_arg_count > 0)
326                               {
327                                 *invalid_reason =
328                                   INVALID_MIXES_NUMBERED_UNNUMBERED ();
329                                 FDI_SET (format - 1, FMTDIR_ERROR);
330                                 goto bad_format;
331                               }
332
333                             if (spec.allocated == spec.numbered_arg_count)
334                               {
335                                 spec.allocated = 2 * spec.allocated + 1;
336                                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
337                               }
338                             spec.numbered[spec.numbered_arg_count].number = precision_number;
339                             spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
340                             spec.numbered_arg_count++;
341                           }
342                         else
343                           {
344                             /* Unnumbered argument.  */
345
346                             /* Numbered and unnumbered specifications are
347                                exclusive.  */
348                             if (spec.numbered_arg_count > 0)
349                               {
350                                 *invalid_reason =
351                                   INVALID_MIXES_NUMBERED_UNNUMBERED ();
352                                 FDI_SET (format - 1, FMTDIR_ERROR);
353                                 goto bad_format;
354                               }
355
356                             if (spec.allocated == unnumbered_arg_count)
357                               {
358                                 spec.allocated = 2 * spec.allocated + 1;
359                                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated  * sizeof (struct numbered_arg));
360                               }
361                             spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
362                             spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
363                             unnumbered_arg_count++;
364                           }
365                       }
366                     else if (isdigit (*format))
367                       {
368                         do format++; while (isdigit (*format));
369                       }
370                   }
371
372                 /* Parse size.  */
373                 for (;;)
374                   {
375                     if (*format == 'h' || *format == 'l' || *format == 'L')
376                       format++;
377                     else
378                       break;
379                   }
380
381                 switch (*format++)
382                   {
383                   case 'c': case 'C':
384                     type = FAT_CHAR;
385                     break;
386                   case 's': case 'S':
387                     type = FAT_ANY;
388                     break;
389                   case 'i': case 'd': case 'o': case 'u': case 'x': case 'X':
390                     type = FAT_INTEGER;
391                     break;
392                   case 'e': case 'E': case 'f': case 'g': case 'G':
393                     type = FAT_DOUBLE;
394                     break;
395                   case 'p':
396                     type = FAT_POINTER;
397                     break;
398                   case 't':
399                     type = FAT_NONE;
400                     break;
401                   case 'T':
402                     if (*format == '\0')
403                       {
404                         *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
405                         FDI_SET (format - 1, FMTDIR_ERROR);
406                         goto bad_format;
407                       }
408                     format++;
409                     type = FAT_NONE;
410                     break;
411                   case 'n':
412                     type = FAT_NONE;
413                     break;
414                   case '|':
415                     if (brackets)
416                       {
417                         --format;
418                         type = FAT_ANY;
419                         break;
420                       }
421                     /*FALLTHROUGH*/
422                   default:
423                     --format;
424                     if (*format == '\0')
425                       {
426                         *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
427                         FDI_SET (format - 1, FMTDIR_ERROR);
428                       }
429                     else
430                       {
431                         *invalid_reason =
432                           INVALID_CONVERSION_SPECIFIER (spec.directives,
433                                                         *format);
434                         FDI_SET (format, FMTDIR_ERROR);
435                       }
436                     goto bad_format;
437                   }
438                 if (brackets)
439                   {
440                     if (*format != '|')
441                       {
442                         if (*format == '\0')
443                           {
444                             *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
445                             FDI_SET (format - 1, FMTDIR_ERROR);
446                           }
447                         else
448                           {
449                             *invalid_reason =
450                               xasprintf (_("The directive number %u starts with | but does not end with |."),
451                                          spec.directives);
452                             FDI_SET (format, FMTDIR_ERROR);
453                           }
454                         goto bad_format;
455                       }
456                     format++;
457                   }
458               }
459
460             if (type != FAT_NONE)
461               {
462                 if (number)
463                   {
464                     /* Numbered argument.  */
465
466                     /* Numbered and unnumbered specifications are exclusive.  */
467                     if (unnumbered_arg_count > 0)
468                       {
469                         *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
470                         FDI_SET (format - 1, FMTDIR_ERROR);
471                         goto bad_format;
472                       }
473
474                     if (spec.allocated == spec.numbered_arg_count)
475                       {
476                         spec.allocated = 2 * spec.allocated + 1;
477                         spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
478                       }
479                     spec.numbered[spec.numbered_arg_count].number = number;
480                     spec.numbered[spec.numbered_arg_count].type = type;
481                     spec.numbered_arg_count++;
482                   }
483                 else
484                   {
485                     /* Unnumbered argument.  */
486
487                     /* Numbered and unnumbered specifications are exclusive.  */
488                     if (spec.numbered_arg_count > 0)
489                       {
490                         *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
491                         FDI_SET (format - 1, FMTDIR_ERROR);
492                         goto bad_format;
493                       }
494
495                     if (spec.allocated == unnumbered_arg_count)
496                       {
497                         spec.allocated = 2 * spec.allocated + 1;
498                         spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
499                       }
500                     spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
501                     spec.numbered[unnumbered_arg_count].type = type;
502                     unnumbered_arg_count++;
503                   }
504               }
505           }
506
507         FDI_SET (format - 1, FMTDIR_END);
508       }
509
510   /* Convert the unnumbered argument array to numbered arguments.  */
511   if (unnumbered_arg_count > 0)
512     spec.numbered_arg_count = unnumbered_arg_count;
513   /* Sort the numbered argument array, and eliminate duplicates.  */
514   else if (spec.numbered_arg_count > 1)
515     {
516       unsigned int i, j;
517       bool err;
518
519       qsort (spec.numbered, spec.numbered_arg_count,
520              sizeof (struct numbered_arg), numbered_arg_compare);
521
522       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
523       err = false;
524       for (i = j = 0; i < spec.numbered_arg_count; i++)
525         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
526           {
527             enum format_arg_type type1 = spec.numbered[i].type;
528             enum format_arg_type type2 = spec.numbered[j-1].type;
529             enum format_arg_type type_both;
530
531             if (type1 == type2 || type2 == FAT_ANY)
532               type_both = type1;
533             else if (type1 == FAT_ANY)
534               type_both = type2;
535             else
536               {
537                 /* Incompatible types.  */
538                 type_both = FAT_NONE;
539                 if (!err)
540                   *invalid_reason =
541                     INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
542                 err = true;
543               }
544
545             spec.numbered[j-1].type = type_both;
546           }
547         else
548           {
549             if (j < i)
550               {
551                 spec.numbered[j].number = spec.numbered[i].number;
552                 spec.numbered[j].type = spec.numbered[i].type;
553               }
554             j++;
555           }
556       spec.numbered_arg_count = j;
557       if (err)
558         /* *invalid_reason has already been set above.  */
559         goto bad_format;
560     }
561
562   result = XMALLOC (struct spec);
563   *result = spec;
564   return result;
565
566  bad_format:
567   if (spec.numbered != NULL)
568     free (spec.numbered);
569   return NULL;
570 }
571
572 static void
573 format_free (void *descr)
574 {
575   struct spec *spec = (struct spec *) descr;
576
577   if (spec->numbered != NULL)
578     free (spec->numbered);
579   free (spec);
580 }
581
582 static int
583 format_get_number_of_directives (void *descr)
584 {
585   struct spec *spec = (struct spec *) descr;
586
587   return spec->directives;
588 }
589
590 static bool
591 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
592               formatstring_error_logger_t error_logger,
593               const char *pretty_msgid, const char *pretty_msgstr)
594 {
595   struct spec *spec1 = (struct spec *) msgid_descr;
596   struct spec *spec2 = (struct spec *) msgstr_descr;
597   bool err = false;
598
599   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
600     {
601       unsigned int i, j;
602       unsigned int n1 = spec1->numbered_arg_count;
603       unsigned int n2 = spec2->numbered_arg_count;
604
605       /* Check the argument names are the same.
606          Both arrays are sorted.  We search for the first difference.  */
607       for (i = 0, j = 0; i < n1 || j < n2; )
608         {
609           int cmp = (i >= n1 ? 1 :
610                      j >= n2 ? -1 :
611                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
612                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
613                      0);
614
615           if (cmp > 0)
616             {
617               if (error_logger)
618                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
619                               spec2->numbered[j].number, pretty_msgstr,
620                               pretty_msgid);
621               err = true;
622               break;
623             }
624           else if (cmp < 0)
625             {
626               if (equality)
627                 {
628                   if (error_logger)
629                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
630                                   spec1->numbered[i].number, pretty_msgstr);
631                   err = true;
632                   break;
633                 }
634               else
635                 i++;
636             }
637           else
638             j++, i++;
639         }
640       /* Check the argument types are the same.  */
641       if (!err)
642         for (i = 0, j = 0; j < n2; )
643           {
644             if (spec1->numbered[i].number == spec2->numbered[j].number)
645               {
646                 if (spec1->numbered[i].type != spec2->numbered[j].type)
647                   {
648                     if (error_logger)
649                       error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
650                                     pretty_msgid, pretty_msgstr,
651                                     spec2->numbered[j].number);
652                     err = true;
653                     break;
654                   }
655                 j++, i++;
656               }
657             else
658               i++;
659           }
660     }
661
662   return err;
663 }
664
665
666 struct formatstring_parser formatstring_boost =
667 {
668   format_parse,
669   format_free,
670   format_get_number_of_directives,
671   NULL,
672   format_check
673 };
674
675
676 #ifdef TEST
677
678 /* Test program: Print the argument list specification returned by
679    format_parse for strings read from standard input.  */
680
681 #include <stdio.h>
682
683 static void
684 format_print (void *descr)
685 {
686   struct spec *spec = (struct spec *) descr;
687   unsigned int last;
688   unsigned int i;
689
690   if (spec == NULL)
691     {
692       printf ("INVALID");
693       return;
694     }
695
696   printf ("(");
697   last = 1;
698   for (i = 0; i < spec->numbered_arg_count; i++)
699     {
700       unsigned int number = spec->numbered[i].number;
701
702       if (i > 0)
703         printf (" ");
704       if (number < last)
705         abort ();
706       for (; last < number; last++)
707         printf ("_ ");
708       switch (spec->numbered[i].type)
709         {
710         case FAT_INTEGER:
711           printf ("i");
712           break;
713         case FAT_DOUBLE:
714           printf ("f");
715           break;
716         case FAT_CHAR:
717           printf ("c");
718           break;
719         case FAT_POINTER:
720           printf ("p");
721           break;
722         case FAT_ANY:
723           printf ("*");
724           break;
725         default:
726           abort ();
727         }
728       last = number + 1;
729     }
730   printf (")");
731 }
732
733 int
734 main ()
735 {
736   for (;;)
737     {
738       char *line = NULL;
739       size_t line_size = 0;
740       int line_len;
741       char *invalid_reason;
742       void *descr;
743
744       line_len = getline (&line, &line_size, stdin);
745       if (line_len < 0)
746         break;
747       if (line_len > 0 && line[line_len - 1] == '\n')
748         line[--line_len] = '\0';
749
750       invalid_reason = NULL;
751       descr = format_parse (line, false, NULL, &invalid_reason);
752
753       format_print (descr);
754       printf ("\n");
755       if (descr == NULL)
756         printf ("%s\n", invalid_reason);
757
758       free (invalid_reason);
759       free (line);
760     }
761
762   return 0;
763 }
764
765 /*
766  * For Emacs M-x compile
767  * Local Variables:
768  * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-boost.c ../gnulib-lib/libgettextlib.la"
769  * End:
770  */
771
772 #endif /* TEST */
773