Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
From: simon.marchi@polymtl.ca
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@efficios.com>
Subject: [PATCH 09/17] gdb: move cp-name-parser.y's support code to cp-name-parser.c
Date: Fri,  4 Sep 2026 12:56:41 -0400	[thread overview]
Message-ID: <20260904170338.1643894-10-simon.marchi@polymtl.ca> (raw)
In-Reply-To: <20260904170338.1643894-1-simon.marchi@polymtl.ca>

From: Simon Marchi <simon.marchi@efficios.com>

The goal of this patch (and the following ones for the other parsers) is
to move the C++ support code (everything but the parser rules
themselves) from cp-name-parser.y to cp-name-parser.c.  The reason for
this is that I find it hard to read and maintain complex code in a .y
file, where standard C++ tooling doesn't work.

Move all the epilogue out to cp-name-parser.c, leaving cp-name-parser.y
with just the prologue and the grammar rules.  The code is mostly moved
as-is.

 - struct cpname_state, along with the flag constants for d_qualify and
   d_int_type and the d_left/d_right macros, moves to the
   cp-name-parser.h header, so that both cp-name-parser-gen.c and
   cp-name-parser.c can see it.

 - The old yylex and yyerror functions are renamed explicitly to
   cpname_yylex and cpname_yyerror.  They used to be effectively named
   that, thanks to the parser generator's -p flag, but now that they
   live in cp-name-parser.c, they just have that name.  They are
   declared in cp-name-parser.h, so that cp-name-parser-gen.c can see
   them.  cp-name-parser-gen.c calls yylex and yyerror, which -p turns
   into cpname_yylex and cpname_yyerror.

 - cp_demangled_name_to_comp is the function that calls into the
   generated parser's entry point, yyparse, or rather cpname_yyparse,
   thanks to the -p flag.  Bison declares yyparse in the generated
   header, but byacc does not, so cp-name-parser.c has to declare it by
   hand unfortunately.

 - cp-support.c had its own copies of the d_left and d_right macros,
   drop them and use the ones from cp-name-parser.h.

I wrapped most of cp-name-parser.{c,h} in namespace `cp_name_parser`.
There is no real risk of symbol collisions here, but this is for
consistency with the subsequent patches, so that all parsers use the
same patterns.

Add cp-name-parser.c to STALE_GENERATED_FILES, such that a stale
cp-name-parser.c file in the build directory will make the build stop with
a clear error.

Change-Id: I2a47664d8e68f3e9ab41cf42378d2c61dcaa27f1
---
 gdb/Makefile.in      |    4 +-
 gdb/cp-name-parser.c | 1050 +++++++++++++++++++++++++++++++++++++++
 gdb/cp-name-parser.h |  130 +++++
 gdb/cp-name-parser.y | 1115 +-----------------------------------------
 gdb/cp-support.c     |    4 +-
 5 files changed, 1187 insertions(+), 1116 deletions(-)
 create mode 100644 gdb/cp-name-parser.c
 create mode 100644 gdb/cp-name-parser.h

diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 6cb5482bbc66..a7d79e08655e 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1077,6 +1077,7 @@ COMMON_SFILES = \
 	corefile.c \
 	corelow.c \
 	cp-abi.c \
+	cp-name-parser.c \
 	cp-namespace.c \
 	cp-support.c \
 	cp-valprint.c \
@@ -1374,6 +1375,7 @@ HFILES_NO_SRCDIR = \
 	config/nm-linux.h \
 	config/sparc/nm-sol2.h \
 	cp-abi.h \
+	cp-name-parser.h \
 	cp-support.h \
 	cris-tdep.h \
 	csky-tdep.h \
@@ -2081,7 +2083,7 @@ YYHFILES = $(patsubst %-gen.c,%-gen.h,$(YYFILES))
 #
 # Files in there can be removed after a while, once build directories with them
 # are unlikely to be around.
-STALE_GENERATED_FILES =
+STALE_GENERATED_FILES = cp-name-parser.c
 
 # Things which need to be built when making a distribution.
 
