public inbox for gdb-cvs@sourceware.org
help / color / mirror / Atom feed
* [binutils-gdb] Add context-sensitive field name completion to Ada parser
@ 2022-04-04 18:51 Tom Tromey
0 siblings, 0 replies; only message in thread
From: Tom Tromey @ 2022-04-04 18:51 UTC (permalink / raw)
To: gdb-cvs
https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;h=d4da1b2c1b7b85968da608dde03e054cc0b1f7ca
commit d4da1b2c1b7b85968da608dde03e054cc0b1f7ca
Author: Tom Tromey <tromey@adacore.com>
Date: Tue Feb 22 13:12:02 2022 -0700
Add context-sensitive field name completion to Ada parser
This updates the Ada expression parser to implement context-sensitive
field name completion. This is PR ada/28727.
This is somewhat complicated due to some choices in the Ada lexer --
it chooses to represent a sequence of "."-separated identifiers as a
single token, so the parser must partially recreate the completer's
logic to find the completion word boundaries.
Despite the minor warts in this patch, though, it is a decent
improvement. It's possible that the DWARF reader rewrite will help
fix the package completion problem pointed out in this patch as well.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=28727
Diff:
---
gdb/ada-exp.h | 23 +++++++
gdb/ada-exp.y | 117 ++++++++++++++++++++++++++++++++--
gdb/ada-lex.l | 24 +++++--
gdb/eval.c | 18 +++---
gdb/expop.h | 10 ++-
gdb/testsuite/gdb.ada/ptype_field.exp | 31 +++++++++
6 files changed, 202 insertions(+), 21 deletions(-)
diff --git a/gdb/ada-exp.h b/gdb/ada-exp.h
index 44ca2545670..d85349c7fcc 100644
--- a/gdb/ada-exp.h
+++ b/gdb/ada-exp.h
@@ -443,6 +443,29 @@ public:
enum exp_opcode opcode () const override
{ return STRUCTOP_STRUCT; }
+
+ /* Set the completion prefix. */
+ void set_prefix (std::string &&prefix)
+ {
+ m_prefix = std::move (prefix);
+ }
+
+ bool complete (struct expression *exp, completion_tracker &tracker) override
+ {
+ return structop_base_operation::complete (exp, tracker, m_prefix.c_str ());
+ }
+
+ void dump (struct ui_file *stream, int depth) const override
+ {
+ structop_base_operation::dump (stream, depth);
+ dump_for_expression (stream, depth + 1, m_prefix);
+ }
+
+private:
+
+ /* We may need to provide a prefix to field name completion. See
+ ada-exp.y:find_completion_bounds for details. */
+ std::string m_prefix;
};
/* Function calls for Ada. */
diff --git a/gdb/ada-exp.y b/gdb/ada-exp.y
index d84cdcebf58..5099e40f677 100644
--- a/gdb/ada-exp.y
+++ b/gdb/ada-exp.y
@@ -68,6 +68,9 @@ struct name_info {
static struct parser_state *pstate = NULL;
+/* The original expression string. */
+static const char *original_expr;
+
int yyparse (void);
static int yylex (void);
@@ -82,6 +85,9 @@ static void write_object_renaming (struct parser_state *,
static struct type* write_var_or_type (struct parser_state *,
const struct block *, struct stoken);
+static struct type *write_var_or_type_completion (struct parser_state *,
+ const struct block *,
+ struct stoken);
static void write_name_assoc (struct parser_state *, struct stoken);
@@ -104,6 +110,8 @@ static struct type *type_boolean (struct parser_state *);
static struct type *type_system_address (struct parser_state *);
+static std::string find_completion_bounds (struct parser_state *);
+
using namespace expr;
/* Handle Ada type resolution for OP. DEPROCEDURE_P and CONTEXT_TYPE
@@ -444,7 +452,7 @@ make_tick_completer (struct stoken tok)
%token <typed_val_float> FLOAT
%token TRUEKEYWORD FALSEKEYWORD
%token COLONCOLON
-%token <sval> STRING NAME DOT_ID TICK_COMPLETE
+%token <sval> STRING NAME DOT_ID TICK_COMPLETE DOT_COMPLETE NAME_COMPLETE
%type <bval> block
%type <lval> arglist tick_arglist
@@ -475,7 +483,7 @@ make_tick_completer (struct stoken tok)
/* The following are right-associative only so that reductions at this
precedence have lower precedence than '.' and '('. The syntax still
forces a.b.c, e.g., to be LEFT-associated. */
-%right '.' '(' '[' DOT_ID
+%right '.' '(' '[' DOT_ID DOT_COMPLETE
%token NEW OTHERS
@@ -518,6 +526,20 @@ primary : primary DOT_ID
}
;
+primary : primary DOT_COMPLETE
+ {
+ /* This is done even for ".all", because
+ that might be a prefix. */
+ operation_up arg = ada_pop ();
+ ada_structop_operation *str_op
+ = (new ada_structop_operation
+ (std::move (arg), copy_name ($2)));
+ str_op->set_prefix (find_completion_bounds (pstate));
+ pstate->push (operation_up (str_op));
+ pstate->mark_struct_expression (str_op);
+ }
+ ;
+
primary : primary '(' arglist ')'
{ ada_funcall ($3); }
| var_or_type '(' arglist ')'
@@ -928,8 +950,20 @@ primary : NEW NAME
var_or_type: NAME %prec VAR
{ $$ = write_var_or_type (pstate, NULL, $1); }
+ | NAME_COMPLETE %prec VAR
+ {
+ $$ = write_var_or_type_completion (pstate,
+ NULL,
+ $1);
+ }
| block NAME %prec VAR
{ $$ = write_var_or_type (pstate, $1, $2); }
+ | block NAME_COMPLETE %prec VAR
+ {
+ $$ = write_var_or_type_completion (pstate,
+ $1,
+ $2);
+ }
| NAME TICK_ACCESS
{
$$ = write_var_or_type (pstate, NULL, $1);
@@ -1109,6 +1143,7 @@ ada_parse (struct parser_state *par_state)
scoped_restore pstate_restore = make_scoped_restore (&pstate);
gdb_assert (par_state != NULL);
pstate = par_state;
+ original_expr = par_state->lexptr;
scoped_restore restore_yydebug = make_scoped_restore (&yydebug,
parser_debug);
@@ -1440,10 +1475,12 @@ chop_separator (const char *name)
/* Given that SELS is a string of the form (<sep><identifier>)*, where
<sep> is '__' or '.', write the indicated sequence of
- STRUCTOP_STRUCT expression operators. */
-static void
+ STRUCTOP_STRUCT expression operators. Returns a pointer to the
+ last operation that was pushed. */
+static ada_structop_operation *
write_selectors (struct parser_state *par_state, const char *sels)
{
+ ada_structop_operation *result = nullptr;
while (*sels != '\0')
{
const char *p = chop_separator (sels);
@@ -1452,9 +1489,11 @@ write_selectors (struct parser_state *par_state, const char *sels)
&& (sels[0] != '_' || sels[1] != '_'))
sels += 1;
operation_up arg = ada_pop ();
- pstate->push_new<ada_structop_operation>
- (std::move (arg), std::string (p, sels - p));
+ result = new ada_structop_operation (std::move (arg),
+ std::string (p, sels - p));
+ pstate->push (operation_up (result));
}
+ return result;
}
/* Write a variable access (OP_VAR_VALUE) to ambiguous encoded name
@@ -1701,6 +1740,72 @@ write_var_or_type (struct parser_state *par_state,
}
+/* Because ada_completer_word_break_characters does not contain '.' --
+ and it cannot easily be added, this breaks other completions -- we
+ have to recreate the completion word-splitting here, so that we can
+ provide a prefix that is then used when completing field names.
+ Without this, an attempt like "complete print abc.d" will give a
+ result like "print def" rather than "print abc.def". */
+
+static std::string
+find_completion_bounds (struct parser_state *par_state)
+{
+ const char *end = pstate->lexptr;
+ /* First the end of the prefix. Here we stop at the token start or
+ at '.' or space. */
+ for (; end > original_expr && end[-1] != '.' && !isspace (end[-1]); --end)
+ {
+ /* Nothing. */
+ }
+ /* Now find the start of the prefix. */
+ const char *ptr = end;
+ /* Here we allow '.'. */
+ for (;
+ ptr > original_expr && (ptr[-1] == '.'
+ || ptr[-1] == '_'
+ || (ptr[-1] >= 'a' && ptr[-1] <= 'z')
+ || (ptr[-1] >= 'A' && ptr[-1] <= 'Z')
+ || (ptr[-1] & 0xff) >= 0x80);
+ --ptr)
+ {
+ /* Nothing. */
+ }
+ /* ... except, skip leading spaces. */
+ ptr = skip_spaces (ptr);
+
+ return std::string (ptr, end);
+}
+
+/* A wrapper for write_var_or_type that is used specifically when
+ completion is requested for the last of a sequence of
+ identifiers. */
+
+static struct type *
+write_var_or_type_completion (struct parser_state *par_state,
+ const struct block *block, struct stoken name0)
+{
+ int tail_index = chop_selector (name0.ptr, name0.length);
+ /* If there's no separator, just defer to ordinary symbol
+ completion. */
+ if (tail_index == -1)
+ return write_var_or_type (par_state, block, name0);
+
+ std::string copy (name0.ptr, tail_index);
+ struct type *type = write_var_or_type (par_state, block,
+ { copy.c_str (),
+ (int) copy.length () });
+ /* For completion purposes, it's enough that we return a type
+ here. */
+ if (type != nullptr)
+ return type;
+
+ ada_structop_operation *op = write_selectors (par_state,
+ name0.ptr + tail_index);
+ op->set_prefix (find_completion_bounds (par_state));
+ par_state->mark_struct_expression (op);
+ return nullptr;
+}
+
/* Write a left side of a component association (e.g., NAME in NAME =>
exp). If NAME has the form of a selected component, write it as an
ordinary expression. If it is a simple variable that unambiguously
diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index ea35c7a53af..3980889f5ab 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -108,8 +108,6 @@ static bool returned_complete = false;
pstate->lexptr += 1; \
}
-static int find_dot_all (const char *);
-
/* Depth of parentheses. */
static int paren_depth;
@@ -289,12 +287,20 @@ false { return FALSEKEYWORD; }
}
}
-"."{WHITE}*{ID} {
+"."{WHITE}*{ID}{COMPLETE}? {
yylval.sval = processId (yytext+1, yyleng-1);
+ if (yytext[yyleng - 1] == COMPLETE_CHAR)
+ return DOT_COMPLETE;
return DOT_ID;
}
-{ID}({WHITE}*"."{WHITE}*({ID}|\"{OPER}\"))*(" "*"'")? {
+"."{WHITE}*{COMPLETE} {
+ yylval.sval.ptr = "";
+ yylval.sval.length = 0;
+ return DOT_COMPLETE;
+ }
+
+{ID}({WHITE}*"."{WHITE}*({ID}|\"{OPER}\"))*(" "*"'"|{COMPLETE})? {
int all_posn = find_dot_all (yytext);
if (all_posn == -1 && yytext[yyleng-1] == '\'')
@@ -304,8 +310,9 @@ false { return FALSEKEYWORD; }
}
else if (all_posn >= 0)
yyless (all_posn);
+ bool is_completion = yytext[yyleng - 1] == COMPLETE_CHAR;
yylval.sval = processId (yytext, yyleng);
- return NAME;
+ return is_completion ? NAME_COMPLETE : NAME;
}
@@ -541,7 +548,12 @@ processId (const char *name0, int len)
i = i0 = 0;
while (i0 < len)
{
- if (in_quotes)
+ if (name0[i0] == COMPLETE_CHAR)
+ {
+ /* Just ignore. */
+ ++i0;
+ }
+ else if (in_quotes)
name[i++] = name0[i0++];
else if (isalnum (name0[i0]))
{
diff --git a/gdb/eval.c b/gdb/eval.c
index eded4845865..ce1d883aa86 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -969,11 +969,11 @@ structop_base_operation::evaluate_funcall
/* Helper for structop_base_operation::complete which recursively adds
field and method names from TYPE, a struct or union type, to the
- OUTPUT list. */
+ OUTPUT list. PREFIX is prepended to each result. */
static void
add_struct_fields (struct type *type, completion_list &output,
- const char *fieldname, int namelen)
+ const char *fieldname, int namelen, const char *prefix)
{
int i;
int computed_type_name = 0;
@@ -984,20 +984,21 @@ add_struct_fields (struct type *type, completion_list &output,
{
if (i < TYPE_N_BASECLASSES (type))
add_struct_fields (TYPE_BASECLASS (type, i),
- output, fieldname, namelen);
+ output, fieldname, namelen, prefix);
else if (type->field (i).name ())
{
if (type->field (i).name ()[0] != '\0')
{
if (! strncmp (type->field (i).name (),
fieldname, namelen))
- output.emplace_back (xstrdup (type->field (i).name ()));
+ output.emplace_back (concat (prefix, type->field (i).name (),
+ nullptr));
}
else if (type->field (i).type ()->code () == TYPE_CODE_UNION)
{
/* Recurse into anonymous unions. */
add_struct_fields (type->field (i).type (),
- output, fieldname, namelen);
+ output, fieldname, namelen, prefix);
}
}
}
@@ -1015,7 +1016,7 @@ add_struct_fields (struct type *type, completion_list &output,
}
/* Omit constructors from the completion list. */
if (!type_name || strcmp (type_name, name))
- output.emplace_back (xstrdup (name));
+ output.emplace_back (concat (prefix, name, nullptr));
}
}
}
@@ -1024,7 +1025,8 @@ add_struct_fields (struct type *type, completion_list &output,
bool
structop_base_operation::complete (struct expression *exp,
- completion_tracker &tracker)
+ completion_tracker &tracker,
+ const char *prefix)
{
const std::string &fieldname = std::get<1> (m_storage);
@@ -1045,7 +1047,7 @@ structop_base_operation::complete (struct expression *exp,
completion_list result;
add_struct_fields (type, result, fieldname.c_str (),
- fieldname.length ());
+ fieldname.length (), prefix);
tracker.add_completions (std::move (result));
return true;
}
diff --git a/gdb/expop.h b/gdb/expop.h
index c159d96a561..a17311f74e5 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1005,10 +1005,18 @@ public:
/* Try to complete this operation in the context of EXP. TRACKER is
the completion tracker to update. Return true if completion was
possible, false otherwise. */
- bool complete (struct expression *exp, completion_tracker &tracker);
+ virtual bool complete (struct expression *exp, completion_tracker &tracker)
+ {
+ return complete (exp, tracker, "");
+ }
protected:
+ /* Do the work of the public 'complete' method. PREFIX is prepended
+ to each result. */
+ bool complete (struct expression *exp, completion_tracker &tracker,
+ const char *prefix);
+
using tuple_holding_operation::tuple_holding_operation;
};
diff --git a/gdb/testsuite/gdb.ada/ptype_field.exp b/gdb/testsuite/gdb.ada/ptype_field.exp
index cd45fbe157e..25147a88743 100644
--- a/gdb/testsuite/gdb.ada/ptype_field.exp
+++ b/gdb/testsuite/gdb.ada/ptype_field.exp
@@ -42,3 +42,34 @@ gdb_test "ptype circle.pos" \
gdb_test "ptype circle.pos.x" \
"type = <\[0-9\]+-byte integer>"
+
+gdb_test "complete print my_circ" "print my_circle"
+gdb_test "complete print my_circle.r" "print my_circle\\.radius"
+gdb_test "complete print my_circle.po" "print my_circle\\.pos"
+gdb_test "complete print my_circle . po" "print my_circle \\. pos" \
+ "complete with spaces"
+gdb_test "complete print my_circle." \
+ [multi_line \
+ "print my_circle\\.pos" \
+ "print my_circle\\.radius"]
+gdb_test "complete print (my_circle).r" "print \\(my_circle\\)\\.radius"
+gdb_test "complete print (my_circle).po" "print \\(my_circle\\)\\.pos"
+gdb_test "complete print (my_circle)." \
+ [multi_line \
+ "print \\(my_circle\\)\\.pos" \
+ "print \\(my_circle\\)\\.radius"]
+
+gdb_test "complete ptype pck.pos" "ptype pck\\.position"
+gdb_test "complete ptype pck.c" "ptype pck\\.circle"
+
+# We can't query the members of a package yet, and this yields a bit
+# too much output, so comment out for now instead of kfailing.
+# gdb_test "complete ptype pck." \
+# [multi_line \
+# "ptype pck\\.circle" \
+# "ptype pck\\.position"]
+
+gdb_test "complete ptype circle.pos." \
+ [multi_line \
+ "ptype circle\\.pos\\.x" \
+ "ptype circle\\.pos\\.y"]
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2022-04-04 18:51 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-04 18:51 [binutils-gdb] Add context-sensitive field name completion to Ada parser Tom Tromey
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).