]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Add support for C++ format strings.
authorBruno Haible <bruno@clisp.org>
Thu, 8 Jun 2023 18:41:14 +0000 (20:41 +0200)
committerBruno Haible <bruno@clisp.org>
Thu, 8 Jun 2023 21:51:38 +0000 (23:51 +0200)
* gettext-tools/src/message.h (enum format_type): New enum item
format_cplusplus_brace.
(NFORMATS): Increment.
* gettext-tools/src/message.c (format_language, format_language_pretty): Add
entries for format_cplusplus_brace.
* gettext-tools/src/format.h (formatstring_cplusplus_brace): New declaration.
* gettext-tools/src/format-c++-brace.c: New file, based on
gettext-tools/src/format-awk.c.
* gettext-tools/src/format.c (formatstring_parsers): Add an entry for
format_cplusplus_brace.
* gettext-tools/src/Makefile.am (FORMAT_SOURCE): Add format-c++-brace.c.
* gettext-tools/src/FILES: Update.
* gettext-tools/src/x-c.h (SCANNERS_C): In language C++, use
formatstring_cplusplus_brace as second format string parser.
* gettext-tools/src/x-c.c (init_flag_table_c): Initialize flags related to
c++-format.
* gettext-tools/src/xgettext.c (xgettext_record_flag): Handle
format_cplusplus_brace.
(recognize_qt_formatstrings, language_to_extractor): Update.
* gettext-tools/src/xg-arglist-parser.c (arglist_parser_done): Update.
* gettext-tools/libgettextpo/Makefile.am (libgettextpo_la_AUXSOURCES): Add
format-c++-brace.c.
* gettext-tools/tests/format-c++-brace-1: New file, based on
gettext-tools/tests/format-c-1.
* gettext-tools/tests/format-c++-brace-2: New file, based on
gettext-tools/tests/format-c-2.
* gettext-tools/tests/lang-c++20: New file, based on
gettext-tools/tests/lang-c++.
* gettext-tools/tests/Makefile.am (TESTS): Add them.
* gettext-tools/doc/gettext.texi (PO Files): Mention c++-format and
no-c++-format.
(Preparing strings): Add subheading "No programmer-defined format string
directives".
(Mark Keywords): Mention the need to use std::vformat instead of std::format.
(c++-format): New subsection.
* gettext-tools/doc/lang-c.texi: Mention the syntax for C++ format strings.
* NEWS: Mention the change.

tweak c++-format