diff --git a/gdb/cp-name-parser.c b/gdb/cp-name-parser.c
new file mode 100644
index 000000000000..d04ed3594878
--- /dev/null
+++ b/gdb/cp-name-parser.c
@@ -0,0 +1,1050 @@
+/* Helper functions for the C++ name parser, for GDB.
+
+   Copyright (C) 2003-2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 <http://www.gnu.org/licenses/>.  */
+
+#include "cp-name-parser.h"
+#include "cp-name-parser-gen.h"
+#include "c-support.h"
+#include "cp-support.h"
+#include "gdbsupport/gdb_obstack.h"
+#include "gdbsupport/selftest.h"
+
+/* The entry point of the bison/yacc-generated parser, defined in
+   cp-name-parser-gen.c.  Bison produces a declaration for yyparse in
+   cp-name-parser-gen.h, but byacc does not, hence this declaration.  */
+
+int cpname_yyparse (cp_name_parser::cpname_state *state);
+
+namespace cp_name_parser {
+
+struct demangle_component *
+cpname_state::d_grab ()
+{
+  return obstack_new<demangle_component> (&demangle_info->obstack);
+}
+
+/* Helper functions.  These wrap the demangler tree interface, handle
+   allocation from our global store, and return the allocated component.  */
+
+struct demangle_component *
+cpname_state::fill_comp (enum demangle_component_type d_type,
+			 struct demangle_component *lhs,
+			 struct demangle_component *rhs)
+{
+  struct demangle_component *ret = d_grab ();
+  int i;
+
+  i = cplus_demangle_fill_component (ret, d_type, lhs, rhs);
+  gdb_assert (i);
+
+  return ret;
+}
+
+struct demangle_component *
+cpname_state::make_operator (const char *name, int args)
+{
+  struct demangle_component *ret = d_grab ();
+  int i;
+
+  i = cplus_demangle_fill_operator (ret, name, args);
+  gdb_assert (i);
+
+  return ret;
+}
+
+struct demangle_component *
+cpname_state::make_dtor (enum gnu_v3_dtor_kinds kind,
+			 struct demangle_component *name)
+{
+  struct demangle_component *ret = d_grab ();
+  int i;
+
+  i = cplus_demangle_fill_dtor (ret, kind, name);
+  gdb_assert (i);
+
+  return ret;
+}
+
+struct demangle_component *
+cpname_state::make_builtin_type (const char *name)
+{
+  struct demangle_component *ret = d_grab ();
+  int i;
+
+  i = cplus_demangle_fill_builtin_type (ret, name);
+  gdb_assert (i);
+
+  return ret;
+}
+
+struct demangle_component *
+cpname_state::make_name (const char *name, int len)
+{
+  struct demangle_component *ret = d_grab ();
+  int i;
+
+  i = cplus_demangle_fill_name (ret, name, len);
+  gdb_assert (i);
+
+  return ret;
+}
+
+/* Apply QUALIFIERS to LHS and return a qualified component.  IS_METHOD
+   is set if LHS is a method, in which case the qualifiers are logically
+   applied to "this".  We apply qualifiers in a consistent order; LHS
+   may already be qualified; duplicate qualifiers are not created.  */
+
+struct demangle_component *
+cpname_state::d_qualify (struct demangle_component *lhs, int qualifiers,
+			 int is_method)
+{
+  struct demangle_component **inner_p;
+  enum demangle_component_type type;
+
+  /* For now the order is CONST (innermost), VOLATILE, RESTRICT.  */
+
+#define HANDLE_QUAL(TYPE, MTYPE, QUAL)				\
+  if ((qualifiers & QUAL) && (type != TYPE) && (type != MTYPE))	\
+    {								\
+      *inner_p = fill_comp (is_method ? MTYPE : TYPE,		\
+			    *inner_p, NULL);			\
+      inner_p = &d_left (*inner_p);				\
+      type = (*inner_p)->type;					\
+    }								\
+  else if (type == TYPE || type == MTYPE)			\
+    {								\
+      inner_p = &d_left (*inner_p);				\
+      type = (*inner_p)->type;					\
+    }
+
+  inner_p = &lhs;
+
+  type = (*inner_p)->type;
+
+  HANDLE_QUAL (DEMANGLE_COMPONENT_RESTRICT, DEMANGLE_COMPONENT_RESTRICT_THIS, QUAL_RESTRICT);
+  HANDLE_QUAL (DEMANGLE_COMPONENT_VOLATILE, DEMANGLE_COMPONENT_VOLATILE_THIS, QUAL_VOLATILE);
+  HANDLE_QUAL (DEMANGLE_COMPONENT_CONST, DEMANGLE_COMPONENT_CONST_THIS, QUAL_CONST);
+#undef HANDLE_QUAL
+
+  return lhs;
+}
+
+/* Return a builtin type corresponding to FLAGS.  */
+
+struct demangle_component *
+cpname_state::d_int_type (int flags)
+{
+  const char *name;
+
+  switch (flags)
+    {
+    case INT_SIGNED | INT_CHAR:
+      name = "signed char";
+      break;
+    case INT_CHAR:
+      name = "char";
+      break;
+    case INT_UNSIGNED | INT_CHAR:
+      name = "unsigned char";
+      break;
+    case 0:
+    case INT_SIGNED:
+      name = "int";
+      break;
+    case INT_UNSIGNED:
+      name = "unsigned int";
+      break;
+    case INT_LONG:
+    case INT_SIGNED | INT_LONG:
+      name = "long";
+      break;
+    case INT_UNSIGNED | INT_LONG:
+      name = "unsigned long";
+      break;
+    case INT_SHORT:
+    case INT_SIGNED | INT_SHORT:
+      name = "short";
+      break;
+    case INT_UNSIGNED | INT_SHORT:
+      name = "unsigned short";
+      break;
+    case INT_LLONG | INT_LONG:
+    case INT_SIGNED | INT_LLONG | INT_LONG:
+      name = "long long";
+      break;
+    case INT_UNSIGNED | INT_LLONG | INT_LONG:
+      name = "unsigned long long";
+      break;
+    default:
+      return NULL;
+    }
+
+  return make_builtin_type (name);
+}
+
+/* Wrapper to create a unary operation.  */
+
+struct demangle_component *
+cpname_state::d_unary (const char *name, struct demangle_component *lhs)
+{
+  return fill_comp (DEMANGLE_COMPONENT_UNARY, make_operator (name, 1), lhs);
+}
+
+/* Wrapper to create a binary operation.  */
+
+struct demangle_component *
+cpname_state::d_binary (const char *name, struct demangle_component *lhs,
+			struct demangle_component *rhs)
+{
+  return fill_comp (DEMANGLE_COMPONENT_BINARY, make_operator (name, 2),
+		    fill_comp (DEMANGLE_COMPONENT_BINARY_ARGS, lhs, rhs));
+}
+
+/* Take care of parsing a number (anything that starts with a digit).
+   The number starts at P and contains LEN characters.  Store the result in
+   YYLVAL.  */
+
+int
+cpname_state::parse_number (const char *p, int len, int parsed_float,
+			    cp_name_parser_YYSTYPE *lvalp)
+{
+  int unsigned_p = 0;
+
+  /* Number of "L" suffixes encountered.  */
+  int long_p = 0;
+
+  struct demangle_component *type, *name;
+  enum demangle_component_type literal_type;
+
+  if (p[0] == '-')
+    {
+      literal_type = DEMANGLE_COMPONENT_LITERAL_NEG;
+      p++;
+      len--;
+    }
+  else
+    literal_type = DEMANGLE_COMPONENT_LITERAL;
+
+  if (parsed_float)
+    {
+      /* It's a float since it contains a point or an exponent.  */
+      char c;
+
+      /* The GDB lexer checks the result of scanf at this point.  Not doing
+	 this leaves our error checking slightly weaker but only for invalid
+	 data.  */
+
+      /* See if it has `f' or `l' suffix (float or long double).  */
+
+      c = c_tolower (p[len - 1]);
+
+      if (c == 'f')
+	{
+	  len--;
+	  type = make_builtin_type ("float");
+	}
+      else if (c == 'l')
+	{
+	  len--;
+	  type = make_builtin_type ("long double");
+	}
+      else if (c_isdigit (c) || c == '.')
+	type = make_builtin_type ("double");
+      else
+	return ERROR;
+
+      name = make_name (p, len);
+      lvalp->comp = fill_comp (literal_type, type, name);
+
+      return FLOAT;
+    }
+
+  /* Note that we do not automatically generate unsigned types.  This
+     can't be done because we don't have access to the gdbarch
+     here.  */
+
+  int base = 10;
+  if (len > 1 && p[0] == '0')
+    {
+      if (p[1] == 'x' || p[1] == 'X')
+	{
+	  base = 16;
+	  p += 2;
+	  len -= 2;
+	}
+      else if (p[1] == 'b' || p[1] == 'B')
+	{
+	  base = 2;
+	  p += 2;
+	  len -= 2;
+	}
+      else if (p[1] == 'd' || p[1] == 'D' || p[1] == 't' || p[1] == 'T')
+	{
+	  /* Apparently gdb extensions.  */
+	  base = 10;
+	  p += 2;
+	  len -= 2;
+	}
+      else
+	base = 8;
+    }
+
+  long_p = 0;
+  unsigned_p = 0;
+  while (len > 0)
+    {
+      if (p[len - 1] == 'l' || p[len - 1] == 'L')
+	{
+	  len--;
+	  long_p++;
+	  continue;
+	}
+      if (p[len - 1] == 'u' || p[len - 1] == 'U')
+	{
+	  len--;
+	  unsigned_p++;
+	  continue;
+	}
+      break;
+    }
+
+  /* Use gdb_mpz here in case a 128-bit value appears.  */
+  gdb_mpz value (0);
+  for (int off = 0; off < len; ++off)
+    {
+      int dig;
+
+      if (c_isdigit (p[off]))
+	dig = p[off] - '0';
+      else
+	dig = c_tolower (p[off]) - 'a' + 10;
+
+      if (dig >= base)
+	return ERROR;
+
+      value *= base;
+      value += dig;
+    }
+
+  std::string printed = value.str ();
+  const char *copy = obstack_strdup (&demangle_info->obstack, printed);
+
+  if (long_p == 0)
+    {
+      if (unsigned_p)
+	type = make_builtin_type ("unsigned int");
+      else
+	type = make_builtin_type ("int");
+    }
+  else if (long_p == 1)
+    {
+      if (unsigned_p)
+	type = make_builtin_type ("unsigned long");
+      else
+	type = make_builtin_type ("long");
+    }
+  else
+    {
+      if (unsigned_p)
+	type = make_builtin_type ("unsigned long long");
+      else
+	type = make_builtin_type ("long long");
+    }
+
+  name = make_name (copy, strlen (copy));
+  lvalp->comp = fill_comp (literal_type, type, name);
+
+  return INT;
+}
+
+} /* namespace cp_name_parser */
+
+/* See cp-support.h.  */
+
+gdb::unique_xmalloc_ptr<char>
+cp_comp_to_string (struct demangle_component *result, int estimated_len)
+{
+  size_t err;
+
+  char *res = gdb_cplus_demangle_print (DMGL_PARAMS | DMGL_ANSI,
+					result, estimated_len, &err);
+  return gdb::unique_xmalloc_ptr<char> (res);
+}
+
+/* Merge the two parse trees given by DEST and SRC.  The parse tree
+   in SRC is attached to DEST at the node represented by TARGET.
+
+   NOTE 1: Since there is no API to merge obstacks, this function does
+   even attempt to try it.  Fortunately, we do not (yet?) need this ability.
+   The code will assert if SRC->obstack is not empty.
+
+   NOTE 2: The string from which SRC was parsed must not be freed, since
+   this function will place pointers to that string into DEST.  */
+
+void
+cp_merge_demangle_parse_infos (struct demangle_parse_info *dest,
+			       struct demangle_component *target,
+			       demangle_parse_info_up src)
+{
+  /* Copy the SRC's parse data into DEST.  */
+  *target = *src->tree;
+
+  /* Make sure SRC is owned by DEST.  */
+  dest->infos.push_back (std::move (src));
+}
+
+/* Convert a demangled name to a demangle_component tree.  On success,
+   a structure containing the root of the new tree is returned.  On
+   error, NULL is returned, and an error message will be set in
+   *ERRMSG.  */
+
+demangle_parse_info_up
+cp_demangled_name_to_comp (const char *demangled_name,
+			   std::string *errmsg)
+{
+  auto result = std::make_unique<demangle_parse_info> ();
+  cp_name_parser::cpname_state state (demangled_name, result.get ());
+
+  /* Note that we can't set yydebug here, as is done in the other
+     parsers.  Bison implements yydebug as a global, even with a pure
+     parser, and this parser is run from worker threads.  So, changing
+     yydebug causes TSan reports.  If you need to debug this parser,
+     debug gdb and set the global from the outer gdb.  */
+  if (cpname_yyparse (&state))
+    {
+      if (state.global_errmsg && errmsg)
+	*errmsg = state.global_errmsg;
+      return NULL;
+    }
+
+  result->tree = state.global_result;
+
+  return result;
+}
+
+/* Find the end of a symbol name starting at LEXPTR.  */
+
+static const char *
+symbol_end (const char *lexptr)
+{
+  const char *p = lexptr;
+
+  while (*p && (c_ident_is_alnum (*p) || *p == '_' || *p == '$' || *p == '.'))
+    p++;
+
+  return p;
+}
+
+static const char backslashable[] = "abefnrtv";
+static const char represented[] = "\a\b\e\f\n\r\t\v";
+
+/* Translate the backslash the way we would in the host character set.  */
+static int
+c_parse_backslash (int host_char, int *target_char)
+{
+  const char *ix;
+  ix = strchr (backslashable, host_char);
+  if (! ix)
+    return 0;
+  else
+    *target_char = represented[ix - backslashable];
+  return 1;
+}
+
+/* Parse a C escape sequence.  STRING_PTR points to a variable
+   containing a pointer to the string to parse.  That pointer
+   should point to the character after the \.  That pointer
+   is updated past the characters we use.  The value of the
+   escape sequence is returned.
+
+   A negative value means the sequence \ newline was seen,
+   which is supposed to be equivalent to nothing at all.
+
+   If \ is followed by a null character, we return a negative
+   value and leave the string pointer pointing at the null character.
+
+   If \ is followed by 000, we return 0 and leave the string pointer
+   after the zeros.  A value of 0 does not mean end of string.  */
+
+static int
+cp_parse_escape (const char **string_ptr)
+{
+  int target_char;
+  int c = *(*string_ptr)++;
+  if (c_parse_backslash (c, &target_char))
+    return target_char;
+  else
+    switch (c)
+      {
+      case '\n':
+	return -2;
+      case 0:
+	(*string_ptr)--;
+	return 0;
+      case '^':
+	{
+	  c = *(*string_ptr)++;
+
+	  if (c == '?')
+	    return 0177;
+	  else if (c == '\\')
+	    target_char = cp_parse_escape (string_ptr);
+	  else
+	    target_char = c;
+
+	  /* Now target_char is something like `c', and we want to find
+	     its control-character equivalent.  */
+	  target_char = target_char & 037;
+
+	  return target_char;
+	}
+
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+	{
+	  int i = c - '0';
+	  int count = 0;
+	  while (++count < 3)
+	    {
+	      c = (**string_ptr);
+	      if (c >= '0' && c <= '7')
+		{
+		  (*string_ptr)++;
+		  i *= 8;
+		  i += c - '0';
+		}
+	      else
+		{
+		  break;
+		}
+	    }
+	  return i;
+	}
+      default:
+	return c;
+      }
+}
+
+#define HANDLE_SPECIAL(string, comp)				\
+  if (startswith (tokstart, string))				\
+    {								\
+      state->lexptr = tokstart + sizeof (string) - 1;			\
+      lvalp->lval = comp;					\
+      return DEMANGLER_SPECIAL;					\
+    }
+
+#define HANDLE_TOKEN2(string, token)			\
+  if (state->lexptr[1] == string[1])				\
+    {							\
+      state->lexptr += 2;					\
+      lvalp->opname = string;				\
+      return token;					\
+    }
+
+#define HANDLE_TOKEN3(string, token)			\
+  if (state->lexptr[1] == string[1] && state->lexptr[2] == string[2])	\
+    {							\
+      state->lexptr += 3;					\
+      lvalp->opname = string;				\
+      return token;					\
+    }
+
+void
+cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg)
+{
+  if (state->global_errmsg)
+    return;
+
+  state->error_lexptr = state->prev_lexptr;
+  state->global_errmsg = msg ? msg : "parse error";
+}
+
+/* Read one token, getting characters through LEXPTR.  */
+
+int
+cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
+	      cp_name_parser::cpname_state *state)
+{
+  int c;
+  int namelen;
+  const char *tokstart;
+  char *copy;
+
+ retry:
+  state->prev_lexptr = state->lexptr;
+  tokstart = state->lexptr;
+
+  switch (c = *tokstart)
+    {
+    case 0:
+      return 0;
+
+    case ' ':
+    case '\t':
+    case '\n':
+      state->lexptr++;
+      goto retry;
+
+    case '\'':
+      /* We either have a character constant ('0' or '\177' for example)
+	 or we have a quoted symbol reference ('foo(int,int)' in C++
+	 for example). */
+      state->lexptr++;
+      c = *state->lexptr++;
+      if (c == '\\')
+	c = cp_parse_escape (&state->lexptr);
+      else if (c == '\'')
+	{
+	  cpname_yyerror (state, _("empty character constant"));
+	  return ERROR;
+	}
+
+      /* We over-allocate here, but it doesn't really matter . */
+      copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30);
+      xsnprintf (copy, 30, "%d", c);
+
+      c = *state->lexptr++;
+      if (c != '\'')
+	{
+	  cpname_yyerror (state, _("invalid character constant"));
+	  return ERROR;
+	}
+
+      lvalp->comp
+	= state->fill_comp (DEMANGLE_COMPONENT_LITERAL,
+			    state->make_builtin_type ("char"),
+			    state->make_name (copy, strlen (copy)));
+
+      return INT;
+
+    case '(':
+      if (startswith (tokstart, "(anonymous namespace)"))
+	{
+	  state->lexptr += 21;
+	  lvalp->comp = state->make_name ("(anonymous namespace)",
+					  sizeof "(anonymous namespace)" - 1);
+	  return NAME;
+	}
+	[[fallthrough]];
+
+    case ')':
+    case ',':
+      state->lexptr++;
+      return c;
+
+    case '.':
+      if (state->lexptr[1] == '.' && state->lexptr[2] == '.')
+	{
+	  state->lexptr += 3;
+	  return ELLIPSIS;
+	}
+
+      /* Might be a floating point number.  */
+      if (state->lexptr[1] < '0' || state->lexptr[1] > '9')
+	goto symbol;		/* Nope, must be a symbol. */
+
+      goto try_number;
+
+    case '-':
+      HANDLE_TOKEN2 ("-=", ASSIGN_MODIFY);
+      HANDLE_TOKEN2 ("--", DECREMENT);
+      HANDLE_TOKEN2 ("->", ARROW);
+
+      /* For construction vtables.  This is kind of hokey.  */
+      if (startswith (tokstart, "-in-"))
+	{
+	  state->lexptr += 4;
+	  return CONSTRUCTION_IN;
+	}
+
+      if (state->lexptr[1] < '0' || state->lexptr[1] > '9')
+	{
+	  state->lexptr++;
+	  return '-';
+	}
+
+    try_number:
+      [[fallthrough]];
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      {
+	/* It's a number.  */
+	int got_dot = 0, got_e = 0, toktype;
+	const char *p = tokstart;
+	int hex = 0;
+
+	if (c == '-')
+	  p++;
+
+	if (c == '0' && (p[1] == 'x' || p[1] == 'X'))
+	  {
+	    p += 2;
+	    hex = 1;
+	  }
+	else if (c == '0' && (p[1]=='t' || p[1]=='T' || p[1]=='d' || p[1]=='D'))
+	  {
+	    p += 2;
+	    hex = 0;
+	  }
+
+	/* If the token includes the C++14 digits separator, we make a
+	   copy so that we don't have to handle the separator in
+	   parse_number.  */
+	std::optional<std::string> no_tick;
+	for (;; ++p)
+	  {
+	    /* This test includes !hex because 'e' is a valid hex digit
+	       and thus does not indicate a floating point number when
+	       the radix is hex.  */
+	    if (!hex && !got_e && (*p == 'e' || *p == 'E'))
+	      got_dot = got_e = 1;
+	    /* This test does not include !hex, because a '.' always indicates
+	       a decimal floating point number regardless of the radix.
+
+	       NOTE drow/2005-03-09: This comment is not accurate in C99;
+	       however, it's not clear that all the floating point support
+	       in this file is doing any good here.  */
+	    else if (!got_dot && *p == '.')
+	      got_dot = 1;
+	    else if (got_e && (p[-1] == 'e' || p[-1] == 'E')
+		     && (*p == '-' || *p == '+'))
+	      {
+		/* This is the sign of the exponent, not the end of
+		   the number.  */
+	      }
+	    /* C++14 allows a separator.  */
+	    else if (*p == '\'')
+	      {
+		if (!no_tick.has_value ())
+		  no_tick.emplace (tokstart, p);
+		continue;
+	      }
+	    /* We will take any letters or digits.  parse_number will
+	       complain if past the radix, or if L or U are not final.  */
+	    else if (! c_isalnum (*p))
+	      break;
+	    if (no_tick.has_value ())
+	      no_tick->push_back (*p);
+	  }
+	if (no_tick.has_value ())
+	  toktype = state->parse_number (no_tick->c_str (), no_tick->length (),
+					 got_dot | got_e, lvalp);
+	else
+	  toktype = state->parse_number (tokstart, p - tokstart,
+					 got_dot | got_e, lvalp);
+	if (toktype == ERROR)
+	  {
+	    cpname_yyerror (state, _("invalid number"));
+	    return ERROR;
+	  }
+	state->lexptr = p;
+	return toktype;
+      }
+
+    case '+':
+      HANDLE_TOKEN2 ("+=", ASSIGN_MODIFY);
+      HANDLE_TOKEN2 ("++", INCREMENT);
+      state->lexptr++;
+      return c;
+    case '*':
+      HANDLE_TOKEN2 ("*=", ASSIGN_MODIFY);
+      state->lexptr++;
+      return c;
+    case '/':
+      HANDLE_TOKEN2 ("/=", ASSIGN_MODIFY);
+      state->lexptr++;
+      return c;
+    case '%':
+      HANDLE_TOKEN2 ("%=", ASSIGN_MODIFY);
+      state->lexptr++;
+      return c;
+    case '|':
+      HANDLE_TOKEN2 ("|=", ASSIGN_MODIFY);
+      HANDLE_TOKEN2 ("||", OROR);
+      state->lexptr++;
+      return c;
+    case '&':
+      HANDLE_TOKEN2 ("&=", ASSIGN_MODIFY);
+      HANDLE_TOKEN2 ("&&", ANDAND);
+      state->lexptr++;
+      return c;
+    case '^':
+      HANDLE_TOKEN2 ("^=", ASSIGN_MODIFY);
+      state->lexptr++;
+      return c;
+    case '!':
+      HANDLE_TOKEN2 ("!=", NOTEQUAL);
+      state->lexptr++;
+      return c;
+    case '<':
+      HANDLE_TOKEN3 ("<<=", ASSIGN_MODIFY);
+      HANDLE_TOKEN3 ("<=>", SPACESHIP);
+      HANDLE_TOKEN2 ("<=", LEQ);
+      HANDLE_TOKEN2 ("<<", LSH);
+      state->lexptr++;
+      return c;
+    case '>':
+      HANDLE_TOKEN3 (">>=", ASSIGN_MODIFY);
+      HANDLE_TOKEN2 (">=", GEQ);
+      HANDLE_TOKEN2 (">>", RSH);
+      state->lexptr++;
+      return c;
+    case '=':
+      HANDLE_TOKEN2 ("==", EQUAL);
+      state->lexptr++;
+      return c;
+    case ':':
+      HANDLE_TOKEN2 ("::", COLONCOLON);
+      state->lexptr++;
+      return c;
+
+    case '[':
+    case ']':
+    case '?':
+    case '@':
+    case '~':
+    case '{':
+    case '}':
+    symbol:
+      state->lexptr++;
+      return c;
+
+    case '"':
+      /* These can't occur in C++ names.  */
+      cpname_yyerror (state, _("unexpected string literal"));
+      return ERROR;
+    }
+
+  if (!(c == '_' || c == '$' || c_ident_is_alpha (c)))
+    {
+      /* We must have come across a bad character (e.g. ';').  */
+      cpname_yyerror (state, _("invalid character"));
+      return ERROR;
+    }
+
+  /* It's a name.  See how long it is.  */
+  namelen = 0;
+  do
+    c = tokstart[++namelen];
+  while (c_ident_is_alnum (c) || c == '_' || c == '$');
+
+  state->lexptr += namelen;
+
+  /* Catch specific keywords.  Notice that some of the keywords contain
+     spaces, and are sorted by the length of the first word.  They must
+     all include a trailing space in the string comparison.  */
+  switch (namelen)
+    {
+    case 16:
+      if (startswith (tokstart, "reinterpret_cast"))
+	return REINTERPRET_CAST;
+      break;
+    case 12:
+      if (startswith (tokstart, "construction vtable for "))
+	{
+	  state->lexptr = tokstart + 24;
+	  return CONSTRUCTION_VTABLE;
+	}
+      if (startswith (tokstart, "dynamic_cast"))
+	return DYNAMIC_CAST;
+      break;
+    case 11:
+      if (startswith (tokstart, "static_cast"))
+	return STATIC_CAST;
+      break;
+    case 9:
+      HANDLE_SPECIAL ("covariant return thunk to ", DEMANGLE_COMPONENT_COVARIANT_THUNK);
+      HANDLE_SPECIAL ("reference temporary for ", DEMANGLE_COMPONENT_REFTEMP);
+      break;
+    case 8:
+      HANDLE_SPECIAL ("typeinfo for ", DEMANGLE_COMPONENT_TYPEINFO);
+      HANDLE_SPECIAL ("typeinfo fn for ", DEMANGLE_COMPONENT_TYPEINFO_FN);
+      HANDLE_SPECIAL ("typeinfo name for ", DEMANGLE_COMPONENT_TYPEINFO_NAME);
+      if (startswith (tokstart, "operator"))
+	return OPERATOR;
+      if (startswith (tokstart, "restrict"))
+	return RESTRICT;
+      if (startswith (tokstart, "unsigned"))
+	return UNSIGNED;
+      if (startswith (tokstart, "template"))
+	return TEMPLATE;
+      if (startswith (tokstart, "volatile"))
+	return VOLATILE_KEYWORD;
+      break;
+    case 7:
+      HANDLE_SPECIAL ("virtual thunk to ", DEMANGLE_COMPONENT_VIRTUAL_THUNK);
+      if (startswith (tokstart, "wchar_t"))
+	return WCHAR_T;
+      break;
+    case 6:
+      if (startswith (tokstart, "global constructors keyed to "))
+	{
+	  const char *p;
+	  state->lexptr = tokstart + 29;
+	  lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS;
+	  /* Find the end of the symbol.  */
+	  p = symbol_end (state->lexptr);
+	  lvalp->comp = state->make_name (state->lexptr, p - state->lexptr);
+	  state->lexptr = p;
+	  return DEMANGLER_SPECIAL;
+	}
+      if (startswith (tokstart, "global destructors keyed to "))
+	{
+	  const char *p;
+	  state->lexptr = tokstart + 28;
+	  lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS;
+	  /* Find the end of the symbol.  */
+	  p = symbol_end (state->lexptr);
+	  lvalp->comp = state->make_name (state->lexptr, p - state->lexptr);
+	  state->lexptr = p;
+	  return DEMANGLER_SPECIAL;
+	}
+
+      HANDLE_SPECIAL ("vtable for ", DEMANGLE_COMPONENT_VTABLE);
+      if (startswith (tokstart, "delete"))
+	return DELETE;
+      if (startswith (tokstart, "struct"))
+	return STRUCT;
+      if (startswith (tokstart, "signed"))
+	return SIGNED_KEYWORD;
+      if (startswith (tokstart, "sizeof"))
+	return SIZEOF;
+      if (startswith (tokstart, "double"))
+	return DOUBLE_KEYWORD;
+      break;
+    case 5:
+      HANDLE_SPECIAL ("guard variable for ", DEMANGLE_COMPONENT_GUARD);
+      if (startswith (tokstart, "false"))
+	return FALSEKEYWORD;
+      if (startswith (tokstart, "class"))
+	return CLASS;
+      if (startswith (tokstart, "union"))
+	return UNION;
+      if (startswith (tokstart, "float"))
+	return FLOAT_KEYWORD;
+      if (startswith (tokstart, "short"))
+	return SHORT;
+      if (startswith (tokstart, "const"))
+	return CONST_KEYWORD;
+      break;
+    case 4:
+      if (startswith (tokstart, "void"))
+	return VOID;
+      if (startswith (tokstart, "bool"))
+	return BOOL;
+      if (startswith (tokstart, "char"))
+	return CHAR;
+      if (startswith (tokstart, "enum"))
+	return ENUM;
+      if (startswith (tokstart, "long"))
+	return LONG;
+      if (startswith (tokstart, "true"))
+	return TRUEKEYWORD;
+      break;
+    case 3:
+      HANDLE_SPECIAL ("VTT for ", DEMANGLE_COMPONENT_VTT);
+      HANDLE_SPECIAL ("non-virtual thunk to ", DEMANGLE_COMPONENT_THUNK);
+      if (startswith (tokstart, "new"))
+	return NEW;
+      if (startswith (tokstart, "int"))
+	return INT_KEYWORD;
+      break;
+    default:
+      break;
+    }
+
+  lvalp->comp = state->make_name (tokstart, namelen);
+  return NAME;
+}
+
+#if GDB_SELF_TEST
+
+static void
+should_be_the_same (const char *one, const char *two)
+{
+  gdb::unique_xmalloc_ptr<char> cpone = cp_canonicalize_string (one);
+  gdb::unique_xmalloc_ptr<char> cptwo = cp_canonicalize_string (two);
+
+  if (cpone != nullptr)
+    one = cpone.get ();
+  if (cptwo != nullptr)
+    two = cptwo.get ();
+
+  SELF_CHECK (streq (one, two));
+}
+
+static void
+should_parse (const char *name)
+{
+  auto parsed = cp_demangled_name_to_comp (name);
+  SELF_CHECK (parsed != nullptr);
+}
+
+static void
+canonicalize_tests ()
+{
+  should_be_the_same ("short int", "short");
+  should_be_the_same ("int short", "short");
+
+  should_be_the_same ("C<(char) 1>::m()", "C<(char) '\\001'>::m()");
+  should_be_the_same ("x::y::z<1>", "x::y::z<0x01>");
+  should_be_the_same ("x::y::z<1>", "x::y::z<01>");
+  should_be_the_same ("x::y::z<(unsigned long long) 1>", "x::y::z<01ull>");
+  should_be_the_same ("x::y::z<0b111>", "x::y::z<7>");
+  should_be_the_same ("x::y::z<0b111>", "x::y::z<0t7>");
+  should_be_the_same ("x::y::z<0b111>", "x::y::z<0D7>");
+
+  should_be_the_same ("x::y::z<0xff'ff>", "x::y::z<65535>");
+
+  should_be_the_same ("something<void ()>", "something<  void()  >");
+  should_be_the_same ("something<void ()>", "something<void (void)>");
+
+  should_parse ("void whatever::operator<=><int, int>");
+
+  should_be_the_same ("Foozle<int>::fogey<Empty<int> > (Empty<int>)",
+		      "Foozle<int>::fogey<Empty<int>> (Empty<int>)");
+
+  should_be_the_same ("something :: operator new [ ]",
+		      "something::operator new[]");
+  should_be_the_same ("something :: operator   new",
+		      "something::operator new");
+  should_be_the_same ("operator()", "operator ()");
+}
+
+#endif
+
+INIT_GDB_FILE (cp_name_parser)
+{
+#if GDB_SELF_TEST
+  selftests::register_test ("canonicalize", canonicalize_tests);
+#endif
+}
diff --git a/gdb/cp-name-parser.h b/gdb/cp-name-parser.h
new file mode 100644
index 000000000000..31b2227227fc
--- /dev/null
+++ b/gdb/cp-name-parser.h
@@ -0,0 +1,130 @@
+/* State of the C++ name parser, for GDB.
+
+   Copyright (C) 2003-2026 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 <http://www.gnu.org/licenses/>.  */
+
+#ifndef GDB_CP_NAME_PARSER_H
+#define GDB_CP_NAME_PARSER_H
+
+#include "demangle.h"
+
+union cp_name_parser_YYSTYPE;
+struct demangle_parse_info;
+
+/* Flags passed to cpname_state::d_qualify.  */
+
+#define QUAL_CONST 1
+#define QUAL_RESTRICT 2
+#define QUAL_VOLATILE 4
+
+/* Flags passed to cpname_state::d_int_type.  */
+
+#define INT_CHAR	(1 << 0)
+#define INT_SHORT	(1 << 1)
+#define INT_LONG	(1 << 2)
+#define INT_LLONG	(1 << 3)
+
+#define INT_SIGNED	(1 << 4)
+#define INT_UNSIGNED	(1 << 5)
+
+#define d_left(dc) (dc)->u.s_binary.left
+#define d_right(dc) (dc)->u.s_binary.right
+
+namespace cp_name_parser {
+
+/* State of an ongoing parse.  */
+
+struct cpname_state
+{
+  cpname_state (const char *input, demangle_parse_info *info)
+    : lexptr (input),
+      prev_lexptr (input),
+      demangle_info (info)
+  { }
+
+  /* Un-push a character into the lexer.  This can only un-push the
+     previous character in the input string.  */
+  void unpush (char c)
+  {
+    gdb_assert (lexptr[-1] == c);
+    --lexptr;
+  }
+
+  /* LEXPTR is the current pointer into our lex buffer.  PREV_LEXPTR
+     is the start of the last token lexed, only used for diagnostics.
+     ERROR_LEXPTR is the first place an error occurred.  GLOBAL_ERRMSG
+     is the first error message encountered.  */
+
+  const char *lexptr, *prev_lexptr;
+  const char *error_lexptr = nullptr;
+  const char *global_errmsg = nullptr;
+
+  demangle_parse_info *demangle_info;
+
+  /* The parse tree created by the parser is stored here after a
+     successful parse.  */
+
+  struct demangle_component *global_result = nullptr;
+
+  struct demangle_component *d_grab ();
+
+  /* Helper functions.  These wrap the demangler tree interface,
+     handle allocation from our global store, and return the allocated
+     component.  */
+
+  struct demangle_component *fill_comp (enum demangle_component_type d_type,
+					struct demangle_component *lhs,
+					struct demangle_component *rhs);
+
+  struct demangle_component *make_operator (const char *name, int args);
+
+  struct demangle_component *make_dtor (enum gnu_v3_dtor_kinds kind,
+					struct demangle_component *name);
+
+  struct demangle_component *make_builtin_type (const char *name);
+
+  struct demangle_component *make_name (const char *name, int len);
+
+  struct demangle_component *d_qualify (struct demangle_component *lhs,
+					int qualifiers, int is_method);
+
+  struct demangle_component *d_int_type (int flags);
+
+  struct demangle_component *d_unary (const char *name,
+				      struct demangle_component *lhs);
+
+  struct demangle_component *d_binary (const char *name,
+				       struct demangle_component *lhs,
+				       struct demangle_component *rhs);
+
+  int parse_number (const char *p, int len, int parsed_float,
+		    cp_name_parser_YYSTYPE *lvalp);
+};
+
+} /* namespace cp_name_parser */
+
+/* The lexer used by the generated parser.  */
+
+int cpname_yylex (cp_name_parser_YYSTYPE *lvalp,
+		  cp_name_parser::cpname_state *state);
+
+/* The error handler invoked by the generated parser.  Report MSG as a
+   parse error on the current parser state.  */
+
+void cpname_yyerror (cp_name_parser::cpname_state *state, const char *msg);
+
+#endif /* GDB_CP_NAME_PARSER_H */
diff --git a/gdb/cp-name-parser.y b/gdb/cp-name-parser.y
index 44ef1a41eb39..687380c4ca68 100644
--- a/gdb/cp-name-parser.y
+++ b/gdb/cp-name-parser.y
@@ -32,18 +32,14 @@
    this uses %lex-param and %parse-param rather than the simpler
    %param -- Byacc does not support the latter.  */
 %pure-parser
