Add -lm dependency for gettextlib to fix LTO build
[platform/upstream/gettext.git] / gettext-tools / src / format-php.c
1 /* PHP format strings.
2    Copyright (C) 2001-2004, 2006-2007, 2009, 2015 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <bruno@clisp.org>, 2002.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <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 /* PHP format strings are described in phpdoc-4.0.6, file
36    phpdoc/manual/function.sprintf.html, and are implemented in
37    php-4.1.0/ext/standard/formatted_print.c.
38    A directive
39    - starts with '%' or '%m$' where m is a positive integer,
40    - is optionally followed by any of the characters '0', '-', ' ', or
41      "'<anychar>", each of which acts as a flag,
42    - is optionally followed by a width specification: a nonempty digit
43      sequence,
44    - is optionally followed by '.' and a precision specification: a nonempty
45      digit sequence,
46    - is optionally followed by a size specifier 'l', which is ignored,
47    - is finished by a specifier
48        - 's', that needs a string argument,
49        - 'b', 'd', 'u', 'o', 'x', 'X', that need an integer argument,
50        - 'e', 'f', that need a floating-point argument,
51        - 'c', that needs a character argument.
52    Additionally there is the directive '%%', which takes no argument.
53    Numbered and unnumbered argument specifications can be used in the same
54    string.  Numbered argument specifications have no influence on the
55    "current argument index", that is incremented each time an argument is read.
56  */
57
58 enum format_arg_type
59 {
60   FAT_INTEGER,
61   FAT_FLOAT,
62   FAT_CHARACTER,
63   FAT_STRING
64 };
65
66 struct numbered_arg
67 {
68   unsigned int number;
69   enum format_arg_type type;
70 };
71
72 struct spec
73 {
74   unsigned int directives;
75   unsigned int numbered_arg_count;
76   unsigned int allocated;
77   struct numbered_arg *numbered;
78 };
79
80 /* Locale independent test for a decimal digit.
81    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
82    <ctype.h> isdigit must be an 'unsigned char'.)  */
83 #undef isdigit
84 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
85
86
87 static int
88 numbered_arg_compare (const void *p1, const void *p2)
89 {
90   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
91   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
92
93   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
94 }
95
96 static void *
97 format_parse (const char *format, bool translated, char *fdi,
98               char **invalid_reason)
99 {
100   const char *const format_start = format;
101   unsigned int directives;
102   unsigned int numbered_arg_count;
103   unsigned int allocated;
104   struct numbered_arg *numbered;
105   unsigned int unnumbered_arg_count;
106   struct spec *result;
107
108   directives = 0;
109   numbered_arg_count = 0;
110   allocated = 0;
111   numbered = NULL;
112   unnumbered_arg_count = 0;
113
114   for (; *format != '\0';)
115     if (*format++ == '%')
116       {
117         /* A directive.  */
118         FDI_SET (format - 1, FMTDIR_START);
119         directives++;
120
121         if (*format != '%')
122           {
123             /* A complex directive.  */
124             unsigned int number;
125             enum format_arg_type type;
126
127             number = ++unnumbered_arg_count;
128             if (isdigit (*format))
129               {
130                 const char *f = format;
131                 unsigned int m = 0;
132
133                 do
134                   {
135                     m = 10 * m + (*f - '0');
136                     f++;
137                   }
138                 while (isdigit (*f));
139
140                 if (*f == '$')
141                   {
142                     if (m == 0)
143                       {
144                         *invalid_reason = INVALID_ARGNO_0 (directives);
145                         FDI_SET (f, FMTDIR_ERROR);
146                         goto bad_format;
147                       }
148                     number = m;
149                     format = ++f;
150                     --unnumbered_arg_count;
151                   }
152               }
153
154             /* Parse flags.  */
155             for (;;)
156               {
157                 if (*format == '0' || *format == '-' || *format == ' ')
158                   format++;
159                 else if (*format == '\'')
160                   {
161                     format++;
162                     if (*format == '\0')
163                       {
164                         *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
165                         FDI_SET (format - 1, FMTDIR_ERROR);
166                         goto bad_format;
167                       }
168                     format++;
169                   }
170                 else
171                   break;
172               }
173
174             /* Parse width.  */
175             if (isdigit (*format))
176               {
177                 do
178                   format++;
179                 while (isdigit (*format));
180               }
181
182             /* Parse precision.  */
183             if (*format == '.')
184               {
185                 format++;
186
187                 if (isdigit (*format))
188                   {
189                     do
190                       format++;
191                     while (isdigit (*format));
192                   }
193                 else
194                   --format;     /* will jump to bad_format */
195               }
196
197             /* Parse size.  */
198             if (*format == 'l')
199               format++;
200
201             switch (*format)
202               {
203               case 'b': case 'd': case 'u': case 'o': case 'x': case 'X':
204                 type = FAT_INTEGER;
205                 break;
206               case 'e': case 'f':
207                 type = FAT_FLOAT;
208                 break;
209               case 'c':
210                 type = FAT_CHARACTER;
211                 break;
212               case 's':
213                 type = FAT_STRING;
214                 break;
215               default:
216                 if (*format == '\0')
217                   {
218                     *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
219                     FDI_SET (format - 1, FMTDIR_ERROR);
220                   }
221                 else
222                   {
223                     *invalid_reason =
224                       INVALID_CONVERSION_SPECIFIER (directives, *format);
225                     FDI_SET (format, FMTDIR_ERROR);
226                   }
227                 goto bad_format;
228               }
229
230             if (allocated == numbered_arg_count)
231               {
232                 allocated = 2 * allocated + 1;
233                 numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg));
234               }
235             numbered[numbered_arg_count].number = number;
236             numbered[numbered_arg_count].type = type;
237             numbered_arg_count++;
238           }
239
240         FDI_SET (format, FMTDIR_END);
241
242         format++;
243       }
244
245   /* Sort the numbered argument array, and eliminate duplicates.  */
246   if (numbered_arg_count > 1)
247     {
248       unsigned int i, j;
249       bool err;
250
251       qsort (numbered, numbered_arg_count,
252              sizeof (struct numbered_arg), numbered_arg_compare);
253
254       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
255       err = false;
256       for (i = j = 0; i < numbered_arg_count; i++)
257         if (j > 0 && numbered[i].number == numbered[j-1].number)
258           {
259             enum format_arg_type type1 = numbered[i].type;
260             enum format_arg_type type2 = numbered[j-1].type;
261             enum format_arg_type type_both;
262
263             if (type1 == type2)
264               type_both = type1;
265             else
266               {
267                 /* Incompatible types.  */
268                 type_both = type1;
269                 if (!err)
270                   *invalid_reason =
271                     INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number);
272                 err = true;
273               }
274
275             numbered[j-1].type = type_both;
276           }
277         else
278           {
279             if (j < i)
280               {
281                 numbered[j].number = numbered[i].number;
282                 numbered[j].type = numbered[i].type;
283               }
284             j++;
285           }
286       numbered_arg_count = j;
287       if (err)
288         /* *invalid_reason has already been set above.  */
289         goto bad_format;
290     }
291
292   result = XMALLOC (struct spec);
293   result->directives = directives;
294   result->numbered_arg_count = numbered_arg_count;
295   result->allocated = allocated;
296   result->numbered = numbered;
297   return result;
298
299  bad_format:
300   if (numbered != NULL)
301     free (numbered);
302   return NULL;
303 }
304
305 static void
306 format_free (void *descr)
307 {
308   struct spec *spec = (struct spec *) descr;
309
310   if (spec->numbered != NULL)
311     free (spec->numbered);
312   free (spec);
313 }
314
315 static int
316 format_get_number_of_directives (void *descr)
317 {
318   struct spec *spec = (struct spec *) descr;
319
320   return spec->directives;
321 }
322
323 static bool
324 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
325               formatstring_error_logger_t error_logger,
326               const char *pretty_msgid, const char *pretty_msgstr)
327 {
328   struct spec *spec1 = (struct spec *) msgid_descr;
329   struct spec *spec2 = (struct spec *) msgstr_descr;
330   bool err = false;
331
332   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
333     {
334       unsigned int i, j;
335       unsigned int n1 = spec1->numbered_arg_count;
336       unsigned int n2 = spec2->numbered_arg_count;
337
338       /* Check the argument names are the same.
339          Both arrays are sorted.  We search for the first difference.  */
340       for (i = 0, j = 0; i < n1 || j < n2; )
341         {
342           int cmp = (i >= n1 ? 1 :
343                      j >= n2 ? -1 :
344                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
345                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
346                      0);
347
348           if (cmp > 0)
349             {
350               if (error_logger)
351                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
352                               spec2->numbered[j].number, pretty_msgstr,
353                               pretty_msgid);
354               err = true;
355               break;
356             }
357           else if (cmp < 0)
358             {
359               if (equality)
360                 {
361                   if (error_logger)
362                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
363                                   spec1->numbered[i].number, pretty_msgstr);
364                   err = true;
365                   break;
366                 }
367               else
368                 i++;
369             }
370           else
371             j++, i++;
372         }
373       /* Check the argument types are the same.  */
374       if (!err)
375         for (i = 0, j = 0; j < n2; )
376           {
377             if (spec1->numbered[i].number == spec2->numbered[j].number)
378               {
379                 if (spec1->numbered[i].type != spec2->numbered[j].type)
380                   {
381                     if (error_logger)
382                       error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
383                                     pretty_msgid, pretty_msgstr,
384                                     spec2->numbered[j].number);
385                     err = true;
386                     break;
387                   }
388                 j++, i++;
389               }
390             else
391               i++;
392           }
393     }
394
395   return err;
396 }
397
398
399 struct formatstring_parser formatstring_php =
400 {
401   format_parse,
402   format_free,
403   format_get_number_of_directives,
404   NULL,
405   format_check
406 };
407
408
409 #ifdef TEST
410
411 /* Test program: Print the argument list specification returned by
412    format_parse for strings read from standard input.  */
413
414 #include <stdio.h>
415
416 static void
417 format_print (void *descr)
418 {
419   struct spec *spec = (struct spec *) descr;
420   unsigned int last;
421   unsigned int i;
422
423   if (spec == NULL)
424     {
425       printf ("INVALID");
426       return;
427     }
428
429   printf ("(");
430   last = 1;
431   for (i = 0; i < spec->numbered_arg_count; i++)
432     {
433       unsigned int number = spec->numbered[i].number;
434
435       if (i > 0)
436         printf (" ");
437       if (number < last)
438         abort ();
439       for (; last < number; last++)
440         printf ("_ ");
441       switch (spec->numbered[i].type)
442         {
443         case FAT_INTEGER:
444           printf ("i");
445           break;
446         case FAT_FLOAT:
447           printf ("f");
448           break;
449         case FAT_CHARACTER:
450           printf ("c");
451           break;
452         case FAT_STRING:
453           printf ("s");
454           break;
455         default:
456           abort ();
457         }
458       last = number + 1;
459     }
460   printf (")");
461 }
462
463 int
464 main ()
465 {
466   for (;;)
467     {
468       char *line = NULL;
469       size_t line_size = 0;
470       int line_len;
471       char *invalid_reason;
472       void *descr;
473
474       line_len = getline (&line, &line_size, stdin);
475       if (line_len < 0)
476         break;
477       if (line_len > 0 && line[line_len - 1] == '\n')
478         line[--line_len] = '\0';
479
480       invalid_reason = NULL;
481       descr = format_parse (line, false, NULL, &invalid_reason);
482
483       format_print (descr);
484       printf ("\n");
485       if (descr == NULL)
486         printf ("%s\n", invalid_reason);
487
488       free (invalid_reason);
489       free (line);
490     }
491
492   return 0;
493 }
494
495 /*
496  * For Emacs M-x compile
497  * Local Variables:
498  * 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-php.c ../gnulib-lib/libgettextlib.la"
499  * End:
500  */
501
502 #endif /* TEST */