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.
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.
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.
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/>. */
28 #include "xvasprintf.h"
29 #include "format-invalid.h"
32 #define _(str) gettext (str)
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 '}}'.
54 unsigned int directives;
55 unsigned int named_arg_count;
56 unsigned int allocated;
57 struct named_arg *named;
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);
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. */
74 parse_named_field (struct spec *spec,
75 const char **formatp, bool translated, char *fdi,
76 char **invalid_reason)
78 const char *format = *formatp;
82 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
86 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'
87 || (c >= '0' && c <= '9'));
95 parse_numeric_field (struct spec *spec,
96 const char **formatp, bool translated, char *fdi,
97 char **invalid_reason)
99 const char *format = *formatp;
103 if (c >= '0' && c <= '9')
107 while (c >= '0' && c <= '9');
115 parse_directive (struct spec *spec,
116 const char **formatp, bool is_toplevel,
117 bool translated, char *fdi, char **invalid_reason)
119 const char *format = *formatp;
120 const char *const format_start = format;
121 const char *name_start;
132 if (!parse_named_field (spec, &format, translated, fdi, invalid_reason)
133 && !parse_numeric_field (spec, &format, translated, fdi, invalid_reason))
136 xasprintf (_("In the directive number %u, '%c' cannot start a field name."), spec->directives, *format);
137 FDI_SET (format, FMTDIR_ERROR);
145 if (!parse_named_field (spec, &format, translated, fdi,
149 xasprintf (_("In the directive number %u, '%c' cannot start a getattr argument."), spec->directives, *format);
150 FDI_SET (format, FMTDIR_ERROR);
158 if (!parse_named_field (spec, &format, translated, fdi,
160 && !parse_numeric_field (spec, &format, translated, fdi,
164 xasprintf (_("In the directive number %u, '%c' cannot start a getitem argument."), spec->directives, *format);
165 FDI_SET (format, FMTDIR_ERROR);
172 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
173 FDI_SET (format, FMTDIR_ERROR);
184 xasprintf (_("In the directive number %u, no more nesting is allowed in a format specifier."), spec->directives);
185 FDI_SET (format, FMTDIR_ERROR);
190 if (!parse_upto (spec, &format, false, '}', translated, fdi,
193 /* FDI and INVALID_REASON will be set by a recursive call of
200 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
201 FDI_SET (format, FMTDIR_ERROR);
210 xasprintf (_("In the directive number %u, there is an unterminated format directive."), spec->directives);
211 FDI_SET (format, FMTDIR_ERROR);
218 size_t n = format - name_start;
220 FDI_SET (name_start - 1, FMTDIR_START);
222 name = XNMALLOC (n + 1, char);
223 memcpy (name, name_start, n);
228 if (spec->allocated == spec->named_arg_count)
230 spec->allocated = 2 * spec->allocated + 1;
231 spec->named = (struct named_arg *) xrealloc (spec->named, spec->allocated * sizeof (struct named_arg));
233 spec->named[spec->named_arg_count].name = name;
234 spec->named_arg_count++;
236 FDI_SET (format, FMTDIR_END);
244 parse_upto (struct spec *spec,
245 const char **formatp, bool is_toplevel, char terminator,
246 bool translated, char *fdi, char **invalid_reason)
248 const char *format = *formatp;
250 for (; *format != terminator && *format != '\0';)
254 if (!parse_directive (spec, &format, is_toplevel, translated, fdi,
267 named_arg_compare (const void *p1, const void *p2)
269 return strcmp (((const struct named_arg *) p1)->name,
270 ((const struct named_arg *) p2)->name);
274 format_parse (const char *format, bool translated, char *fdi,
275 char **invalid_reason)
281 spec.named_arg_count = 0;
285 if (!parse_upto (&spec, &format, true, '\0', translated, fdi, invalid_reason))
287 free_named_args (&spec);
291 /* Sort the named argument array, and eliminate duplicates. */
292 if (spec.named_arg_count > 1)
296 qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
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);
306 spec.named[j].name = spec.named[i].name;
309 spec.named_arg_count = j;
312 result = XMALLOC (struct spec);
318 free_named_args (struct spec *spec)
320 if (spec->named != NULL)
323 for (i = 0; i < spec->named_arg_count; i++)
324 free (spec->named[i].name);
330 format_free (void *descr)
332 struct spec *spec = (struct spec *) descr;
334 free_named_args (spec);
339 format_get_number_of_directives (void *descr)
341 struct spec *spec = (struct spec *) descr;
343 return spec->directives;
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)
351 struct spec *spec1 = (struct spec *) msgid_descr;
352 struct spec *spec2 = (struct spec *) msgstr_descr;
355 if (spec1->named_arg_count + spec2->named_arg_count > 0)
358 unsigned int n1 = spec1->named_arg_count;
359 unsigned int n2 = spec2->named_arg_count;
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; )
365 int cmp = (i >= n1 ? 1 :
367 strcmp (spec1->named[i].name, spec2->named[j].name));
374 error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
375 spec2->named[i].name, pretty_msgid);
387 error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
388 spec1->named[i].name, pretty_msgstr);
404 struct formatstring_parser formatstring_python_brace =
408 format_get_number_of_directives,
416 /* Test program: Print the argument list specification returned by
417 format_parse for strings read from standard input. */
422 format_print (void *descr)
424 struct spec *spec = (struct spec *) descr;
434 for (i = 0; i < spec->named_arg_count; i++)
438 printf ("'%s'", spec->named[i].name);
449 size_t line_size = 0;
451 char *invalid_reason;
454 line_len = getline (&line, &line_size, stdin);
457 if (line_len > 0 && line[line_len - 1] == '\n')
458 line[--line_len] = '\0';
460 invalid_reason = NULL;
461 descr = format_parse (line, false, NULL, &invalid_reason);
463 format_print (descr);
466 printf ("%s\n", invalid_reason);
468 free (invalid_reason);
476 * For Emacs M-x compile
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"