]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
xgettext: C++: Support --keyword option with a qualified name.
authorBruno Haible <bruno@clisp.org>
Fri, 8 May 2026 21:08:14 +0000 (23:08 +0200)
committerBruno Haible <bruno@clisp.org>
Fri, 8 May 2026 21:08:14 +0000 (23:08 +0200)
* 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.

NEWS
gettext-tools/src/x-c.c
gettext-tools/tests/Makefile.am
gettext-tools/tests/xgettext-c-c++-3 [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index f7981b2a54ec3e65395e1fe3a24a59f3621f5dbb..25a9c0e11a3667dfef96b79230707ee4e98e83c6 100644 (file)
--- 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:
index ad391be36ea2d35f409915c4cac1e9119666063f..efd210f299a5c82e00faeb78deff98b74484ab66 100644 (file)
@@ -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:
index 06760eba26486aa0a34dae7bbe7a478bd1aa7d49..4b1d4c0eb5e6c1a19fec1af310d1afdb0367e417 100644 (file)
@@ -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 (executable)
index 0000000..a7347ca
--- /dev/null
@@ -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