From: simon.marchi@polymtl.ca
To: gdb-patches@sourceware.org
Cc: Simon Marchi <simon.marchi@polymtl.ca>
Subject: [PATCH 13/17] gdb: move d-exp-parser.y's support code to d-exp-parser.c
Date: Fri, 4 Sep 2026 12:56:45 -0400 [thread overview]
Message-ID: <20260904170338.1643894-14-simon.marchi@polymtl.ca> (raw)
In-Reply-To: <20260904170338.1643894-1-simon.marchi@polymtl.ca>
From: Simon Marchi <simon.marchi@polymtl.ca>
Similar to the previous commits, but for the D expression parser.
One wrinkle that was not in previous commits is the type_stack global.
Once it moves into the d_exp_parser namespace and is brought in with a
using-directive, its name clashes with the struct type_stack type. Use a
using declaration specifically for it in the .y prologue to restore the
original name hiding, so the grammar actions can work unmodified.
Put the parser support code inside the d_exp_parser namespace.
Change-Id: I8d45f1ba606fa8748976f34f39808703c8f6843d
---
gdb/Makefile.in | 2 +
gdb/d-exp-parser.c | 1050 ++++++++++++++++++++++++++++++++++++++++++++
gdb/d-exp-parser.h | 83 ++++
gdb/d-exp-parser.y | 1043 +------------------------------------------
gdb/d-lang.c | 1 +
gdb/d-lang.h | 4 -
6 files changed, 1142 insertions(+), 1041 deletions(-)
create mode 100644 gdb/d-exp-parser.c
create mode 100644 gdb/d-exp-parser.h
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index d2cfecb0def9..f861b1f53261 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1084,6 +1084,7 @@ COMMON_SFILES = \
cp-support.c \
cp-valprint.c \
ctfread.c \
+ d-exp-parser.c \
d-lang.c \
d-namespace.c \
d-valprint.c \
@@ -1395,6 +1396,7 @@ HFILES_NO_SRCDIR = \
disasm.h \
disasm-selftests.h \
displaced-stepping.h \
+ d-exp-parser.h \
d-lang.h \
dummy-frame.h \
dwarf2/abbrev.h \
diff --git a/gdb/d-exp-parser.c b/gdb/d-exp-parser.c
new file mode 100644
index 000000000000..e6b5f3af8498
--- /dev/null
+++ b/gdb/d-exp-parser.c
@@ -0,0 +1,1050 @@
+/* Support code for the D expression parser, for GDB.
+
+ Copyright (C) 2014-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 "d-exp-parser.h"
+#include "d-exp-parser-gen.h"
+#include "block.h"
+#include "c-exp-parser.h"
+#include "c-lang.h"
+#include "charset.h"
+#include "expression.h"
+#include "language.h"
+#include "value.h"
+
+/* The entry point of the bison/yacc-generated parser, defined in
+ d-exp-parser-gen.c. Bison produces a declaration for d_yyparse in
+ d-exp-parser-gen.h, but byacc does not, hence this declaration. */
+
+int d_yyparse ();
+
+/* Likewise, byacc does not produce a declaration for d_yydebug. */
+
+extern int d_yydebug;
+
+namespace d_exp_parser
+{
+
+/* See d-exp-parser.h. */
+
+parser_state *pstate;
+
+/* See d-exp-parser.h. */
+
+struct type_stack *type_stack;
+
+/* See d-exp-parser.h. */
+
+int
+type_aggregate_p (struct type *type)
+{
+ return (type->code () == TYPE_CODE_STRUCT
+ || type->code () == TYPE_CODE_UNION
+ || type->code () == TYPE_CODE_MODULE
+ || (type->code () == TYPE_CODE_ENUM
+ && type->is_declared_class ()));
+}
+
+/* See d-exp-parser.h. */
+
+int
+parse_number (struct parser_state *ps, const char *p,
+ int len, int parsed_float, d_exp_parser_YYSTYPE *putithere)
+{
+ ULONGEST n = 0;
+ ULONGEST prevn = 0;
+ ULONGEST un;
+
+ int i = 0;
+ int c;
+ int base = input_radix;
+ int unsigned_p = 0;
+ int long_p = 0;
+
+ /* We have found a "L" or "U" suffix. */
+ int found_suffix = 0;
+
+ ULONGEST high_bit;
+ struct type *signed_type;
+ struct type *unsigned_type;
+
+ if (parsed_float)
+ {
+ char *s, *sp;
+
+ /* Strip out all embedded '_' before passing to parse_float. */
+ s = (char *) alloca (len + 1);
+ sp = s;
+ while (len-- > 0)
+ {
+ if (*p != '_')
+ *sp++ = *p;
+ p++;
+ }
+ *sp = '\0';
+ len = strlen (s);
+
+ /* Check suffix for `i' , `fi' or `li' (idouble, ifloat or ireal). */
+ if (len >= 1 && c_tolower (s[len - 1]) == 'i')
+ {
+ if (len >= 2 && c_tolower (s[len - 2]) == 'f')
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_ifloat;
+ len -= 2;
+ }
+ else if (len >= 2 && c_tolower (s[len - 2]) == 'l')
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_ireal;
+ len -= 2;
+ }
+ else
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_idouble;
+ len -= 1;
+ }
+ }
+ /* Check suffix for `f' or `l'' (float or real). */
+ else if (len >= 1 && c_tolower (s[len - 1]) == 'f')
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_float;
+ len -= 1;
+ }
+ else if (len >= 1 && c_tolower (s[len - 1]) == 'l')
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_real;
+ len -= 1;
+ }
+ /* Default type if no suffix. */
+ else
+ {
+ putithere->typed_val_float.type
+ = parse_d_type (ps)->builtin_double;
+ }
+
+ if (!parse_float (s, len,
+ putithere->typed_val_float.type,
+ putithere->typed_val_float.val))
+ return ERROR;
+
+ return FLOAT_LITERAL;
+ }
+
+ /* Handle base-switching prefixes 0x, 0b, 0 */
+ if (p[0] == '0')
+ switch (p[1])
+ {
+ case 'x':
+ case 'X':
+ if (len >= 3)
+ {
+ p += 2;
+ base = 16;
+ len -= 2;
+ }
+ break;
+
+ case 'b':
+ case 'B':
+ if (len >= 3)
+ {
+ p += 2;
+ base = 2;
+ len -= 2;
+ }
+ break;
+
+ default:
+ base = 8;
+ break;
+ }
+
+ while (len-- > 0)
+ {
+ c = *p++;
+ if (c == '_')
+ continue; /* Ignore embedded '_'. */
+ if (c >= 'A' && c <= 'Z')
+ c += 'a' - 'A';
+ if (c != 'l' && c != 'u')
+ n *= base;
+ if (c >= '0' && c <= '9')
+ {
+ if (found_suffix)
+ return ERROR;
+ n += i = c - '0';
+ }
+ else
+ {
+ if (base > 10 && c >= 'a' && c <= 'f')
+ {
+ if (found_suffix)
+ return ERROR;
+ n += i = c - 'a' + 10;
+ }
+ else if (c == 'l' && long_p == 0)
+ {
+ long_p = 1;
+ found_suffix = 1;
+ }
+ else if (c == 'u' && unsigned_p == 0)
+ {
+ unsigned_p = 1;
+ found_suffix = 1;
+ }
+ else
+ return ERROR; /* Char not a digit */
+ }
+ if (i >= base)
+ return ERROR; /* Invalid digit in this base. */
+ /* Portably test for integer overflow. */
+ if (c != 'l' && c != 'u')
+ {
+ ULONGEST n2 = prevn * base;
+ if ((n2 / base != prevn) || (n2 + i < prevn))
+ error (_("Numeric constant too large."));
+ }
+ prevn = n;
+ }
+
+ /* An integer constant is an int or a long. An L suffix forces it to
+ be long, and a U suffix forces it to be unsigned. To figure out
+ whether it fits, we shift it right and see whether anything remains.
+ Note that we can't shift sizeof (LONGEST) * HOST_CHAR_BIT bits or
+ more in one operation, because many compilers will warn about such a
+ shift (which always produces a zero result). To deal with the case
+ where it is we just always shift the value more than once, with fewer
+ bits each time. */
+ un = (ULONGEST) n >> 2;
+ if (long_p == 0 && (un >> 30) == 0)
+ {
+ high_bit = ((ULONGEST) 1) << 31;
+ signed_type = parse_d_type (ps)->builtin_int;
+ /* For decimal notation, keep the sign of the worked out type. */
+ if (base == 10 && !unsigned_p)
+ unsigned_type = parse_d_type (ps)->builtin_long;
+ else
+ unsigned_type = parse_d_type (ps)->builtin_uint;
+ }
+ else
+ {
+ int shift;
+ if (sizeof (ULONGEST) * HOST_CHAR_BIT < 64)
+ /* A long long does not fit in a LONGEST. */
+ shift = (sizeof (ULONGEST) * HOST_CHAR_BIT - 1);
+ else
+ shift = 63;
+ high_bit = (ULONGEST) 1 << shift;
+ signed_type = parse_d_type (ps)->builtin_long;
+ unsigned_type = parse_d_type (ps)->builtin_ulong;
+ }
+
+ putithere->typed_val_int.val = n;
+
+ /* If the high bit of the worked out type is set then this number
+ has to be unsigned_type. */
+ if (unsigned_p || (n & high_bit))
+ putithere->typed_val_int.type = unsigned_type;
+ else
+ putithere->typed_val_int.type = signed_type;
+
+ return INTEGER_LITERAL;
+}
+
+/* Temporary obstack used for holding strings. */
+static struct obstack tempbuf;
+static int tempbuf_init;
+
+/* Parse a string or character literal from TOKPTR. The string or
+ character may be wide or unicode. *OUTPTR is set to just after the
+ end of the literal in the input string. The resulting token is
+ stored in VALUE. This returns a token value, either STRING or
+ CHAR, depending on what was parsed. *HOST_CHARS is set to the
+ number of host characters in the literal. */
+
+static int
+parse_string_or_char (const char *tokptr, const char **outptr,
+ struct typed_stoken *value, int *host_chars)
+{
+ int quote;
+
+ /* Build the gdb internal form of the input string in tempbuf. Note
+ that the buffer is null byte terminated *only* for the
+ convenience of debugging gdb itself and printing the buffer
+ contents when the buffer contains no embedded nulls. Gdb does
+ not depend upon the buffer being null byte terminated, it uses
+ the length string instead. This allows gdb to handle C strings
+ (as well as strings in other languages) with embedded null
+ bytes */
+
+ if (!tempbuf_init)
+ tempbuf_init = 1;
+ else
+ obstack_free (&tempbuf, NULL);
+ obstack_init (&tempbuf);
+
+ /* Skip the quote. */
+ quote = *tokptr;
+ ++tokptr;
+
+ *host_chars = 0;
+
+ while (*tokptr)
+ {
+ char c = *tokptr;
+ if (c == '\\')
+ {
+ ++tokptr;
+ *host_chars += c_parse_escape (&tokptr, &tempbuf);
+ }
+ else if (c == quote)
+ break;
+ else
+ {
+ obstack_1grow (&tempbuf, c);
+ ++tokptr;
+ /* FIXME: this does the wrong thing with multi-byte host
+ characters. We could use mbrlen here, but that would
+ make "set host-charset" a bit less useful. */
+ ++*host_chars;
+ }
+ }
+
+ if (*tokptr != quote)
+ {
+ if (quote == '"' || quote == '`')
+ error (_("Unterminated string in expression."));
+ else
+ error (_("Unmatched single quote."));
+ }
+ ++tokptr;
+
+ /* FIXME: should instead use own language string_type enum
+ and handle D-specific string suffixes here. */
+ if (quote == '\'')
+ value->type = C_CHAR;
+ else
+ value->type = C_STRING;
+
+ value->ptr = (char *) obstack_base (&tempbuf);
+ value->length = obstack_object_size (&tempbuf);
+
+ *outptr = tokptr;
+
+ return quote == '\'' ? CHARACTER_LITERAL : STRING_LITERAL;
+}
+
+struct d_token
+{
+ const char *oper;
+ int token;
+ enum exp_opcode opcode;
+};
+
+static const struct d_token tokentab3[] =
+ {
+ {"^^=", ASSIGN_MODIFY, BINOP_EXP},
+ {"<<=", ASSIGN_MODIFY, BINOP_LSH},
+ {">>=", ASSIGN_MODIFY, BINOP_RSH},
+ };
+
+static const struct d_token tokentab2[] =
+ {
+ {"+=", ASSIGN_MODIFY, BINOP_ADD},
+ {"-=", ASSIGN_MODIFY, BINOP_SUB},
+ {"*=", ASSIGN_MODIFY, BINOP_MUL},
+ {"/=", ASSIGN_MODIFY, BINOP_DIV},
+ {"%=", ASSIGN_MODIFY, BINOP_REM},
+ {"|=", ASSIGN_MODIFY, BINOP_BITWISE_IOR},
+ {"&=", ASSIGN_MODIFY, BINOP_BITWISE_AND},
+ {"^=", ASSIGN_MODIFY, BINOP_BITWISE_XOR},
+ {"++", INCREMENT, OP_NULL},
+ {"--", DECREMENT, OP_NULL},
+ {"&&", ANDAND, OP_NULL},
+ {"||", OROR, OP_NULL},
+ {"^^", HATHAT, OP_NULL},
+ {"<<", LSH, OP_NULL},
+ {">>", RSH, OP_NULL},
+ {"==", EQUAL, OP_NULL},
+ {"!=", NOTEQUAL, OP_NULL},
+ {"<=", LEQ, OP_NULL},
+ {">=", GEQ, OP_NULL},
+ {"..", DOTDOT, OP_NULL},
+ };
+
+/* Identifier-like tokens. */
+static const struct d_token ident_tokens[] =
+ {
+ {"is", IDENTITY, OP_NULL},
+ {"!is", NOTIDENTITY, OP_NULL},
+
+ {"cast", CAST_KEYWORD, OP_NULL},
+ {"const", CONST_KEYWORD, OP_NULL},
+ {"immutable", IMMUTABLE_KEYWORD, OP_NULL},
+ {"shared", SHARED_KEYWORD, OP_NULL},
+ {"super", SUPER_KEYWORD, OP_NULL},
+
+ {"null", NULL_KEYWORD, OP_NULL},
+ {"true", TRUE_KEYWORD, OP_NULL},
+ {"false", FALSE_KEYWORD, OP_NULL},
+
+ {"init", INIT_KEYWORD, OP_NULL},
+ {"sizeof", SIZEOF_KEYWORD, OP_NULL},
+ {"typeof", TYPEOF_KEYWORD, OP_NULL},
+ {"typeid", TYPEID_KEYWORD, OP_NULL},
+
+ {"delegate", DELEGATE_KEYWORD, OP_NULL},
+ {"function", FUNCTION_KEYWORD, OP_NULL},
+ {"struct", STRUCT_KEYWORD, OP_NULL},
+ {"union", UNION_KEYWORD, OP_NULL},
+ {"class", CLASS_KEYWORD, OP_NULL},
+ {"interface", INTERFACE_KEYWORD, OP_NULL},
+ {"enum", ENUM_KEYWORD, OP_NULL},
+ {"template", TEMPLATE_KEYWORD, OP_NULL},
+ };
+
+/* This is set if a NAME token appeared at the very end of the input
+ string, with no whitespace separating the name from the EOF. This
+ is used only when parsing to do field name completion. */
+static int saw_name_at_eof;
+
+/* This is set if the previously-returned token was a structure operator.
+ This is used only when parsing to do field name completion. */
+static int last_was_structop;
+
+/* Depth of parentheses. */
+static int paren_depth;
+
+/* Read one token, getting characters through lexptr. */
+
+static int
+lex_one_token (struct parser_state *par_state)
+{
+ int c;
+ int namelen;
+ const char *tokstart;
+ int saw_structop = last_was_structop;
+
+ last_was_structop = 0;
+
+ retry:
+
+ pstate->prev_lexptr = pstate->lexptr;
+
+ tokstart = pstate->lexptr;
+ /* See if it is a special token of length 3. */
+ for (const auto &token : tokentab3)
+ if (strncmp (tokstart, token.oper, 3) == 0)
+ {
+ pstate->lexptr += 3;
+ d_yylval.opcode = token.opcode;
+ return token.token;
+ }
+
+ /* See if it is a special token of length 2. */
+ for (const auto &token : tokentab2)
+ if (strncmp (tokstart, token.oper, 2) == 0)
+ {
+ pstate->lexptr += 2;
+ d_yylval.opcode = token.opcode;
+ return token.token;
+ }
+
+ switch (c = *tokstart)
+ {
+ case 0:
+ /* If we're parsing for field name completion, and the previous
+ token allows such completion, return a COMPLETE token.
+ Otherwise, we were already scanning the original text, and
+ we're really done. */
+ if (saw_name_at_eof)
+ {
+ saw_name_at_eof = 0;
+ return COMPLETE;
+ }
+ else if (saw_structop)
+ return COMPLETE;
+ else
+ return 0;
+
+ case ' ':
+ case '\t':
+ case '\n':
+ pstate->lexptr++;
+ goto retry;
+
+ case '[':
+ case '(':
+ paren_depth++;
+ pstate->lexptr++;
+ return c;
+
+ case ']':
+ case ')':
+ if (paren_depth == 0)
+ return 0;
+ paren_depth--;
+ pstate->lexptr++;
+ return c;
+
+ case ',':
+ if (pstate->comma_terminates && paren_depth == 0)
+ return 0;
+ pstate->lexptr++;
+ return c;
+
+ case '.':
+ /* Might be a floating point number. */
+ if (pstate->lexptr[1] < '0' || pstate->lexptr[1] > '9')
+ {
+ if (pstate->parse_completion)
+ last_was_structop = 1;
+ goto symbol; /* Nope, must be a symbol. */
+ }
+ [[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 = input_radix > 10;
+
+ if (c == '0' && (p[1] == 'x' || p[1] == 'X'))
+ {
+ p += 2;
+ hex = 1;
+ }
+
+ for (;; ++p)
+ {
+ /* Hex exponents start with 'p', 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 && c_tolower (p[0]) == 'e')
+ || (hex && !got_e && c_tolower (p[0] == 'p')))
+ got_dot = got_e = 1;
+ /* A '.' always indicates a decimal floating point number
+ regardless of the radix. If we have a '..' then its the
+ end of the number and the beginning of a slice. */
+ else if (!got_dot && (p[0] == '.' && p[1] != '.'))
+ got_dot = 1;
+ /* This is the sign of the exponent, not the end of the number. */
+ else if (got_e && (c_tolower (p[-1]) == 'e'
+ || c_tolower (p[-1]) == 'p')
+ && (*p == '-' || *p == '+'))
+ continue;
+ /* We will take any letters or digits, ignoring any embedded '_'.
+ parse_number will complain if past the radix, or if L or U are
+ not final. */
+ else if ((*p < '0' || *p > '9') && (*p != '_')
+ && ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')))
+ break;
+ }
+
+ toktype = parse_number (par_state, tokstart, p - tokstart,
+ got_dot|got_e, &d_yylval);
+ if (toktype == ERROR)
+ error (_("Invalid number \"%.*s\"."), (int) (p - tokstart),
+ tokstart);
+ pstate->lexptr = p;
+ return toktype;
+ }
+
+ case '@':
+ {
+ const char *p = &tokstart[1];
+ size_t len = strlen ("entry");
+
+ while (c_isspace (*p))
+ p++;
+ if (strncmp (p, "entry", len) == 0 && !c_isalnum (p[len])
+ && p[len] != '_')
+ {
+ pstate->lexptr = &p[len];
+ return ENTRY;
+ }
+ }
+ [[fallthrough]];
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '%':
+ case '|':
+ case '&':
+ case '^':
+ case '~':
+ case '!':
+ case '<':
+ case '>':
+ case '?':
+ case ':':
+ case '=':
+ case '{':
+ case '}':
+ symbol:
+ pstate->lexptr++;
+ return c;
+
+ case '\'':
+ case '"':
+ case '`':
+ {
+ int host_len;
+ int result = parse_string_or_char (tokstart, &pstate->lexptr,
+ &d_yylval.tsval, &host_len);
+ if (result == CHARACTER_LITERAL)
+ {
+ if (host_len == 0)
+ error (_("Empty character constant."));
+ else if (host_len > 2 && c == '\'')
+ {
+ ++tokstart;
+ namelen = pstate->lexptr - tokstart - 1;
+ goto tryname;
+ }
+ else if (host_len > 1)
+ error (_("Invalid character constant."));
+ }
+ return result;
+ }
+ }
+
+ if (!(c == '_' || c == '$'
+ || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
+ /* We must have come across a bad character (e.g. ';'). */
+ error (_("Invalid character '%c' in expression"), c);
+
+ /* It's a name. See how long it is. */
+ namelen = 0;
+ for (c = tokstart[namelen];
+ (c == '_' || c == '$' || (c >= '0' && c <= '9')
+ || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));)
+ c = tokstart[++namelen];
+
+ /* The token "if" terminates the expression and is NOT
+ removed from the input stream. */
+ if (namelen == 2 && tokstart[0] == 'i' && tokstart[1] == 'f')
+ return 0;
+
+ /* For the same reason (breakpoint conditions), "thread N"
+ terminates the expression. "thread" could be an identifier, but
+ an identifier is never followed by a number without intervening
+ punctuation. "task" is similar. Handle abbreviations of these,
+ similarly to breakpoint.c:find_condition_and_thread. */
+ if (namelen >= 1
+ && (strncmp (tokstart, "thread", namelen) == 0
+ || strncmp (tokstart, "task", namelen) == 0)
+ && (tokstart[namelen] == ' ' || tokstart[namelen] == '\t'))
+ {
+ const char *p = skip_spaces (tokstart + namelen + 1);
+ if (*p >= '0' && *p <= '9')
+ return 0;
+ }
+
+ pstate->lexptr += namelen;
+
+ tryname:
+
+ d_yylval.sval.ptr = tokstart;
+ d_yylval.sval.length = namelen;
+
+ /* Catch specific keywords. */
+ std::string copy = copy_name (d_yylval.sval);
+ for (const auto &token : ident_tokens)
+ if (copy == token.oper)
+ {
+ /* It is ok to always set this, even though we don't always
+ strictly need to. */
+ d_yylval.opcode = token.opcode;
+ return token.token;
+ }
+
+ if (*tokstart == '$')
+ return DOLLAR_VARIABLE;
+
+ d_yylval.tsym.type
+ = language_lookup_primitive_type (par_state->language (),
+ par_state->gdbarch (), copy.c_str ());
+ if (d_yylval.tsym.type != NULL)
+ return TYPENAME;
+
+ /* Input names that aren't symbols but ARE valid hex numbers,
+ when the input radix permits them, can be names or numbers
+ depending on the parse. Note we support radixes > 16 here. */
+ if ((tokstart[0] >= 'a' && tokstart[0] < 'a' + input_radix - 10)
+ || (tokstart[0] >= 'A' && tokstart[0] < 'A' + input_radix - 10))
+ {
+ d_exp_parser_YYSTYPE newlval; /* Its value is ignored. */
+ int hextype = parse_number (par_state, tokstart, namelen, 0, &newlval);
+ if (hextype == INTEGER_LITERAL)
+ return NAME_OR_INT;
+ }
+
+ if (pstate->parse_completion && *pstate->lexptr == '\0')
+ saw_name_at_eof = 1;
+
+ return IDENTIFIER;
+}
+
+/* An object of this type is pushed on a FIFO by the "outer" lexer. */
+struct d_token_and_value
+{
+ int token;
+ d_exp_parser_YYSTYPE value;
+};
+
+
+/* A FIFO of tokens that have been read but not yet returned to the
+ parser. */
+static std::vector<d_token_and_value> token_fifo;
+
+/* Non-zero if the lexer should return tokens from the FIFO. */
+static int popping;
+
+/* Temporary storage for yylex; this holds symbol names as they are
+ built up. */
+static auto_obstack name_obstack;
+
+/* Classify an IDENTIFIER token. The contents of the token are in `yylval'.
+ Updates yylval and returns the new token type. BLOCK is the block
+ in which lookups start; this can be NULL to mean the global scope. */
+
+static int
+classify_name (struct parser_state *par_state, const struct block *block)
+{
+ struct block_symbol sym;
+ struct field_of_this_result is_a_field_of_this;
+
+ std::string copy = copy_name (d_yylval.sval);
+
+ sym = lookup_symbol (copy.c_str (), block, SEARCH_VFT, &is_a_field_of_this);
+ if (sym.symbol && sym.symbol->loc_class () == LOC_TYPEDEF)
+ {
+ d_yylval.tsym.type = sym.symbol->type ();
+ return TYPENAME;
+ }
+ else if (sym.symbol == NULL)
+ {
+ /* Look-up first for a module name, then a type. */
+ sym = lookup_symbol (copy.c_str (), block, SEARCH_MODULE_DOMAIN,
+ nullptr);
+ if (sym.symbol == NULL)
+ sym = lookup_symbol (copy.c_str (), block, SEARCH_STRUCT_DOMAIN,
+ nullptr);
+
+ if (sym.symbol != NULL)
+ {
+ d_yylval.tsym.type = sym.symbol->type ();
+ return TYPENAME;
+ }
+
+ return UNKNOWN_NAME;
+ }
+
+ return IDENTIFIER;
+}
+
+/* Like classify_name, but used by the inner loop of the lexer, when a
+ name might have already been seen. CONTEXT is the context type, or
+ NULL if this is the first component of a name. */
+
+static int
+classify_inner_name (struct parser_state *par_state,
+ const struct block *block, struct type *context)
+{
+ struct type *type;
+
+ if (context == NULL)
+ return classify_name (par_state, block);
+
+ type = check_typedef (context);
+ if (!type_aggregate_p (type))
+ return ERROR;
+
+ std::string copy = copy_name (d_yylval.ssym.stoken);
+ d_yylval.ssym.sym = d_lookup_nested_symbol (type, copy.c_str (), block);
+
+ if (d_yylval.ssym.sym.symbol == NULL)
+ return ERROR;
+
+ if (d_yylval.ssym.sym.symbol->loc_class () == LOC_TYPEDEF)
+ {
+ d_yylval.tsym.type = d_yylval.ssym.sym.symbol->type ();
+ return TYPENAME;
+ }
+
+ return IDENTIFIER;
+}
+
+/* See d-exp-parser.h. */
+
+int
+d_yylex (void)
+{
+ d_token_and_value current;
+ int last_was_dot;
+ struct type *context_type = NULL;
+ int last_to_examine, next_to_examine, checkpoint;
+ const struct block *search_block;
+
+ if (popping && !token_fifo.empty ())
+ goto do_pop;
+ popping = 0;
+
+ /* Read the first token and decide what to do. */
+ current.token = lex_one_token (pstate);
+ if (current.token != IDENTIFIER && current.token != '.')
+ return current.token;
+
+ /* Read any sequence of alternating "." and identifier tokens into
+ the token FIFO. */
+ current.value = d_yylval;
+ token_fifo.push_back (current);
+ last_was_dot = current.token == '.';
+
+ while (1)
+ {
+ current.token = lex_one_token (pstate);
+ current.value = d_yylval;
+ token_fifo.push_back (current);
+
+ if ((last_was_dot && current.token != IDENTIFIER)
+ || (!last_was_dot && current.token != '.'))
+ break;
+
+ last_was_dot = !last_was_dot;
+ }
+ popping = 1;
+
+ /* We always read one extra token, so compute the number of tokens
+ to examine accordingly. */
+ last_to_examine = token_fifo.size () - 2;
+ next_to_examine = 0;
+
+ current = token_fifo[next_to_examine];
+ ++next_to_examine;
+
+ /* If we are not dealing with a typename, now is the time to find out. */
+ if (current.token == IDENTIFIER)
+ {
+ d_yylval = current.value;
+ current.token = classify_name (pstate, pstate->expression_context_block);
+ current.value = d_yylval;
+ }
+
+ /* If the IDENTIFIER is not known, it could be a package symbol,
+ first try building up a name until we find the qualified module. */
+ if (current.token == UNKNOWN_NAME)
+ {
+ name_obstack.clear ();
+ obstack_grow (&name_obstack, current.value.sval.ptr,
+ current.value.sval.length);
+
+ last_was_dot = 0;
+
+ while (next_to_examine <= last_to_examine)
+ {
+ d_token_and_value next;
+
+ next = token_fifo[next_to_examine];
+ ++next_to_examine;
+
+ if (next.token == IDENTIFIER && last_was_dot)
+ {
+ /* Update the partial name we are constructing. */
+ obstack_grow_str (&name_obstack, ".");
+ obstack_grow (&name_obstack, next.value.sval.ptr,
+ next.value.sval.length);
+
+ d_yylval.sval.ptr = (char *) obstack_base (&name_obstack);
+ d_yylval.sval.length = obstack_object_size (&name_obstack);
+
+ current.token = classify_name (pstate,
+ pstate->expression_context_block);
+ current.value = d_yylval;
+
+ /* We keep going until we find a TYPENAME. */
+ if (current.token == TYPENAME)
+ {
+ /* Install it as the first token in the FIFO. */
+ token_fifo[0] = current;
+ token_fifo.erase (token_fifo.begin () + 1,
+ token_fifo.begin () + next_to_examine);
+ break;
+ }
+ }
+ else if (next.token == '.' && !last_was_dot)
+ last_was_dot = 1;
+ else
+ {
+ /* We've reached the end of the name. */
+ break;
+ }
+ }
+
+ /* Reset our current token back to the start, if we found nothing
+ this means that we will just jump to do pop. */
+ current = token_fifo[0];
+ next_to_examine = 1;
+ }
+ if (current.token != TYPENAME && current.token != '.')
+ goto do_pop;
+
+ name_obstack.clear ();
+ checkpoint = 0;
+ if (current.token == '.')
+ search_block = NULL;
+ else
+ {
+ gdb_assert (current.token == TYPENAME);
+ search_block = pstate->expression_context_block;
+ obstack_grow (&name_obstack, current.value.sval.ptr,
+ current.value.sval.length);
+ context_type = current.value.tsym.type;
+ checkpoint = 1;
+ }
+
+ last_was_dot = current.token == '.';
+
+ while (next_to_examine <= last_to_examine)
+ {
+ d_token_and_value next;
+
+ next = token_fifo[next_to_examine];
+ ++next_to_examine;
+
+ if (next.token == IDENTIFIER && last_was_dot)
+ {
+ int classification;
+
+ d_yylval = next.value;
+ classification = classify_inner_name (pstate, search_block,
+ context_type);
+ /* We keep going until we either run out of names, or until
+ we have a qualified name which is not a type. */
+ if (classification != TYPENAME && classification != IDENTIFIER)
+ break;
+
+ /* Accept up to this token. */
+ checkpoint = next_to_examine;
+
+ /* Update the partial name we are constructing. */
+ if (context_type != NULL)
+ {
+ /* We don't want to put a leading "." into the name. */
+ obstack_grow_str (&name_obstack, ".");
+ }
+ obstack_grow (&name_obstack, next.value.sval.ptr,
+ next.value.sval.length);
+
+ d_yylval.sval.ptr = (char *) obstack_base (&name_obstack);
+ d_yylval.sval.length = obstack_object_size (&name_obstack);
+ current.value = d_yylval;
+ current.token = classification;
+
+ last_was_dot = 0;
+
+ if (classification == IDENTIFIER)
+ break;
+
+ context_type = d_yylval.tsym.type;
+ }
+ else if (next.token == '.' && !last_was_dot)
+ last_was_dot = 1;
+ else
+ {
+ /* We've reached the end of the name. */
+ break;
+ }
+ }
+
+ /* If we have a replacement token, install it as the first token in
+ the FIFO, and delete the other constituent tokens. */
+ if (checkpoint > 0)
+ {
+ token_fifo[0] = current;
+ if (checkpoint > 1)
+ token_fifo.erase (token_fifo.begin () + 1,
+ token_fifo.begin () + checkpoint);
+ }
+
+ do_pop:
+ current = token_fifo[0];
+ token_fifo.erase (token_fifo.begin ());
+ d_yylval = current.value;
+ return current.token;
+}
+
+/* See d-exp-parser.h. */
+
+void
+d_yyerror (const char *msg)
+{
+ pstate->parse_error (msg);
+}
+
+} /* namespace d_exp_parser */
+
+/* See d-exp-parser.h. */
+
+int
+d_parse (struct parser_state *par_state)
+{
+ using namespace d_exp_parser;
+
+ /* Setting up the parser state. */
+ scoped_restore pstate_restore = make_scoped_restore (&pstate);
+ gdb_assert (par_state != NULL);
+ pstate = par_state;
+
+ scoped_restore restore_yydebug = make_scoped_restore (&d_yydebug,
+ par_state->debug);
+
+ struct type_stack stack;
+ scoped_restore restore_type_stack
+ = make_scoped_restore (&d_exp_parser::type_stack, &stack);
+
+ /* Initialize some state used by the lexer. */
+ last_was_structop = 0;
+ saw_name_at_eof = 0;
+ paren_depth = 0;
+
+ token_fifo.clear ();
+ popping = 0;
+ name_obstack.clear ();
+
+ int result = d_yyparse ();
+ if (!result)
+ pstate->set_operation (pstate->pop ());
+ return result;
+}
diff --git a/gdb/d-exp-parser.h b/gdb/d-exp-parser.h
new file mode 100644
index 000000000000..d73575eb83b9
--- /dev/null
+++ b/gdb/d-exp-parser.h
@@ -0,0 +1,83 @@
+/* Support code for the D expression parser, for GDB.
+
+ Copyright (C) 2014-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_D_EXP_PARSER_H
+#define GDB_D_EXP_PARSER_H
+
+#include "parser-defs.h"
+#include "type-stack.h"
+#include "d-lang.h"
+
+union d_exp_parser_YYSTYPE;
+
+namespace d_exp_parser {
+
+/* The state of the parser, used internally when we are parsing the
+ expression. */
+
+extern parser_state *pstate;
+
+/* The current type stack. */
+
+extern struct type_stack *type_stack;
+
+/* Return the D type table for the architecture associated to PS. */
+
+static inline const struct builtin_d_type *
+parse_d_type (parser_state *ps)
+{
+ return builtin_d_type (ps->gdbarch ());
+}
+
+/* Return true if the type is aggregate-like. */
+
+int type_aggregate_p (struct type *type);
+
+/* Take care of parsing a number (anything that starts with a digit).
+ Set yylval and return the token type; update lexptr.
+ LEN is the number of characters in it. */
+
+/*** Needs some error checking for the float case ***/
+
+int parse_number (struct parser_state *ps, const char *p, int len,
+ int parsed_float, d_exp_parser_YYSTYPE *putithere);
+
+/* The outer level of a two-level lexer. This calls the inner lexer
+ to return tokens. It then either returns these tokens, or
+ aggregates them into a larger token. This lets us work around a
+ problem in our parsing approach, where the parser could not
+ distinguish between qualified names and qualified types at the
+ right point. */
+
+int d_yylex ();
+
+/* The error handler invoked by the generated parser. Report MSG as a
+ parse error on the current parser state. */
+
+void d_yyerror (const char *msg);
+
+} /* namespace d_exp_parser */
+
+/* Parse a D expression using the lexer input and context held in
+ PAR_STATE. On success, return 0 and leave the resulting operation
+ set on PAR_STATE. On failure, return non-zero. */
+
+int d_parse (struct parser_state *par_state);
+
+#endif /* GDB_D_EXP_PARSER_H */
diff --git a/gdb/d-exp-parser.y b/gdb/d-exp-parser.y
index c35d78b83140..a83b1aaa4ec4 100644
--- a/gdb/d-exp-parser.y
+++ b/gdb/d-exp-parser.y
@@ -42,41 +42,20 @@
#include "value.h"
#include "parser-defs.h"
#include "language.h"
-#include "c-lang.h"
-#include "c-exp-parser.h"
#include "d-lang.h"
-#include "charset.h"
+#include "d-exp-parser.h"
#include "block.h"
#include "type-stack.h"
#include "expop.h"
#include "cli/cli-style.h"
-/* The state of the parser, used internally when we are parsing the
- expression. */
-
-static struct parser_state *pstate = NULL;
-
-/* The current type stack. */
-static struct type_stack *type_stack;
-
-int yyparse (void);
-
-static int yylex (void);
-
-static void yyerror (const char *);
-
-static int type_aggregate_p (struct type *);
-
-/* Return the D type table for the architecture associated to PS. */
-
-static inline const struct builtin_d_type *
-parse_d_type (parser_state *ps)
-{
- return builtin_d_type (ps->gdbarch ());
-}
-
+using namespace d_exp_parser;
using namespace expr;
+/* Bring the d_exp_parser::type_stack global into this scope, so that it hides
+ the struct type_stack type name. */
+using d_exp_parser::type_stack;
+
%}
/* Although the yacc "value" of an expression is not used,
@@ -105,12 +84,6 @@ using namespace expr;
struct stoken_vector svec;
}
-%{
-/* YYSTYPE gets defined by %union */
-static int parse_number (struct parser_state *, const char *,
- int, int, YYSTYPE *);
-%}
-
%token <sval> IDENTIFIER UNKNOWN_NAME
%token <tsym> TYPENAME
%token <voidval> COMPLETE
@@ -625,1007 +598,3 @@ BasicType:
TYPENAME
{ $$ = $1.type; }
;
-
-%%
-
-/* Return true if the type is aggregate-like. */
-
-static int
-type_aggregate_p (struct type *type)
-{
- return (type->code () == TYPE_CODE_STRUCT
- || type->code () == TYPE_CODE_UNION
- || type->code () == TYPE_CODE_MODULE
- || (type->code () == TYPE_CODE_ENUM
- && type->is_declared_class ()));
-}
-
-/* Take care of parsing a number (anything that starts with a digit).
- Set yylval and return the token type; update lexptr.
- LEN is the number of characters in it. */
-
-/*** Needs some error checking for the float case ***/
-
-static int
-parse_number (struct parser_state *ps, const char *p,
- int len, int parsed_float, YYSTYPE *putithere)
-{
- ULONGEST n = 0;
- ULONGEST prevn = 0;
- ULONGEST un;
-
- int i = 0;
- int c;
- int base = input_radix;
- int unsigned_p = 0;
- int long_p = 0;
-
- /* We have found a "L" or "U" suffix. */
- int found_suffix = 0;
-
- ULONGEST high_bit;
- struct type *signed_type;
- struct type *unsigned_type;
-
- if (parsed_float)
- {
- char *s, *sp;
-
- /* Strip out all embedded '_' before passing to parse_float. */
- s = (char *) alloca (len + 1);
- sp = s;
- while (len-- > 0)
- {
- if (*p != '_')
- *sp++ = *p;
- p++;
- }
- *sp = '\0';
- len = strlen (s);
-
- /* Check suffix for `i' , `fi' or `li' (idouble, ifloat or ireal). */
- if (len >= 1 && c_tolower (s[len - 1]) == 'i')
- {
- if (len >= 2 && c_tolower (s[len - 2]) == 'f')
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_ifloat;
- len -= 2;
- }
- else if (len >= 2 && c_tolower (s[len - 2]) == 'l')
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_ireal;
- len -= 2;
- }
- else
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_idouble;
- len -= 1;
- }
- }
- /* Check suffix for `f' or `l'' (float or real). */
- else if (len >= 1 && c_tolower (s[len - 1]) == 'f')
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_float;
- len -= 1;
- }
- else if (len >= 1 && c_tolower (s[len - 1]) == 'l')
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_real;
- len -= 1;
- }
- /* Default type if no suffix. */
- else
- {
- putithere->typed_val_float.type
- = parse_d_type (ps)->builtin_double;
- }
-
- if (!parse_float (s, len,
- putithere->typed_val_float.type,
- putithere->typed_val_float.val))
- return ERROR;
-
- return FLOAT_LITERAL;
- }
-
- /* Handle base-switching prefixes 0x, 0b, 0 */
- if (p[0] == '0')
- switch (p[1])
- {
- case 'x':
- case 'X':
- if (len >= 3)
- {
- p += 2;
- base = 16;
- len -= 2;
- }
- break;
-
- case 'b':
- case 'B':
- if (len >= 3)
- {
- p += 2;
- base = 2;
- len -= 2;
- }
- break;
-
- default:
- base = 8;
- break;
- }
-
- while (len-- > 0)
- {
- c = *p++;
- if (c == '_')
- continue; /* Ignore embedded '_'. */
- if (c >= 'A' && c <= 'Z')
- c += 'a' - 'A';
- if (c != 'l' && c != 'u')
- n *= base;
- if (c >= '0' && c <= '9')
- {
- if (found_suffix)
- return ERROR;
- n += i = c - '0';
- }
- else
- {
- if (base > 10 && c >= 'a' && c <= 'f')
- {
- if (found_suffix)
- return ERROR;
- n += i = c - 'a' + 10;
- }
- else if (c == 'l' && long_p == 0)
- {
- long_p = 1;
- found_suffix = 1;
- }
- else if (c == 'u' && unsigned_p == 0)
- {
- unsigned_p = 1;
- found_suffix = 1;
- }
- else
- return ERROR; /* Char not a digit */
- }
- if (i >= base)
- return ERROR; /* Invalid digit in this base. */
- /* Portably test for integer overflow. */
- if (c != 'l' && c != 'u')
- {
- ULONGEST n2 = prevn * base;
- if ((n2 / base != prevn) || (n2 + i < prevn))
- error (_("Numeric constant too large."));
- }
- prevn = n;
- }
-
- /* An integer constant is an int or a long. An L suffix forces it to
- be long, and a U suffix forces it to be unsigned. To figure out
- whether it fits, we shift it right and see whether anything remains.
- Note that we can't shift sizeof (LONGEST) * HOST_CHAR_BIT bits or
- more in one operation, because many compilers will warn about such a
- shift (which always produces a zero result). To deal with the case
- where it is we just always shift the value more than once, with fewer
- bits each time. */
- un = (ULONGEST) n >> 2;
- if (long_p == 0 && (un >> 30) == 0)
- {
- high_bit = ((ULONGEST) 1) << 31;
- signed_type = parse_d_type (ps)->builtin_int;
- /* For decimal notation, keep the sign of the worked out type. */
- if (base == 10 && !unsigned_p)
- unsigned_type = parse_d_type (ps)->builtin_long;
- else
- unsigned_type = parse_d_type (ps)->builtin_uint;
- }
- else
- {
- int shift;
- if (sizeof (ULONGEST) * HOST_CHAR_BIT < 64)
- /* A long long does not fit in a LONGEST. */
- shift = (sizeof (ULONGEST) * HOST_CHAR_BIT - 1);
- else
- shift = 63;
- high_bit = (ULONGEST) 1 << shift;
- signed_type = parse_d_type (ps)->builtin_long;
- unsigned_type = parse_d_type (ps)->builtin_ulong;
- }
-
- putithere->typed_val_int.val = n;
-
- /* If the high bit of the worked out type is set then this number
- has to be unsigned_type. */
- if (unsigned_p || (n & high_bit))
- putithere->typed_val_int.type = unsigned_type;
- else
- putithere->typed_val_int.type = signed_type;
-
- return INTEGER_LITERAL;
-}
-
-/* Temporary obstack used for holding strings. */
-static struct obstack tempbuf;
-static int tempbuf_init;
-
-/* Parse a string or character literal from TOKPTR. The string or
- character may be wide or unicode. *OUTPTR is set to just after the
- end of the literal in the input string. The resulting token is
- stored in VALUE. This returns a token value, either STRING or
- CHAR, depending on what was parsed. *HOST_CHARS is set to the
- number of host characters in the literal. */
-
-static int
-parse_string_or_char (const char *tokptr, const char **outptr,
- struct typed_stoken *value, int *host_chars)
-{
- int quote;
-
- /* Build the gdb internal form of the input string in tempbuf. Note
- that the buffer is null byte terminated *only* for the
- convenience of debugging gdb itself and printing the buffer
- contents when the buffer contains no embedded nulls. Gdb does
- not depend upon the buffer being null byte terminated, it uses
- the length string instead. This allows gdb to handle C strings
- (as well as strings in other languages) with embedded null
- bytes */
-
- if (!tempbuf_init)
- tempbuf_init = 1;
- else
- obstack_free (&tempbuf, NULL);
- obstack_init (&tempbuf);
-
- /* Skip the quote. */
- quote = *tokptr;
- ++tokptr;
-
- *host_chars = 0;
-
- while (*tokptr)
- {
- char c = *tokptr;
- if (c == '\\')
- {
- ++tokptr;
- *host_chars += c_parse_escape (&tokptr, &tempbuf);
- }
- else if (c == quote)
- break;
- else
- {
- obstack_1grow (&tempbuf, c);
- ++tokptr;
- /* FIXME: this does the wrong thing with multi-byte host
- characters. We could use mbrlen here, but that would
- make "set host-charset" a bit less useful. */
- ++*host_chars;
- }
- }
-
- if (*tokptr != quote)
- {
- if (quote == '"' || quote == '`')
- error (_("Unterminated string in expression."));
- else
- error (_("Unmatched single quote."));
- }
- ++tokptr;
-
- /* FIXME: should instead use own language string_type enum
- and handle D-specific string suffixes here. */
- if (quote == '\'')
- value->type = C_CHAR;
- else
- value->type = C_STRING;
-
- value->ptr = (char *) obstack_base (&tempbuf);
- value->length = obstack_object_size (&tempbuf);
-
- *outptr = tokptr;
-
- return quote == '\'' ? CHARACTER_LITERAL : STRING_LITERAL;
-}
-
-struct d_token
-{
- const char *oper;
- int token;
- enum exp_opcode opcode;
-};
-
-static const struct d_token tokentab3[] =
- {
- {"^^=", ASSIGN_MODIFY, BINOP_EXP},
- {"<<=", ASSIGN_MODIFY, BINOP_LSH},
- {">>=", ASSIGN_MODIFY, BINOP_RSH},
- };
-
-static const struct d_token tokentab2[] =
- {
- {"+=", ASSIGN_MODIFY, BINOP_ADD},
- {"-=", ASSIGN_MODIFY, BINOP_SUB},
- {"*=", ASSIGN_MODIFY, BINOP_MUL},
- {"/=", ASSIGN_MODIFY, BINOP_DIV},
- {"%=", ASSIGN_MODIFY, BINOP_REM},
- {"|=", ASSIGN_MODIFY, BINOP_BITWISE_IOR},
- {"&=", ASSIGN_MODIFY, BINOP_BITWISE_AND},
- {"^=", ASSIGN_MODIFY, BINOP_BITWISE_XOR},
- {"++", INCREMENT, OP_NULL},
- {"--", DECREMENT, OP_NULL},
- {"&&", ANDAND, OP_NULL},
- {"||", OROR, OP_NULL},
- {"^^", HATHAT, OP_NULL},
- {"<<", LSH, OP_NULL},
- {">>", RSH, OP_NULL},
- {"==", EQUAL, OP_NULL},
- {"!=", NOTEQUAL, OP_NULL},
- {"<=", LEQ, OP_NULL},
- {">=", GEQ, OP_NULL},
- {"..", DOTDOT, OP_NULL},
- };
-
-/* Identifier-like tokens. */
-static const struct d_token ident_tokens[] =
- {
- {"is", IDENTITY, OP_NULL},
- {"!is", NOTIDENTITY, OP_NULL},
-
- {"cast", CAST_KEYWORD, OP_NULL},
- {"const", CONST_KEYWORD, OP_NULL},
- {"immutable", IMMUTABLE_KEYWORD, OP_NULL},
- {"shared", SHARED_KEYWORD, OP_NULL},
- {"super", SUPER_KEYWORD, OP_NULL},
-
- {"null", NULL_KEYWORD, OP_NULL},
- {"true", TRUE_KEYWORD, OP_NULL},
- {"false", FALSE_KEYWORD, OP_NULL},
-
- {"init", INIT_KEYWORD, OP_NULL},
- {"sizeof", SIZEOF_KEYWORD, OP_NULL},
- {"typeof", TYPEOF_KEYWORD, OP_NULL},
- {"typeid", TYPEID_KEYWORD, OP_NULL},
-
- {"delegate", DELEGATE_KEYWORD, OP_NULL},
- {"function", FUNCTION_KEYWORD, OP_NULL},
- {"struct", STRUCT_KEYWORD, OP_NULL},
- {"union", UNION_KEYWORD, OP_NULL},
- {"class", CLASS_KEYWORD, OP_NULL},
- {"interface", INTERFACE_KEYWORD, OP_NULL},
- {"enum", ENUM_KEYWORD, OP_NULL},
- {"template", TEMPLATE_KEYWORD, OP_NULL},
- };
-
-/* This is set if a NAME token appeared at the very end of the input
- string, with no whitespace separating the name from the EOF. This
- is used only when parsing to do field name completion. */
-static int saw_name_at_eof;
-
-/* This is set if the previously-returned token was a structure operator.
- This is used only when parsing to do field name completion. */
-static int last_was_structop;
-
-/* Depth of parentheses. */
-static int paren_depth;
-
-/* Read one token, getting characters through lexptr. */
-
-static int
-lex_one_token (struct parser_state *par_state)
-{
- int c;
- int namelen;
- const char *tokstart;
- int saw_structop = last_was_structop;
-
- last_was_structop = 0;
-
- retry:
-
- pstate->prev_lexptr = pstate->lexptr;
-
- tokstart = pstate->lexptr;
- /* See if it is a special token of length 3. */
- for (const auto &token : tokentab3)
- if (strncmp (tokstart, token.oper, 3) == 0)
- {
- pstate->lexptr += 3;
- yylval.opcode = token.opcode;
- return token.token;
- }
-
- /* See if it is a special token of length 2. */
- for (const auto &token : tokentab2)
- if (strncmp (tokstart, token.oper, 2) == 0)
- {
- pstate->lexptr += 2;
- yylval.opcode = token.opcode;
- return token.token;
- }
-
- switch (c = *tokstart)
- {
- case 0:
- /* If we're parsing for field name completion, and the previous
- token allows such completion, return a COMPLETE token.
- Otherwise, we were already scanning the original text, and
- we're really done. */
- if (saw_name_at_eof)
- {
- saw_name_at_eof = 0;
- return COMPLETE;
- }
- else if (saw_structop)
- return COMPLETE;
- else
- return 0;
-
- case ' ':
- case '\t':
- case '\n':
- pstate->lexptr++;
- goto retry;
-
- case '[':
- case '(':
- paren_depth++;
- pstate->lexptr++;
- return c;
-
- case ']':
- case ')':
- if (paren_depth == 0)
- return 0;
- paren_depth--;
- pstate->lexptr++;
- return c;
-
- case ',':
- if (pstate->comma_terminates && paren_depth == 0)
- return 0;
- pstate->lexptr++;
- return c;
-
- case '.':
- /* Might be a floating point number. */
- if (pstate->lexptr[1] < '0' || pstate->lexptr[1] > '9')
- {
- if (pstate->parse_completion)
- last_was_structop = 1;
- goto symbol; /* Nope, must be a symbol. */
- }
- [[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 = input_radix > 10;
-
- if (c == '0' && (p[1] == 'x' || p[1] == 'X'))
- {
- p += 2;
- hex = 1;
- }
-
- for (;; ++p)
- {
- /* Hex exponents start with 'p', 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 && c_tolower (p[0]) == 'e')
- || (hex && !got_e && c_tolower (p[0] == 'p')))
- got_dot = got_e = 1;
- /* A '.' always indicates a decimal floating point number
- regardless of the radix. If we have a '..' then its the
- end of the number and the beginning of a slice. */
- else if (!got_dot && (p[0] == '.' && p[1] != '.'))
- got_dot = 1;
- /* This is the sign of the exponent, not the end of the number. */
- else if (got_e && (c_tolower (p[-1]) == 'e'
- || c_tolower (p[-1]) == 'p')
- && (*p == '-' || *p == '+'))
- continue;
- /* We will take any letters or digits, ignoring any embedded '_'.
- parse_number will complain if past the radix, or if L or U are
- not final. */
- else if ((*p < '0' || *p > '9') && (*p != '_')
- && ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')))
- break;
- }
-
- toktype = parse_number (par_state, tokstart, p - tokstart,
- got_dot|got_e, &yylval);
- if (toktype == ERROR)
- error (_("Invalid number \"%.*s\"."), (int) (p - tokstart),
- tokstart);
- pstate->lexptr = p;
- return toktype;
- }
-
- case '@':
- {
- const char *p = &tokstart[1];
- size_t len = strlen ("entry");
-
- while (c_isspace (*p))
- p++;
- if (strncmp (p, "entry", len) == 0 && !c_isalnum (p[len])
- && p[len] != '_')
- {
- pstate->lexptr = &p[len];
- return ENTRY;
- }
- }
- [[fallthrough]];
- case '+':
- case '-':
- case '*':
- case '/':
- case '%':
- case '|':
- case '&':
- case '^':
- case '~':
- case '!':
- case '<':
- case '>':
- case '?':
- case ':':
- case '=':
- case '{':
- case '}':
- symbol:
- pstate->lexptr++;
- return c;
-
- case '\'':
- case '"':
- case '`':
- {
- int host_len;
- int result = parse_string_or_char (tokstart, &pstate->lexptr,
- &yylval.tsval, &host_len);
- if (result == CHARACTER_LITERAL)
- {
- if (host_len == 0)
- error (_("Empty character constant."));
- else if (host_len > 2 && c == '\'')
- {
- ++tokstart;
- namelen = pstate->lexptr - tokstart - 1;
- goto tryname;
- }
- else if (host_len > 1)
- error (_("Invalid character constant."));
- }
- return result;
- }
- }
-
- if (!(c == '_' || c == '$'
- || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')))
- /* We must have come across a bad character (e.g. ';'). */
- error (_("Invalid character '%c' in expression"), c);
-
- /* It's a name. See how long it is. */
- namelen = 0;
- for (c = tokstart[namelen];
- (c == '_' || c == '$' || (c >= '0' && c <= '9')
- || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'));)
- c = tokstart[++namelen];
-
- /* The token "if" terminates the expression and is NOT
- removed from the input stream. */
- if (namelen == 2 && tokstart[0] == 'i' && tokstart[1] == 'f')
- return 0;
-
- /* For the same reason (breakpoint conditions), "thread N"
- terminates the expression. "thread" could be an identifier, but
- an identifier is never followed by a number without intervening
- punctuation. "task" is similar. Handle abbreviations of these,
- similarly to breakpoint.c:find_condition_and_thread. */
- if (namelen >= 1
- && (strncmp (tokstart, "thread", namelen) == 0
- || strncmp (tokstart, "task", namelen) == 0)
- && (tokstart[namelen] == ' ' || tokstart[namelen] == '\t'))
- {
- const char *p = skip_spaces (tokstart + namelen + 1);
- if (*p >= '0' && *p <= '9')
- return 0;
- }
-
- pstate->lexptr += namelen;
-
- tryname:
-
- yylval.sval.ptr = tokstart;
- yylval.sval.length = namelen;
-
- /* Catch specific keywords. */
- std::string copy = copy_name (yylval.sval);
- for (const auto &token : ident_tokens)
- if (copy == token.oper)
- {
- /* It is ok to always set this, even though we don't always
- strictly need to. */
- yylval.opcode = token.opcode;
- return token.token;
- }
-
- if (*tokstart == '$')
- return DOLLAR_VARIABLE;
-
- yylval.tsym.type
- = language_lookup_primitive_type (par_state->language (),
- par_state->gdbarch (), copy.c_str ());
- if (yylval.tsym.type != NULL)
- return TYPENAME;
-
- /* Input names that aren't symbols but ARE valid hex numbers,
- when the input radix permits them, can be names or numbers
- depending on the parse. Note we support radixes > 16 here. */
- if ((tokstart[0] >= 'a' && tokstart[0] < 'a' + input_radix - 10)
- || (tokstart[0] >= 'A' && tokstart[0] < 'A' + input_radix - 10))
- {
- YYSTYPE newlval; /* Its value is ignored. */
- int hextype = parse_number (par_state, tokstart, namelen, 0, &newlval);
- if (hextype == INTEGER_LITERAL)
- return NAME_OR_INT;
- }
-
- if (pstate->parse_completion && *pstate->lexptr == '\0')
- saw_name_at_eof = 1;
-
- return IDENTIFIER;
-}
-
-/* An object of this type is pushed on a FIFO by the "outer" lexer. */
-struct d_token_and_value
-{
- int token;
- YYSTYPE value;
-};
-
-
-/* A FIFO of tokens that have been read but not yet returned to the
- parser. */
-static std::vector<d_token_and_value> token_fifo;
-
-/* Non-zero if the lexer should return tokens from the FIFO. */
-static int popping;
-
-/* Temporary storage for yylex; this holds symbol names as they are
- built up. */
-static auto_obstack name_obstack;
-
-/* Classify an IDENTIFIER token. The contents of the token are in `yylval'.
- Updates yylval and returns the new token type. BLOCK is the block
- in which lookups start; this can be NULL to mean the global scope. */
-
-static int
-classify_name (struct parser_state *par_state, const struct block *block)
-{
- struct block_symbol sym;
- struct field_of_this_result is_a_field_of_this;
-
- std::string copy = copy_name (yylval.sval);
-
- sym = lookup_symbol (copy.c_str (), block, SEARCH_VFT, &is_a_field_of_this);
- if (sym.symbol && sym.symbol->loc_class () == LOC_TYPEDEF)
- {
- yylval.tsym.type = sym.symbol->type ();
- return TYPENAME;
- }
- else if (sym.symbol == NULL)
- {
- /* Look-up first for a module name, then a type. */
- sym = lookup_symbol (copy.c_str (), block, SEARCH_MODULE_DOMAIN,
- nullptr);
- if (sym.symbol == NULL)
- sym = lookup_symbol (copy.c_str (), block, SEARCH_STRUCT_DOMAIN,
- nullptr);
-
- if (sym.symbol != NULL)
- {
- yylval.tsym.type = sym.symbol->type ();
- return TYPENAME;
- }
-
- return UNKNOWN_NAME;
- }
-
- return IDENTIFIER;
-}
-
-/* Like classify_name, but used by the inner loop of the lexer, when a
- name might have already been seen. CONTEXT is the context type, or
- NULL if this is the first component of a name. */
-
-static int
-classify_inner_name (struct parser_state *par_state,
- const struct block *block, struct type *context)
-{
- struct type *type;
-
- if (context == NULL)
- return classify_name (par_state, block);
-
- type = check_typedef (context);
- if (!type_aggregate_p (type))
- return ERROR;
-
- std::string copy = copy_name (yylval.ssym.stoken);
- yylval.ssym.sym = d_lookup_nested_symbol (type, copy.c_str (), block);
-
- if (yylval.ssym.sym.symbol == NULL)
- return ERROR;
-
- if (yylval.ssym.sym.symbol->loc_class () == LOC_TYPEDEF)
- {
- yylval.tsym.type = yylval.ssym.sym.symbol->type ();
- return TYPENAME;
- }
-
- return IDENTIFIER;
-}
-
-/* The outer level of a two-level lexer. This calls the inner lexer
- to return tokens. It then either returns these tokens, or
- aggregates them into a larger token. This lets us work around a
- problem in our parsing approach, where the parser could not
- distinguish between qualified names and qualified types at the
- right point. */
-
-static int
-yylex (void)
-{
- d_token_and_value current;
- int last_was_dot;
- struct type *context_type = NULL;
- int last_to_examine, next_to_examine, checkpoint;
- const struct block *search_block;
-
- if (popping && !token_fifo.empty ())
- goto do_pop;
- popping = 0;
-
- /* Read the first token and decide what to do. */
- current.token = lex_one_token (pstate);
- if (current.token != IDENTIFIER && current.token != '.')
- return current.token;
-
- /* Read any sequence of alternating "." and identifier tokens into
- the token FIFO. */
- current.value = yylval;
- token_fifo.push_back (current);
- last_was_dot = current.token == '.';
-
- while (1)
- {
- current.token = lex_one_token (pstate);
- current.value = yylval;
- token_fifo.push_back (current);
-
- if ((last_was_dot && current.token != IDENTIFIER)
- || (!last_was_dot && current.token != '.'))
- break;
-
- last_was_dot = !last_was_dot;
- }
- popping = 1;
-
- /* We always read one extra token, so compute the number of tokens
- to examine accordingly. */
- last_to_examine = token_fifo.size () - 2;
- next_to_examine = 0;
-
- current = token_fifo[next_to_examine];
- ++next_to_examine;
-
- /* If we are not dealing with a typename, now is the time to find out. */
- if (current.token == IDENTIFIER)
- {
- yylval = current.value;
- current.token = classify_name (pstate, pstate->expression_context_block);
- current.value = yylval;
- }
-
- /* If the IDENTIFIER is not known, it could be a package symbol,
- first try building up a name until we find the qualified module. */
- if (current.token == UNKNOWN_NAME)
- {
- name_obstack.clear ();
- obstack_grow (&name_obstack, current.value.sval.ptr,
- current.value.sval.length);
-
- last_was_dot = 0;
-
- while (next_to_examine <= last_to_examine)
- {
- d_token_and_value next;
-
- next = token_fifo[next_to_examine];
- ++next_to_examine;
-
- if (next.token == IDENTIFIER && last_was_dot)
- {
- /* Update the partial name we are constructing. */
- obstack_grow_str (&name_obstack, ".");
- obstack_grow (&name_obstack, next.value.sval.ptr,
- next.value.sval.length);
-
- yylval.sval.ptr = (char *) obstack_base (&name_obstack);
- yylval.sval.length = obstack_object_size (&name_obstack);
-
- current.token = classify_name (pstate,
- pstate->expression_context_block);
- current.value = yylval;
-
- /* We keep going until we find a TYPENAME. */
- if (current.token == TYPENAME)
- {
- /* Install it as the first token in the FIFO. */
- token_fifo[0] = current;
- token_fifo.erase (token_fifo.begin () + 1,
- token_fifo.begin () + next_to_examine);
- break;
- }
- }
- else if (next.token == '.' && !last_was_dot)
- last_was_dot = 1;
- else
- {
- /* We've reached the end of the name. */
- break;
- }
- }
-
- /* Reset our current token back to the start, if we found nothing
- this means that we will just jump to do pop. */
- current = token_fifo[0];
- next_to_examine = 1;
- }
- if (current.token != TYPENAME && current.token != '.')
- goto do_pop;
-
- name_obstack.clear ();
- checkpoint = 0;
- if (current.token == '.')
- search_block = NULL;
- else
- {
- gdb_assert (current.token == TYPENAME);
- search_block = pstate->expression_context_block;
- obstack_grow (&name_obstack, current.value.sval.ptr,
- current.value.sval.length);
- context_type = current.value.tsym.type;
- checkpoint = 1;
- }
-
- last_was_dot = current.token == '.';
-
- while (next_to_examine <= last_to_examine)
- {
- d_token_and_value next;
-
- next = token_fifo[next_to_examine];
- ++next_to_examine;
-
- if (next.token == IDENTIFIER && last_was_dot)
- {
- int classification;
-
- yylval = next.value;
- classification = classify_inner_name (pstate, search_block,
- context_type);
- /* We keep going until we either run out of names, or until
- we have a qualified name which is not a type. */
- if (classification != TYPENAME && classification != IDENTIFIER)
- break;
-
- /* Accept up to this token. */
- checkpoint = next_to_examine;
-
- /* Update the partial name we are constructing. */
- if (context_type != NULL)
- {
- /* We don't want to put a leading "." into the name. */
- obstack_grow_str (&name_obstack, ".");
- }
- obstack_grow (&name_obstack, next.value.sval.ptr,
- next.value.sval.length);
-
- yylval.sval.ptr = (char *) obstack_base (&name_obstack);
- yylval.sval.length = obstack_object_size (&name_obstack);
- current.value = yylval;
- current.token = classification;
-
- last_was_dot = 0;
-
- if (classification == IDENTIFIER)
- break;
-
- context_type = yylval.tsym.type;
- }
- else if (next.token == '.' && !last_was_dot)
- last_was_dot = 1;
- else
- {
- /* We've reached the end of the name. */
- break;
- }
- }
-
- /* If we have a replacement token, install it as the first token in
- the FIFO, and delete the other constituent tokens. */
- if (checkpoint > 0)
- {
- token_fifo[0] = current;
- if (checkpoint > 1)
- token_fifo.erase (token_fifo.begin () + 1,
- token_fifo.begin () + checkpoint);
- }
-
- do_pop:
- current = token_fifo[0];
- token_fifo.erase (token_fifo.begin ());
- yylval = current.value;
- return current.token;
-}
-
-int
-d_parse (struct parser_state *par_state)
-{
- /* Setting up the parser state. */
- scoped_restore pstate_restore = make_scoped_restore (&pstate);
- gdb_assert (par_state != NULL);
- pstate = par_state;
-
- scoped_restore restore_yydebug = make_scoped_restore (&yydebug,
- par_state->debug);
-
- struct type_stack stack;
- scoped_restore restore_type_stack = make_scoped_restore (&type_stack,
- &stack);
-
- /* Initialize some state used by the lexer. */
- last_was_structop = 0;
- saw_name_at_eof = 0;
- paren_depth = 0;
-
- token_fifo.clear ();
- popping = 0;
- name_obstack.clear ();
-
- int result = yyparse ();
- if (!result)
- pstate->set_operation (pstate->pop ());
- return result;
-}
-
-static void
-yyerror (const char *msg)
-{
- pstate->parse_error (msg);
-}
diff --git a/gdb/d-lang.c b/gdb/d-lang.c
index dc1f41512a26..4793eb8826ed 100644
--- a/gdb/d-lang.c
+++ b/gdb/d-lang.c
@@ -21,6 +21,7 @@
#include "language.h"
#include "varobj.h"
#include "d-lang.h"
+#include "d-exp-parser.h"
#include "c-lang.h"
#include "demangle.h"
#include "cp-support.h"
diff --git a/gdb/d-lang.h b/gdb/d-lang.h
index e8752ac95306..9d424c06f223 100644
--- a/gdb/d-lang.h
+++ b/gdb/d-lang.h
@@ -54,10 +54,6 @@ struct builtin_d_type
struct type *builtin_dchar = nullptr;
};
-/* Defined in d-exp-parser.y. */
-
-extern int d_parse (struct parser_state *);
-
/* Defined in d-lang.c */
extern const char *d_main_name (void);
--
2.55.0
next prev parent reply other threads:[~2026-09-04 17:15 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 ` [PATCH 09/17] gdb: move cp-name-parser.y's support code to cp-name-parser.c simon.marchi
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 ` simon.marchi [this message]
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-14-simon.marchi@polymtl.ca \
--to=simon.marchi@polymtl.ca \
--cc=gdb-patches@sourceware.org \
/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