From 18a978c09ebe0e56afa26f0b2b67da91ffe4301d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?K=C3=A9vin=20Commaille?= <zecakeh@tedomum.fr>
Date: Wed, 5 Jan 2022 11:08:15 +0100
Subject: [PATCH 1/2] Add support for Rust format strings.

* gettext-tools/src/message.h (enum format_type): Add format_rust.
* gettext-tools/src/message.c (format_language, format_language_pretty): Add an
entry for format_rust.
(NFORMATS): Increment.
* gettext-tools/src/xg-message.c (warn_format_string): Add a warning
for Rust.
* gettext-tools/src/format.h (formatstring_rust,
get_rust_format_unnumbered_arg_count): New declarations.
* gettext-tools/src/format.c (formatstring_parsers): Add an entry for
format_rust.
* gettext-tools/src/format-rust.c: New file, based on
gettext-tools/src/format-ruby.c.
* gettext-tools/woe32dll/gettextsrc-exports.c: Add formatstring_rust variable.
* gettext-tools/src/FILES: Add format-rust.c.
* gettext-tools/src/Makefile.am (FORMAT_SOURCE): Likewise.
* gettext-tools/src/libgettextpo/Makefile.am (AUXSOURCES): Likewise.
* gettext-tools/src/po/POTFILES.in: Likewise.
* gettext-tools/doc/gettext.texi (PO Files): Mention rust-format.
(rust-format): New section.
---
 gettext-tools/doc/gettext.texi              |   18 +-
 gettext-tools/libgettextpo/Makefile.am      |    1 +
 gettext-tools/po/POTFILES.in                |    1 +
 gettext-tools/src/FILES                     |    1 +
 gettext-tools/src/Makefile.am               |    5 +-
 gettext-tools/src/format-rust.c             | 1011 +++++++++++++++++++
 gettext-tools/src/format.c                  |    5 +-
 gettext-tools/src/format.h                  |    7 +-
 gettext-tools/src/message.c                 |    8 +-
 gettext-tools/src/message.h                 |    7 +-
 gettext-tools/src/xg-message.c              |   19 +
 gettext-tools/woe32dll/gettextsrc-exports.c |    1 +
 12 files changed, 1071 insertions(+), 13 deletions(-)
 create mode 100644 gettext-tools/src/format-rust.c

diff --git a/gettext-tools/doc/gettext.texi b/gettext-tools/doc/gettext.texi
index 2e12696e7..635e2c37e 100644
--- a/gettext-tools/doc/gettext.texi
+++ b/gettext-tools/doc/gettext.texi
@@ -79,7 +79,7 @@ This file provides documentation for GNU @code{gettext} utilities.
 It also serves as a reference for the free Translation Project.
 
 @copying
-Copyright (C) 1995-1998, 2001-2020 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020, 2022 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
@@ -114,7 +114,7 @@ A copy of the license is included in @ref{GNU GPL}.
 @page
 @vskip 0pt plus 1filll
 @c @insertcopying
-Copyright (C) 1995-1998, 2001-2020 Free Software Foundation, Inc.
+Copyright (C) 1995-1998, 2001-2020, 2022 Free Software Foundation, Inc.
 
 This manual is free documentation.  It is dually licensed under the
 GNU FDL and the GNU GPL.  This means that you can redistribute this
@@ -415,6 +415,7 @@ The Translator's View
 * gcc-internal-format::         GCC internal Format Strings
 * gfc-internal-format::         GFC internal Format Strings
 * ycp-format::                  YCP Format Strings
+* rust-format::                 Rust Format Strings
 
 Individual Programming Languages
 
@@ -1733,6 +1734,12 @@ Likewise for the GNU Fortran Compiler sources, see @ref{gfc-internal-format}.
 @kwindex no-ycp-format@r{ flag}
 Likewise for YCP, see @ref{ycp-format}.
 
+@item rust-format
+@kwindex rust-format@r{ flag}
+@itemx no-rust-format
+@kwindex no-rust-format@r{ flag}
+Likewise for Rust, see @ref{rust-format}.
+
 @end table
 
 @kwindex msgctxt
@@ -9492,6 +9499,7 @@ strings.
 * gcc-internal-format::         GCC internal Format Strings
 * gfc-internal-format::         GFC internal Format Strings
 * ycp-format::                  YCP Format Strings
+* rust-format::                 Rust Format Strings
 @end menu
 
 @node c-format
@@ -9815,6 +9823,12 @@ YCP sformat strings are described in the libycp documentation
 In summary, a directive starts with @samp{%} and is followed by @samp{%}
 or a nonzero digit (@samp{1} to @samp{9}).
 
