Add -lm dependency for gettextlib to fix LTO build
[platform/upstream/gettext.git] / gettext-tools / src / format-tcl.c
1 /* Tcl 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>, 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 /* Tcl format strings are described in the tcl8.3.3/doc/format.n manual
36    page and implemented in the function Tcl_FormatObjCmd in
37    tcl8.3.3/generic/tclCmdAH.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', '-', ' ', '+',
41      each of which acts as a flag,
42    - is optionally followed by a width specification: '*' (reads an argument)
43      or a nonempty digit sequence,
44    - is optionally followed by '.' and a precision specification: '*' (reads
45      an argument) or a nonempty digit sequence,
46    - is optionally followed by a size specifier, 'h' or 'l'. 'l' is ignored.
47    - is finished by a specifier
48        - '%', that needs no argument,
49        - 'c', that needs a character argument,
50        - 's', that needs a string argument,
51        - 'i', 'd', that need a signed integer argument,
52        - 'o', 'u', 'x', 'X', that need an unsigned integer argument,
53        - 'e', 'E', 'f', 'g', 'G', that need a floating-point argument.
54    Numbered ('%m$') and unnumbered argument specifications cannot be used
55    in the same string.
56  */
57
58 enum format_arg_type
59 {
60   FAT_NONE,
61   FAT_CHARACTER,
62   FAT_STRING,
63   FAT_INTEGER,
64   FAT_UNSIGNED_INTEGER,
65   FAT_SHORT_INTEGER,
66   FAT_SHORT_UNSIGNED_INTEGER,
67   FAT_FLOAT
68 };
69
70 struct numbered_arg
71 {
72   unsigned int number;
73   enum format_arg_type type;
74 };
75
76 struct spec
77 {
78   unsigned int directives;
79   unsigned int numbered_arg_count;
80   unsigned int allocated;
81   struct numbered_arg *numbered;
82 };
83
84 /* Locale independent test for a decimal digit.
85    Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
86    <ctype.h> isdigit must be an 'unsigned char'.)  */
87 #undef isdigit
88 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
89
90
91 static int
92 numbered_arg_compare (const void *p1, const void *p2)
93 {
94   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
95   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
96
97   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
98 }
99
100 static void *
101 format_parse (const char *format, bool translated, char *fdi,
102               char **invalid_reason)
103 {
104   const char *const format_start = format;
105   struct spec spec;
106   struct spec *result;
107   bool seen_numbered_arg;
108   bool seen_unnumbered_arg;
109   unsigned int number;
110
111   spec.directives = 0;
112   spec.numbered_arg_count = 0;
113   spec.allocated = 0;
114   spec.numbered = NULL;
115   seen_numbered_arg = false;
116   seen_unnumbered_arg = false;
117   number = 1;
118
119   for (; *format != '\0';)
120     if (*format++ == '%')
121       {
122         /* A directive.  */
123         FDI_SET (format - 1, FMTDIR_START);
124         spec.directives++;
125
126         if (*format != '%')
127           {
128             bool is_numbered_arg;
129             bool short_flag;
130             enum format_arg_type type;
131
132             is_numbered_arg = false;
133             if (isdigit (*format))
134               {
135                 const char *f = format;
136                 unsigned int m = 0;
137
138                 do
139                   {
140                     m = 10 * m + (*f - '0');
141                     f++;
142                   }
143                 while (isdigit (*f));
144
145                 if (*f == '$')
146                   {
147                     if (m == 0)
148                       {
149                         *invalid_reason = INVALID_ARGNO_0 (spec.directives);
150                         FDI_SET (f, FMTDIR_ERROR);
151                         goto bad_format;
152                       }
153                     number = m;
154                     format = ++f;
155
156                     /* Numbered and unnumbered specifications are exclusive.  */
157                     if (seen_unnumbered_arg)
158                       {
159                         *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
160                         FDI_SET (format - 1, FMTDIR_ERROR);
161                         goto bad_format;
162                       }
163                     is_numbered_arg = true;
164                     seen_numbered_arg = true;
165                   }
166               }
167
168             /* Numbered and unnumbered specifications are exclusive.  */
169             if (!is_numbered_arg)
170               {
171                 if (seen_numbered_arg)
172                   {
173                     *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
174                     FDI_SET (format - 1, FMTDIR_ERROR);
175                     goto bad_format;
176                   }
177                 seen_unnumbered_arg = true;
178               }
179
180             /* Parse flags.  */
181             while (*format == ' ' || *format == '+' || *format == '-'
182                    || *format == '#' || *format == '0')
183               format++;
184
185             /* Parse width.  */
186             if (*format == '*')
187               {
188                 format++;
189
190                 if (spec.allocated == spec.numbered_arg_count)
191                   {
192                     spec.allocated = 2 * spec.allocated + 1;
193                     spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
194                   }
195                 spec.numbered[spec.numbered_arg_count].number = number;
196                 spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
197                 spec.numbered_arg_count++;
198
199                 number++;
200               }
201             else if (isdigit (*format))
202               {
203                 do format++; while (isdigit (*format));
204               }
205
206             /* Parse precision.  */
207             if (*format == '.')
208               {
209                 format++;
210
211                 if (*format == '*')
212                   {
213                     format++;
214
215                     if (spec.allocated == spec.numbered_arg_count)
216                       {
217                         spec.allocated = 2 * spec.allocated + 1;
218                         spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
219                       }
220                     spec.numbered[spec.numbered_arg_count].number = number;
221                     spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
222                     spec.numbered_arg_count++;
223
224                     number++;
225                   }
226                 else if (isdigit (*format))
227                   {
228                     do format++; while (isdigit (*format));
229                   }
230               }
231
232             /* Parse optional size specification.  */
233             short_flag = false;
234             if (*format == 'h')
235               short_flag = true, format++;
236             else if (*format == 'l')
237               format++;
238
239             switch (*format)
240               {
241               case 'c':
242                 type = FAT_CHARACTER;
243                 break;
244               case 's':
245                 type = FAT_STRING;
246                 break;
247               case 'i': case 'd':
248                 type = (short_flag ? FAT_SHORT_INTEGER : FAT_INTEGER);
249                 break;
250               case 'u': case 'o': case 'x': case 'X':
251                 type = (short_flag ? FAT_SHORT_UNSIGNED_INTEGER : FAT_UNSIGNED_INTEGER);
252                 break;
253               case 'e': case 'E': case 'f': case 'g': case 'G':
254                 type = FAT_FLOAT;
255                 break;
256               default:
257                 if (*format == '\0')
258                   {
259                     *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
260                     FDI_SET (format - 1, FMTDIR_ERROR);
261                   }
262                 else
263                   {
264                     *invalid_reason =
265                       INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
266                     FDI_SET (format, FMTDIR_ERROR);
267                   }
268                 goto bad_format;
269               }
270
271             if (spec.allocated == spec.numbered_arg_count)
272               {
273                 spec.allocated = 2 * spec.allocated + 1;
274                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.allocated * sizeof (struct numbered_arg));
275               }
276             spec.numbered[spec.numbered_arg_count].number = number;
277             spec.numbered[spec.numbered_arg_count].type = type;
278             spec.numbered_arg_count++;
279
280             number++;
281           }
282
283         FDI_SET (format, FMTDIR_END);
284
285         format++;
286       }
287
288   /* Sort the numbered argument array, and eliminate duplicates.  */
289   if (spec.numbered_arg_count > 1)
290     {
291       unsigned int i, j;
292       bool err;
293
294       qsort (spec.numbered, spec.numbered_arg_count,
295              sizeof (struct numbered_arg), numbered_arg_compare);
296
297       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
298       err = false;
299       for (i = j = 0; i < spec.numbered_arg_count; i++)
300         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
301           {
302             enum format_arg_type type1 = spec.numbered[i].type;
303             enum format_arg_type type2 = spec.numbered[j-1].type;
304             enum format_arg_type type_both;
305
306             if (type1 == type2)
307               type_both = type1;
308             else
309               {
310                 /* Incompatible types.  */
311                 type_both = FAT_NONE;
312                 if (!err)
313                   *invalid_reason =
314                     INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
315                 err = true;
316               }
317
318             spec.numbered[j-1].type = type_both;
319           }
320         else
321           {
322             if (j < i)
323               {
324                 spec.numbered[j].number = spec.numbered[i].number;
325                 spec.numbered[j].type = spec.numbered[i].type;
326               }
327             j++;
328           }
329       spec.numbered_arg_count = j;
330       if (err)
331         /* *invalid_reason has already been set above.  */
332         goto bad_format;
333     }
334
335   result = XMALLOC (struct spec);
336   *result = spec;
337   return result;
338
339  bad_format:
340   if (spec.numbered != NULL)
341     free (spec.numbered);
342   return NULL;
343 }
344
345 static void
346 format_free (void *descr)
347 {
348   struct spec *spec = (struct spec *) descr;
349
350   if (spec->numbered != NULL)
351     free (spec->numbered);
352   free (spec);
353 }
354
355 static int
356 format_get_number_of_directives (void *descr)
357 {
358   struct spec *spec = (struct spec *) descr;
359
360   return spec->directives;
361 }
362
363 static bool
364 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
365               formatstring_error_logger_t error_logger,
366               const char *pretty_msgid, const char *pretty_msgstr)
367 {
368   struct spec *spec1 = (struct spec *) msgid_descr;
369   struct spec *spec2 = (struct spec *) msgstr_descr;
370   bool err = false;
371
372   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
373     {
374       unsigned int i, j;
375       unsigned int n1 = spec1->numbered_arg_count;
376       unsigned int n2 = spec2->numbered_arg_count;
377
378       /* Check the argument names are the same.
379          Both arrays are sorted.  We search for the first difference.  */
380       for (i = 0, j = 0; i < n1 || j < n2; )
381         {
382           int cmp = (i >= n1 ? 1 :
383                      j >= n2 ? -1 :
384                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
385                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
386                      0);
387
388           if (cmp > 0)
389             {
390               if (error_logger)
391                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
392                               spec2->numbered[j].number, pretty_msgstr,
393                               pretty_msgid);
394               err = true;
395               break;
396             }
397           else if (cmp < 0)
398             {
399               if (equality)
400                 {
401                   if (error_logger)
402                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
403                                   spec1->numbered[i].number, pretty_msgstr);
404                   err = true;
405                   break;
406                 }
407               else
408                 i++;
409             }
410           else
411             j++, i++;
412         }
413       /* Check the argument types are the same.  */
414       if (!err)
415         for (i = 0, j = 0; j < n2; )
416           {
417             if (spec1->numbered[i].number == spec2->numbered[j].number)
418               {
419                 if (spec1->numbered[i].type != spec2->numbered[j].type)
420                   {
421                     if (error_logger)
422                       error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
423                                     pretty_msgid, pretty_msgstr,
424                                     spec2->numbered[j].number);
425                     err = true;
426                     break;
427                   }
428                 j++, i++;
429               }
430             else
431               i++;
432           }
433     }
434
435   return err;
436 }
437
438
439 struct formatstring_parser formatstring_tcl =
440 {
441   format_parse,
442   format_free,
443   format_get_number_of_directives,
444   NULL,
445   format_check
446 };
447
448
449 #ifdef TEST
450
451 /* Test program: Print the argument list specification returned by
452    format_parse for strings read from standard input.  */
453
454 #include <stdio.h>
455
456 static void
457 format_print (void *descr)
458 {
459   struct spec *spec = (struct spec *) descr;
460   unsigned int last;
461   unsigned int i;
462
463   if (spec == NULL)
464     {
465       printf ("INVALID");
466       return;
467     }
468
469   printf ("(");
470   last = 1;
471   for (i = 0; i < spec->numbered_arg_count; i++)
472     {
473       unsigned int number = spec->numbered[i].number;
474
475       if (i > 0)
476         printf (" ");
477       if (number < last)
478         abort ();
479       for (; last < number; last++)
480         printf ("_ ");
481       switch (spec->numbered[i].type)
482         {
483         case FAT_CHARACTER:
484           printf ("c");
485           break;
486         case FAT_STRING:
487           printf ("s");
488           break;
489         case FAT_INTEGER:
490           printf ("i");
491           break;
492         case FAT_UNSIGNED_INTEGER:
493           printf ("[unsigned]i");
494           break;
495         case FAT_SHORT_INTEGER:
496           printf ("hi");
497           break;
498         case FAT_SHORT_UNSIGNED_INTEGER:
499           printf ("[unsigned]hi");
500           break;
501         case FAT_FLOAT:
502           printf ("f");
503           break;
504         default:
505           abort ();
506         }
507       last = number + 1;
508     }
509   printf (")");
510 }
511
512 int
513 main ()
514 {
515   for (;;)
516     {
517       char *line = NULL;
518       size_t line_size = 0;
519       int line_len;
520       char *invalid_reason;
521       void *descr;
522
523       line_len = getline (&line, &line_size, stdin);
524       if (line_len < 0)
525         break;
526       if (line_len > 0 && line[line_len - 1] == '\n')
527         line[--line_len] = '\0';
528
529       invalid_reason = NULL;
530       descr = format_parse (line, false, NULL, &invalid_reason);
531
532       format_print (descr);
533       printf ("\n");
534       if (descr == NULL)
535         printf ("%s\n", invalid_reason);
536
537       free (invalid_reason);
538       free (line);
539     }
540
541   return 0;
542 }
543
544 /*
545  * For Emacs M-x compile
546  * Local Variables:
547  * 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-tcl.c ../gnulib-lib/libgettextlib.la"
548  * End:
549  */
550
551 #endif /* TEST */