19 files changed:
NEWS
gettext-tools/doc/gettext.texi
gettext-tools/doc/lang-c.texi
gettext-tools/libgettextpo/Makefile.am
gettext-tools/src/FILES
gettext-tools/src/Makefile.am
gettext-tools/src/format-c++-brace.c [new file with mode: 0644]
gettext-tools/src/format.c
gettext-tools/src/format.h
gettext-tools/src/message.c
gettext-tools/src/message.h
gettext-tools/src/x-c.c
gettext-tools/src/x-c.h
gettext-tools/src/xg-arglist-parser.c
gettext-tools/src/xgettext.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/format-c++-brace-1 [new file with mode: 0755]
gettext-tools/tests/format-c++-brace-2 [new file with mode: 0755]
gettext-tools/tests/lang-c++20 [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index ef1cf3b18a3cf9de405fbe95ae35496fc42cb932..700e42e26194474f01b8cdfe220940b8ffff4724 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,8 @@ Version 0.21.2 - May 2023
     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.
 
index 610b3960eed1537541e4e01b39bc43bf8dfc521d..75f87291c0f1c8192076fec49734357b097e05ef 100644 (file)
@@ -390,6 +390,7 @@ The Translator's View
 
 * 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
@@ -1571,6 +1572,12 @@ program does some more tests to check the validity of the translation.
 @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
@@ -2048,6 +2055,10 @@ Use format strings instead of string concatenation.
 @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
@@ -2295,18 +2306,61 @@ The same holds for email addresses.
 
 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
 
@@ -2414,6 +2468,19 @@ adjacent string tokens.  Automatic string concatenation is performed
 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
@@ -9498,6 +9565,7 @@ strings.
 @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
@@ -9577,6 +9645,17 @@ Objective C format strings are like C format strings.  They support an
 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
 
index dc00fa0f57978df0ac7eb9d3db1fb3e1930f14af..5cceaaa6919adbff7604d3fa36d058ecdc15aa11 100644 (file)
@@ -1,5 +1,5 @@
 @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
@@ -52,6 +52,7 @@ Use
 @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
index 798ae98f31181aa921b77d866800709e5f48de66..a4c03cbd872b1033da858451aed64b95eaddc134 100644 (file)
@@ -69,6 +69,7 @@ libgettextpo_la_AUXSOURCES = \
   ../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 \
index 90471bccdc0b7ff9e11ed0e2afdf1bc46366cc2a..705cf8edc31c8367cb144c09381c6683c36c93b5 100644 (file)
@@ -220,6 +220,7 @@ format.h        Declarations of the language dependent format string handlers.
 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.
index fc3ae26233b52a091adc355efe18bc1556413308..3850e250940d788c515e1eb782b3ea17ee01cf4a 100644 (file)
@@ -145,6 +145,7 @@ endif
 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 \
diff --git a/gettext-tools/src/format-c++-brace.c b/gettext-tools/src/format-c++-brace.c
new file mode 100644 (file)
index 0000000..8082e6c
--- /dev/null
@@ -0,0 +1,1109 @@
+/* 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 */
index 07550707eb4daa5f98e5d3413c07784d464ee792..c70da951870eca4c0a40c32f4f840c2a2e9ad274 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -36,6 +36,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
 {
   /* 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,
index 4cd3186548f86547bb7045f6edab451c3a7beb34..650f0ee4576af2a6a4e31777e89a71f0c1498f30 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -98,6 +98,7 @@ struct formatstring_parser
 /* 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;
index d4c1a90ce47d1fac084ad6bf7f2d80774dba76cf..b852d0df8f5d8376633c54fd875a139fd500bcde 100644 (file)
@@ -1,5 +1,5 @@
 /* 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>
 
@@ -36,6 +36,7 @@ const char *const format_language[NFORMATS] =
 {
   /* format_c */                "c",
   /* format_objc */             "objc",
+  /* format_cplusplus_brace */  "c++",
   /* format_python */           "python",
   /* format_python_brace */     "python-brace",
   /* format_java */             "java",
@@ -70,6 +71,7 @@ const char *const format_language_pretty[NFORMATS] =
 {
   /* format_c */                "C",
   /* format_objc */             "Objective C",
+  /* format_cplusplus_brace */  "C++",
   /* format_python */           "Python",
   /* format_python_brace */     "Python brace",
   /* format_java */             "Java MessageFormat",
index f6936dfc7afcb9a2e516e8c24ea8f69f55825726..4fa65989e23b44fd3d92d43f6c564e04e103972d 100644 (file)
@@ -1,5 +1,5 @@
 /* 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>
 
@@ -45,6 +45,7 @@ enum format_type
 {
   format_c,
   format_objc,
+  format_cplusplus_brace,
   format_python,
   format_python_brace,
   format_java,
@@ -74,7 +75,7 @@ enum format_type
   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];
 
index 763f0317c2d94340be5412629b633a19d62cfae9..49c6b12e47355319c3d3de6197cd7637822bc14f 100644 (file)
@@ -297,6 +297,30 @@ init_flag_table_c ()
   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");
index c1635e7f431684fdf4c26a47c130e45a2a65184f..2f64b8445fee1caffb62cbf8908fe19a2fa67629 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -46,7 +46,7 @@ extern "C" {
                         &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 },          \
index cadcb18c391aaa5be94cef957ae891c148d50b36..56e197d9623c0942dfc73f9ef14bd52be39259c1 100644 (file)
@@ -411,8 +411,8 @@ arglist_parser_done (struct arglist_parser *ap, int argnum)
           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 =
index 27f703f59e411d75f00fcd8fb0c0aa4f044d793f..1c8649fb1557563e65d60686cc33be624e8a8295 100644 (file)
@@ -1537,6 +1537,20 @@ xgettext_record_flag (const char *optionstring)
                                                         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,
@@ -1617,27 +1631,27 @@ xgettext_record_flag (const char *optionstring)
                   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;
@@ -2030,7 +2044,7 @@ bool
 recognize_qt_formatstrings (void)
 {
   return recognize_format_qt
-         && current_formatstring_parser3 == &formatstring_qt_plural;
+         && current_formatstring_parser4 == &formatstring_qt_plural;
 }
 
 
@@ -2254,21 +2268,21 @@ language_to_extractor (const char *name)
         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;
index 857300f2953fc7d8373178637fd5bba231d2a1dd..04a61264a14581701f9e5d5db6ae5dc45d3306ed 100644 (file)
@@ -170,6 +170,7 @@ TESTS = gettext-1 gettext-2 \
        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 \
@@ -199,7 +200,7 @@ TESTS = gettext-1 gettext-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 \
diff --git a/gettext-tools/tests/format-c++-brace-1 b/gettext-tools/tests/format-c++-brace-1
new file mode 100755 (executable)
index 0000000..7db8183
--- /dev/null
@@ -0,0 +1,569 @@
+#! /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
diff --git a/gettext-tools/tests/format-c++-brace-2 b/gettext-tools/tests/format-c++-brace-2
new file mode 100755 (executable)
index 0000000..b7c0ede
--- /dev/null
@@ -0,0 +1,421 @@
+#! /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
diff --git a/gettext-tools/tests/lang-c++20 b/gettext-tools/tests/lang-c++20
new file mode 100755 (executable)
index 0000000..521ca6f
--- /dev/null
@@ -0,0 +1,196 @@
+#! /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