Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / format-python-brace.c
1 /* Python brace format strings.
2    Copyright (C) 2004, 2006-2007, 2013 Free Software Foundation, Inc.
3    Written by Daiki Ueno <ueno@gnu.org>, 2013.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 #include <stdbool.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "format.h"
27 #include "xalloc.h"
28 #include "xvasprintf.h"
29 #include "format-invalid.h"
30 #include "gettext.h"
31
32 #define _(str) gettext (str)
33
34 /* Python brace format strings are defined by PEP3101 together with
35    'format' method of string class.
36    A format string directive here consists of
37      - an opening brace '{',
38      - an identifier [_A-Za-z][_0-9A-Za-z]*|[0-9]+,
39      - an optional getattr ('.') or getitem ('['..']') operator with
40        an identifier as argument,
41      - an optional width specifier starting with ':', with a
42        (unnested) format string as argument,
43      - a closing brace '}'.
44    Brace characters '{' and '}' can be escaped by doubles '{{' and '}}'.
45  */
46
47 struct named_arg
48 {
49   char *name;
50 };
51
52 struct spec
53 {
54   unsigned int directives;
55   unsigned int named_arg_count;
56   unsigned int allocated;
57   struct named_arg *named;
58 };
59
60
61 static bool parse_upto (struct spec *spec, const char **formatp,
62                         bool is_toplevel, char terminator,
63                         bool translated, char *fdi, char **invalid_reason);
64 static void free_named_args (struct spec *spec);
65
66
67 /* All the parse_* functions (except parse_upto) follow the same
68    calling convention.  FORMATP shall point to the beginning of a token.
69    If parsing succeeds, FORMATP will point to the next character after
70    the token, and true is returned.  Otherwise, FORMATP will be
71    unchanged and false is returned.  */
72
73 static bool
74 parse_named_field (struct spec *spec,
75                    const char **formatp, bool translated, char *fdi,
76                    char **invalid_reason)
77 {
78   const char *format = *formatp;
79   char c;
80
81   c = *format;
82   if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
83     {
84       do
85         c = *++format;
86       while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'
87              || (c >= '0' && c <= '9'));
88       *formatp = format;
89       return true;
90     }
91   return false;
92 }
93
94 static bool
95 parse_numeric_field (struct spec *spec,
96                      const char **formatp, bool translated, char *fdi,
97                      char **invalid_reason)
98 {
99   const char *format = *formatp;
100   char c;
101
102   c = *format;
103   if (c >= '0' && c <= '9')
104     {
105       do
106         c = *++format;
107       while (c >= '0' && c <= '9');
108       *formatp = format;
109       return true;
110     }
111   return false;
112 }
113
114 static bool
115 parse_directive (struct spec *spec,
116                  const char **formatp, bool is_toplevel,
117                  bool translated, char *fdi, char **invalid_reason)
118 {
119   const char *format = *formatp;
120   const char *const format_start = format;
121   const char *name_start;
122   char c;
123
124   c = *++format;
125   if (c == '{')
126     {
127       *formatp = ++format;
128       return true;
129     }
130
131   name_start = format;
132   if (!parse_named_field (spec, &format, translated, fdi, invalid_reason)
133       && !parse_numeric_field (spec, &format, translated, fdi, invalid_reason))
134     {
135       *invalid_reason =
136         xasprintf (_("In the directive number %u, '%c' cannot start a field name."), spec->directives, *format);
137       FDI_SET (format, FMTDIR_ERROR);
138       return false;
139     }
140
141   c = *format;
142   if (c == '.')
143     {
144       format++;
145       if (!parse_named_field (spec, &format, translated, fdi,
146                               invalid_reason))
147         {
148           *invalid_reason =
149             xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."), spec->directives, *format);
150           FDI_SET (format, FMTDIR_ERROR);
151           return false;
152         }
153       c = *format;
154     }
155   else if (c == '[')
156     {
157       format++;
158       if (!parse_named_field (spec, &format, translated, fdi,
159                               invalid_reason)
160           && !parse_numeric_field (spec, &format, translated, fdi,
161                                    invalid_reason))
162         {
163           *invalid_reason =
164             xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."), spec->directives, *format);
165           FDI_SET (format, FMTDIR_ERROR);
166           return false;
167         }
168
169       c = *format++;
170       if (c != ']')
171         {
172           *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
173           FDI_SET (format, FMTDIR_ERROR);
174           return false;
175         }
176       c = *format;
177     }
178
179   if (c == ':')
180     {
181       if (!is_toplevel)
182         {
183           *invalid_reason =
184             xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."), spec->directives);
185           FDI_SET (format, FMTDIR_ERROR);
186           return false;
187         }
188
189       format++;
190       if (!parse_upto (spec, &format, false, '}', translated, fdi,
191                        invalid_reason))
192         {
193           /* FDI and INVALID_REASON will be set by a recursive call of
194              parse_directive.  */
195           return false;
196         }
197
198       if (*format == '\0')
199         {
200           *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
201           FDI_SET (format, FMTDIR_ERROR);
202           return false;
203         }
204       c = *format;
205     }
206
207   if (c != '}')
208     {
209       *invalid_reason =
210         xasprintf (_("In the directive number %u, there is an unterminated format directive."), spec->directives);
211       FDI_SET (format, FMTDIR_ERROR);
212       return false;
213     }
214
215   if (is_toplevel)
216     {
217       char *name;
218       size_t n = format - name_start;
219
220       FDI_SET (name_start - 1, FMTDIR_START);
221
222       name = XNMALLOC (n + 1, char);
223       memcpy (name, name_start, n);
224       name[n] = '\0';
225
226       spec->directives++;
227
228       if (spec->allocated == spec->named_arg_count)
229         {
230           spec->allocated = 2 * spec->allocated + 1;
231           spec->named = (struct named_arg *) xrealloc (spec->named, spec->allocated * sizeof (struct named_arg));
232         }
233       spec->named[spec->named_arg_count].name = name;
234       spec->named_arg_count++;
235
236       FDI_SET (format, FMTDIR_END);
237     }
238
239   *formatp = ++format;
240   return true;
241 }
242
243 static bool
244 parse_upto (struct spec *spec,
245             const char **formatp, bool is_toplevel, char terminator,
246             bool translated, char *fdi, char **invalid_reason)
247 {
248   const char *format = *formatp;
249
250   for (; *format != terminator && *format != '\0';)
251     {
252       if (*format == '{')
253         {
254           if (!parse_directive (spec, &format, is_toplevel, translated, fdi,
255                                 invalid_reason))
256             return false;
257         }
258       else
259         format++;
260     }
261
262   *formatp = format;
263   return true;
264 }
265
266 static int
267 named_arg_compare (const void *p1, const void *p2)
268 {
269   return strcmp (((const struct named_arg *) p1)->name,
270                  ((const struct named_arg *) p2)->name);
271 }
272
273 static void *
274 format_parse (const char *format, bool translated, char *fdi,
275               char **invalid_reason)
276 {
277   struct spec spec;
278   struct spec *result;
279
280   spec.directives = 0;
281   spec.named_arg_count = 0;
282   spec.allocated = 0;
283   spec.named = NULL;
284
285   if (!parse_upto (&spec, &format, true, '\0', translated, fdi, invalid_reason))
286     {
287       free_named_args (&spec);
288       return NULL;
289     }
290
291   /* Sort the named argument array, and eliminate duplicates.  */
292   if (spec.named_arg_count > 1)
293     {
294       unsigned int i, j;
295
296       qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
297              named_arg_compare);
298
299       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
300       for (i = j = 0; i < spec.named_arg_count; i++)
301         if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
302           free (spec.named[i].name);
303         else
304           {
305             if (j < i)
306               spec.named[j].name = spec.named[i].name;
307             j++;
308           }
309       spec.named_arg_count = j;
310     }
311
312   result = XMALLOC (struct spec);
313   *result = spec;
314   return result;
315 }
316
317 static void
318 free_named_args (struct spec *spec)
319 {
320   if (spec->named != NULL)
321     {
322       unsigned int i;
323       for (i = 0; i < spec->named_arg_count; i++)
324         free (spec->named[i].name);
325       free (spec->named);
326     }
327 }
328
329 static void
330 format_free (void *descr)
331 {
332   struct spec *spec = (struct spec *) descr;
333
334   free_named_args (spec);
335   free (spec);
336 }
337
338 static int
339 format_get_number_of_directives (void *descr)
340 {
341   struct spec *spec = (struct spec *) descr;
342
343   return spec->directives;
344 }
345
346 static bool
347 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
348               formatstring_error_logger_t error_logger,
349               const char *pretty_msgid, const char *pretty_msgstr)
350 {
351   struct spec *spec1 = (struct spec *) msgid_descr;
352   struct spec *spec2 = (struct spec *) msgstr_descr;
353   bool err = false;
354
355   if (spec1->named_arg_count + spec2->named_arg_count > 0)
356     {
357       unsigned int i, j;
358       unsigned int n1 = spec1->named_arg_count;
359       unsigned int n2 = spec2->named_arg_count;
360
361       /* Check the argument names in spec1 are contained in those of spec2.
362          Both arrays are sorted.  We search for the differences.  */
363       for (i = 0, j = 0; i < n1 || j < n2; )
364         {
365           int cmp = (i >= n1 ? 1 :
366                      j >= n2 ? -1 :
367                      strcmp (spec1->named[i].name, spec2->named[j].name));
368
369           if (cmp > 0)
370             {
371               if (equality)
372                 {
373                   if (error_logger)
374                     error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
375                                   spec2->named[i].name, pretty_msgid);
376                   err = true;
377                   break;
378                 }
379               else
380                 j++;
381             }
382           else if (cmp < 0)
383             {
384               if (equality)
385                 {
386                   if (error_logger)
387                     error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
388                                   spec1->named[i].name, pretty_msgstr);
389                   err = true;
390                   break;
391                 }
392               else
393                 i++;
394             }
395           else
396             j++, i++;
397         }
398     }
399
400   return err;
401 }
402
403
404 struct formatstring_parser formatstring_python_brace =
405 {
406   format_parse,
407   format_free,
408   format_get_number_of_directives,
409   NULL,
410   format_check
411 };
412
413
414 #ifdef TEST
415
416 /* Test program: Print the argument list specification returned by
417    format_parse for strings read from standard input.  */
418
419 #include <stdio.h>
420
421 static void
422 format_print (void *descr)
423 {
424   struct spec *spec = (struct spec *) descr;
425   unsigned int i;
426
427   if (spec == NULL)
428     {
429       printf ("INVALID");
430       return;
431     }
432
433   printf ("{");
434   for (i = 0; i < spec->named_arg_count; i++)
435     {
436       if (i > 0)
437         printf (", ");
438       printf ("'%s'", spec->named[i].name);
439     }
440   printf ("}");
441 }
442
443 int
444 main ()
445 {
446   for (;;)
447     {
448       char *line = NULL;
449       size_t line_size = 0;
450       int line_len;
451       char *invalid_reason;
452       void *descr;
453
454       line_len = getline (&line, &line_size, stdin);
455       if (line_len < 0)
456         break;
457       if (line_len > 0 && line[line_len - 1] == '\n')
458         line[--line_len] = '\0';
459
460       invalid_reason = NULL;
461       descr = format_parse (line, false, NULL, &invalid_reason);
462
463       format_print (descr);
464       printf ("\n");
465       if (descr == NULL)
466         printf ("%s\n", invalid_reason);
467
468       free (invalid_reason);
469       free (line);
470     }
471
472   return 0;
473 }
474
475 /*
476  * For Emacs M-x compile
477  * Local Variables:
478  * 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-python-brace.c ../gnulib-lib/libgettextlib.la"
479  * End:
480  */
481
482 #endif /* TEST */