-%lex-param {struct cpname_state *state}
-%parse-param {struct cpname_state *state}
+%lex-param {cp_name_parser::cpname_state *state}
+%parse-param {cp_name_parser::cpname_state *state}
 
 %{
 
-
-#include <unistd.h>
 #include "demangle.h"
+#include "cp-name-parser.h"
 #include "cp-support.h"
-#include "c-support.h"
-#include "parser-defs.h"
-#include "gdbsupport/selftest.h"
 
 %}
 
@@ -67,169 +63,6 @@
     const char *opname;
   }
 
-%{
-
-struct cpname_state
-{
-  cpname_state (const char *input, demangle_parse_info *info)
-    : lexptr (input),
-      prev_lexptr (input),
-      demangle_info (info)
-  { }
-
-  /* Un-push a character into the lexer.  This can only un-push the
-     previous character in the input string.  */
-  void unpush (char c)
-  {
-    gdb_assert (lexptr[-1] == c);
-    --lexptr;
-  }
-
-  /* LEXPTR is the current pointer into our lex buffer.  PREV_LEXPTR
-     is the start of the last token lexed, only used for diagnostics.
-     ERROR_LEXPTR is the first place an error occurred.  GLOBAL_ERRMSG
-     is the first error message encountered.  */
-
-  const char *lexptr, *prev_lexptr;
-  const char *error_lexptr = nullptr;
-  const char *global_errmsg = nullptr;
-
-  demangle_parse_info *demangle_info;
-
-  /* The parse tree created by the parser is stored here after a
-     successful parse.  */
-
-  struct demangle_component *global_result = nullptr;
-
-  struct demangle_component *d_grab ();
-
-  /* Helper functions.  These wrap the demangler tree interface,
-     handle allocation from our global store, and return the allocated
-     component.  */
-
-  struct demangle_component *fill_comp (enum demangle_component_type d_type,
-					struct demangle_component *lhs,
-					struct demangle_component *rhs);
-
-  struct demangle_component *make_operator (const char *name, int args);
-
-  struct demangle_component *make_dtor (enum gnu_v3_dtor_kinds kind,
-					struct demangle_component *name);
-
-  struct demangle_component *make_builtin_type (const char *name);
-
-  struct demangle_component *make_name (const char *name, int len);
-
-  struct demangle_component *d_qualify (struct demangle_component *lhs,
-					int qualifiers, int is_method);
-
-  struct demangle_component *d_int_type (int flags);
-
-  struct demangle_component *d_unary (const char *name,
-				      struct demangle_component *lhs);
-
-  struct demangle_component *d_binary (const char *name,
-				       struct demangle_component *lhs,
-				       struct demangle_component *rhs);
-
-  int parse_number (const char *p, int len, int parsed_float, YYSTYPE *lvalp);
-};
-
-struct demangle_component *
-cpname_state::d_grab ()
-{
-  return obstack_new<demangle_component> (&demangle_info->obstack);
-}
-
-/* Flags passed to d_qualify.  */
-
-#define QUAL_CONST 1
-#define QUAL_RESTRICT 2
-#define QUAL_VOLATILE 4
-
-/* Flags passed to d_int_type.  */
-
-#define INT_CHAR	(1 << 0)
-#define INT_SHORT	(1 << 1)
-#define INT_LONG	(1 << 2)
-#define INT_LLONG	(1 << 3)
-
-#define INT_SIGNED	(1 << 4)
-#define INT_UNSIGNED	(1 << 5)
-
-/* Helper functions.  These wrap the demangler tree interface, handle
-   allocation from our global store, and return the allocated component.  */
-
-struct demangle_component *
-cpname_state::fill_comp (enum demangle_component_type d_type,
-			 struct demangle_component *lhs,
-			 struct demangle_component *rhs)
-{
-  struct demangle_component *ret = d_grab ();
-  int i;
-
-  i = cplus_demangle_fill_component (ret, d_type, lhs, rhs);
-  gdb_assert (i);
-
-  return ret;
-}
-
-struct demangle_component *
-cpname_state::make_operator (const char *name, int args)
-{
-  struct demangle_component *ret = d_grab ();
-  int i;
-
-  i = cplus_demangle_fill_operator (ret, name, args);
-  gdb_assert (i);
-
-  return ret;
-}
-
-struct demangle_component *
-cpname_state::make_dtor (enum gnu_v3_dtor_kinds kind,
-			 struct demangle_component *name)
-{
-  struct demangle_component *ret = d_grab ();
-  int i;
-
-  i = cplus_demangle_fill_dtor (ret, kind, name);
-  gdb_assert (i);
-
-  return ret;
-}
-
-struct demangle_component *
-cpname_state::make_builtin_type (const char *name)
-{
-  struct demangle_component *ret = d_grab ();
-  int i;
-
-  i = cplus_demangle_fill_builtin_type (ret, name);
-  gdb_assert (i);
-
-  return ret;
-}
-
-struct demangle_component *
-cpname_state::make_name (const char *name, int len)
-{
-  struct demangle_component *ret = d_grab ();
-  int i;
-
-  i = cplus_demangle_fill_name (ret, name, len);
-  gdb_assert (i);
-
-  return ret;
-}
-
-#define d_left(dc) (dc)->u.s_binary.left
-#define d_right(dc) (dc)->u.s_binary.right
-
-static int yylex (YYSTYPE *, cpname_state *);
-static void yyerror (cpname_state *, const char *);
-%}
-
 %type <comp> exp exp1 type start start_opt oper colon_name
 %type <comp> unqualified_name colon_ext_name
 %type <comp> templ template_arg