+@node rust-format
+@subsection Rust Format Strings
+
+Rust format strings are described in the documentation of the Rust
+module @code{std::fmt}, in @uref{https://doc.rust-lang.org/std/fmt/}.
+
 
 @node Maintainers for other Languages
 @section The Maintainer's View
diff --git a/gettext-tools/libgettextpo/Makefile.am b/gettext-tools/libgettextpo/Makefile.am
index f88f8d1d6..9e64e89e5 100644
--- a/gettext-tools/libgettextpo/Makefile.am
+++ b/gettext-tools/libgettextpo/Makefile.am
@@ -93,6 +93,7 @@ libgettextpo_la_AUXSOURCES = \
   ../src/format-php.c \
   ../src/format-gcc-internal.c \
   ../src/format-gfc-internal.c \
+  ../src/format-rust.c \
   ../src/format.c \
   ../src/plural-exp.c \
   ../src/plural-eval.c \
diff --git a/gettext-tools/po/POTFILES.in b/gettext-tools/po/POTFILES.in
index aa95e5a74..120548182 100644
--- a/gettext-tools/po/POTFILES.in
+++ b/gettext-tools/po/POTFILES.in
@@ -59,6 +59,7 @@ src/format-python.c
 src/format-qt.c
 src/format-qt-plural.c
 src/format-ruby.c
+src/format-rust.c
 src/format-scheme.c
 src/format-sh.c
 src/format-smalltalk.c
diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES
index 90471bccd..cde4be905 100644
--- a/gettext-tools/src/FILES
+++ b/gettext-tools/src/FILES
@@ -247,6 +247,7 @@ format-perl-brace.c    Format string handling for Perl, braced syntax.
 format-php.c           Format string handling for PHP.
 format-gcc-internal.c  Format string handling GCC internal.
 format-gfc-internal.c  Format string handling GFC internal.
+format-rust.c           Format string handling for Rust.
 format.c        Table of the language dependent format string handlers.
 
 plural-exp.c
diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am
index f5e52ea8e..5f96f8635 100644
--- a/gettext-tools/src/Makefile.am
+++ b/gettext-tools/src/Makefile.am
@@ -1,5 +1,5 @@
 ## Makefile for the gettext-tools/src subdirectory of GNU gettext
-## Copyright (C) 1995-1998, 2000-2021 Free Software Foundation, Inc.
+## Copyright (C) 1995-1998, 2000-2022 Free Software Foundation, Inc.
 ##
 ## 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
@@ -167,7 +167,8 @@ FORMAT_SOURCE += \
   format-perl-brace.c \
   format-php.c \
   format-gcc-internal.c \
-  format-gfc-internal.c
+  format-gfc-internal.c \
+  format-rust.c
 
 # libgettextsrc contains all code that is needed by at least two programs.
 libgettextsrc_la_SOURCES = \
diff --git a/gettext-tools/src/format-rust.c b/gettext-tools/src/format-rust.c
new file mode 100644
index 000000000..d184efa32
--- /dev/null
+++ b/gettext-tools/src/format-rust.c
@@ -0,0 +1,1011 @@
+/* Rust format strings.
+   Copyright (C) 2001-2004, 2006-2009, 2019-2020, 2022 Free Software
+   Foundation, Inc. Written by Kévin Commaille <zecakeh@tedomum.fr>, 2022.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "c-ctype.h"
+#include "format-invalid.h"
+#include "format.h"
+#include "gettext.h"
+#include "unictype.h"
+#include "unistr.h"
+#include "unitypes.h"
+#include "xalloc.h"
+#include "xvasprintf.h"
+
+#define _(str) gettext (str)
+
+#define SIZEOF(a) (sizeof (a) / sizeof (a[0]))
+
+/* Rust format strings are described in https://doc.rust-lang.org/std/fmt/ and
+   are implemented in compiler/rustc_parse_format/src/lib.rs.
+   A format string consists of literal text and directives.
+   The following definitions will be used:
+     - integer: a digit sequence,
+     - numbered argument: an integer,
+     - named argument:
+       - an optional 'r#' for raw identifiers,
+       - one of those unicode character sequences:
+         - XID_Start and zero or more XID_Continue,
+         - '_' and one or more XID_Continue,
+     - count: an argument followed by '$' or an integer.
+   A format string directive here consists of
+     - an opening brace '{',
+     - an optional argument,
+     - an optional format specifier starting with ':', followed by:
+       - an optional align character '<' | '^' | '>', preceded by an optional
+         unicode fill character,
+       - an optional sign character '+' | '-',
+       - an optional alternate character '#',
+       - an optional zero-padding character '0',
+       - an optional width, specified by a count,
+       - an optional precision character '.', followed by '*' or a count,
+       - an optional type character '?' | 'x?' | 'X?' | 'o' | 'x' | 'X' | 'p' |
+         'b' | 'e' | 'E',
+     - a closing brace '}'.
+   Brace characters '{' and '}' can be escaped by doubles '{{' and '}}'.
+   A numbered argument cannot be used after a named argument.
+ */
+
+struct named_arg
+{
+  char *name;
+};
+
+struct numbered_arg
+{
+  unsigned int number;
+};
+
+struct spec
+{
+  unsigned int directives;
+  unsigned int named_arg_count;
+  unsigned int numbered_arg_count;
+  unsigned int unnumbered_arg_count;
+  struct named_arg *named;
+  struct numbered_arg *numbered;
+};
+
+/* Locale independent test for a decimal digit.
+   Argument can be  'char' or 'unsigned char'.  (Whereas the argument of
+   <ctype.h> isdigit must be an 'unsigned char'.)  */
+#undef isdigit
+#define isdigit(c) ((unsigned int)((c) - '0') < 10)
+
+static int
+named_arg_compare (const void *p1, const void *p2)
+{
+  return strcmp (((const struct named_arg *)p1)->name,
+                 ((const struct named_arg *)p2)->name);
+}
+
+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);
+}
+
+#define INVALID_RAW_IDENTIFIER(directive_number)                              \
+  xasprintf (_ ("The directive number %u has an invalid raw identifier."),    \
+             directive_number)
+
+#define INVALID_NUMBERED_AFTER_NAMED(directive_number)                        \
+  xasprintf (                                                                 \
+      _ ("The directive number %u has a numbered argument that is used "      \
+         "after a named argument."),                                          \
+      directive_number)
+
+#define INVALID_MISSING_DOLLAR(directive_number)                              \
+  xasprintf (                                                                 \
+      _ ("In the directive number %u, a string is given where it was not "    \
+         "expected. It might be a named argument missing a trailing '$'."),   \
+      directive_number)
+
+#define INVALID_PRECISION_AFTER_TYPE(directive_number)                        \
+  xasprintf (_ ("In the directive number %u, the precision is given after "   \
+                "the type."),                                                 \
+             directive_number)
+
+/* Read an UTF-8 encoded character and put it into uc.
+   Returns the length of the character.  */
+static int
+get_uc (const char **formatp, ucs4_t *uc)
+{
+  const char *format = *formatp;
+  unsigned char buf[6];
+  unsigned int count;
+  char c;
+
+  c = *format++;
+  if (c == EOF)
+    return EOF;
+  buf[0] = c;
+  count = 1;
+
+  if (buf[0] >= 0xc0)
+    {
+      c = *format++;
+      if (c == EOF)
+        return EOF;
+      buf[1] = c;
+      count = 2;
+    }
+
+  if (buf[0] >= 0xe0 && ((buf[1] ^ 0x80) < 0x40))
+    {
+      c = *format++;
+      if (c == EOF)
+        return EOF;
+      buf[2] = c;
+      count = 3;
+    }
+
+  if (buf[0] >= 0xf0 && ((buf[1] ^ 0x80) < 0x40) && ((buf[2] ^ 0x80) < 0x40))
+    {
+      c = *format++;
+      if (c == EOF)
+        return EOF;
+      buf[3] = c;
+      count = 4;
+    }
+
+  if (buf[0] >= 0xf8 && ((buf[1] ^ 0x80) < 0x40) && ((buf[2] ^ 0x80) < 0x40)
+      && ((buf[3] ^ 0x80) < 0x40))
+    {
+      c = *format++;
+      if (c == EOF)
+        return EOF;
+      buf[4] = c;
+      count = 5;
+    }
+
+  if (buf[0] >= 0xfc && ((buf[1] ^ 0x80) < 0x40) && ((buf[2] ^ 0x80) < 0x40)
+      && ((buf[3] ^ 0x80) < 0x40) && ((buf[4] ^ 0x80) < 0x40))
+    {
+      c = *format++;
+      if (c == EOF)
+        return EOF;
+      buf[5] = c;
+      count = 6;
+    }
+
+  u8_mbtouc (uc, buf, count);
+  *formatp = format;
+  return count;
+}
+
+static char *
+parse_identifier (const char **formatp)
+{
+  const char *const format_start = *formatp;
+  const char *format = *formatp;
+  char *identifier = NULL;
+  ucs4_t c = 0;
+  int len_c;
+
+  len_c = get_uc (&format, &c);
+  if (uc_is_property_xid_start (c) || c == '_')
+    {
+      do
+        len_c = get_uc (&format, &c);
+      while (uc_is_property_xid_continue (c));
+      unsigned char buf[6];
+      format -= len_c;
+
+      size_t n = format - format_start;
+      identifier = XNMALLOC (n + 1, char);
+      memcpy (identifier, format_start, n);
+      identifier[n] = '\0';
+
+      *formatp = format;
+      return identifier;
+    }
+
+  return NULL;
+}
+
+static int
+parse_integer (const char **formatp)
+{
+  const char *format = *formatp;
+  int integer = 0;
+
+  if (isdigit (*format))
+    {
+      do
+        {
+          if (integer < UINT_MAX / 10)
+            integer = 10 * integer + (*format - '0');
+          else
+            integer = UINT_MAX - 1;
+          format++;
+        }
+      while (isdigit (*format));
+      *formatp = format;
+      return integer;
+    }
+  return -1;
+}
+
+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 allocated_named, allocated_numbered;
+  struct spec *result;
+
+  spec.directives = 0;
+  spec.named_arg_count = 0;
+  spec.numbered_arg_count = 0;
+  spec.unnumbered_arg_count = 0;
+  spec.named = NULL;
+  spec.numbered = NULL;
+  allocated_named = 0;
+  allocated_numbered = 0;
+
+  for (; *format != '\0';)
+    if (*format == '{')
+      {
+        format++;
+        if (*format == '{')
+          {
+            format++;
+            continue;
+          }
+
+        /* A directive.  */
+        int number = 0;
+        char *name = NULL;
+
+        FDI_SET (format - 1, FMTDIR_START);
+        spec.directives++;
+        if (isdigit (*format))
+          {
+            if (spec.named_arg_count > 0)
+              {
+                *invalid_reason
+                    = INVALID_NUMBERED_AFTER_NAMED (spec.directives);
+                FDI_SET (format, FMTDIR_ERROR);
+                goto bad_format;
+              }
+
+            int number = parse_integer (&format);
+            if (allocated_numbered == spec.numbered_arg_count)
+              {
+                allocated_numbered = 2 * allocated_numbered + 1;
+                spec.numbered = (struct numbered_arg *)xrealloc (
+                    spec.numbered,
+                    allocated_numbered * sizeof (struct numbered_arg));
+              }
+            spec.numbered[spec.numbered_arg_count].number = number;
+            spec.numbered_arg_count++;
+          }
+        else
+          {
+            bool is_raw = false;
+            int c1 = format[0];
+            int c2 = format[1];
+            if (c1 == 'r' && c2 == '#')
+              {
+                is_raw = true;
+                format += 2;
+              }
+
+            char *name = parse_identifier (&format);
+            if (name != NULL)
+              {
+                if (allocated_named == spec.named_arg_count)
+                  {
+                    allocated_named = 2 * allocated_named + 1;
+                    spec.named = (struct named_arg *)xrealloc (
+                        spec.named,
+                        allocated_named * sizeof (struct named_arg));
+                  }
+                spec.named[spec.named_arg_count].name = name;
+                spec.named_arg_count++;
+              }
+            else if (is_raw)
+              {
+                *invalid_reason = INVALID_RAW_IDENTIFIER (spec.directives);
+                FDI_SET (format, FMTDIR_ERROR);
+                goto bad_format;
+              }
+            else
+              {
+                spec.unnumbered_arg_count++;
+              }
+          }
+
+        if (*format == ':')
+          {
+            /* Format specifiers */
+            format++;
+            bool type_seen = false;
+            bool specifier_seen = false;
+
+            ucs4_t c = 0;
+            int len_c = get_uc (&format, &c);
+            if (*format == '<' || *format == '^' || *format == '>')
+              {
+                /* Fill and align */
+                format++;
+                specifier_seen = true;
+              }
+            else if (c == '<' || c == '^' || c == '>')
+              {
+                /* Only align */
+                specifier_seen = true;
+              }
+            else
+              {
+                format -= len_c;
+              }
+
+            if (*format == '+' || *format == '-')
+              {
+                /* Sign */
+                format++;
+                specifier_seen = true;
+              }
+
+            if (*format == '#')
+              {
+                /* Alternate */
+                format++;
+                specifier_seen = true;
+              }
+
+            if (*format == '0')
+              {
+                /* Zero-padding */
+                format++;
+                specifier_seen = true;
+              }
+
+            if (isdigit (*format))
+              {
+                /* Width */
+                specifier_seen = true;
+                int width = parse_integer (&format);
+                if (*format == '$')
+                  {
+                    /* Width with numbered argument */
+                    if (spec.named_arg_count > 0)
+                      {
+                        *invalid_reason
+                            = INVALID_NUMBERED_AFTER_NAMED (spec.directives);
+                        FDI_SET (format, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+
+                    if (allocated_numbered == spec.numbered_arg_count)
+                      {
+                        allocated_numbered = 2 * allocated_numbered + 1;
+                        spec.numbered = (struct numbered_arg *)xrealloc (
+                            spec.numbered,
+                            allocated_numbered * sizeof (struct numbered_arg));
+                      }
+                    spec.numbered[spec.numbered_arg_count].number = width;
+                    spec.numbered_arg_count++;
+                    format++;
+                  }
+              }
+            else
+              {
+                bool is_raw = false;
+                int c1 = format[0];
+                int c2 = format[1];
+                if (c1 == 'r' && c2 == '#')
+                  {
+                    is_raw = true;
+                    format += 2;
+                  }
+
+                char *name = parse_identifier (&format);
+                if (name != NULL)
+                  {
+                    /* Width with named argument */
+                    specifier_seen = true;
+                    if (*format == '$')
+                      {
+                        if (allocated_named == spec.named_arg_count)
+                          {
+                            allocated_named = 2 * allocated_named + 1;
+                            spec.named = (struct named_arg *)xrealloc (
+                                spec.named,
+                                allocated_named * sizeof (struct named_arg));
+                          }
+                        spec.named[spec.named_arg_count].name = name;
+                        spec.named_arg_count++;
+                        format++;
+                      }
+                    else if (strlen (name) == 1
+                             && (*name == 'o' || *name == 'x' || *name == 'X'
+                                 || *name == 'p' || *name == 'b'
+                                 || *name == 'e' || *name == 'E'))
+                      {
+                        /* Type */
+                        specifier_seen = true;
+                        type_seen = true;
+                      }
+                    else
+                      {
+                        *invalid_reason
+                            = INVALID_MISSING_DOLLAR (spec.directives);
+                        FDI_SET (format, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                  }
+                else if (is_raw)
+                  {
+                    *invalid_reason = INVALID_RAW_IDENTIFIER (spec.directives);
+                    FDI_SET (format, FMTDIR_ERROR);
+                    goto bad_format;
+                  }
+              }
+
+            if (*format == '.')
+              {
+                /* Precision */
+                if (type_seen)
+                  {
+                    *invalid_reason
+                        = INVALID_PRECISION_AFTER_TYPE (spec.directives);
+                    FDI_SET (format, FMTDIR_ERROR);
+                    goto bad_format;
+                  }
+
+                format++;
+                specifier_seen = true;
+
+                if (*format == '*')
+                  {
+                    format++;
+                  }
+                else if (isdigit (*format))
+                  {
+                    int precision = parse_integer (&format);
+                    if (*format == '$')
+                      {
+                        /* Precision with numbered argument */
+                        if (spec.named_arg_count > 0)
+                          {
+                            *invalid_reason = INVALID_NUMBERED_AFTER_NAMED (
+                                spec.directives);
+                            FDI_SET (format, FMTDIR_ERROR);
+                            goto bad_format;
+                          }
+
+                        if (allocated_numbered == spec.numbered_arg_count)
+                          {
+                            allocated_numbered = 2 * allocated_numbered + 1;
+                            spec.numbered = (struct numbered_arg *)xrealloc (
+                                spec.numbered,
+                                allocated_numbered
+                                    * sizeof (struct numbered_arg));
+                          }
+                        spec.numbered[spec.numbered_arg_count].number
+                            = precision;
+                        spec.numbered_arg_count++;
+                        format++;
+                      }
+                  }
+                else
+                  {
+                    bool is_raw = false;
+                    int c1 = format[0];
+                    int c2 = format[1];
+                    if (c1 == 'r' && c2 == '#')
+                      {
+                        is_raw = true;
+                        format += 2;
+                      }
+
+                    char *name = parse_identifier (&format);
+                    if (name != NULL)
+                      {
+                        if (*format == '$')
+                          {
+                            /* Precision with named argument */
+                            if (allocated_named == spec.named_arg_count)
+                              {
+                                allocated_named = 2 * allocated_named + 1;
+                                spec.named = (struct named_arg *)xrealloc (
+                                    spec.named,
+                                    allocated_named
+                                        * sizeof (struct named_arg));
+                              }
+                            spec.named[spec.named_arg_count].name = name;
+                            spec.named_arg_count++;
+                            format++;
+                          }
+                        else if (strlen (name) == 1
+                                 && (*name == 'o' || *name == 'x'
+                                     || *name == 'X' || *name == 'p'
+                                     || *name == 'b' || *name == 'e'
+                                     || *name == 'E'))
+                          {
+                            /* Type */
+                            *invalid_reason = xasprintf (
+                                _ ("In the directive number %u, the "
+                                   "precision count is "
+                                   "missing before the type."),
+                                spec.directives);
+                            FDI_SET (format, FMTDIR_ERROR);
+                            goto bad_format;
+                          }
+                        else
+                          {
+                            *invalid_reason
+                                = INVALID_MISSING_DOLLAR (spec.directives);
+                            FDI_SET (format, FMTDIR_ERROR);
+                            goto bad_format;
+                          }
+                      }
+                    else if (is_raw)
+                      {
+                        *invalid_reason
+                            = INVALID_RAW_IDENTIFIER (spec.directives);
+                        FDI_SET (format, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                    else
+                      {
+                        *invalid_reason
+                            = xasprintf (_ ("In the directive number %u, the "
+                                            "precision count is "
+                                            "missing"),
+                                         spec.directives);
+                        FDI_SET (format, FMTDIR_ERROR);
+                        goto bad_format;
+                      }
+                  }
+              }
+
+            if (*format == 'o' || *format == 'x' || *format == 'X'
+                || *format == 'p' || *format == 'b' || *format == 'e'
+                || *format == 'E')
+              {
+                /* Type */
+                if (type_seen)
+                  {
+                    *invalid_reason
+                        = xasprintf (_ ("In the directive number %u, a type "
+                                        "is given twice."),
+                                     spec.directives);
+                    FDI_SET (format, FMTDIR_ERROR);
+                    goto bad_format;
+                  }
+                format++;
+                specifier_seen = true;
+              }
+
+            if (*format == '?')
+              {
+                /* Debug Type */
+                format++;
+                specifier_seen = true;
+              }
+
+            if (!specifier_seen)
+              {
+                *invalid_reason = xasprintf (
+                    _ ("In the directive number %u, a format specifier "
+                       "is missing after ':'."),
+                    spec.directives);
+                FDI_SET (format, FMTDIR_ERROR);
+                goto bad_format;
+              }
+          }
+
+        if (*format == '}')
+          {
+            /* End of directive */
+            FDI_SET (format, FMTDIR_END);
+            format++;
+          }
+        else if (*format == '\0')
+          {
+            *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
+            FDI_SET (format, FMTDIR_ERROR);
+            goto bad_format;
+          }
+        else
+          {
+            *invalid_reason
+                = xasprintf (_ ("In the directive number %u, there is an "
+                                "unknown format specifier, or the order "
+                                "of the format specifiers is wrong."),
+                             spec.directives);
+            FDI_SET (format, FMTDIR_ERROR);
+            goto bad_format;
+          }
+      }
+    else if (*format++ == '}')
+      {
+        if (*format == '}')
+          {
+            format++;
+            continue;
+          }
+        else
+          {
+            *invalid_reason = xstrdup (
+                _ ("The string has a closing brace before an opening brace"));
+            FDI_SET (format, FMTDIR_ERROR);
+            goto bad_format;
+          }
+      }
+
+  /* Sort the numbered argument array, and eliminate duplicates.  */
+  if (spec.numbered_arg_count > 1)
+    {
+      unsigned int i, j;
+
+      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.  */
+      for (i = j = 0; i < spec.numbered_arg_count; i++)
+        if (j > 0 && spec.numbered[i].number != spec.numbered[j - 1].number)
+          {
+            if (j < i)
+              {
+                spec.numbered[j].number = spec.numbered[i].number;
+              }
+            j++;
+          }
+      spec.numbered_arg_count = j;
+    }
+
+  /* Sort the named argument array, and eliminate duplicates.  */
+  if (spec.named_arg_count > 1)
+    {
+      unsigned int i, j;
+
+      qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
+             named_arg_compare);
+
+      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
+      for (i = j = 0; i < spec.named_arg_count; i++)
+        if (j > 0 && strcmp (spec.named[i].name, spec.named[j - 1].name) == 0)
+          {
+            free (spec.named[i].name);
+          }
+        else
+          {
+            if (j < i)
+              {
+                spec.named[j].name = spec.named[i].name;
+              }
+            j++;
+          }
+      spec.named_arg_count = j;
+    }
+
+  result = XMALLOC (struct spec);
+  *result = spec;
+  return result;
+
+bad_format:
+  if (spec.named != NULL)
+    {
+      unsigned int i;
+      for (i = 0; i < spec.named_arg_count; i++)
+        free (spec.named[i].name);
+      free (spec.named);
+    }
+  if (spec.numbered != NULL)
+    free (spec.numbered);
+  return NULL;
+}
+
+static void
+format_free (void *descr)
+{
+  struct spec *spec = (struct spec *)descr;
+
+  if (spec->named != NULL)
+    {
+      unsigned int i;
+      for (i = 0; i < spec->named_arg_count; i++)
+        free (spec->named[i].name);
+      free (spec->named);
+    }
+  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;
+}
+
+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->named_arg_count + spec2->named_arg_count > 0)
+    {
+      unsigned int i, j;
+      unsigned int n1 = spec1->named_arg_count;
+      unsigned int n2 = spec2->named_arg_count;
+
+      /* Check the argument names 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
+                               : strcmp (spec1->named[i].name,
+                                         spec2->named[j].name));
+
+          if (cmp > 0)
+            {
+              if (error_logger)
+                error_logger (_ ("a format specification for named argument "
+                                 "'%s', as in "
+                                 "'%s', doesn't exist in '%s'"),
+                              spec2->named[j].name, pretty_msgstr,
+                              pretty_msgid);
+              err = true;
+              break;
+            }
+          else if (cmp < 0)
+            {
+              if (equality)
+                {
+                  if (error_logger)
+                    error_logger (
+                        _ ("a format specification for named argument "
+                           "'%s' doesn't exist in '%s'"),
+                        spec1->named[i].name, pretty_msgstr);
+                  err = true;
+                  break;
+                }
+              else
+                i++;
+            }
+          else
+            j++, i++;
+        }
+    }
+
+  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 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
+                               : numbered_arg_compare (&spec1->numbered[i],
+                                                       &spec2->numbered[j]));
+
+          if (cmp > 0)
+            {
+              if (error_logger)
+                error_logger (_ ("a format specification for numbered "
+                                 "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 numbered argument "
+                           "'%u' doesn't exist in '%s'"),
+                        spec1->numbered[i].number, pretty_msgstr);
+                  err = true;
+                  break;
+                }
+              else
+                i++;
+            }
+          else
+            j++, i++;
+        }
+    }
+
+  if (spec1->unnumbered_arg_count + spec2->unnumbered_arg_count > 0)
+    {
+      unsigned int n1 = spec1->unnumbered_arg_count;
+      unsigned int n2 = spec2->unnumbered_arg_count;
+
+      if (n2 > n1)
+        {
+          if (error_logger)
+            error_logger (
+                _ ("'%s' has %u more unnumbered arguments than '%s'"),
+                pretty_msgstr, n2 - n1, pretty_msgid);
+          err = true;
+        }
+      else if (n2 < n1)
+        {
+          if (equality)
+            {
+              if (error_logger)
+                error_logger (_ ("'%s' is missing %u unnumbered arguments"),
+                              pretty_msgstr, n1 - n2);
+              err = true;
+            }
+        }
+    }
+
+  return err;
+}
+
+struct formatstring_parser formatstring_rust
+    = { format_parse, format_free, format_get_number_of_directives, NULL,
+        format_check };
+
+unsigned int
+get_rust_format_unnumbered_arg_count (const char *string)
+{
+  /* Parse the format string.  */
+  char *invalid_reason = NULL;
+  struct spec *descr
+      = (struct spec *)format_parse (string, false, NULL, &invalid_reason);
+
+  if (descr != NULL)
+    {
+      unsigned int result = descr->unnumbered_arg_count;
+
+      format_free (descr);
+      return result;
+    }
+  else
+    {
+      free (invalid_reason);
+      return 0;
+    }
+}
+
+#ifdef TEST
+
+/* Test program: Print the argument list specification returned by
+   format_parse for strings read from standard input.  */
+
+#include <stdio.h>
+
+static void
+format_print (void *descr)
+{
+  struct spec *spec = (struct spec *)descr;
+  unsigned int i;
+  bool first = true;
+
+  if (spec == NULL)
+    {
+      printf ("INVALID");
+      return;
+    }
+
+  if (spec->named_arg_count > 0)
+    {
+      first = false;
+      printf ("({");
+      for (i = 0; i < spec->named_arg_count; i++)
+        {
+          if (i > 0)
+            printf (", ");
+          printf ("'%s'", spec->named[i].name);
+        }
+      printf ("})");
+    }
+
+  if (spec->numbered_arg_count > 0)
+    {
+      if (!first)
+        printf (", ");
+      else
+        first = false;
+
+      printf ("(");
+      for (i = 0; i < spec->numbered_arg_count; i++)
+        {
+          if (i > 0)
+            printf (" ");
+          printf ("'%u'", spec->numbered[i].number);
+        }
+      printf (")");
+    }
+
+  if (spec->unnumbered_arg_count > 0)
+    {
+      if (!first)
+        printf (", ");
+
+      printf ("%u", spec->unnumbered_arg_count);
+    }
+}
+
+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-rust.c ../gnulib-lib/libgettextlib.la" End:
+ */
+
+#endif /* TEST */
diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c
index 07550707e..1ddd941d5 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, 2022 Free Software Foundation, Inc.
    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
 
    This program is free software: you can redistribute it and/or modify
@@ -63,7 +63,8 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
   /* format_php */              &formatstring_php,
   /* format_gcc_internal */     &formatstring_gcc_internal,
   /* format_gfc_internal */     &formatstring_gfc_internal,
-  /* format_ycp */              &formatstring_ycp
+  /* format_ycp */              &formatstring_ycp,
+  /* format_rust */             &formatstring_rust
 };
 
 /* Check whether both formats strings contain compatible format
diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h
index 4cd318654..3bc12e267 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, 2022 Free Software Foundation, Inc.
    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
 
    This program is free software: you can redistribute it and/or modify
@@ -126,6 +126,7 @@ extern DLL_VARIABLE struct formatstring_parser formatstring_php;
 extern DLL_VARIABLE struct formatstring_parser formatstring_gcc_internal;
 extern DLL_VARIABLE struct formatstring_parser formatstring_gfc_internal;
 extern DLL_VARIABLE struct formatstring_parser formatstring_ycp;
+extern DLL_VARIABLE struct formatstring_parser formatstring_rust;
 
 /* Table of all format string parsers.  */
 extern DLL_VARIABLE struct formatstring_parser *formatstring_parsers[NFORMATS];
@@ -148,6 +149,10 @@ extern void
    string.  */
 extern unsigned int get_python_format_unnamed_arg_count (const char *string);
 
+/* Returns the number of unnamed arguments consumed by a Rust format
+   string.  */
+extern unsigned int get_rust_format_unnumbered_arg_count (const char *string);
+
 /* Check whether both formats strings contain compatible format
    specifications for format type i (0 <= i < NFORMATS).
    Return the number of errors that were seen.  */
diff --git a/gettext-tools/src/message.c b/gettext-tools/src/message.c
index d4c1a90ce..cce3f5686 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, 2022 Free Software Foundation, Inc.
 
    This file was written by Peter Miller <millerp@canb.auug.org.au>
 
@@ -63,7 +63,8 @@ const char *const format_language[NFORMATS] =
   /* format_php */              "php",
   /* format_gcc_internal */     "gcc-internal",
   /* format_gfc_internal */     "gfc-internal",
-  /* format_ycp */              "ycp"
+  /* format_ycp */              "ycp",
+  /* format_rust */             "rust"
 };
 
 const char *const format_language_pretty[NFORMATS] =
@@ -97,7 +98,8 @@ const char *const format_language_pretty[NFORMATS] =
   /* format_php */              "PHP",
   /* format_gcc_internal */     "GCC internal",
   /* format_gfc_internal */     "GFC internal",
-  /* format_ycp */              "YCP"
+  /* format_ycp */              "YCP",
+  /* format_rust */             "Rust"
 };
 
 
