From: Bruno Haible Date: Thu, 8 Jun 2023 18:41:14 +0000 (+0200) Subject: Add support for C++ format strings. X-Git-Tag: v0.22~27 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e4e4e06e21dff1733ab195770130834811190dd6;p=thirdparty%2Fgettext.git Add support for C++ format strings. * 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 --- diff --git a/NEWS b/NEWS index ef1cf3b18..700e42e26 100644 --- 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. diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi index 610b3960e..75f87291c 100644 --- a/gettext-tools/doc/gettext.texi +++ b/gettext-tools/doc/gettext.texi @@ -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 \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{} facility and the C++ standard library's @code{} 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 diff --git a/gettext-tools/doc/lang-c.texi b/gettext-tools/doc/lang-c.texi index dc00fa0f5..5cceaaa69 100644 --- a/gettext-tools/doc/lang-c.texi +++ b/gettext-tools/doc/lang-c.texi @@ -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 diff --git a/gettext-tools/libgettextpo/Makefile.am b/gettext-tools/libgettextpo/Makefile.am index 798ae98f3..a4c03cbd8 100644 --- a/gettext-tools/libgettextpo/Makefile.am +++ b/gettext-tools/libgettextpo/Makefile.am @@ -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 \ diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES index 90471bccd..705cf8edc 100644 --- a/gettext-tools/src/FILES +++ b/gettext-tools/src/FILES @@ -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. diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am index fc3ae2623..3850e2509 100644 --- a/gettext-tools/src/Makefile.am +++ b/gettext-tools/src/Makefile.am @@ -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 index 000000000..8082e6c9d --- /dev/null +++ b/gettext-tools/src/format-c++-brace.c @@ -0,0 +1,1109 @@ +/* C++ format strings. + Copyright (C) 2003-2023 Free Software Foundation, Inc. + Written by Bruno Haible , 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include + +#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 + 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 + +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 */ diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c index 07550707e..c70da9518 100644 --- a/gettext-tools/src/format.c +++ b/gettext-tools/src/format.c @@ -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 , 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, diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h index 4cd318654..650f0ee45 100644 --- a/gettext-tools/src/format.h +++ b/gettext-tools/src/format.h @@ -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 , 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; diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c index d4c1a90ce..b852d0df8 100644 --- a/gettext-tools/src/message.c +++ b/gettext-tools/src/message.c @@ -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 @@ -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", diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h index f6936dfc7..4fa65989e 100644 --- a/gettext-tools/src/message.h +++ b/gettext-tools/src/message.h @@ -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 @@ -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]; diff --git a/gettext-tools/src/x-c.c b/gettext-tools/src/x-c.c index 763f0317c..49c6b12e4 100644 --- a/gettext-tools/src/x-c.c +++ b/gettext-tools/src/x-c.c @@ -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++ */ + 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"); diff --git a/gettext-tools/src/x-c.h b/gettext-tools/src/x-c.h index c1635e7f4..2f64b8445 100644 --- a/gettext-tools/src/x-c.h +++ b/gettext-tools/src/x-c.h @@ -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 , 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 }, \ diff --git a/gettext-tools/src/xg-arglist-parser.c b/gettext-tools/src/xg-arglist-parser.c index cadcb18c3..56e197d96 100644 --- a/gettext-tools/src/xg-arglist-parser.c +++ b/gettext-tools/src/xg-arglist-parser.c @@ -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 = diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c index 27f703f59..1c8649fb1 100644 --- a/gettext-tools/src/xgettext.c +++ b/gettext-tools/src/xgettext.c @@ -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; diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index 857300f29..04a61264a 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -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 index 000000000..7db818363 --- /dev/null +++ b/gettext-tools/tests/format-c++-brace-1 @@ -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 < 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 index 000000000..b7c0edec6 --- /dev/null +++ b/gettext-tools/tests/format-c++-brace-2 @@ -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 < 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 index 000000000..521ca6f97 --- /dev/null +++ b/gettext-tools/tests/lang-c++20 @@ -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 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 +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 +#include +using namespace std; + +#include +#include +#include +#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 < 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