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