diff --git a/gettext-tools/src/message.h b/gettext-tools/src/message.h
index f6936dfc7..12cb14e57 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, 2022 Free Software Foundation, Inc.
 
    This file was written by Peter Miller <millerp@canb.auug.org.au>
 
@@ -72,9 +72,10 @@ enum format_type
   format_php,
   format_gcc_internal,
   format_gfc_internal,
-  format_ycp
+  format_ycp,
+  format_rust
 };
-#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/xg-message.c b/gettext-tools/src/xg-message.c
index 7b053798c..4ee4cfaf3 100644
--- a/gettext-tools/src/xg-message.c
+++ b/gettext-tools/src/xg-message.c
@@ -234,6 +234,25 @@ and a mapping instead of a tuple for the arguments.\n"),
                                     pretty_msgstr));
       error_with_progname = true;
     }
+  else if (possible_format_p (is_format[format_rust])
+      && get_rust_format_unnumbered_arg_count (string) > 1)
+    {
+      char buffer[21];
+
+      error_with_progname = false;
+      if (pos->line_number == (size_t)(-1))
+        buffer[0] = '\0';
+      else
+        sprintf (buffer, ":%ld", (long) pos->line_number);
+      multiline_warning (xasprintf (_("%s%s: warning: "),
+                                    pos->file_name, buffer),
+                         xasprintf (_("\
+'%s' format string with unnamed or unnumbered arguments cannot be properly localized:\n\
+The translator cannot reorder the arguments.\n\
+Please consider using a format string with named or numbered arguments.\n"),
+                                    pretty_msgstr));
+      error_with_progname = true;
+    }
 }
 
 
diff --git a/gettext-tools/woe32dll/gettextsrc-exports.c b/gettext-tools/woe32dll/gettextsrc-exports.c
index 6e8ea9c0f..bdd7052fa 100644
--- a/gettext-tools/woe32dll/gettextsrc-exports.c
+++ b/gettext-tools/woe32dll/gettextsrc-exports.c
@@ -51,6 +51,7 @@ VARIABLE(formatstring_python_brace)
 VARIABLE(formatstring_qt)
 VARIABLE(formatstring_qt_plural)
 VARIABLE(formatstring_ruby)
+VARIABLE(formatstring_rust)
 VARIABLE(formatstring_scheme)
 VARIABLE(formatstring_sh)
 VARIABLE(formatstring_smalltalk)
-- 
2.34.1