@@ -1196,945 +1029,3 @@ exp     :       FALSEKEYWORD
 	;
 
 /* end of C++.  */
-
-%%
-
-/* Apply QUALIFIERS to LHS and return a qualified component.  IS_METHOD
-   is set if LHS is a method, in which case the qualifiers are logically
-   applied to "this".  We apply qualifiers in a consistent order; LHS
-   may already be qualified; duplicate qualifiers are not created.  */
-
-struct demangle_component *
-cpname_state::d_qualify (struct demangle_component *lhs, int qualifiers,
-			 int is_method)
-{
-  struct demangle_component **inner_p;
-  enum demangle_component_type type;
-
-  /* For now the order is CONST (innermost), VOLATILE, RESTRICT.  */
-
-#define HANDLE_QUAL(TYPE, MTYPE, QUAL)				\
-  if ((qualifiers & QUAL) && (type != TYPE) && (type != MTYPE))	\
-    {								\
-      *inner_p = fill_comp (is_method ? MTYPE : TYPE,		\
-			    *inner_p, NULL);			\
-      inner_p = &d_left (*inner_p);				\
-      type = (*inner_p)->type;					\
-    }								\
-  else if (type == TYPE || type == MTYPE)			\
-    {								\
-      inner_p = &d_left (*inner_p);				\
-      type = (*inner_p)->type;					\
-    }
-
-  inner_p = &lhs;
-
-  type = (*inner_p)->type;
-
-  HANDLE_QUAL (DEMANGLE_COMPONENT_RESTRICT, DEMANGLE_COMPONENT_RESTRICT_THIS, QUAL_RESTRICT);
-  HANDLE_QUAL (DEMANGLE_COMPONENT_VOLATILE, DEMANGLE_COMPONENT_VOLATILE_THIS, QUAL_VOLATILE);
-  HANDLE_QUAL (DEMANGLE_COMPONENT_CONST, DEMANGLE_COMPONENT_CONST_THIS, QUAL_CONST);
-
-  return lhs;
-}
-
-/* Return a builtin type corresponding to FLAGS.  */
-
-struct demangle_component *
-cpname_state::d_int_type (int flags)
-{
-  const char *name;
-
-  switch (flags)
-    {
-    case INT_SIGNED | INT_CHAR:
-      name = "signed char";
-      break;
-    case INT_CHAR:
-      name = "char";
-      break;
-    case INT_UNSIGNED | INT_CHAR:
-      name = "unsigned char";
-      break;
-    case 0:
-    case INT_SIGNED:
-      name = "int";
-      break;
-    case INT_UNSIGNED:
-      name = "unsigned int";
-      break;
-    case INT_LONG:
-    case INT_SIGNED | INT_LONG:
-      name = "long";
-      break;
-    case INT_UNSIGNED | INT_LONG:
-      name = "unsigned long";
-      break;
-    case INT_SHORT:
-    case INT_SIGNED | INT_SHORT:
-      name = "short";
-      break;
-    case INT_UNSIGNED | INT_SHORT:
-      name = "unsigned short";
-      break;
-    case INT_LLONG | INT_LONG:
-    case INT_SIGNED | INT_LLONG | INT_LONG:
-      name = "long long";
-      break;
-    case INT_UNSIGNED | INT_LLONG | INT_LONG:
-      name = "unsigned long long";
-      break;
-    default:
-      return NULL;
-    }
-
-  return make_builtin_type (name);
-}
-
-/* Wrapper to create a unary operation.  */
-
-struct demangle_component *
-cpname_state::d_unary (const char *name, struct demangle_component *lhs)
-{
-  return fill_comp (DEMANGLE_COMPONENT_UNARY, make_operator (name, 1), lhs);
-}
-
-/* Wrapper to create a binary operation.  */
-
-struct demangle_component *
-cpname_state::d_binary (const char *name, struct demangle_component *lhs,
-			struct demangle_component *rhs)
-{
-  return fill_comp (DEMANGLE_COMPONENT_BINARY, make_operator (name, 2),
-		    fill_comp (DEMANGLE_COMPONENT_BINARY_ARGS, lhs, rhs));
-}
-
-/* Find the end of a symbol name starting at LEXPTR.  */
-
-static const char *
-symbol_end (const char *lexptr)
-{
-  const char *p = lexptr;
-
-  while (*p && (c_ident_is_alnum (*p) || *p == '_' || *p == '$' || *p == '.'))
-    p++;
-
-  return p;
-}
-
-/* Take care of parsing a number (anything that starts with a digit).
-   The number starts at P and contains LEN characters.  Store the result in
-   YYLVAL.  */
-
-int
-cpname_state::parse_number (const char *p, int len, int parsed_float,
-			    YYSTYPE *lvalp)
-{
-  int unsigned_p = 0;
-
-  /* Number of "L" suffixes encountered.  */
-  int long_p = 0;
-
-  struct demangle_component *type, *name;
-  enum demangle_component_type literal_type;
-
-  if (p[0] == '-')
-    {
-      literal_type = DEMANGLE_COMPONENT_LITERAL_NEG;
-      p++;
-      len--;
-    }
-  else
-    literal_type = DEMANGLE_COMPONENT_LITERAL;
-
-  if (parsed_float)
-    {
-      /* It's a float since it contains a point or an exponent.  */
-      char c;
-
-      /* The GDB lexer checks the result of scanf at this point.  Not doing
-	 this leaves our error checking slightly weaker but only for invalid
-	 data.  */
-
-      /* See if it has `f' or `l' suffix (float or long double).  */
-
-      c = c_tolower (p[len - 1]);
-
-      if (c == 'f')
-	{
-	  len--;
-	  type = make_builtin_type ("float");
-	}
-      else if (c == 'l')
-	{
-	  len--;
-	  type = make_builtin_type ("long double");
-	}
-      else if (c_isdigit (c) || c == '.')
-	type = make_builtin_type ("double");
-      else
-	return ERROR;
-
-      name = make_name (p, len);
-      lvalp->comp = fill_comp (literal_type, type, name);
-
-      return FLOAT;
-    }
-
-  /* Note that we do not automatically generate unsigned types.  This
-     can't be done because we don't have access to the gdbarch
-     here.  */
-
-  int base = 10;
-  if (len > 1 && p[0] == '0')
-    {
-      if (p[1] == 'x' || p[1] == 'X')
-	{
-	  base = 16;
-	  p += 2;
-	  len -= 2;
-	}
-      else if (p[1] == 'b' || p[1] == 'B')
-	{
-	  base = 2;
-	  p += 2;
-	  len -= 2;
-	}
-      else if (p[1] == 'd' || p[1] == 'D' || p[1] == 't' || p[1] == 'T')
-	{
-	  /* Apparently gdb extensions.  */
-	  base = 10;
-	  p += 2;
-	  len -= 2;
-	}
-      else
-	base = 8;
-    }
-
-  long_p = 0;
-  unsigned_p = 0;
-  while (len > 0)
-    {
-      if (p[len - 1] == 'l' || p[len - 1] == 'L')
-	{
-	  len--;
-	  long_p++;
-	  continue;
-	}
-      if (p[len - 1] == 'u' || p[len - 1] == 'U')
-	{
-	  len--;
-	  unsigned_p++;
-	  continue;
-	}
-      break;
-    }
-
-  /* Use gdb_mpz here in case a 128-bit value appears.  */
-  gdb_mpz value (0);
-  for (int off = 0; off < len; ++off)
-    {
-      int dig;
-      if (c_isdigit (p[off]))
-	dig = p[off] - '0';
-      else
-	dig = c_tolower (p[off]) - 'a' + 10;
-      if (dig >= base)
-	return ERROR;
-      value *= base;
-      value += dig;
-    }
-
-  std::string printed = value.str ();
-  const char *copy = obstack_strdup (&demangle_info->obstack, printed);
-
-  if (long_p == 0)
-    {
-      if (unsigned_p)
-	type = make_builtin_type ("unsigned int");
-      else
-	type = make_builtin_type ("int");
-    }
-  else if (long_p == 1)
-    {
-      if (unsigned_p)
-	type = make_builtin_type ("unsigned long");
-      else
-	type = make_builtin_type ("long");
-    }
-  else
-    {
-      if (unsigned_p)
-	type = make_builtin_type ("unsigned long long");
-      else
-	type = make_builtin_type ("long long");
-    }
-
-  name = make_name (copy, strlen (copy));
-  lvalp->comp = fill_comp (literal_type, type, name);
-
-  return INT;
-}
-
-static const char backslashable[] = "abefnrtv";
-static const char represented[] = "\a\b\e\f\n\r\t\v";
-
-/* Translate the backslash the way we would in the host character set.  */
-static int
-c_parse_backslash (int host_char, int *target_char)
-{
-  const char *ix;
-  ix = strchr (backslashable, host_char);
-  if (! ix)
-    return 0;
-  else
-    *target_char = represented[ix - backslashable];
-  return 1;
-}
-
-/* Parse a C escape sequence.  STRING_PTR points to a variable
-   containing a pointer to the string to parse.  That pointer
-   should point to the character after the \.  That pointer
-   is updated past the characters we use.  The value of the
-   escape sequence is returned.
-
-   A negative value means the sequence \ newline was seen,
-   which is supposed to be equivalent to nothing at all.
-
-   If \ is followed by a null character, we return a negative
-   value and leave the string pointer pointing at the null character.
-
-   If \ is followed by 000, we return 0 and leave the string pointer
-   after the zeros.  A value of 0 does not mean end of string.  */
-
-static int
-cp_parse_escape (const char **string_ptr)
-{
-  int target_char;
-  int c = *(*string_ptr)++;
-  if (c_parse_backslash (c, &target_char))
-    return target_char;
-  else
-    switch (c)
-      {
-      case '\n':
-	return -2;
-      case 0:
-	(*string_ptr)--;
-	return 0;
-      case '^':
-	{
-	  c = *(*string_ptr)++;
-
-	  if (c == '?')
-	    return 0177;
-	  else if (c == '\\')
-	    target_char = cp_parse_escape (string_ptr);
-	  else
-	    target_char = c;
-
-	  /* Now target_char is something like `c', and we want to find
-	     its control-character equivalent.  */
-	  target_char = target_char & 037;
-
-	  return target_char;
-	}
-
-      case '0':
-      case '1':
-      case '2':
-      case '3':
-      case '4':
-      case '5':
-      case '6':
-      case '7':
-	{
-	  int i = c - '0';
-	  int count = 0;
-	  while (++count < 3)
-	    {
-	      c = (**string_ptr);
-	      if (c >= '0' && c <= '7')
-		{
-		  (*string_ptr)++;
-		  i *= 8;
-		  i += c - '0';
-		}
-	      else
-		{
-		  break;
-		}
-	    }
-	  return i;
-	}
-      default:
-	return c;
-      }
-}
-
-#define HANDLE_SPECIAL(string, comp)				\
-  if (startswith (tokstart, string))				\
-    {								\
-      state->lexptr = tokstart + sizeof (string) - 1;			\
-      lvalp->lval = comp;					\
-      return DEMANGLER_SPECIAL;					\
-    }
-
-#define HANDLE_TOKEN2(string, token)			\
-  if (state->lexptr[1] == string[1])				\
-    {							\
-      state->lexptr += 2;					\
-      lvalp->opname = string;				\
-      return token;					\
-    }
-
-#define HANDLE_TOKEN3(string, token)			\
-  if (state->lexptr[1] == string[1] && state->lexptr[2] == string[2])	\
-    {							\
-      state->lexptr += 3;					\
-      lvalp->opname = string;				\
-      return token;					\
-    }
-
-/* Read one token, getting characters through LEXPTR.  */
-
-static int
-yylex (YYSTYPE *lvalp, cpname_state *state)
-{
-  int c;
-  int namelen;
-  const char *tokstart;
-  char *copy;
-
- retry:
-  state->prev_lexptr = state->lexptr;
-  tokstart = state->lexptr;
-
-  switch (c = *tokstart)
-    {
-    case 0:
-      return 0;
-
-    case ' ':
-    case '\t':
-    case '\n':
-      state->lexptr++;
-      goto retry;
-
-    case '\'':
-      /* We either have a character constant ('0' or '\177' for example)
-	 or we have a quoted symbol reference ('foo(int,int)' in C++
-	 for example). */
-      state->lexptr++;
-      c = *state->lexptr++;
-      if (c == '\\')
-	c = cp_parse_escape (&state->lexptr);
-      else if (c == '\'')
-	{
-	  yyerror (state, _("empty character constant"));
-	  return ERROR;
-	}
-
-      /* We over-allocate here, but it doesn't really matter . */
-      copy = (char *) obstack_alloc (&state->demangle_info->obstack, 30);
-      xsnprintf (copy, 30, "%d", c);
-
-      c = *state->lexptr++;
-      if (c != '\'')
-	{
-	  yyerror (state, _("invalid character constant"));
-	  return ERROR;
-	}
-
-      lvalp->comp
-	= state->fill_comp (DEMANGLE_COMPONENT_LITERAL,
-			    state->make_builtin_type ("char"),
-			    state->make_name (copy, strlen (copy)));
-
-      return INT;
-
-    case '(':
-      if (startswith (tokstart, "(anonymous namespace)"))
-	{
-	  state->lexptr += 21;
-	  lvalp->comp = state->make_name ("(anonymous namespace)",
-					  sizeof "(anonymous namespace)" - 1);
-	  return NAME;
-	}
-	[[fallthrough]];
-
-    case ')':
-    case ',':
-      state->lexptr++;
-      return c;
-
-    case '.':
-      if (state->lexptr[1] == '.' && state->lexptr[2] == '.')
-	{
-	  state->lexptr += 3;
-	  return ELLIPSIS;
-	}
-
-      /* Might be a floating point number.  */
-      if (state->lexptr[1] < '0' || state->lexptr[1] > '9')
-	goto symbol;		/* Nope, must be a symbol. */
-
-      goto try_number;
-
-    case '-':
-      HANDLE_TOKEN2 ("-=", ASSIGN_MODIFY);
-      HANDLE_TOKEN2 ("--", DECREMENT);
-      HANDLE_TOKEN2 ("->", ARROW);
-
-      /* For construction vtables.  This is kind of hokey.  */
-      if (startswith (tokstart, "-in-"))
-	{
-	  state->lexptr += 4;
-	  return CONSTRUCTION_IN;
-	}
-
-      if (state->lexptr[1] < '0' || state->lexptr[1] > '9')
-	{
-	  state->lexptr++;
-	  return '-';
-	}
-
-    try_number:
-      [[fallthrough]];
-    case '0':
-    case '1':
-    case '2':
-    case '3':
-    case '4':
-    case '5':
-    case '6':
-    case '7':
-    case '8':
-    case '9':
-      {
-	/* It's a number.  */
-	int got_dot = 0, got_e = 0, toktype;
-	const char *p = tokstart;
-	int hex = 0;
-
-	if (c == '-')
-	  p++;
-
-	if (c == '0' && (p[1] == 'x' || p[1] == 'X'))
-	  {
-	    p += 2;
-	    hex = 1;
-	  }
-	else if (c == '0' && (p[1]=='t' || p[1]=='T' || p[1]=='d' || p[1]=='D'))
-	  {
-	    p += 2;
-	    hex = 0;
-	  }
-
-	/* If the token includes the C++14 digits separator, we make a
-	   copy so that we don't have to handle the separator in
-	   parse_number.  */
-	std::optional<std::string> no_tick;
-	for (;; ++p)
-	  {
-	    /* This test includes !hex because 'e' is a valid hex digit
-	       and thus does not indicate a floating point number when
-	       the radix is hex.  */
-	    if (!hex && !got_e && (*p == 'e' || *p == 'E'))
-	      got_dot = got_e = 1;
-	    /* This test does not include !hex, because a '.' always indicates
-	       a decimal floating point number regardless of the radix.
-
-	       NOTE drow/2005-03-09: This comment is not accurate in C99;
-	       however, it's not clear that all the floating point support
-	       in this file is doing any good here.  */
-	    else if (!got_dot && *p == '.')
-	      got_dot = 1;
-	    else if (got_e && (p[-1] == 'e' || p[-1] == 'E')
-		     && (*p == '-' || *p == '+'))
-	      {
-		/* This is the sign of the exponent, not the end of
-		   the number.  */
-	      }
-	    /* C++14 allows a separator.  */
-	    else if (*p == '\'')
-	      {
-		if (!no_tick.has_value ())
-		  no_tick.emplace (tokstart, p);
-		continue;
-	      }
-	    /* We will take any letters or digits.  parse_number will
-	       complain if past the radix, or if L or U are not final.  */
-	    else if (! c_isalnum (*p))
-	      break;
-	    if (no_tick.has_value ())
-	      no_tick->push_back (*p);
-	  }
-	if (no_tick.has_value ())
-	  toktype = state->parse_number (no_tick->c_str (),
-					 no_tick->length (),
-					 got_dot|got_e, lvalp);
-	else
-	  toktype = state->parse_number (tokstart, p - tokstart,
-					 got_dot|got_e, lvalp);
-	if (toktype == ERROR)
-	  {
-	    yyerror (state, _("invalid number"));
-	    return ERROR;
-	  }
-	state->lexptr = p;
-	return toktype;
-      }
-
-    case '+':
-      HANDLE_TOKEN2 ("+=", ASSIGN_MODIFY);
-      HANDLE_TOKEN2 ("++", INCREMENT);
-      state->lexptr++;
-      return c;
-    case '*':
-      HANDLE_TOKEN2 ("*=", ASSIGN_MODIFY);
-      state->lexptr++;
-      return c;
-    case '/':
-      HANDLE_TOKEN2 ("/=", ASSIGN_MODIFY);
-      state->lexptr++;
-      return c;
-    case '%':
-      HANDLE_TOKEN2 ("%=", ASSIGN_MODIFY);
-      state->lexptr++;
-      return c;
-    case '|':
-      HANDLE_TOKEN2 ("|=", ASSIGN_MODIFY);
-      HANDLE_TOKEN2 ("||", OROR);
-      state->lexptr++;
-      return c;
-    case '&':
-      HANDLE_TOKEN2 ("&=", ASSIGN_MODIFY);
-      HANDLE_TOKEN2 ("&&", ANDAND);
-      state->lexptr++;
-      return c;
-    case '^':
-      HANDLE_TOKEN2 ("^=", ASSIGN_MODIFY);
-      state->lexptr++;
-      return c;
-    case '!':
-      HANDLE_TOKEN2 ("!=", NOTEQUAL);
-      state->lexptr++;
-      return c;
-    case '<':
-      HANDLE_TOKEN3 ("<<=", ASSIGN_MODIFY);
-      HANDLE_TOKEN3 ("<=>", SPACESHIP);
-      HANDLE_TOKEN2 ("<=", LEQ);
-      HANDLE_TOKEN2 ("<<", LSH);
-      state->lexptr++;
-      return c;
-    case '>':
-      HANDLE_TOKEN3 (">>=", ASSIGN_MODIFY);
-      HANDLE_TOKEN2 (">=", GEQ);
-      HANDLE_TOKEN2 (">>", RSH);
-      state->lexptr++;
-      return c;
-    case '=':
-      HANDLE_TOKEN2 ("==", EQUAL);
-      state->lexptr++;
-      return c;
-    case ':':
-      HANDLE_TOKEN2 ("::", COLONCOLON);
-      state->lexptr++;
-      return c;
-
-    case '[':
-    case ']':
-    case '?':
-    case '@':
-    case '~':
-    case '{':
-    case '}':
-    symbol:
-      state->lexptr++;
-      return c;
-
-    case '"':
-      /* These can't occur in C++ names.  */
-      yyerror (state, _("unexpected string literal"));
-      return ERROR;
-    }
-
-  if (!(c == '_' || c == '$' || c_ident_is_alpha (c)))
-    {
-      /* We must have come across a bad character (e.g. ';').  */
-      yyerror (state, _("invalid character"));
-      return ERROR;
-    }
-
-  /* It's a name.  See how long it is.  */
-  namelen = 0;
-  do
-    c = tokstart[++namelen];
-  while (c_ident_is_alnum (c) || c == '_' || c == '$');
-
-  state->lexptr += namelen;
-
-  /* Catch specific keywords.  Notice that some of the keywords contain
-     spaces, and are sorted by the length of the first word.  They must
-     all include a trailing space in the string comparison.  */
-  switch (namelen)
-    {
-    case 16:
-      if (startswith (tokstart, "reinterpret_cast"))
-	return REINTERPRET_CAST;
-      break;
-    case 12:
-      if (startswith (tokstart, "construction vtable for "))
-	{
-	  state->lexptr = tokstart + 24;
-	  return CONSTRUCTION_VTABLE;
-	}
-      if (startswith (tokstart, "dynamic_cast"))
-	return DYNAMIC_CAST;
-      break;
-    case 11:
-      if (startswith (tokstart, "static_cast"))
-	return STATIC_CAST;
-      break;
-    case 9:
-      HANDLE_SPECIAL ("covariant return thunk to ", DEMANGLE_COMPONENT_COVARIANT_THUNK);
-      HANDLE_SPECIAL ("reference temporary for ", DEMANGLE_COMPONENT_REFTEMP);
-      break;
-    case 8:
-      HANDLE_SPECIAL ("typeinfo for ", DEMANGLE_COMPONENT_TYPEINFO);
-      HANDLE_SPECIAL ("typeinfo fn for ", DEMANGLE_COMPONENT_TYPEINFO_FN);
-      HANDLE_SPECIAL ("typeinfo name for ", DEMANGLE_COMPONENT_TYPEINFO_NAME);
-      if (startswith (tokstart, "operator"))
-	return OPERATOR;
-      if (startswith (tokstart, "restrict"))
-	return RESTRICT;
-      if (startswith (tokstart, "unsigned"))
-	return UNSIGNED;
-      if (startswith (tokstart, "template"))
-	return TEMPLATE;
-      if (startswith (tokstart, "volatile"))
-	return VOLATILE_KEYWORD;
-      break;
-    case 7:
-      HANDLE_SPECIAL ("virtual thunk to ", DEMANGLE_COMPONENT_VIRTUAL_THUNK);
-      if (startswith (tokstart, "wchar_t"))
-	return WCHAR_T;
-      break;
-    case 6:
-      if (startswith (tokstart, "global constructors keyed to "))
-	{
-	  const char *p;
-	  state->lexptr = tokstart + 29;
-	  lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_CONSTRUCTORS;
-	  /* Find the end of the symbol.  */
-	  p = symbol_end (state->lexptr);
-	  lvalp->comp = state->make_name (state->lexptr, p - state->lexptr);
-	  state->lexptr = p;
-	  return DEMANGLER_SPECIAL;
-	}
-      if (startswith (tokstart, "global destructors keyed to "))
-	{
-	  const char *p;
-	  state->lexptr = tokstart + 28;
-	  lvalp->lval = DEMANGLE_COMPONENT_GLOBAL_DESTRUCTORS;
-	  /* Find the end of the symbol.  */
-	  p = symbol_end (state->lexptr);
-	  lvalp->comp = state->make_name (state->lexptr, p - state->lexptr);
-	  state->lexptr = p;
-	  return DEMANGLER_SPECIAL;
-	}
-
-      HANDLE_SPECIAL ("vtable for ", DEMANGLE_COMPONENT_VTABLE);
-      if (startswith (tokstart, "delete"))
-	return DELETE;
-      if (startswith (tokstart, "struct"))
-	return STRUCT;
-      if (startswith (tokstart, "signed"))
-	return SIGNED_KEYWORD;
-      if (startswith (tokstart, "sizeof"))
-	return SIZEOF;
-      if (startswith (tokstart, "double"))
-	return DOUBLE_KEYWORD;
-      break;
-    case 5:
-      HANDLE_SPECIAL ("guard variable for ", DEMANGLE_COMPONENT_GUARD);
-      if (startswith (tokstart, "false"))
-	return FALSEKEYWORD;
-      if (startswith (tokstart, "class"))
-	return CLASS;
-      if (startswith (tokstart, "union"))
-	return UNION;
-      if (startswith (tokstart, "float"))
-	return FLOAT_KEYWORD;
-      if (startswith (tokstart, "short"))
-	return SHORT;
-      if (startswith (tokstart, "const"))
-	return CONST_KEYWORD;
-      break;
-    case 4:
-      if (startswith (tokstart, "void"))
-	return VOID;
-      if (startswith (tokstart, "bool"))
-	return BOOL;
-      if (startswith (tokstart, "char"))
-	return CHAR;
-      if (startswith (tokstart, "enum"))
-	return ENUM;
-      if (startswith (tokstart, "long"))
-	return LONG;
-      if (startswith (tokstart, "true"))
-	return TRUEKEYWORD;
-      break;
-    case 3:
-      HANDLE_SPECIAL ("VTT for ", DEMANGLE_COMPONENT_VTT);
-      HANDLE_SPECIAL ("non-virtual thunk to ", DEMANGLE_COMPONENT_THUNK);
-      if (startswith (tokstart, "new"))
-	return NEW;
-      if (startswith (tokstart, "int"))
-	return INT_KEYWORD;
-      break;
-    default:
-      break;
-    }
-
-  lvalp->comp = state->make_name (tokstart, namelen);
-  return NAME;
-}
-
-static void
-yyerror (cpname_state *state, const char *msg)
-{
-  if (state->global_errmsg)
-    return;
-
-  state->error_lexptr = state->prev_lexptr;
-  state->global_errmsg = msg ? msg : "parse error";
-}
-
-/* See cp-support.h.  */
-
-gdb::unique_xmalloc_ptr<char>
-cp_comp_to_string (struct demangle_component *result, int estimated_len)
-{
-  size_t err;
-
-  char *res = gdb_cplus_demangle_print (DMGL_PARAMS | DMGL_ANSI,
-					result, estimated_len, &err);
-  return gdb::unique_xmalloc_ptr<char> (res);
-}
-
-/* Merge the two parse trees given by DEST and SRC.  The parse tree
-   in SRC is attached to DEST at the node represented by TARGET.
-
-   NOTE 1: Since there is no API to merge obstacks, this function does
-   even attempt to try it.  Fortunately, we do not (yet?) need this ability.
-   The code will assert if SRC->obstack is not empty.
-
-   NOTE 2: The string from which SRC was parsed must not be freed, since
-   this function will place pointers to that string into DEST.  */
-
-void
-cp_merge_demangle_parse_infos (struct demangle_parse_info *dest,
-			       struct demangle_component *target,
-			       demangle_parse_info_up src)
-
-{
-  /* Copy the SRC's parse data into DEST.  */
-  *target = *src->tree;
-
-  /* Make sure SRC is owned by DEST.  */
-  dest->infos.push_back (std::move (src));
-}
-
-/* Convert a demangled name to a demangle_component tree.  On success,
-   a structure containing the root of the new tree is returned.  On
-   error, NULL is returned, and an error message will be set in
-   *ERRMSG.  */
-
-demangle_parse_info_up
-cp_demangled_name_to_comp (const char *demangled_name,
-			   std::string *errmsg)
-{
-  auto result = std::make_unique<demangle_parse_info> ();
-  cpname_state state (demangled_name, result.get ());
-
-  /* Note that we can't set yydebug here, as is done in the other
-     parsers.  Bison implements yydebug as a global, even with a pure
-     parser, and this parser is run from worker threads.  So, changing
-     yydebug causes TSan reports.  If you need to debug this parser,
-     debug gdb and set the global from the outer gdb.  */
-  if (yyparse (&state))
-    {
-      if (state.global_errmsg && errmsg)
-	*errmsg = state.global_errmsg;
-      return NULL;
-    }
-
-  result->tree = state.global_result;
-
-  return result;
-}
-
-#if GDB_SELF_TEST
-
-static void
-should_be_the_same (const char *one, const char *two)
-{
-  gdb::unique_xmalloc_ptr<char> cpone = cp_canonicalize_string (one);
-  gdb::unique_xmalloc_ptr<char> cptwo = cp_canonicalize_string (two);
-
-  if (cpone != nullptr)
-    one = cpone.get ();
-  if (cptwo != nullptr)
-    two = cptwo.get ();
-
-  SELF_CHECK (streq (one, two));
-}
-
-static void
-should_parse (const char *name)
-{
-  auto parsed = cp_demangled_name_to_comp (name);
-  SELF_CHECK (parsed != nullptr);
-}
-
-static void
-canonicalize_tests ()
-{
-  should_be_the_same ("short int", "short");
-  should_be_the_same ("int short", "short");
-
-  should_be_the_same ("C<(char) 1>::m()", "C<(char) '\\001'>::m()");
-  should_be_the_same ("x::y::z<1>", "x::y::z<0x01>");
-  should_be_the_same ("x::y::z<1>", "x::y::z<01>");
-  should_be_the_same ("x::y::z<(unsigned long long) 1>", "x::y::z<01ull>");
-  should_be_the_same ("x::y::z<0b111>", "x::y::z<7>");
-  should_be_the_same ("x::y::z<0b111>", "x::y::z<0t7>");
-  should_be_the_same ("x::y::z<0b111>", "x::y::z<0D7>");
-
-  should_be_the_same ("x::y::z<0xff'ff>", "x::y::z<65535>");
-
-  should_be_the_same ("something<void ()>", "something<  void()  >");
-  should_be_the_same ("something<void ()>", "something<void (void)>");
-
-  should_parse ("void whatever::operator<=><int, int>");
-
-  should_be_the_same ("Foozle<int>::fogey<Empty<int> > (Empty<int>)",
-		      "Foozle<int>::fogey<Empty<int>> (Empty<int>)");
-
-  should_be_the_same ("something :: operator new [ ]",
-		      "something::operator new[]");
-  should_be_the_same ("something :: operator   new",
-		      "something::operator new");
-  should_be_the_same ("operator()", "operator ()");
-}
-
-#endif
-
-INIT_GDB_FILE (cp_name_parser)
-{
-#if GDB_SELF_TEST
-  selftests::register_test ("canonicalize", canonicalize_tests);
-#endif
-}
diff --git a/gdb/cp-support.c b/gdb/cp-support.c
index b17f1e77d683..2eb4539d7b4a 100644
--- a/gdb/cp-support.c
+++ b/gdb/cp-support.c
@@ -19,6 +19,7 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 #include "cp-support.h"
+#include "cp-name-parser.h"
 #include "language.h"
 #include "demangle.h"
 #include "cli/cli-cmds.h"
