From: Bruno Haible Date: Fri, 8 May 2026 21:08:14 +0000 (+0200) Subject: xgettext: C++: Support --keyword option with a qualified name. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=231743d5e9759aee63ce3db30d9fe04dfcf5a01b;p=thirdparty%2Fgettext.git xgettext: C++: Support --keyword option with a qualified name. * gettext-tools/src/x-c.c (add_keyword): Call split_keywordspec_ok2 instead of split_keywordspec_ok. (enum token_type_ty): New enum value token_type_scope. (phase5_get): Recognize '::' as token_type_scope. (enum xgettext_token_type_ty): New enum value xgettext_token_type_scope. (phase9_pushback, phase9_pushback_length): New variables. (x_c_lex): Implement pushback. Handle token_type_scope. (x_c_unlex): New function. (extract_parenthesized): Look for qualified names. Invoke hash_find_entry for each tail of such a qualified name. * gettext-tools/tests/xgettext-c-c++-3: New file. * gettext-tools/tests/Makefile.am (TESTS): Add it. * NEWS: Mention the change. --- diff --git a/NEWS b/NEWS index f7981b2a5..25a9c0e11 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,11 @@ +Version 1.1 - July 2026 + +# Programming languages support: + * C++: + - The --keyword option now supports "qualified names" of the form + 'namespace::function', that specify a namespace or possibly even + a nested namespace. + Version 1.0 - January 2026 # Improvements for maintainers and distributors: diff --git a/gettext-tools/src/x-c.c b/gettext-tools/src/x-c.c index ad391be36..efd210f29 100644 --- a/gettext-tools/src/x-c.c +++ b/gettext-tools/src/x-c.c @@ -129,9 +129,10 @@ add_keyword (const char *name, hash_table *keywords) const char *end; struct callshape shape; split_keywordspec (name, &end, &shape); - if (split_keywordspec_ok (name, end - name)) - /* The characters between name and end should form a valid - C identifier. */ + if (split_keywordspec_ok2 (name, end - name)) + /* The characters between name and end should form a valid C identifier + or (in C++) a qualified identifier, i.e. a sequence of names + connected by scope resolution operators '::'. */ insert_keyword_callshape (keywords, name, end - name, &shape); } } @@ -959,6 +960,7 @@ enum token_type_ty token_type_rparen, /* ) */ token_type_comma, /* , */ token_type_colon, /* : */ + token_type_scope, /* :: (C++ only) */ token_type_name, /* abc */ token_type_number, /* 2.7 */ token_type_string_literal, /* "abc" */ @@ -1767,6 +1769,16 @@ phase5_get (token_ty *tp) return; case ':': + if (cxx_extensions) + { + c = phase4_getc (); + if (c == ':') + { + tp->type = token_type_scope; + return; + } + phase4_ungetc (c); + } tp->type = token_type_colon; return; @@ -2116,6 +2128,7 @@ enum xgettext_token_type_ty xgettext_token_type_rparen, xgettext_token_type_comma, xgettext_token_type_colon, + xgettext_token_type_scope, xgettext_token_type_string_literal, xgettext_token_type_other }; @@ -2144,9 +2157,18 @@ struct xgettext_token_ty /* 9. Convert the remaining preprocessing tokens to C tokens and discards any white space from the translation unit. */ +static xgettext_token_ty phase9_pushback[2]; +static int phase9_pushback_length; + static void x_c_lex (xgettext_token_ty *tp) { + if (phase9_pushback_length) + { + *tp = phase9_pushback[--phase9_pushback_length]; + return; + } + for (;;) { token_ty token; @@ -2193,6 +2215,12 @@ x_c_lex (xgettext_token_ty *tp) tp->type = xgettext_token_type_colon; return; + case token_type_scope: + last_non_comment_line = newline_count; + + tp->type = xgettext_token_type_scope; + return; + case token_type_string_literal: last_non_comment_line = newline_count; @@ -2216,6 +2244,18 @@ x_c_lex (xgettext_token_ty *tp) } } +/* Supports 2 tokens of pushback. */ +static void +x_c_unlex (xgettext_token_ty *tp) +{ + if (tp->type != xgettext_token_type_eof) + { + if (phase9_pushback_length == SIZEOF (phase9_pushback)) + abort (); + phase9_pushback[phase9_pushback_length++] = *tp; + } +} + /* ========================= Extracting strings. ========================== */ @@ -2287,36 +2327,95 @@ extract_parenthesized (message_list_ty *mlp, { case xgettext_token_type_symbol: { - void *keyword_value; - if (hash_find_entry (objc_extensions ? &objc_keywords : &c_keywords, - token.string, strlen (token.string), - &keyword_value) - == 0) + /* Combine symbol1 :: ... :: symbolN to a single string, so that + we can recognize function calls with qualified names. The + information present for symbolI::...::symbolN has precedence + over the information for symbolJ::...::symbolN with J > I. */ + char *sum = token.string; + size_t sum_len = strlen (sum); + + for (;;) { - next_shapes = (const struct callshapes *) keyword_value; - state = 1; + xgettext_token_ty token2; + x_c_lex (&token2); + + if (token2.type == xgettext_token_type_scope) + { + xgettext_token_ty token3; + x_c_lex (&token3); + + if (token3.type == xgettext_token_type_symbol) + { + char *addend = token3.string; + size_t addend_len = strlen (addend); + + sum = + (char *) xrealloc (sum, sum_len + 2 + addend_len + 1); + memcpy (sum + sum_len, "::", 2); + memcpy (sum + sum_len + 2, addend, addend_len + 1); + sum_len += 2 + addend_len; + + free (addend); + continue; + } + x_c_unlex (&token3); + } + x_c_unlex (&token2); + break; } - else - state = 0; - } - next_context_iter = - flag_context_list_iterator ( - flag_context_list_table_lookup ( - flag_context_list_table, - token.string, strlen (token.string))); - if (objc_extensions) - { - size_t token_string_len = strlen (token.string); - token.string = xrealloc (token.string, token_string_len + 2); - token.string[token_string_len] = ':'; - token.string[token_string_len + 1] = '\0'; - selectorcall_context_iter = - flag_context_list_iterator ( + + for (const char *qualifiedname = sum;;) + { + void *keyword_value; + if (hash_find_entry (objc_extensions ? &objc_keywords : &c_keywords, + qualifiedname, strlen (qualifiedname), + &keyword_value) + == 0) + { + next_shapes = (const struct callshapes *) keyword_value; + state = 1; + break; + } + + qualifiedname = strstr (qualifiedname, "::"); + if (qualifiedname == NULL) + { + state = 0; + break; + } + qualifiedname += 2; + } + + flag_context_list_ty *context_list; + for (const char *qualifiedname = sum;;) + { + context_list = flag_context_list_table_lookup ( flag_context_list_table, - token.string, token_string_len + 1)); - } - free (token.string); + qualifiedname, strlen (qualifiedname)); + if (context_list != NULL) + break; + + qualifiedname = strstr (qualifiedname, "::"); + if (qualifiedname == NULL) + break; + qualifiedname += 2; + } + next_context_iter = flag_context_list_iterator (context_list); + + if (objc_extensions) + { + sum = xrealloc (sum, sum_len + 2); + sum[sum_len] = ':'; + sum[sum_len + 1] = '\0'; + selectorcall_context_iter = + flag_context_list_iterator ( + flag_context_list_table_lookup ( + flag_context_list_table, + sum, sum_len + 1)); + } + free (sum); + } break; case xgettext_token_type_lparen: diff --git a/gettext-tools/tests/Makefile.am b/gettext-tools/tests/Makefile.am index 06760eba2..4b1d4c0eb 100644 --- a/gettext-tools/tests/Makefile.am +++ b/gettext-tools/tests/Makefile.am @@ -99,7 +99,7 @@ TESTS = gettext-1 gettext-2 \ xgettext-c-format-1 xgettext-c-format-2 xgettext-c-format-3 \ xgettext-c-format-4 xgettext-c-format-5 xgettext-c-format-6 \ xgettext-c-ctxt-1 xgettext-c-ctxt-2 xgettext-c-ctxt-3 \ - xgettext-c-c++-1 xgettext-c-c++-2 \ + xgettext-c-c++-1 xgettext-c-c++-2 xgettext-c-c++-3 \ xgettext-c-stackovfl-1 xgettext-c-stackovfl-2 \ xgettext-csharp-1 xgettext-csharp-2 xgettext-csharp-3 \ xgettext-csharp-4 xgettext-csharp-5 xgettext-csharp-6 \ diff --git a/gettext-tools/tests/xgettext-c-c++-3 b/gettext-tools/tests/xgettext-c-c++-3 new file mode 100755 index 000000000..a7347ca01 --- /dev/null +++ b/gettext-tools/tests/xgettext-c-c++-3 @@ -0,0 +1,51 @@ +#! /bin/sh +. "${srcdir=.}/init.sh"; path_prepend_ . ../src + +# Test C++ support: --keyword option with a composed function name. + +cat <<\EOF > xg-c-c++-3.cc +gettext ("Test 1"); +translate ("Test 2"); +foo ("Test 3"); +bar ("Test 4"); +baz ("Test 5"); +ResourceBundles::gettext ("Test 11"); +ResourceBundles::translate ("Test 12"); +i18n::translate ("Test 13"); +bar::baz ("Test 14"); +ResourceBundles::i18n::translate ("Test 21"); +foo::bar::baz ("Test 22"); +foo :: bar :: baz ("Test 23"); +EOF + +: ${XGETTEXT=xgettext} +${XGETTEXT} --omit-header --no-location \ + --keyword=gettext --keyword=i18n::translate --keyword=foo::bar::baz \ + -d xg-c-c++-3.tmp xg-c-c++-3.cc || Exit 1 +LC_ALL=C tr -d '\r' < xg-c-c++-3.tmp.po > xg-c-c++-3.po || Exit 1 + +cat <<\EOF > xg-c-c++-3.ok +msgid "Test 1" +msgstr "" + +msgid "Test 11" +msgstr "" + +msgid "Test 13" +msgstr "" + +msgid "Test 21" +msgstr "" + +msgid "Test 22" +msgstr "" + +msgid "Test 23" +msgstr "" +EOF + +: ${DIFF=diff} +${DIFF} xg-c-c++-3.ok xg-c-c++-3.po +result=$? + +exit $result