2 Copyright (C) 2001-2004, 2006-2007, 2009 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2002.
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 /* PHP format strings are described in phpdoc-4.0.6, file
35 phpdoc/manual/function.sprintf.html, and are implemented in
36 php-4.1.0/ext/standard/formatted_print.c.
38 - starts with '%' or '%m$' where m is a positive integer,
39 - is optionally followed by any of the characters '0', '-', ' ', or
40 "'<anychar>", each of which acts as a flag,
41 - is optionally followed by a width specification: a nonempty digit
43 - is optionally followed by '.' and a precision specification: a nonempty
45 - is optionally followed by a size specifier 'l', which is ignored,
46 - is finished by a specifier
47 - 's', that needs a string argument,
48 - 'b', 'd', 'u', 'o', 'x', 'X', that need an integer argument,
49 - 'e', 'f', that need a floating-point argument,
50 - 'c', that needs a character argument.
51 Additionally there is the directive '%%', which takes no argument.
52 Numbered and unnumbered argument specifications can be used in the same
53 string. Numbered argument specifications have no influence on the
54 "current argument index", that is incremented each time an argument is read.
68 enum format_arg_type type;
73 unsigned int directives;
74 unsigned int numbered_arg_count;
75 unsigned int allocated;
76 struct numbered_arg *numbered;
79 /* Locale independent test for a decimal digit.
80 Argument can be 'char' or 'unsigned char'. (Whereas the argument of
81 <ctype.h> isdigit must be an 'unsigned char'.) */
83 #define isdigit(c) ((unsigned int) ((c) - '0') < 10)
87 numbered_arg_compare (const void *p1, const void *p2)
89 unsigned int n1 = ((const struct numbered_arg *) p1)->number;
90 unsigned int n2 = ((const struct numbered_arg *) p2)->number;
92 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
96 format_parse (const char *format, bool translated, char *fdi,
97 char **invalid_reason)
99 const char *const format_start = format;
100 unsigned int directives;
101 unsigned int numbered_arg_count;
102 unsigned int allocated;
103 struct numbered_arg *numbered;
104 unsigned int unnumbered_arg_count;
108 numbered_arg_count = 0;
111 unnumbered_arg_count = 0;
113 for (; *format != '\0';)
114 if (*format++ == '%')
117 FDI_SET (format - 1, FMTDIR_START);
122 /* A complex directive. */
124 enum format_arg_type type;
126 number = ++unnumbered_arg_count;
127 if (isdigit (*format))
129 const char *f = format;
134 m = 10 * m + (*f - '0');
137 while (isdigit (*f));
143 *invalid_reason = INVALID_ARGNO_0 (directives);
144 FDI_SET (f, FMTDIR_ERROR);
149 --unnumbered_arg_count;
156 if (*format == '0' || *format == '-' || *format == ' ')
158 else if (*format == '\'')
163 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
164 FDI_SET (format - 1, FMTDIR_ERROR);
174 if (isdigit (*format))
178 while (isdigit (*format));
181 /* Parse precision. */
186 if (isdigit (*format))
190 while (isdigit (*format));
193 --format; /* will jump to bad_format */
202 case 'b': case 'd': case 'u': case 'o': case 'x': case 'X':
209 type = FAT_CHARACTER;
217 *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
218 FDI_SET (format - 1, FMTDIR_ERROR);
223 INVALID_CONVERSION_SPECIFIER (directives, *format);
224 FDI_SET (format, FMTDIR_ERROR);
229 if (allocated == numbered_arg_count)
231 allocated = 2 * allocated + 1;
232 numbered = (struct numbered_arg *) xrealloc (numbered, allocated * sizeof (struct numbered_arg));
234 numbered[numbered_arg_count].number = number;
235 numbered[numbered_arg_count].type = type;
236 numbered_arg_count++;
239 FDI_SET (format, FMTDIR_END);
244 /* Sort the numbered argument array, and eliminate duplicates. */
245 if (numbered_arg_count > 1)
250 qsort (numbered, numbered_arg_count,
251 sizeof (struct numbered_arg), numbered_arg_compare);
253 /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
255 for (i = j = 0; i < numbered_arg_count; i++)
256 if (j > 0 && numbered[i].number == numbered[j-1].number)
258 enum format_arg_type type1 = numbered[i].type;
259 enum format_arg_type type2 = numbered[j-1].type;
260 enum format_arg_type type_both;
266 /* Incompatible types. */
270 INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number);
274 numbered[j-1].type = type_both;
280 numbered[j].number = numbered[i].number;
281 numbered[j].type = numbered[i].type;
285 numbered_arg_count = j;
287 /* *invalid_reason has already been set above. */
291 result = XMALLOC (struct spec);
292 result->directives = directives;
293 result->numbered_arg_count = numbered_arg_count;
294 result->allocated = allocated;
295 result->numbered = numbered;
299 if (numbered != NULL)
305 format_free (void *descr)
307 struct spec *spec = (struct spec *) descr;
309 if (spec->numbered != NULL)
310 free (spec->numbered);
315 format_get_number_of_directives (void *descr)
317 struct spec *spec = (struct spec *) descr;
319 return spec->directives;
323 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
324 formatstring_error_logger_t error_logger,
325 const char *pretty_msgid, const char *pretty_msgstr)
327 struct spec *spec1 = (struct spec *) msgid_descr;
328 struct spec *spec2 = (struct spec *) msgstr_descr;
331 if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
334 unsigned int n1 = spec1->numbered_arg_count;
335 unsigned int n2 = spec2->numbered_arg_count;
337 /* Check the argument names are the same.
338 Both arrays are sorted. We search for the first difference. */
339 for (i = 0, j = 0; i < n1 || j < n2; )
341 int cmp = (i >= n1 ? 1 :
343 spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
344 spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
350 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
351 spec2->numbered[j].number, pretty_msgstr,
361 error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
362 spec1->numbered[i].number, pretty_msgstr);
372 /* Check the argument types are the same. */
374 for (i = 0, j = 0; j < n2; )
376 if (spec1->numbered[i].number == spec2->numbered[j].number)
378 if (spec1->numbered[i].type != spec2->numbered[j].type)
381 error_logger (_("format specifications in '%s' and '%s' for argument %u are not the same"),
382 pretty_msgid, pretty_msgstr,
383 spec2->numbered[j].number);
398 struct formatstring_parser formatstring_php =
402 format_get_number_of_directives,
410 /* Test program: Print the argument list specification returned by
411 format_parse for strings read from standard input. */
416 format_print (void *descr)
418 struct spec *spec = (struct spec *) descr;
430 for (i = 0; i < spec->numbered_arg_count; i++)
432 unsigned int number = spec->numbered[i].number;
438 for (; last < number; last++)
440 switch (spec->numbered[i].type)
468 size_t line_size = 0;
470 char *invalid_reason;
473 line_len = getline (&line, &line_size, stdin);
476 if (line_len > 0 && line[line_len - 1] == '\n')
477 line[--line_len] = '\0';
479 invalid_reason = NULL;
480 descr = format_parse (line, false, NULL, &invalid_reason);
482 format_print (descr);
485 printf ("%s\n", invalid_reason);
487 free (invalid_reason);
495 * For Emacs M-x compile
497 * 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"