@@ -43,9 +44,6 @@
 #include "typeprint.h"
 #include "inferior.h"
 
-#define d_left(dc) (dc)->u.s_binary.left
-#define d_right(dc) (dc)->u.s_binary.right
-
 /* Functions related to demangled name parsing.  */
 
 static unsigned int cp_find_first_component_aux (const char *name,
-- 
2.55.0


  parent reply	other threads:[~2026-09-04 17:07 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-04 16:56 [PATCH 00/17] Move C++ support code out of .y files simon.marchi
2026-09-04 16:56 ` [PATCH 01/17] gdb/ada-exp-parser: remove name_info struct simon.marchi
2026-09-04 16:56 ` [PATCH 02/17] gdb: replace parse_type macros with functions simon.marchi
2026-09-04 16:56 ` [PATCH 03/17] gdb: suffix flex/bison output files with -gen.c simon.marchi
2026-09-04 16:56 ` [PATCH 04/17] gdb: move parser output post-processing to a script simon.marchi
2026-09-05  0:38   ` Kevin Buettner
2026-09-05  3:59     ` Simon Marchi
2026-09-04 16:56 ` [PATCH 05/17] gdb: let the parser and lexer generators prefix their symbols simon.marchi
2026-09-04 16:56 ` [PATCH 06/17] gdb: separate cp-name-parser's symbol prefix with an underscore simon.marchi
2026-09-04 16:56 ` [PATCH 07/17] gdb: make $(YACC) and $(FLEX) generate headers simon.marchi
2026-09-04 16:56 ` [PATCH 08/17] gdb: add check for stale build generated files simon.marchi
2026-09-04 16:56 ` simon.marchi [this message]
2026-09-04 16:56 ` [PATCH 10/17] gdb: rename LANG-exp.y to LANG-exp-parser.y simon.marchi
2026-09-04 16:56 ` [PATCH 11/17] gdb: move c-exp-parser.y's support code to c-exp-parser.c simon.marchi
2026-09-04 16:56 ` [PATCH 12/17] gdb: move ada-exp-parser.y's support code to ada-exp-parser.c simon.marchi
2026-09-05  0:16   ` Kevin Buettner
2026-09-04 16:56 ` [PATCH 13/17] gdb: move d-exp-parser.y's support code to d-exp-parser.c simon.marchi
2026-09-04 16:56 ` [PATCH 14/17] gdb: move f-exp-parser.y's support code to f-exp-parser.c simon.marchi
2026-09-04 16:56 ` [PATCH 15/17] gdb: move go-exp-parser.y's support code to go-exp-parser.c simon.marchi
2026-09-04 16:56 ` [PATCH 16/17] gdb: move m2-exp-parser.y's support code to m2-exp-parser.c simon.marchi
2026-09-05  0:30   ` Kevin Buettner
2026-09-05  4:04     ` Simon Marchi
2026-09-04 16:56 ` [PATCH 17/17] gdb: move p-exp-parser.y's support code to p-exp-parser.c simon.marchi
2026-09-05  0:29   ` Kevin Buettner
2026-09-05  0:50 ` [PATCH 00/17] Move C++ support code out of .y files Kevin Buettner

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260904170338.1643894-10-simon.marchi@polymtl.ca \
    --to=simon.marchi@polymtl.ca \
    --cc=gdb-patches@sourceware.org \
    --cc=simon.marchi@efficios.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox