o xgettext and msgfmt now recognize the argument size specifiers
w8, w16, w32, w64, wf8, wf16, wf32, wf64 (as defined by ISO C 23)
in format strings.
+ o xgettext and msgfmt now recognize C++ format strings, as defined by
+ ISO C++ 20. They are marked as 'c++-format' in POT and PO files.
- Tcl: xgettext now supports the \x, \u, and \U escapes as defined in
Tcl 8.6.
* c-format:: C Format Strings
* objc-format:: Objective C Format Strings
+* c++-format:: C++ Format Strings
* python-format:: Python Format Strings
* java-format:: Java Format Strings
* csharp-format:: C# Format Strings
@kwindex no-objc-format@r{ flag}
Likewise for Objective C, see @ref{objc-format}.
+@item c++-format
+@kwindex c++-format@r{ flag}
+@itemx no-c++-format
+@kwindex no-c++-format@r{ flag}
+Likewise for C++, see @ref{c++-format}.
+
@item python-format
@kwindex python-format@r{ flag}
@itemx no-python-format
@item
Use placeholders in format strings instead of embedded URLs.
+@item
+Use placeholders in format strings instead of programmer-defined format
+string directives.
+
@item
Avoid unusual markup and unusual control characters.
@end itemize
So, you would change
-@example
+@smallexample
fputs (_("GNU GPL version 3 <https://gnu.org/licenses/gpl.html>\n"),
stream);
-@end example
+@end smallexample
@noindent
to
-@example
+@smallexample
fprintf (stream, _("GNU GPL version 3 <%s>\n"),
"https://gnu.org/licenses/gpl.html");
-@end example
+@end smallexample
+
+@subheading No programmer-defined format string directives
+
+The GNU C Library's @code{<printf.h>} facility and the C++ standard library's @code{<format>} header file make it possible for the programmer to define their own format string directives. However, such format directives cannot be used in translatable strings, for two reasons:
+@itemize @bullet
+@item
+There is no reference documentation for format strings with such directives, that the translators could consult. They would therefore have to guess where the directive starts and where it ends.
+@item
+An @samp{msgfmt -c} invocation cannot check whether the translator has produced a compatible translation of the format string. As a consequence, when a format string contains a programmer-defined directive, the program may crash at runtime when it uses the translated format string.
+@end itemize
+
+To avoid this situation, you need to move the formatting with the custom directive into a format string that does not get translated.
+
+For example, assuming code that makes use of a @code{%r} directive:
+
+@smallexample
+fprintf (stream, _("The contents is: %r"), data);
+@end smallexample
+
+@noindent
+you would rewrite it to:
+
+@smallexample
+char *tmp;
+if (asprintf (&tmp, "%r", data) < 0)
+ error (...);
+fprintf (stream, _("The contents is: %s"), tmp);
+free (tmp);
+@end smallexample
+
+Similarly, in C++, assuming you have defined a custom @code{formatter} for the type of @code{data}, the code
+
+@smallexample
+cout << format (_("The contents is: @{:#$#@}"), data);
+@end smallexample
+
+@noindent
+should be rewritten to:
+
+@smallexample
+string tmp = format ("@{:#$#@}", data);
+cout << format (_("The contents is: @{@}"), tmp);
+@end smallexample
@subheading No unusual markup
at compile time according to ISO C and ISO C++; @code{xgettext} also
supports this syntax.
+In C++, marking a C++ format string requires a small code change,
+because the first argument to @code{std::format} must be a constant
+expression.
+For example,
+@smallexample
+std::format ("@{@} @{@}!", "Hello", "world")
+@end smallexample
+@noindent
+needs to be changed to
+@smallexample
+std::vformat (gettext ("@{@} @{@}!"), std::make_format_args("Hello", "world"))
+@end smallexample
+
Later on, the maintenance is relatively easy. If, as a programmer,
you add or modify a string, you will have to ask yourself if the
new or altered string requires translation, and include it within
@menu
* c-format:: C Format Strings
* objc-format:: Objective C Format Strings
+* c++-format:: C++ Format Strings
* python-format:: Python Format Strings
* java-format:: Java Format Strings
* csharp-format:: C# Format Strings
additional format directive: "%@@", which when executed consumes an argument
of type @code{Object *}.
+@node c++-format
+@subsection C++ Format Strings
+
+C++ format strings are described in ISO C++ 20, namely in
+@uref{https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4861.pdf},
+section 20.20.2 Format string [format.string].
+
+An easier-to-read description is found at
+@uref{https://en.cppreference.com/w/cpp/utility/format/format#Parameters} and
+@uref{https://en.cppreference.com/w/cpp/utility/format/formatter#Standard_format_specification}.
+
@node python-format
@subsection Python Format Strings
@c This file is part of the GNU gettext manual.
-@c Copyright (C) 1995-2020 Free Software Foundation, Inc.
+@c Copyright (C) 1995-2023 Free Software Foundation, Inc.
@c See the file gettext.texi for copying conditions.
@node C
@code{fprintf "%2$d %1$d"}
@*In C++: @code{autosprintf "%2$d %1$d"}
(@pxref{Top, , Introduction, autosprintf, GNU autosprintf})
+@*In C++ 20 or newer: @code{std::vformat "@{1@} @{0@}"}
@item Portability
autoconf (gettext.m4) and #if ENABLE_NLS
../src/read-catalog.c \
../src/plural-table.c \
../src/format-c.c \
+ ../src/format-c++-brace.c \
../src/format-python.c \
../src/format-python-brace.c \
../src/format-java.c \
format-invalid.h Declarations of some error messages for invalid strings.
format-c.c Format string handling for C.
format-c-parse.h Format string handling for C, parsing routine.
+format-c++-brace.c Format string handling for C++.
format-python.c Format string handling for Python.
format-python-brace.c Format string handling for Python, braced syntax.
format-java.c Format string handling for Java.
FORMAT_SOURCE += \
format-invalid.h \
format-c.c format-c-parse.h \
+ format-c++-brace.c \
format-python.c \
format-python-brace.c \
format-java.c \
--- /dev/null
+/* C++ format strings.
+ Copyright (C) 2003-2023 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2023.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "format.h"
+#include "c-ctype.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+#include "format-invalid.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+/* C++ format strings are specified in ISO C++ 20.
+ Reference:
+ - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4861.pdf
+ § 20.20 Formatting [format]
+ corrected in
+ https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4950.pdf
+ § 22.14 Formatting [format]
+ - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4861.pdf
+ § 27.12 Formatting [time.format]
+ - https://en.cppreference.com/w/cpp/utility/format/format
+ Implemented in GCC 13.1, usable with option '-std=c++20' or '-std=gnu++20'.
+
+ In GNU gettext, we don't support custom format directives. See the
+ documentation, section "Preparing Translatable Strings".
+ Also, we don't support time formatting, because its translation should be
+ looked up according to the locale's LC_TIME category, where gettext() and
+ dgettext() use the LC_MESSAGES category. Support for time format strings
+ may be added later, separately, with a 'c++-time-format' tag.
+
+ There are two kinds of directives: replacement fields and escape sequences.
+ A replacement field
+ - starts with '{',
+ - is optionally followed by an "argument index" specification: a nonempty
+ digit sequence without redundant leading zeroes,
+ - is optionally followed by ':' and
+ - optionally a "fill-and-align" specification:
+ an optional character other than '{' and '}', followed by one of
+ '<', '>', '^',
+ - optionally a "sign" specification: one of '+', '-', ' ',
+ (but only for an [integer], [float] argument or when the type is one
+ of 'b', 'B', 'd', 'o', 'x', 'X')
+ - optionally: '#',
+ (but only for an [integer], [float] argument or when the type is one
+ of 'b', 'B', 'd', 'o', 'x', 'X')
+ - optionally: '0',
+ (but only for an [integer], [float] argument or when the type is one
+ of 'b', 'B', 'd', 'o', 'x', 'X')
+ - optionally a "width" specification:
+ - a nonempty digit sequence with the first digit being non-zero, or
+ - '{',
+ then an "argument index" specification: a digit sequence without
+ redundant leading zeroes,
+ then '}',
+ - optionally a "precision" specification:
+ - '.', then
+ - a nonempty digit sequence, or
+ - '{',
+ then an "argument index" specification: a digit sequence without
+ redundant leading zeroes,
+ then '}',
+ (but only for a [float], [string] argument)
+ - optionally: 'L',
+ (but only for an [integer], [float], [character], [bool] argument)
+ - optionally a "type" specification: one of
+ [integer, character, bool] 'b', 'B', 'd', 'o', 'x', 'X',
+ [float] 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
+ [integer, character] 'c',
+ [string, bool] 's',
+ [pointer] 'p',
+ - is finished by '}'.
+ An escape sequence is either '{{' or '}}'.
+ Numbered ({n} or {n:spec}) and unnumbered ({} or {:spec}) argument
+ specifications cannot be used in the same string.
+
+ The translator may add *argument index* specifications everywhere, i.e.
+ convert all unnumbered argument specifications to numbered argument
+ specifications. Or vice versa.
+
+ The translator may add or remove *fill-and-align* specifications, because
+ they are valid for all types.
+
+ The translator may add a *sign* specification when the type is one of
+ 'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
+ but not when it is 'c' (because for [character] arguments, "{:c}" is valid
+ but "{:-c}" is not).
+ The translator may remove *sign* specifications.
+
+ The translator may add a '#' flag when the type is one of
+ 'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
+ but not when it is 'c' (because for [character] arguments, "{:c}" is valid
+ but "{:#c}" is not).
+ The translator may remove a '#' flag.
+
+ The translator may add a '0' flag when the type is one of
+ 'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
+ but not when it is 'c' (because for [character] arguments, "{:c}" is valid
+ but "{:0c}" is not).
+ The translator may remove a '0' flag.
+
+ The translator may add or remove *width* specifications, because they are
+ valid for all types.
+
+ The translator may add a *precision* specification when the type is one of
+ 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
+ but not when it is 's' (because for [bool] arguments, "{:s}" is valid but
+ "{:.9s}" is not).
+ The translator may remove *precision* specifications.
+
+ The translator may add an 'L' flag when the type is one of
+ 'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G', 'c',
+ but not when it is 's' (because for [string] arguments, "{:s}" is valid but
+ "{:Ls}" is not).
+ The translator may remove an 'L' flag.
+ */
+
+/* Describes the valid argument types for a given directive. */
+enum format_arg_type
+{
+ FAT_INTEGER = 1 << 0,
+ FAT_FLOAT = 1 << 1,
+ FAT_CHARACTER = 1 << 2,
+ FAT_STRING = 1 << 3,
+ FAT_BOOL = 1 << 4,
+ FAT_POINTER = 1 << 5,
+ FAT_NONE = 0,
+ FAT_ANY = (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_STRING
+ | FAT_BOOL | FAT_POINTER)
+};
+
+struct numbered_arg
+{
+ /* The number of the argument, 0-based. */
+ unsigned int number;
+
+ /* The type is a bit mask that is the logical OR of the 'enum format_arg_type'
+ values that represent each possible argument types for the argument.
+ We use this field in order to report an error in format_check for cases like
+ #, c++-format
+ msgid "{:c}"
+ msgstr "{:-c}"
+ because "{:c}" is valid for argument type [integer, character], whereas
+ "{:-c}" is valid only for [integer]. */
+ unsigned int type;
+
+ /* The presentation is a bit mask that is the logical OR of the
+ 'enum format_arg_type' values of the directives that reference
+ the argument. Here the "type" specifications are mapped like this:
+ 'b', 'B', 'd', 'o', 'x', 'X' -> FAT_INTEGER
+ 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G' -> FAT_FLOAT
+ 'c' -> FAT_CHARACTER
+ 's' -> FAT_STRING
+ 'p' -> FAT_POINTER
+ The use of this presentation field is that
+ * We want to allow the translator to change e.g. an 'o' type to an 'x'
+ type, or an 'e' type to a 'g' type, and so on. (This is possible
+ for c-format strings too.)
+ * But we do not want to allow the translator to change e.g. a 'c' type
+ to a 'd' type:
+ #, c++-format
+ msgid "{:c}"
+ msgstr "{:d}"
+ We use this field in order to report an error in format_check. */
+ unsigned int presentation;
+};
+
+struct spec
+{
+ unsigned int directives;
+ unsigned int numbered_arg_count;
+ struct numbered_arg *numbered;
+};
+
+/* Locale independent test for a decimal digit.
+ Argument can be 'char' or 'unsigned char'. (Whereas the argument of
+ <ctype.h> isdigit must be an 'unsigned char'.) */
+#undef isdigit
+#define isdigit(c) ((unsigned int) ((c) - '0') < 10)
+
+
+static int
+numbered_arg_compare (const void *p1, const void *p2)
+{
+ unsigned int n1 = ((const struct numbered_arg *) p1)->number;
+ unsigned int n2 = ((const struct numbered_arg *) p2)->number;
+
+ return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
+}
+
+static void *
+format_parse (const char *format, bool translated, char *fdi,
+ char **invalid_reason)
+{
+ const char *const format_start = format;
+ struct spec spec;
+ unsigned int numbered_allocated;
+ unsigned int unnumbered_arg_count;
+ struct spec *result;
+
+ spec.directives = 0;
+ spec.numbered_arg_count = 0;
+ spec.numbered = NULL;
+ numbered_allocated = 0;
+ unnumbered_arg_count = 0;
+
+ for (; *format != '\0';)
+ /* Invariant: spec.numbered_arg_count == 0 || unnumbered_arg_count == 0. */
+ if (*format == '{')
+ {
+ FDI_SET (format, FMTDIR_START);
+ format++;
+ spec.directives++;
+ if (*format == '{')
+ /* An escape sequence '{{'. */
+ ;
+ else
+ {
+ /* A replacement field. */
+ unsigned int arg_array_index;
+ bool have_sign = false;
+ bool have_hash_flag = false;
+ bool have_zero_flag = false;
+ bool have_precision = false;
+ bool have_L_flag = false;
+ char type_spec;
+ unsigned int type;
+ unsigned int presentation;
+
+ /* Parse arg-id. */
+ if (isdigit (*format))
+ {
+ /* Numbered argument. */
+ unsigned int arg_id;
+
+ arg_id = (*format - '0');
+ if (*format == '0')
+ format++;
+ else
+ {
+ format++;
+ while (isdigit (*format))
+ {
+ if (arg_id >= UINT_MAX / 10)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the arg-id is too large."), spec.directives);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ /* Here arg_id <= floor(UINT_MAX/10) - 1. */
+ arg_id = arg_id * 10 + (*format - '0');
+ /* Here arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX. */
+ format++;
+ }
+ }
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (unnumbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == spec.numbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ arg_array_index = spec.numbered_arg_count;
+ spec.numbered[spec.numbered_arg_count].number = arg_id + 1;
+ spec.numbered_arg_count++;
+ }
+ else
+ {
+ /* Unnumbered argument. */
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (spec.numbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == unnumbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ arg_array_index = unnumbered_arg_count;
+ spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
+ unnumbered_arg_count++;
+ }
+
+ type = FAT_ANY;
+ presentation = 0;
+
+ if (*format == ':')
+ {
+ format++;
+ /* Parse format-spec. */
+
+ /* Parse fill-and-align. */
+ if ((*format != '0' && *format != '{' && *format != '}')
+ && (format[1] == '<' || format[1] == '>'
+ || format[1] == '^'))
+ format += 2;
+ else if (*format == '<' || *format == '>' || *format == '^')
+ format++;
+
+ /* Parse sign. */
+ if (*format == '+' || *format == '-' || *format == ' ')
+ {
+ format++;
+ have_sign = true;
+ }
+
+ /* Parse '#' flag. */
+ if (*format == '#')
+ {
+ format++;
+ have_hash_flag = true;
+ }
+
+ /* Parse '0' flag. */
+ if (*format == '0')
+ {
+ format++;
+ have_zero_flag = true;
+ }
+
+ /* Parse width. */
+ if (isdigit (*format) && *format != '0')
+ {
+ do
+ format++;
+ while (isdigit (*format));
+ }
+ else if (*format == '{')
+ {
+ format++;
+ if (isdigit (*format))
+ {
+ /* Numbered argument. */
+ unsigned int width_arg_id;
+
+ width_arg_id = (*format - '0');
+ if (*format == '0')
+ format++;
+ else
+ {
+ format++;
+ while (isdigit (*format))
+ {
+ if (width_arg_id >= UINT_MAX / 10)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the width's arg-id is too large."), spec.directives);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ /* Here width_arg_id <= floor(UINT_MAX/10) - 1. */
+ width_arg_id = width_arg_id * 10 + (*format - '0');
+ /* Here width_arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX. */
+ format++;
+ }
+ }
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (unnumbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == spec.numbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ spec.numbered[spec.numbered_arg_count].number = width_arg_id + 1;
+ spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+ spec.numbered[spec.numbered_arg_count].presentation = 0;
+ spec.numbered_arg_count++;
+ }
+ else
+ {
+ /* Unnumbered argument. */
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (spec.numbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == unnumbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
+ spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
+ spec.numbered[unnumbered_arg_count].presentation = 0;
+ unnumbered_arg_count++;
+ }
+
+ if (*format != '}')
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the width's arg-id is not terminated through '}'."), spec.directives);
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ format++;
+ }
+
+ /* Parse precision. */
+ if (*format == '.')
+ {
+ format++;
+
+ if (isdigit (*format))
+ {
+ do
+ format++;
+ while (isdigit (*format));
+
+ have_precision = true;
+ }
+ else if (*format == '{')
+ {
+ format++;
+ if (isdigit (*format))
+ {
+ /* Numbered argument. */
+ unsigned int precision_arg_id;
+
+ precision_arg_id = (*format - '0');
+ if (*format == '0')
+ format++;
+ else
+ {
+ format++;
+ while (isdigit (*format))
+ {
+ if (precision_arg_id >= UINT_MAX / 10)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the width's arg-id is too large."), spec.directives);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ /* Here precision_arg_id <= floor(UINT_MAX/10) - 1. */
+ precision_arg_id = precision_arg_id * 10 + (*format - '0');
+ /* Here precision_arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX. */
+ format++;
+ }
+ }
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (unnumbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == spec.numbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ spec.numbered[spec.numbered_arg_count].number = precision_arg_id + 1;
+ spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
+ spec.numbered[spec.numbered_arg_count].presentation = 0;
+ spec.numbered_arg_count++;
+ }
+ else
+ {
+ /* Unnumbered argument. */
+
+ /* Numbered and unnumbered specifications are exclusive. */
+ if (spec.numbered_arg_count > 0)
+ {
+ *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (numbered_allocated == unnumbered_arg_count)
+ {
+ numbered_allocated = 2 * numbered_allocated + 1;
+ spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
+ }
+ spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
+ spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
+ spec.numbered[unnumbered_arg_count].presentation = 0;
+ unnumbered_arg_count++;
+ }
+
+ if (*format != '}')
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the precision's arg-id is not terminated through '}'."), spec.directives);
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ format++;
+
+ have_precision = true;
+ }
+ else
+ format--;
+ }
+
+ /* Parse 'L' flag. */
+ if (*format == 'L')
+ {
+ format++;
+ have_L_flag = true;
+ }
+
+ /* Parse type. */
+ type_spec = '\0';
+ if (*format != '\0' && *format != '}')
+ {
+ type_spec = *format;
+
+ switch (type_spec)
+ {
+ case 'b': case 'B':
+ case 'd':
+ case 'o':
+ case 'x': case 'X':
+ type = FAT_INTEGER | FAT_CHARACTER | FAT_BOOL;
+ presentation = FAT_INTEGER;
+ break;
+
+ case 'a': case 'A':
+ case 'e': case 'E':
+ case 'f': case 'F':
+ case 'g': case 'G':
+ type = FAT_FLOAT;
+ presentation = FAT_FLOAT;
+ break;
+
+ case 'c':
+ type = FAT_INTEGER | FAT_CHARACTER;
+ presentation = FAT_CHARACTER;
+ break;
+
+ case 's':
+ type = FAT_STRING | FAT_BOOL;
+ presentation = FAT_STRING;
+ break;
+
+ case 'p':
+ type = FAT_POINTER;
+ presentation = FAT_POINTER;
+ break;
+
+ default:
+ *invalid_reason =
+ (c_isprint (type_spec)
+ ? xasprintf (_("In the directive number %u, the character '%c' is not a standard type specifier."), spec.directives, type_spec)
+ : xasprintf (_("The character that terminates the directive number %u is not a standard type specifier."), spec.directives));
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (have_sign && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the sign specification is incompatible with the type specifier '%c'."), spec.directives, type_spec);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (have_hash_flag && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the '#' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (have_zero_flag && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the '0' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (have_precision && (type & (FAT_FLOAT | FAT_STRING)) == 0)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the precision specification is incompatible with the type specifier '%c'."), spec.directives, type_spec);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (have_L_flag && (type & (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL)) == 0)
+ {
+ *invalid_reason =
+ xasprintf (_("In the directive number %u, the 'L' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
+ FDI_SET (format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ format++;
+ }
+
+ if (have_sign)
+ {
+ /* Citing ISO C++ 20:
+ "The sign option is only valid for arithmetic types other
+ than charT and bool or when an integer presentation type
+ is specified." */
+ switch (type_spec)
+ {
+ case 'b': case 'B':
+ case 'd':
+ case 'o':
+ case 'x': case 'X':
+ break;
+ default:
+ /* No type_spec: FAT_ANY -> FAT_INTEGER | FAT_FLOAT
+ type_spec = 'c': FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
+ */
+ type = type & (FAT_INTEGER | FAT_FLOAT);
+ break;
+ }
+ }
+
+ if (have_hash_flag)
+ {
+ /* Citing ISO C++ 20:
+ "The # option ... This option is valid for arithmetic
+ types other than charT and bool or when an integer
+ presentation type is specified, and not otherwise." */
+ switch (type_spec)
+ {
+ case 'b': case 'B':
+ case 'd':
+ case 'o':
+ case 'x': case 'X':
+ break;
+ default:
+ /* No type_spec: FAT_ANY -> FAT_INTEGER | FAT_FLOAT
+ type_spec = 'c': FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
+ */
+ type = type & (FAT_INTEGER | FAT_FLOAT);
+ break;
+ }
+ }
+
+ if (have_zero_flag)
+ {
+ /* Citing ISO C++ 20:
+ "A zero (0) character preceding the width field ... This
+ option is only valid for arithmetic types other than
+ charT and bool or when an integer presentation type is
+ specified." */
+ switch (type_spec)
+ {
+ case 'b': case 'B':
+ case 'd':
+ case 'o':
+ case 'x': case 'X':
+ break;
+ default:
+ /* No type_spec: FAT_ANY -> FAT_INTEGER | FAT_FLOAT
+ type_spec = 'c': FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
+ */
+ type = type & (FAT_INTEGER | FAT_FLOAT);
+ break;
+ }
+ }
+
+ if (have_precision)
+ {
+ /* Citing ISO C++ 20:
+ "The nonnegative-integer in precision ... It can only be
+ used with floating-point and string types." */
+ /* No type_spec: FAT_ANY -> FAT_FLOAT | FAT_STRING
+ type_spec = 's': FAT_STRING | FAT_BOOL -> FAT_STRING
+ */
+ type = type & (FAT_FLOAT | FAT_STRING);
+ }
+
+ if (have_L_flag)
+ {
+ /* Citing ISO C++ 20:
+ "The L option is only valid for arithmetic types" */
+ /* No type_spec: FAT_ANY -> FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL
+ type_spec = 's': FAT_STRING | FAT_BOOL -> FAT_BOOL
+ */
+ type = type & (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL);
+ }
+
+ /* If no possible type is left for the directive, e.g. in the
+ format string "{:.9Ls}", the format string is invalid. */
+ if (type == FAT_NONE)
+ {
+ *invalid_reason =
+ xasprintf (_("The directive number %u, with all of its options, is not applicable to any type."), spec.directives);
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ }
+
+ spec.numbered[arg_array_index].type = type;
+ spec.numbered[arg_array_index].presentation = presentation;
+
+ if (*format == '\0')
+ {
+ *invalid_reason =
+ xasprintf (_("The string ends in the middle of the directive number %u."), spec.directives);
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ if (*format != '}')
+ {
+ *invalid_reason =
+ xasprintf (_("The directive number %u is not terminated through '}'."), spec.directives);
+ FDI_SET (format - 1, FMTDIR_ERROR);
+ goto bad_format;
+ }
+ }
+
+ FDI_SET (format, FMTDIR_END);
+
+ format++;
+ }
+ else if (*format == '}')
+ {
+ FDI_SET (format, FMTDIR_START);
+ format++;
+ spec.directives++;
+ if (*format == '}')
+ /* An escape sequence '}}'. */
+ ;
+ else
+ {
+ *invalid_reason =
+ (spec.directives == 0
+ ? xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'."))
+ : xasprintf (_("The string contains a lone '}' after directive number %u."), spec.directives));
+ FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR);
+ goto bad_format;
+ }
+
+ FDI_SET (format, FMTDIR_END);
+
+ format++;
+ }
+ else
+ format++;
+
+ /* Convert the unnumbered argument array to numbered arguments. */
+ if (unnumbered_arg_count > 0)
+ spec.numbered_arg_count = unnumbered_arg_count;
+ /* Sort the numbered argument array, and eliminate duplicates. */
+ else if (spec.numbered_arg_count > 1)
+ {
+ unsigned int i, j;
+ bool err;
+
+ qsort (spec.numbered, spec.numbered_arg_count,
+ sizeof (struct numbered_arg), numbered_arg_compare);
+
+ /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i. */
+ err = false;
+ for (i = j = 0; i < spec.numbered_arg_count; i++)
+ if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
+ {
+ unsigned int type1 = spec.numbered[i].type;
+ unsigned int type2 = spec.numbered[j-1].type;
+ unsigned int type_both = type1 & type2;
+
+ if (type_both == FAT_NONE)
+ {
+ /* Incompatible types. */
+ if (!err)
+ *invalid_reason =
+ INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
+ err = true;
+ }
+
+ spec.numbered[j-1].type = type_both;
+ spec.numbered[j-1].presentation =
+ spec.numbered[i].presentation | spec.numbered[j-1].presentation;
+ }
+ else
+ {
+ if (j < i)
+ {
+ spec.numbered[j].number = spec.numbered[i].number;
+ spec.numbered[j].type = spec.numbered[i].type;
+ spec.numbered[j].presentation = spec.numbered[i].presentation;
+ }
+ j++;
+ }
+ spec.numbered_arg_count = j;
+ if (err)
+ /* *invalid_reason has already been set above. */
+ goto bad_format;
+ }
+
+ result = XMALLOC (struct spec);
+ *result = spec;
+ return result;
+
+ bad_format:
+ if (spec.numbered != NULL)
+ free (spec.numbered);
+ return NULL;
+}
+
+static void
+format_free (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+
+ if (spec->numbered != NULL)
+ free (spec->numbered);
+ free (spec);
+}
+
+static int
+format_get_number_of_directives (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+
+ return spec->directives;
+}
+
+/* Size of buffer needed for type_description result. */
+#define MAX_TYPE_DESCRIPTION_LEN (50 + 1)
+/* Returns a textual description of TYPE in BUF. */
+static void
+get_type_description (char buf[MAX_TYPE_DESCRIPTION_LEN], unsigned int type)
+{
+ char *p = buf;
+ bool first = true;
+
+ p = stpcpy (p, "[");
+ if (type & FAT_INTEGER)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "integer");
+ first = false;
+ }
+ if (type & FAT_FLOAT)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "float");
+ first = false;
+ }
+ if (type & FAT_CHARACTER)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "character");
+ first = false;
+ }
+ if (type & FAT_STRING)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "string");
+ first = false;
+ }
+ if (type & FAT_BOOL)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "bool");
+ first = false;
+ }
+ if (type & FAT_POINTER)
+ {
+ if (!first)
+ p = stpcpy (p, ", ");
+ p = stpcpy (p, "pointer");
+ first = false;
+ }
+ p = stpcpy (p, "]");
+ *p++ = '\0';
+ /* Verify that the buffer was large enough. */
+ if (p - buf > MAX_TYPE_DESCRIPTION_LEN)
+ abort ();
+}
+
+static bool
+format_check (void *msgid_descr, void *msgstr_descr, bool equality,
+ formatstring_error_logger_t error_logger,
+ const char *pretty_msgid, const char *pretty_msgstr)
+{
+ struct spec *spec1 = (struct spec *) msgid_descr;
+ struct spec *spec2 = (struct spec *) msgstr_descr;
+ bool err = false;
+
+ if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
+ {
+ unsigned int i, j;
+ unsigned int n1 = spec1->numbered_arg_count;
+ unsigned int n2 = spec2->numbered_arg_count;
+
+ /* Check that the argument numbers are the same.
+ Both arrays are sorted. We search for the first difference. */
+ for (i = 0, j = 0; i < n1 || j < n2; )
+ {
+ int cmp = (i >= n1 ? 1 :
+ j >= n2 ? -1 :
+ spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
+ spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
+ 0);
+
+ if (cmp > 0)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
+ spec2->numbered[j].number, pretty_msgstr,
+ pretty_msgid);
+ err = true;
+ break;
+ }
+ else if (cmp < 0)
+ {
+ if (equality)
+ {
+ if (error_logger)
+ error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
+ spec1->numbered[i].number, pretty_msgstr);
+ err = true;
+ break;
+ }
+ else
+ i++;
+ }
+ else
+ j++, i++;
+ }
+ /* Check that the argument types are not being restricted in the msgstr,
+ and that the presentation does not get changed in the msgstr. */
+ if (!err)
+ for (i = 0, j = 0; j < n2; )
+ {
+ if (spec1->numbered[i].number == spec2->numbered[j].number)
+ {
+ unsigned int type_difference = spec1->numbered[i].type & ~spec2->numbered[j].type;
+ if (type_difference != 0)
+ {
+ if (error_logger)
+ {
+ char buf[MAX_TYPE_DESCRIPTION_LEN];
+ get_type_description (buf, type_difference);
+ error_logger (_("The format specification for argument %u in '%s' is applicable to the types %s, but the format specification for argument %u in '%s' is not."),
+ spec1->numbered[i].number, pretty_msgid, buf,
+ spec2->numbered[j].number, pretty_msgstr);
+ }
+ err = true;
+ break;
+ }
+ unsigned int presentation_difference =
+ spec2->numbered[j].presentation & ~spec1->numbered[i].presentation;
+ if (presentation_difference != 0)
+ {
+ if (error_logger)
+ error_logger (_("The format specification for argument %u in '%s' uses a different presentation than the format specification for argument %u in '%s'."),
+ spec2->numbered[j].number, pretty_msgstr,
+ spec1->numbered[i].number, pretty_msgid);
+ err = true;
+ break;
+ }
+ j++, i++;
+ }
+ else
+ i++;
+ }
+ }
+
+ return err;
+}
+
+
+struct formatstring_parser formatstring_cplusplus_brace =
+{
+ format_parse,
+ format_free,
+ format_get_number_of_directives,
+ NULL,
+ format_check
+};
+
+
+#ifdef TEST
+
+/* Test program: Print the argument list specification returned by
+ format_parse for strings read from standard input. */
+
+#include <stdio.h>
+
+static void
+format_print (void *descr)
+{
+ struct spec *spec = (struct spec *) descr;
+ unsigned int last;
+ unsigned int i;
+
+ if (spec == NULL)
+ {
+ printf ("INVALID");
+ return;
+ }
+
+ printf ("(");
+ last = 1;
+ for (i = 0; i < spec->numbered_arg_count; i++)
+ {
+ unsigned int number = spec->numbered[i].number;
+ char buf[MAX_TYPE_DESCRIPTION_LEN];
+
+ if (i > 0)
+ printf (" ");
+ if (number < last)
+ abort ();
+ for (; last < number; last++)
+ printf ("_ ");
+ get_type_description (buf, spec->numbered[i].type);
+ printf ("%s", buf);
+ last = number + 1;
+ }
+ printf (")");
+}
+
+int
+main ()
+{
+ for (;;)
+ {
+ char *line = NULL;
+ size_t line_size = 0;
+ int line_len;
+ char *invalid_reason;
+ void *descr;
+
+ line_len = getline (&line, &line_size, stdin);
+ if (line_len < 0)
+ break;
+ if (line_len > 0 && line[line_len - 1] == '\n')
+ line[--line_len] = '\0';
+
+ invalid_reason = NULL;
+ descr = format_parse (line, false, NULL, &invalid_reason);
+
+ format_print (descr);
+ printf ("\n");
+ if (descr == NULL)
+ printf ("%s\n", invalid_reason);
+
+ free (invalid_reason);
+ free (line);
+ }
+
+ return 0;
+}
+
+/*
+ * For Emacs M-x compile
+ * Local Variables:
+ * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-c++-brace.c ../gnulib-lib/libgettextlib.la"
+ * End:
+ */
+
+#endif /* TEST */
/* Format strings.
- Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020 Free Software Foundation, Inc.
+ Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020, 2023 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software: you can redistribute it and/or modify
{
/* format_c */ &formatstring_c,
/* format_objc */ &formatstring_objc,
+ /* format_cplusplus_brace */ &formatstring_cplusplus_brace,
/* format_python */ &formatstring_python,
/* format_python_brace */ &formatstring_python_brace,
/* format_java */ &formatstring_java,
/* Format strings.
- Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020 Free Software Foundation, Inc.
+ Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020, 2023 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software: you can redistribute it and/or modify
/* Format string parsers, each defined in its own file. */
extern DLL_VARIABLE struct formatstring_parser formatstring_c;
extern DLL_VARIABLE struct formatstring_parser formatstring_objc;
+extern DLL_VARIABLE struct formatstring_parser formatstring_cplusplus_brace;
extern DLL_VARIABLE struct formatstring_parser formatstring_python;
extern DLL_VARIABLE struct formatstring_parser formatstring_python_brace;
extern DLL_VARIABLE struct formatstring_parser formatstring_java;
/* GNU gettext - internationalization aids
- Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019-2020 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019-2020, 2023 Free Software Foundation, Inc.
This file was written by Peter Miller <millerp@canb.auug.org.au>
{
/* format_c */ "c",
/* format_objc */ "objc",
+ /* format_cplusplus_brace */ "c++",
/* format_python */ "python",
/* format_python_brace */ "python-brace",
/* format_java */ "java",
{
/* format_c */ "C",
/* format_objc */ "Objective C",
+ /* format_cplusplus_brace */ "C++",
/* format_python */ "Python",
/* format_python_brace */ "Python brace",
/* format_java */ "Java MessageFormat",
/* GNU gettext - internationalization aids
- Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019-2020 Free Software Foundation, Inc.
+ Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019-2020, 2023 Free Software Foundation, Inc.
This file was written by Peter Miller <millerp@canb.auug.org.au>
{
format_c,
format_objc,
+ format_cplusplus_brace,
format_python,
format_python_brace,
format_java,
format_gfc_internal,
format_ycp
};
-#define NFORMATS 30 /* Number of format_type enum values. */
+#define NFORMATS 31 /* Number of format_type enum values. */
extern DLL_VARIABLE const char *const format_language[NFORMATS];
extern DLL_VARIABLE const char *const format_language_pretty[NFORMATS];
xgettext_record_flag ("argp_failure:2:c-format");
#endif
+ xgettext_record_flag ("gettext:1:pass-c++-format");
+ xgettext_record_flag ("dgettext:2:pass-c++-format");
+ xgettext_record_flag ("dcgettext:2:pass-c++-format");
+ xgettext_record_flag ("ngettext:1:pass-c++-format");
+ xgettext_record_flag ("ngettext:2:pass-c++-format");
+ xgettext_record_flag ("dngettext:2:pass-c++-format");
+ xgettext_record_flag ("dngettext:3:pass-c++-format");
+ xgettext_record_flag ("dcngettext:2:pass-c++-format");
+ xgettext_record_flag ("dcngettext:3:pass-c++-format");
+ xgettext_record_flag ("gettext_noop:1:pass-c++-format");
+ xgettext_record_flag ("pgettext:2:pass-c++-format");
+ xgettext_record_flag ("dpgettext:3:pass-c++-format");
+ xgettext_record_flag ("dcpgettext:3:pass-c++-format");
+ xgettext_record_flag ("npgettext:2:pass-c++-format");
+ xgettext_record_flag ("npgettext:3:pass-c++-format");
+ xgettext_record_flag ("dnpgettext:3:pass-c++-format");
+ xgettext_record_flag ("dnpgettext:4:pass-c++-format");
+ xgettext_record_flag ("dcnpgettext:3:pass-c++-format");
+ xgettext_record_flag ("dcnpgettext:4:pass-c++-format");
+
+ /* C++ <format> */
+ xgettext_record_flag ("vformat:1:c++-format");
+ xgettext_record_flag ("vformat_to:2:c++-format");
+
xgettext_record_flag ("gettext:1:pass-qt-format");
xgettext_record_flag ("dgettext:2:pass-qt-format");
xgettext_record_flag ("dcgettext:2:pass-qt-format");
/* xgettext C/C++/ObjectiveC backend.
- Copyright (C) 2001-2003, 2006, 2009, 2014-2015, 2018, 2020 Free Software Foundation, Inc.
+ Copyright (C) 2001-2003, 2006, 2009, 2014-2015, 2018, 2020, 2023 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software: you can redistribute it and/or modify
&formatstring_c, NULL }, \
{ "C++", extract_cxx, NULL, \
&flag_table_c, \
- &formatstring_c, NULL }, \
+ &formatstring_c, &formatstring_cplusplus_brace }, \
{ "ObjectiveC", extract_objc, NULL, \
&flag_table_objc, \
&formatstring_c, &formatstring_objc }, \
if (recognize_qt_formatstrings ()
&& best_cp->msgid_plural == best_cp->msgid)
{
- msgid_context.is_format3 = yes_according_to_context;
- msgid_plural_context.is_format3 = yes_according_to_context;
+ msgid_context.is_format4 = yes_according_to_context;
+ msgid_plural_context.is_format4 = yes_according_to_context;
}
best_msgctxt =
argnum, value, pass);
}
break;
+ case format_cplusplus_brace:
+ flag_context_list_table_insert (&flag_table_c, 1,
+ name_start, name_end,
+ argnum, value, pass);
+ flag_context_list_table_insert (&flag_table_cxx_qt, 1,
+ name_start, name_end,
+ argnum, value, pass);
+ flag_context_list_table_insert (&flag_table_cxx_kde, 1,
+ name_start, name_end,
+ argnum, value, pass);
+ flag_context_list_table_insert (&flag_table_cxx_boost, 1,
+ name_start, name_end,
+ argnum, value, pass);
+ break;
case format_objc:
flag_context_list_table_insert (&flag_table_objc, 1,
name_start, name_end,
case format_smalltalk:
break;
case format_qt:
- flag_context_list_table_insert (&flag_table_cxx_qt, 1,
+ flag_context_list_table_insert (&flag_table_cxx_qt, 2,
name_start, name_end,
argnum, value, pass);
break;
case format_qt_plural:
- flag_context_list_table_insert (&flag_table_cxx_qt, 2,
+ flag_context_list_table_insert (&flag_table_cxx_qt, 3,
name_start, name_end,
argnum, value, pass);
break;
case format_kde:
- flag_context_list_table_insert (&flag_table_cxx_kde, 1,
+ flag_context_list_table_insert (&flag_table_cxx_kde, 2,
name_start, name_end,
argnum, value, pass);
break;
case format_kde_kuit:
- flag_context_list_table_insert (&flag_table_cxx_kde, 2,
+ flag_context_list_table_insert (&flag_table_cxx_kde, 3,
name_start, name_end,
argnum, value, pass);
break;
case format_boost:
- flag_context_list_table_insert (&flag_table_cxx_boost, 1,
+ flag_context_list_table_insert (&flag_table_cxx_boost, 2,
name_start, name_end,
argnum, value, pass);
break;
recognize_qt_formatstrings (void)
{
return recognize_format_qt
- && current_formatstring_parser3 == &formatstring_qt_plural;
+ && current_formatstring_parser4 == &formatstring_qt_plural;
}
if (recognize_format_qt && strcmp (tp->name, "C++") == 0)
{
result.flag_table = &flag_table_cxx_qt;
- result.formatstring_parser2 = &formatstring_qt;
- result.formatstring_parser3 = &formatstring_qt_plural;
+ result.formatstring_parser3 = &formatstring_qt;
+ result.formatstring_parser4 = &formatstring_qt_plural;
}
/* Likewise for --kde. */
if (recognize_format_kde && strcmp (tp->name, "C++") == 0)
{
result.flag_table = &flag_table_cxx_kde;
- result.formatstring_parser2 = &formatstring_kde;
- result.formatstring_parser3 = &formatstring_kde_kuit;
+ result.formatstring_parser3 = &formatstring_kde;
+ result.formatstring_parser4 = &formatstring_kde_kuit;
}
/* Likewise for --boost. */
if (recognize_format_boost && strcmp (tp->name, "C++") == 0)
{
result.flag_table = &flag_table_cxx_boost;
- result.formatstring_parser2 = &formatstring_boost;
+ result.formatstring_parser3 = &formatstring_boost;
}
return result;
format-awk-1 format-awk-2 \
format-boost-1 format-boost-2 \
format-c-1 format-c-2 format-c-3 format-c-4 format-c-5 \
+ format-c++-brace-1 format-c++-brace-2 \
format-csharp-1 format-csharp-2 \
format-elisp-1 format-elisp-2 \
format-gcc-internal-1 format-gcc-internal-2 \
plural-1 plural-2 \
gettextpo-1 sentence-1 \
lang-po \
- lang-c lang-c++ lang-objc \
+ lang-c lang-c++ lang-c++20 lang-objc \
lang-python-1 lang-python-2 \
lang-java \
lang-csharp \
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test recognition of C++ format strings.
+
+cat <<\EOF > f-c++-1.data
+# Valid: no argument
+"abc{{"
+# Valid: no argument
+"abc}}"
+
+# Valid: one argument of any type
+"abc{}"
+# Valid: one argument of any type
+"abc{:}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:b}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:B}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:d}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:o}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:x}"
+# Valid: one argument of type [integer, character, bool]
+"abc{:X}"
+# Valid: one argument of type [float]
+"abc{:a}"
+# Valid: one argument of type [float]
+"abc{:A}"
+# Valid: one argument of type [float]
+"abc{:e}"
+# Valid: one argument of type [float]
+"abc{:E}"
+# Valid: one argument of type [float]
+"abc{:f}"
+# Valid: one argument of type [float]
+"abc{:F}"
+# Valid: one argument of type [float]
+"abc{:g}"
+# Valid: one argument of type [float]
+"abc{:G}"
+# Valid: one argument of type [integer, character]
+"abc{:c}"
+# Valid: one argument of type [string, bool]
+"abc{:s}"
+# Valid: one argument of type [pointer]
+"abc{:p}"
+
+# Valid: align option
+"abc{:<}"
+# Valid: align option
+"abc{:>}"
+# Valid: align option
+"abc{:^}"
+# Valid: fill-and-align option
+"abc{:_<}"
+# Valid: fill-and-align option
+"abc{:->}"
+# Valid: fill-and-align option
+"abc{:=^}"
+# Invalid: repeated fill-and-align option
+"abc{:_<>}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^b}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^B}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^d}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^o}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^x}"
+# Valid: align option, one argument of type [integer, character, bool]
+"abc{:^X}"
+# Valid: align option, one argument of type [float]
+"abc{:^a}"
+# Valid: align option, one argument of type [float]
+"abc{:^A}"
+# Valid: align option, one argument of type [float]
+"abc{:^e}"
+# Valid: align option, one argument of type [float]
+"abc{:^E}"
+# Valid: align option, one argument of type [float]
+"abc{:^f}"
+# Valid: align option, one argument of type [float]
+"abc{:^F}"
+# Valid: align option, one argument of type [float]
+"abc{:^g}"
+# Valid: align option, one argument of type [float]
+"abc{:^G}"
+# Valid: align option, one argument of type [integer, character]
+"abc{:^c}"
+# Valid: align option, one argument of type [string, bool]
+"abc{:^s}"
+# Valid: align option, one argument of type [pointer]
+"abc{:^p}"
+
+# Valid: sign option, one argument of type [integer, float]
+"abc{:+}"
+# Valid: sign option, one argument of type [integer, float]
+"abc{:-}"
+# Valid: sign option, one argument of type [integer, float]
+"abc{: }"
+# Invalid: repeated sign option
+"abc{:+-}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-b}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-B}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-d}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-o}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-x}"
+# Valid: sign option, one argument of type [integer, character, bool]
+"abc{:-X}"
+# Valid: sign option, one argument of type [float]
+"abc{:-a}"
+# Valid: sign option, one argument of type [float]
+"abc{:-A}"
+# Valid: sign option, one argument of type [float]
+"abc{:-e}"
+# Valid: sign option, one argument of type [float]
+"abc{:-E}"
+# Valid: sign option, one argument of type [float]
+"abc{:-f}"
+# Valid: sign option, one argument of type [float]
+"abc{:-F}"
+# Valid: sign option, one argument of type [float]
+"abc{:-g}"
+# Valid: sign option, one argument of type [float]
+"abc{:-G}"
+# Valid: sign option, one argument of type [integer]
+"abc{:-c}"
+# Invalid: sign option, no possible argument type
+"abc{:-s}"
+# Invalid: sign option, no possible argument type
+"abc{:-p}"
+
+# Valid: '#' option, one argument of type [integer, float]
+"abc{:#}"
+# Invalid: repeated '#' option
+"abc{:##}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#b}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#B}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#d}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#o}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#x}"
+# Valid: '#' option, one argument of type [integer, character, bool]
+"abc{:#X}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#a}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#A}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#e}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#E}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#f}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#F}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#g}"
+# Valid: '#' option, one argument of type [float]
+"abc{:#G}"
+# Valid: '#' option, one argument of type [integer]
+"abc{:#c}"
+# Invalid: '#' option, no possible argument type
+"abc{:#s}"
+# Invalid: '#' option, no possible argument type
+"abc{:#p}"
+
+# Valid: '0' option, one argument of type [integer, float]
+"abc{:0}"
+# Invalid: repeated '0' option
+"abc{:00}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0b}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0B}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0d}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0o}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0x}"
+# Valid: '0' option, one argument of type [integer, character, bool]
+"abc{:0X}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0a}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0A}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0e}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0E}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0f}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0F}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0g}"
+# Valid: '0' option, one argument of type [float]
+"abc{:0G}"
+# Valid: '0' option, one argument of type [integer]
+"abc{:0c}"
+# Invalid: '0' option, no possible argument type
+"abc{:0s}"
+# Invalid: '0' option, no possible argument type
+"abc{:0p}"
+
+# Valid: width option
+"abc{:9}"
+# Valid: width option
+"abc{:99}"
+# Valid: width option
+"abc{:999999999999999999999999}"
+# Valid: width option that takes an argument
+"abc{:{}}"
+# Invalid: repeated width option that takes an argument
+"abc{:{}{}}"
+# Invalid: unterminated directive after width option
+"abc{:{}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9b}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9B}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9d}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9o}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9x}"
+# Valid: width option, one argument of type [integer, character, bool]
+"abc{:9X}"
+# Valid: width option, one argument of type [float]
+"abc{:9a}"
+# Valid: width option, one argument of type [float]
+"abc{:9A}"
+# Valid: width option, one argument of type [float]
+"abc{:9e}"
+# Valid: width option, one argument of type [float]
+"abc{:9E}"
+# Valid: width option, one argument of type [float]
+"abc{:9f}"
+# Valid: width option, one argument of type [float]
+"abc{:9F}"
+# Valid: width option, one argument of type [float]
+"abc{:9g}"
+# Valid: width option, one argument of type [float]
+"abc{:9G}"
+# Valid: width option, one argument of type [integer, character]
+"abc{:9c}"
+# Valid: width option, one argument of type [string, bool]
+"abc{:9s}"
+# Valid: width option, one argument of type [pointer]
+"abc{:9p}"
+
+# Valid: precision option
+"abc{:.9}"
+# Valid: precision option
+"abc{:.99}"
+# Valid: precision option
+"abc{:.999999999999999999999999}"
+# Invalid: unterminated precision option
+"abc{:.}"
+# Valid: precision option that takes an argument
+"abc{:.{}}"
+# Invalid: repeated precision option that takes an argument
+"abc{:.{}.{}}"
+# Invalid: unterminated directive after precision option
+"abc{:.{}"
+# Invalid: precision option, no possible argument type
+"abc{:.9b}"
+# Invalid: precision option, no possible argument type
+"abc{:.9B}"
+# Invalid: precision option, no possible argument type
+"abc{:.9d}"
+# Invalid: precision option, no possible argument type
+"abc{:.9o}"
+# Invalid: precision option, no possible argument type
+"abc{:.9x}"
+# Invalid: precision option, no possible argument type
+"abc{:.9X}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9a}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9A}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9e}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9E}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9f}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9F}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9g}"
+# Valid: precision option, one argument of type [float]
+"abc{:.9G}"
+# Invalid: precision option, no possible argument type
+"abc{:.9c}"
+# Valid: precision option, one argument of type [string]
+"abc{:.9s}"
+# Invalid: precision option, no possible argument type
+"abc{:.9p}"
+
+# Valid: 'L' option, one argument of type [integer, float, character, bool]
+"abc{:L}"
+# Invalid: repeated 'L' option
+"abc{:LL}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:Lb}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:LB}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:Ld}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:Lo}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:Lx}"
+# Valid: 'L' option, one argument of type [integer, character, bool]
+"abc{:LX}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:La}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:LA}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:Le}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:LE}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:Lf}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:LF}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:Lg}"
+# Valid: 'L' option, one argument of type [float]
+"abc{:LG}"
+# Valid: 'L' option, one argument of type [integer, character]
+"abc{:Lc}"
+# Valid: 'L' option, one argument of type [bool]
+"abc{:Ls}"
+# Invalid: 'L' option, no possible argument type
+"abc{:Lp}"
+
+# Valid: align option and sign option
+"abc{:^+}"
+# Valid: fill-and-align option and sign option
+"abc{:-^+}"
+# Invalid: fill-and-align option and sign option, but in reverse order
+"abc{:+-^}"
+# Valid: align option and '#' option
+"abc{:^#}"
+# Valid: fill-and-align option and '#' option
+"abc{:-^#}"
+# Invalid: fill-and-align option and '#' option, but in reverse order
+"abc{:#-^}"
+# Valid: align option and '0' option
+"abc{:^0}"
+# Valid: fill-and-align option and '0' option
+"abc{:-^0}"
+# Invalid: fill-and-align option and '0' option, but in reverse order
+"abc{:0-^}"
+# Valid: align option and width option
+"abc{:^9}"
+# Valid: fill-and-align option and width option
+"abc{:-^9}"
+# Invalid: fill-and-align option and width option, but in reverse order
+"abc{:9-^}"
+# Valid: align option and precision option
+"abc{:^.9}"
+# Valid: fill-and-align option and precision option
+"abc{:-^.9}"
+# Invalid: fill-and-align option and precision option, but in reverse order
+"abc{:.9-^}"
+# Valid: align option and 'L' option
+"abc{:^L}"
+# Valid: fill-and-align option and 'L' option
+"abc{:-^L}"
+# Invalid: fill-and-align option and 'L' option, but in reverse order
+"abc{:L-^}"
+# Valid: align option and type
+"abc{:^d}"
+# Valid: fill-and-align option and type
+"abc{:-^d}"
+# Invalid: fill-and-align option and type, but in reverse order
+"abc{:d-^}"
+
+# Valid: sign option and '#' option
+"abc{:-#}"
+# Invalid: sign option and '#' option, but in reverse order
+"abc{:#-}"
+# Valid: sign option and '0' option
+"abc{:-0}"
+# Invalid: sign option and '0' option, but in reverse order
+"abc{:0-}"
+# Valid: sign option and width option
+"abc{:-9}"
+# Invalid: sign option and width option, but in reverse order
+"abc{:9-}"
+# Valid: sign option and precision option
+"abc{:-.9}"
+# Invalid: sign option and precision option, but in reverse order
+"abc{:.9-}"
+# Valid: sign option and 'L' option
+"abc{:-L}"
+# Invalid: sign option and 'L' option, but in reverse order
+"abc{:L-}"
+# Valid: sign option and type
+"abc{:-d}"
+# Invalid: sign option and type, but in reverse order
+"abc{:d-}"
+
+# Valid: '#' option and '0' option
+"abc{:#0}"
+# Invalid: '#' option and '0' option, but in reverse order
+"abc{:0#}"
+# Valid: '#' option and width option
+"abc{:#9}"
+# Invalid: '#' option and width option, but in reverse order
+"abc{:9#}"
+# Valid: '#' option and precision option
+"abc{:#.9}"
+# Invalid: '#' option and precision option, but in reverse order
+"abc{:.9#}"
+# Valid: '#' option and 'L' option
+"abc{:#L}"
+# Invalid: '#' option and 'L' option, but in reverse order
+"abc{:L#}"
+# Valid: '#' option and type
+"abc{:#d}"
+# Invalid: '#' option and type, but in reverse order
+"abc{:d#}"
+
+# Valid: '0' option and width option
+"abc{:09}"
+# Valid: '0' option and precision option
+"abc{:0.9}"
+# Valid: '0' option and 'L' option
+"abc{:0L}"
+# Invalid: '0' option and 'L' option, but in reverse order
+"abc{:L0}"
+# Valid: '0' option and type
+"abc{:0d}"
+# Invalid: '0' option and type, but in reverse order
+"abc{:d0}"
+
+# Valid: width option and precision option
+"abc{:15.9}"
+# Valid: width option and 'L' option
+"abc{:15L}"
+# Invalid: width option and 'L' option, but in reverse order
+"abc{:L15}"
+# Valid: width option and type
+"abc{:15d}"
+# Invalid: width option and type, but in reverse order
+"abc{:d15}"
+
+# Valid: precision option and 'L' option
+"abc{:.9L}"
+# Invalid: precision option and 'L' option, but in reverse order
+"abc{:L.9}"
+# Valid: precision option and type
+"abc{:.9e}"
+# Invalid: precision option and type, but in reverse order
+"abc{:e.9}"
+
+# Valid: 'L' option and type
+"abc{:Ld}"
+# Invalid: 'L' option and type, but in reverse order
+"abc{:dL}"
+
+# Valid: several options, one argument of type [float]
+"{:.9L}"
+# Valid: several options, one argument of type [string]
+"{:.9s}"
+# Valid: several options, one argument of type [bool]
+"{:Ls}"
+# Invalid: several options, no possible argument type
+"{:.9Ls}"
+
+# Invalid: unterminated
+"abc{"
+# Invalid: unterminated
+"abc{2"
+# Invalid: unterminated
+"abc{:"
+# Invalid: non-standard type specifier
+"abc{:y}"
+# Valid: three arguments
+"abc{}{}{}"
+# Valid: a numbered argument
+"abc{0}"
+# Valid: two-digit numbered arguments
+"abc{10}ef{9}gh{8}ij{7}kl{6}mn{5}op{4}qr{3}st{2}uv{1}wx{0}yz"
+# Valid: three arguments, two with same number, no conflict
+"abc{0:4x},{1:c},{0:d}"
+# Valid: still no conflict between argument types
+"abc{1:{0}x},{2:c},{0:d}"
+# Invalid: argument with conflicting types
+"abc{1:{0}x},{2:c},{0:s}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{:d}{1:x}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{1:d}{:x}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{:{1}x}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{:.{1}x}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{1:{}x}"
+# Invalid: mixing of numbered and unnumbered arguments
+"abc{1:.{}x}"
+# Valid: permutation
+"abc{1:d}def{0:d}"
+# Valid: one argument with width
+"abc{1:#{0}g}"
+# Valid: one argument with width and precision
+"abc{2:{1}.{0}g}"
+EOF
+
+: ${XGETTEXT=xgettext}
+n=0
+while read comment; do
+ if test -z "$comment"; then
+ read comment
+ fi
+ read string
+ n=`expr $n + 1`
+ cat <<EOF > f-c++-1-$n.in
+gettext(${string});
+EOF
+ ${XGETTEXT} -L C++ -o f-c++-1-$n.po f-c++-1-$n.in || Exit 1
+ test -f f-c++-1-$n.po || Exit 1
+ fail=
+ if echo "$comment" | grep 'Valid:' > /dev/null; then
+ if grep c++-format f-c++-1-$n.po > /dev/null; then
+ :
+ else
+ fail=yes
+ fi
+ else
+ if grep c++-format f-c++-1-$n.po > /dev/null; then
+ fail=yes
+ else
+ :
+ fi
+ fi
+ if test -n "$fail"; then
+ echo "Format string recognition error:" 1>&2
+ cat f-c++-1-$n.in 1>&2
+ echo "Got:" 1>&2
+ cat f-c++-1-$n.po 1>&2
+ Exit 1
+ fi
+ rm -f f-c++-1-$n.in f-c++-1-$n.po
+done < f-c++-1.data
+
+Exit 0
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test checking of C++ format strings.
+
+cat <<\EOF > f-c++-2.data
+# Valid: {{ doesn't count
+msgid "abc{{def"
+msgstr "xyz"
+# Valid: }} doesn't count
+msgid "abc}}def"
+msgstr "xyz"
+# Invalid: invalid msgstr
+msgid "abc{{def"
+msgstr "xyz{"
+# Invalid: invalid msgstr
+msgid "abc}}def"
+msgstr "xyz}"
+# Valid: same arguments
+msgid "abc{}{}def"
+msgstr "xyz{}{}"
+# Valid: same arguments
+msgid "abc{:s}{:g}def"
+msgstr "xyz{:s}{:g}"
+# Valid: same arguments, with different widths
+msgid "abc{:2s}def"
+msgstr "xyz{:3s}"
+# Invalid: Swapped argument types
+msgid "abc{:s}{:g}def"
+msgstr "xyz{:g}{:s}"
+# Valid: same arguments but in numbered syntax
+msgid "abc{:s}{:g}def"
+msgstr "xyz{0:s}{1:g}"
+# Valid: same arguments but in unnumbered syntax
+msgid "abc{0:s}{1:g}def"
+msgstr "xyz{:s}{:g}"
+# Valid: permutation
+msgid "abc{:s}{:g}{:c}def"
+msgstr "xyz{2:c}{1:g}{0:s}"
+# Invalid: too few arguments
+msgid "abc{1:d}def{0:s}"
+msgstr "xyz{0:s}"
+# Invalid: too few arguments
+msgid "abc{:s}def{:d}"
+msgstr "xyz{:s}"
+# Invalid: too many arguments
+msgid "abc{:d}def"
+msgstr "xyz{:d}vw{:c}"
+# Valid: same numbered arguments, with different widths
+msgid "abc{1:5s}{0:4s}"
+msgstr "xyz{1:4s}{0:5s}"
+# Invalid: missing argument
+msgid "abc{1:s}def{0:d}"
+msgstr "xyz{0:d}"
+# Invalid: missing argument
+msgid "abc{0:s}def{1:d}"
+msgstr "xyz{1:d}"
+# Invalid: added argument
+msgid "abc{0:d}def"
+msgstr "xyz{0:d}vw{1:c}"
+
+# Valid: type compatibility
+msgid "abc{:b}"
+msgstr "xyz{:B}"
+# Valid: type compatibility
+msgid "abc{:b}"
+msgstr "xyz{:d}"
+# Valid: type compatibility
+msgid "abc{:b}"
+msgstr "xyz{:o}"
+# Valid: type compatibility
+msgid "abc{:b}"
+msgstr "xyz{:x}"
+# Valid: type compatibility
+msgid "abc{:b}"
+msgstr "xyz{:X}"
+
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:A}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:e}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:E}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:f}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:F}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:g}"
+# Valid: type compatibility
+msgid "abc{:a}"
+msgstr "xyz{:G}"
+
+# Invalid: type incompatibility
+msgid "abc{:d}"
+msgstr "xyz{:e}"
+# Invalid: type incompatibility
+msgid "abc{:d}"
+msgstr "xyz{:c}"
+# Invalid: type incompatibility
+msgid "abc{:d}"
+msgstr "xyz{:s}"
+# Invalid: type incompatibility
+msgid "abc{:d}"
+msgstr "xyz{:p}"
+
+# Invalid: type incompatibility
+msgid "abc{:e}"
+msgstr "xyz{:d}"
+# Invalid: type incompatibility
+msgid "abc{:e}"
+msgstr "xyz{:c}"
+# Invalid: type incompatibility
+msgid "abc{:e}"
+msgstr "xyz{:s}"
+# Invalid: type incompatibility
+msgid "abc{:e}"
+msgstr "xyz{:p}"
+
+# Invalid: type incompatibility
+msgid "abc{:c}"
+msgstr "xyz{:d}"
+# Invalid: type incompatibility
+msgid "abc{:c}"
+msgstr "xyz{:e}"
+# Invalid: type incompatibility
+msgid "abc{:c}"
+msgstr "xyz{:s}"
+# Invalid: type incompatibility
+msgid "abc{:c}"
+msgstr "xyz{:p}"
+
+# Invalid: type incompatibility
+msgid "abc{:s}"
+msgstr "xyz{:d}"
+# Invalid: type incompatibility
+msgid "abc{:s}"
+msgstr "xyz{:e}"
+# Invalid: type incompatibility
+msgid "abc{:s}"
+msgstr "xyz{:c}"
+# Invalid: type incompatibility
+msgid "abc{:s}"
+msgstr "xyz{:p}"
+
+# Invalid: type incompatibility
+msgid "abc{:p}"
+msgstr "xyz{:d}"
+# Invalid: type incompatibility
+msgid "abc{:p}"
+msgstr "xyz{:e}"
+# Invalid: type incompatibility
+msgid "abc{:p}"
+msgstr "xyz{:c}"
+# Invalid: type incompatibility
+msgid "abc{:p}"
+msgstr "xyz{:s}"
+
+# Invalid: type incompatibility for width
+msgid "abc{:g}{:{}g}"
+msgstr "xyz{:{}g}{:g}"
+
+# Valid: adding an align specification
+msgid "abc{}def"
+msgstr "xyz{:^}"
+# Valid: adding an align specification
+msgid "abc{:}def"
+msgstr "xyz{:^}"
+# Valid: adding a fill-and-align specification
+msgid "abc{}def"
+msgstr "xyz{: ^}"
+# Valid: adding a fill-and-align specification
+msgid "abc{:}def"
+msgstr "xyz{: ^}"
+# Valid: removing an align specification
+msgid "abc{:^}def"
+msgstr "xyz{}"
+# Valid: removing an align specification
+msgid "abc{:^}def"
+msgstr "xyz{:}"
+# Valid: removing a fill-and-align specification
+msgid "abc{: ^}def"
+msgstr "xyz{}"
+# Valid: removing a fill-and-align specification
+msgid "abc{: ^}def"
+msgstr "xyz{:}"
+
+# Invalid: adding a sign specification, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{}def"
+msgstr "xyz{:-}"
+# Invalid: adding a sign specification, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{:}def"
+msgstr "xyz{:-}"
+# Valid: adding a sign specification
+msgid "abc{:d}def"
+msgstr "xyz{:-d}"
+# Valid: adding a sign specification
+msgid "abc{:e}def"
+msgstr "xyz{:-e}"
+# Invalid: adding a sign specification, since it reduces the type from [integer, character] to [integer]
+msgid "abc{:c}def"
+msgstr "xyz{:-c}"
+# Valid: removing a sign specification
+msgid "abc{:-}def"
+msgstr "xyz{}"
+# Valid: removing a sign specification
+msgid "abc{:-}def"
+msgstr "xyz{:}"
+# Valid: removing a sign specification
+msgid "abc{:-d}def"
+msgstr "xyz{:d}"
+# Valid: removing a sign specification
+msgid "abc{:-e}def"
+msgstr "xyz{:e}"
+# Valid: removing a sign specification
+msgid "abc{:-c}def"
+msgstr "xyz{:c}"
+
+# Invalid: adding a '#' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{}def"
+msgstr "xyz{:#}"
+# Invalid: adding a '#' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{:}def"
+msgstr "xyz{:#}"
+# Valid: adding a '#' option
+msgid "abc{:d}def"
+msgstr "xyz{:#d}"
+# Valid: adding a '#' option
+msgid "abc{:e}def"
+msgstr "xyz{:#e}"
+# Invalid: adding a '#' option, since it reduces the type from [integer, character] to [integer]
+msgid "abc{:c}def"
+msgstr "xyz{:#c}"
+# Valid: removing a '#' option
+msgid "abc{:#}def"
+msgstr "xyz{}"
+# Valid: removing a '#' option
+msgid "abc{:#}def"
+msgstr "xyz{:}"
+# Valid: removing a '#' option
+msgid "abc{:#d}def"
+msgstr "xyz{:d}"
+# Valid: removing a '#' option
+msgid "abc{:#e}def"
+msgstr "xyz{:e}"
+# Valid: removing a '#' option
+msgid "abc{:#c}def"
+msgstr "xyz{:c}"
+
+# Invalid: adding a '0' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{}def"
+msgstr "xyz{:0}"
+# Invalid: adding a '0' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float]
+msgid "abc{:}def"
+msgstr "xyz{:0}"
+# Valid: adding a '0' option
+msgid "abc{:d}def"
+msgstr "xyz{:0d}"
+# Valid: adding a '0' option
+msgid "abc{:e}def"
+msgstr "xyz{:0e}"
+# Invalid: adding a '0' option, since it reduces the type from [integer, character] to [integer]
+msgid "abc{:c}def"
+msgstr "xyz{:0c}"
+# Valid: removing a '0' option
+msgid "abc{:0}def"
+msgstr "xyz{}"
+# Valid: removing a '0' option
+msgid "abc{:0}def"
+msgstr "xyz{:}"
+# Valid: removing a '0' option
+msgid "abc{:0d}def"
+msgstr "xyz{:d}"
+# Valid: removing a '0' option
+msgid "abc{:0e}def"
+msgstr "xyz{:e}"
+# Valid: removing a '0' option
+msgid "abc{:0c}def"
+msgstr "xyz{:c}"
+
+# Valid: adding a width specification
+msgid "abc{}def"
+msgstr "xyz{:9}"
+# Valid: adding a width specification
+msgid "abc{:}def"
+msgstr "xyz{:9}"
+# Valid: adding a width specification
+msgid "abc{:d}def"
+msgstr "xyz{:9d}"
+# Valid: adding a width specification
+msgid "abc{:e}def"
+msgstr "xyz{:9e}"
+# Valid: adding a width specification
+msgid "abc{:c}def"
+msgstr "xyz{:9c}"
+# Valid: removing a width specification
+msgid "abc{:9}def"
+msgstr "xyz{}"
+# Valid: removing a width specification
+msgid "abc{:9}def"
+msgstr "xyz{:}"
+# Valid: removing a width specification
+msgid "abc{:9d}def"
+msgstr "xyz{:d}"
+# Valid: removing a width specification
+msgid "abc{:9e}def"
+msgstr "xyz{:e}"
+# Valid: removing a width specification
+msgid "abc{:9c}def"
+msgstr "xyz{:c}"
+
+# Invalid: adding a precision specification, since it reduces the type from [integer, float, character, string, bool, pointer] to [float, string]
+msgid "abc{}def"
+msgstr "xyz{:.9}"
+# Invalid: adding a precision specification, since it reduces the type from [integer, float, character, string, bool, pointer] to [float, string]
+msgid "abc{:}def"
+msgstr "xyz{:.9}"
+# Valid: adding a precision specification
+msgid "abc{:e}def"
+msgstr "xyz{:.9e}"
+# Invalid: adding a precision specification
+msgid "abc{:s}def"
+msgstr "xyz{:.9s}"
+# Valid: removing a precision specification
+msgid "abc{:.9}def"
+msgstr "xyz{}"
+# Valid: removing a precision specification
+msgid "abc{:.9}def"
+msgstr "xyz{:}"
+# Valid: removing a precision specification
+msgid "abc{:.9d}def"
+msgstr "xyz{:d}"
+# Valid: removing a precision specification
+msgid "abc{:.9e}def"
+msgstr "xyz{:e}"
+# Valid: removing a precision specification
+msgid "abc{:.9c}def"
+msgstr "xyz{:c}"
+
+# Invalid: adding an 'L' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float, character, bool]
+msgid "abc{}def"
+msgstr "xyz{:L}"
+# Invalid: adding an 'L' option, since it reduces the type from [integer, float, character, string, bool, pointer] to [integer, float, character, bool]
+msgid "abc{:}def"
+msgstr "xyz{:L}"
+# Valid: adding an 'L' option
+msgid "abc{:d}def"
+msgstr "xyz{:Ld}"
+# Valid: adding an 'L' option
+msgid "abc{:e}def"
+msgstr "xyz{:Le}"
+# Valid: adding an 'L' option
+msgid "abc{:c}def"
+msgstr "xyz{:Lc}"
+# Invalid: adding an 'L' option
+msgid "abc{:s}def"
+msgstr "xyz{:Ls}"
+# Valid: removing an 'L' option
+msgid "abc{:L}def"
+msgstr "xyz{}"
+# Valid: removing an 'L' option
+msgid "abc{:L}def"
+msgstr "xyz{:}"
+# Valid: removing an 'L' option
+msgid "abc{:Ld}def"
+msgstr "xyz{:d}"
+# Valid: removing an 'L' option
+msgid "abc{:Le}def"
+msgstr "xyz{:e}"
+# Valid: removing an 'L' option
+msgid "abc{:Lc}def"
+msgstr "xyz{:c}"
+# Valid: removing an 'L' option
+msgid "abc{:Ls}def"
+msgstr "xyz{:s}"
+EOF
+
+: ${MSGFMT=msgfmt}
+n=0
+while read comment; do
+ if test -z "$comment"; then
+ read comment
+ fi
+ read msgid_line
+ read msgstr_line
+ n=`expr $n + 1`
+ cat <<EOF > f-c++-2-$n.po
+#, c++-format
+${msgid_line}
+${msgstr_line}
+EOF
+ fail=
+ if echo "$comment" | grep 'Valid:' > /dev/null; then
+ if ${MSGFMT} --check-format -o f-c++-2-$n.mo f-c++-2-$n.po; then
+ :
+ else
+ fail=yes
+ fi
+ else
+ ${MSGFMT} --check-format -o f-c++-2-$n.mo f-c++-2-$n.po 2> /dev/null
+ if test $? = 1; then
+ :
+ else
+ fail=yes
+ fi
+ fi
+ if test -n "$fail"; then
+ echo "Format string checking error:" 1>&2
+ cat f-c++-2-$n.po 1>&2
+ Exit 1
+ fi
+ rm -f f-c++-2-$n.po f-c++-2-$n.mo
+done < f-c++-2.data
+
+Exit 0
--- /dev/null
+#! /bin/sh
+. "${srcdir=.}/init.sh"; path_prepend_ . ../src
+
+# Test of gettext facilities in the C++ language, according to ISO C++ 20.
+# Assumes an fr_FR locale is installed.
+# Assumes the following packages are installed: gcc g++.
+
+# This test fails if the gettext package was configured with --disable-nls,
+# because in this case the gettext-runtime/intl/ directory does not produce
+# a <libintl.h> header file.
+
+# This test may also fail with clang 16 or older. See
+# https://discourse.llvm.org/t/std-format-not-available-in-libc-15/66137
+# https://stackoverflow.com/questions/71777564/
+
+# Test whether a C++ compiler is found.
+test "${CXX}" != "no" || {
+ echo "Skipping test: no C++ compiler found"
+ Exit 77
+}
+
+# Try to find compiler option for ISO C++ 20 support.
+cat <<\EOF > test.cc
+#include <format>
+using std::vformat;
+using std::make_format_args;
+EOF
+if ${CXX} ${CXXFLAGS} ${CPPFLAGS} -c test.cc 2>/dev/null; then
+ CXX20=
+elif ${CXX} ${CXXFLAGS} -std=gnu++20 ${CPPFLAGS} -c test.cc 2>/dev/null; then
+ CXX20='-std=gnu++20'
+else
+ echo "Skipping test: C++ compiler does not support ISO C++ 20"
+ Exit 77
+fi
+CXXFLAGS="${CXXFLAGS} ${CXX20}"
+
+cat <<\EOF > prog.cc
+#include "config.h"
+
+#include <format>
+#include <iostream>
+using namespace std;
+
+#include <libintl.h>
+#include <locale.h>
+#include <stdlib.h>
+#include "xsetenv.h"
+#define _(string) gettext (string)
+
+int main (int argc, char *argv[])
+{
+ int n = atoi (argv[2]);
+
+ xsetenv ("LC_ALL", argv[1], 1);
+ if (setlocale (LC_ALL, "") == NULL)
+ // Couldn't set locale.
+ exit (77);
+
+ textdomain ("prog");
+ bindtextdomain ("prog", ".");
+
+ cout << _("'Your command, please?', asked the waiter.") << endl;
+
+ cout << vformat (ngettext ("a piece of cake", "{} pieces of cake", n),
+ make_format_args (n))
+ << endl;
+
+ cout << vformat (_("{} is replaced by {}."), make_format_args ("FF", "EUR"))
+ << endl;
+}
+EOF
+
+# Compile in two steps from .cc to .o and from .o to 'prog'. This way,
+# relinking is faster because doesn't need to redo the first step.
+# Put the -I flags before ${CXXFLAGS} ${CPPFLAGS}, to make sure that libintl.h
+# is found in the build directory, regardless of -I options present in
+# ${CXXFLAGS} or ${CPPFLAGS}.
+${CXX} -I../.. -I"$abs_top_srcdir"/gnulib-lib -I../../../gettext-runtime/intl ${CXXFLAGS} ${CPPFLAGS} -c prog.cc \
+ || Exit 1
+# Remove the -Wl,--disable-auto-import option here that is added by
+# woe32-dll.m4. Cygwin 1.7.2 does not support it in C++ mode: It gives
+# a link error about 'std::cout'.
+: ${CONFIG_SHELL=${SHELL-/bin/sh}}
+${CONFIG_SHELL} "$top_builddir"/libtool --quiet --tag=CXX --mode=link \
+ ${CXX} ${CXXFLAGS} `echo "X ${LDFLAGS} " | sed -e 's/^X//' -e 's/ -Wl,--disable-auto-import / /'` -o prog prog.${OBJEXT} \
+ ../../gnulib-lib/libgettextlib.la ${LTLIBINTL} \
+ || Exit 1
+
+: ${XGETTEXT=xgettext}
+${XGETTEXT} -o prog.tmp --omit-header --no-location -k_ prog.cc || Exit 1
+LC_ALL=C tr -d '\r' < prog.tmp > prog.pot || Exit 1
+
+cat <<EOF > prog.ok
+msgid "'Your command, please?', asked the waiter."
+msgstr ""
+
+#, c++-format
+msgid "a piece of cake"
+msgid_plural "{} pieces of cake"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c++-format
+msgid "{} is replaced by {}."
+msgstr ""
+EOF
+
+: ${DIFF=diff}
+${DIFF} prog.ok prog.pot || Exit 1
+
+cat <<\EOF > fr.po
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgid "'Your command, please?', asked the waiter."
+msgstr "«Votre commande, s'il vous plait», dit le garçon."
+
+# Les gateaux allemands sont les meilleurs du monde.
+#, c++-format
+msgid "a piece of cake"
+msgid_plural "{} pieces of cake"
+msgstr[0] "un morceau de gateau"
+msgstr[1] "{} morceaux de gateau"
+
+# Reverse the arguments.
+#, c++-format
+msgid "{} is replaced by {}."
+msgstr "{1} remplace {0}."
+EOF
+
+: ${MSGMERGE=msgmerge}
+${MSGMERGE} -q -o fr.po.tmp fr.po prog.pot || Exit 1
+LC_ALL=C tr -d '\r' < fr.po.tmp > fr.po.new || Exit 1
+
+: ${DIFF=diff}
+${DIFF} fr.po fr.po.new || Exit 1
+
+test -d fr || mkdir fr
+test -d fr/LC_MESSAGES || mkdir fr/LC_MESSAGES
+
+: ${MSGFMT=msgfmt}
+${MSGFMT} -o fr/LC_MESSAGES/prog.mo fr.po
+
+: ${DIFF=diff}
+cat <<\EOF > prog.ok
+«Votre commande, s'il vous plait», dit le garçon.
+2 morceaux de gateau
+EUR remplace FF.
+EOF
+cat <<\EOF > prog.oku
+«Votre commande, s'il vous plait», dit le garçon.
+2 morceaux de gateau
+EUR remplace FF.
+EOF
+
+: ${LOCALE_FR=fr_FR}
+: ${LOCALE_FR_UTF8=fr_FR.UTF-8}
+if test $LOCALE_FR != none; then
+ prepare_locale_ fr $LOCALE_FR
+ LANGUAGE= ./prog $LOCALE_FR 2 > prog.tmp
+ case $? in
+ 0) case "$host_os" in
+ mingw*) LC_ALL=C tr -d '\r' < prog.tmp > prog.out || Exit 1 ;;
+ *) cp prog.tmp prog.out || Exit 1 ;;
+ esac
+ ${DIFF} prog.ok prog.out || Exit 1;;
+ 77) LOCALE_FR=none;;
+ *) Exit 1;;
+ esac
+fi
+if test $LOCALE_FR_UTF8 != none; then
+ prepare_locale_ fr $LOCALE_FR_UTF8
+ LANGUAGE= ./prog $LOCALE_FR_UTF8 2 > prog.tmp
+ case $? in
+ 0) case "$host_os" in
+ mingw*) LC_ALL=C tr -d '\r' < prog.tmp > prog.out || Exit 1 ;;
+ *) cp prog.tmp prog.out || Exit 1 ;;
+ esac
+ ${DIFF} prog.oku prog.out || Exit 1;;
+ 77) LOCALE_FR_UTF8=none;;
+ *) Exit 1;;
+ esac
+fi
+if test $LOCALE_FR = none && test $LOCALE_FR_UTF8 = none; then
+ if test -f /usr/bin/localedef; then
+ echo "Skipping test: no french locale is installed"
+ else
+ echo "Skipping test: no french locale is supported"
+ fi
+ Exit 77
+fi
+
+Exit 0