public inbox for gdb-cvs@sourceware.org
help / color / mirror / Atom feed
From: Tom Tromey <tromey@sourceware.org>
To: gdb-cvs@sourceware.org
Subject: [binutils-gdb] Add context-sensitive field name completion to Ada parser
Date: Mon,  4 Apr 2022 18:51:01 +0000 (GMT)	[thread overview]
Message-ID: <20220404185101.1A55B385840C@sourceware.org> (raw)

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"]


                 reply	other threads:[~2022-04-04 18:51 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20220404185101.1A55B385840C@sourceware.org \
    --to=tromey@sourceware.org \
    --cc=gdb-cvs@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;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).