From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 1F0rGAGcm2o87ysAWB0awg (envelope-from ) for ; Sat, 05 Sep 2026 00:35:13 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=VU4nsv8a; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id AA54D1E09B; Sat, 05 Sep 2026 00:35:12 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-2.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id E8DC91E09B for ; Sat, 05 Sep 2026 00:35:01 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 614FD4BB590C for ; Sat, 5 Sep 2026 04:35:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 614FD4BB590C Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=VU4nsv8a Received: from smtp.polymtl.ca (smtp.polymtl.ca [132.207.4.11]) by sourceware.org (Postfix) with ESMTPS id 00F5F4BA2E0F for ; Sat, 5 Sep 2026 04:34:24 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 00F5F4BA2E0F Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=polymtl.ca Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=polymtl.ca ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 00F5F4BA2E0F Authentication-Results: sourceware.org; arc=none smtp.remote-ip=132.207.4.11 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788582865; cv=none; b=RANPREffof3LRxallcV89jzUfs4updkMk1H4N58b26WQ9wuNQJnlF18Py3iVkzhjDNpc88+gGSnfrBKJKYAZ7GUh+VBLz76fOZ3fPrfwLkA/7zCoREiXZ1S1i1V0APZpRz6XUQuVEAPYIbIWQ9E/W1W+n2+dxogvfHz/5ul4xpk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1788582865; c=relaxed/simple; bh=RrHOpoaAABuL8bLlmJ4bl2f10sc001uk3N9p8pJ7tKI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=K/1HAq9mRxQH3K+szFEfs8ZcHbNkbIF3JW9t57auoVwx66EhZ1n6u3frvcI3zvFC+YmWNETyNlnnizq8i3i2xsfx4ZLd5fkOlZyecYuCoXEmv3KqbqpfDfeXUTj/EGpdYA1IhFAjijc0sDcqlXQhjLjBGwl3w0p1xiyW8PNIyUw= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=polymtl.ca header.i=@polymtl.ca header.a=rsa-sha256 header.s=oct2025 header.b=VU4nsv8a DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 00F5F4BA2E0F Received: from simark.ca (simark.ca [158.69.221.121]) (authenticated bits=0) by smtp.polymtl.ca (8.14.7/8.14.7) with ESMTP id 6854YI3R084377 (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT); Sat, 5 Sep 2026 00:34:23 -0400 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp.polymtl.ca 6854YI3R084377 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=polymtl.ca; s=oct2025; t=1788582864; bh=4k/SxvB3rMP3BL7ns462qsp8rgFcrPjuXvurdisdpKo=; h=From:To:Cc:Subject:Date:In-Reply-To:From; b=VU4nsv8aE/uXEqr44A16X5MjQjY/efEvRLh638odQAmuWrI5ls6AQb6l2dJs0yLSb F8FMelTMc81x8EvFXo/tJrAym2hzRXb1e/80LJtIDmUdCTxn9V120Jv+F+4YufyT+I eclL06unbh91RrB63ZdfEiM8Kkn62tZOnR7nCI7+9i5YCDl0nbi0tO7pHKxtQTaqc0 YdarsvMYv4s6xSYi2InbFPa0SgkCGUN6BmDNw+gqB1Z3VF074EEkrp8dADx+jkIEAe SYXI5n6THDOjGiPRFhH4UaygBlvoBRpZC+DdTecq5Vcn+MUFhYug6AxMJJ5+RtIS3y iyr7XFCwDoPEA== Received: by simark.ca (Postfix) id E8DEC1E3B2; Sat, 05 Sep 2026 00:26:01 -0400 (EDT) From: simon.marchi@polymtl.ca To: gdb-patches@sourceware.org Cc: Simon Marchi Subject: [PATCH v2 14/19] gdb: move d-exp-parser.y's support code to d-exp-parser.c Date: Sat, 5 Sep 2026 00:23:17 -0400 Message-ID: <20260905042353.1702204-15-simon.marchi@polymtl.ca> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260905042353.1702204-1-simon.marchi@polymtl.ca> References: <20260905042353.1702204-1-simon.marchi@polymtl.ca> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Poly-FromMTA: (simark.ca [158.69.221.121]) at Sat, 5 Sep 2026 04:34:19 +0000 X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org From: Simon Marchi 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 . */ + +#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 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 . */ + +#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 IDENTIFIER UNKNOWN_NAME %token TYPENAME %token 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 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