public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/9] Context-sensitive completion for Ada
@ 2022-03-07 16:04 Tom Tromey
  2022-03-07 16:04 ` [PATCH 1/9] Remove null sentinel from 'attributes' Tom Tromey
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches

This updates the Ada parser to allow context-sensitive completion, as
is already done for C, C++, and Rust.

In addition to the usual field name completion, Ada now also allows
completion of Ada attributes.

A hack was needed in the last patch in order to work around problems
with the word breaking algorithm.  I didn't see a way to handle this
without a larger rewrite of the Ada parser.

I think the only non-Ada-specific changes are the expression
completion refactoring in patch #4, and then a minor addition to this
code in patch #9.

Regression tested on x86-64 Fedora 34.

Tom



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 1/9] Remove null sentinel from 'attributes'
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 2/9] Fix bug in Ada attributes lexing Tom Tromey
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

In a subsequent patch, it's handy if the 'attributes' array in
ada-lex.l does not have a NULL sentinel at the end.  In C++, this is
easy to avoid.
---
 gdb/ada-lex.l | 22 ++++++++++------------
 1 file changed, 10 insertions(+), 12 deletions(-)

diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index 27470a75653..a0c9816e568 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -655,7 +655,6 @@ attributes[] = {
   { "size", TICK_SIZE },
   { "tag", TICK_TAG },
   { "val", TICK_VAL },
-  { NULL, -1 }
 };
 
 /* Return the syntactic code corresponding to the attribute name or
@@ -664,24 +663,23 @@ attributes[] = {
 static int
 processAttribute (const char *str)
 {
-  int i, k;
+  for (const auto &item : attributes)
+    if (strcasecmp (str, item.name) == 0)
+      return item.code;
 
-  for (i = 0; attributes[i].code != -1; i += 1)
-    if (strcasecmp (str, attributes[i].name) == 0)
-      return attributes[i].code;
-
-  for (i = 0, k = -1; attributes[i].code != -1; i += 1)
-    if (subseqMatch (str, attributes[i].name))
+  gdb::optional<int> found;
+  for (const auto &item : attributes)
+    if (subseqMatch (str, item.name))
       {
-	if (k == -1)
-	  k = i;
+	if (!found.has_value ())
+	  found = item.code;
 	else
 	  error (_("ambiguous attribute name: `%s'"), str);
       }
-  if (k == -1)
+  if (!found.has_value ())
     error (_("unrecognized attribute: `%s'"), str);
 
-  return attributes[k].code;
+  return *found;
 }
 
 /* Back up lexptr by yyleng and then to the rightmost occurrence of
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 2/9] Fix bug in Ada attributes lexing
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
  2022-03-07 16:04 ` [PATCH 1/9] Remove null sentinel from 'attributes' Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 3/9] Enable "set debug parser" for Ada Tom Tromey
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

The Ada lexer allows whitespace between the apostrophe and the
attribute text, but processAttribute does not handle this.  This patch
fixes the problem and introduces a regression test.
---
 gdb/ada-lex.l                           |  7 ++++++-
 gdb/testsuite/gdb.ada/formatted_ref.exp | 16 +++++++++-------
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index a0c9816e568..c6ce1aec53a 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -227,7 +227,7 @@ false		{ return FALSEKEYWORD; }
 
         /* ATTRIBUTES */
 
-{TICK}[a-z][a-z_]+ { BEGIN INITIAL; return processAttribute (yytext+1); }
+{TICK}[a-z][a-z_]+ { BEGIN INITIAL; return processAttribute (yytext); }
 
 	/* PUNCTUATION */
 
@@ -663,6 +663,11 @@ attributes[] = {
 static int
 processAttribute (const char *str)
 {
+  gdb_assert (*str == '\'');
+  ++str;
+  while (isspace (*str))
+    ++str;
+
   for (const auto &item : attributes)
     if (strcasecmp (str, item.name) == 0)
       return item.code;
diff --git a/gdb/testsuite/gdb.ada/formatted_ref.exp b/gdb/testsuite/gdb.ada/formatted_ref.exp
index bb5f78c0d72..882dbf17725 100644
--- a/gdb/testsuite/gdb.ada/formatted_ref.exp
+++ b/gdb/testsuite/gdb.ada/formatted_ref.exp
@@ -70,13 +70,15 @@ proc test_p_x_addr { var addr } {
     global gdb_prompt
 
     foreach attr {access unchecked_access unrestricted_access} {
-	set test "print/x $var'$attr"
-	gdb_test_multiple $test $test {
-	    -re "\\$\[0-9\]+ = $addr.*$gdb_prompt $" {
-		pass $test
-	    }
-	    -re "\\$\[0-9\]+ = 0x\[a-f0-9+\]+.*$gdb_prompt $" {
-		fail "$test (prints unexpected address)"
+	foreach space {"" "  "} {
+	    set test "print/x $var'$space$attr"
+	    gdb_test_multiple $test $test {
+		-re "\\$\[0-9\]+ = $addr.*$gdb_prompt $" {
+		    pass $test
+		}
+		-re "\\$\[0-9\]+ = 0x\[a-f0-9+\]+.*$gdb_prompt $" {
+		    fail "$test (prints unexpected address)"
+		}
 	    }
 	}
     }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 3/9] Enable "set debug parser" for Ada
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
  2022-03-07 16:04 ` [PATCH 1/9] Remove null sentinel from 'attributes' Tom Tromey
  2022-03-07 16:04 ` [PATCH 2/9] Fix bug in Ada attributes lexing Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 4/9] Refactor expression completion Tom Tromey
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

I noticed that "set debug parser 1" did not affect Ada parsing.  This
patch fixes the problem.

Because this is rarely useful, and pretty much only for maintainers, I
didn't write a test case.
---
 gdb/ada-exp.y | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/gdb/ada-exp.y b/gdb/ada-exp.y
index c974657dbcd..a46ec991d70 100644
--- a/gdb/ada-exp.y
+++ b/gdb/ada-exp.y
@@ -1081,6 +1081,9 @@ ada_parse (struct parser_state *par_state)
   gdb_assert (par_state != NULL);
   pstate = par_state;
 
+  scoped_restore restore_yydebug = make_scoped_restore (&yydebug,
+							parser_debug);
+
   lexer_init (yyin);		/* (Re-)initialize lexer.  */
   obstack_free (&temp_parse_space, NULL);
   obstack_init (&temp_parse_space);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 4/9] Refactor expression completion
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (2 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 3/9] Enable "set debug parser" for Ada Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 5/9] Implement completion for Ada attributes Tom Tromey
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This refactors the gdb expression completion code to make it easier to
add more types of completers.

In the old approach, just two kinds of completers were supported:
field names for some sub-expression, or tag names (like "enum
something").  The data for each kind was combined in single structure,
"expr_completion_state", and handled explicitly by
complete_expression.

In the new approach, the parser state just holds an object that is
responsible for implementing completion.  This way, new completion
types can be added by subclassing this base object.

The structop completer is moved into structop_base_operation, and new
objects are defined for use by the completion code.  This moves much
of the logic of expression completion out of completer.c as well.
---
 gdb/completer.c   | 92 +++++------------------------------------------
 gdb/eval.c        | 85 +++++++++++++++++++++++++++++++++++++++++++
 gdb/expop.h       | 12 +++----
 gdb/expression.h  | 22 ++++++++++--
 gdb/parse.c       | 84 +++++++++++++++++++------------------------
 gdb/parser-defs.h | 49 +++++++++++++++++++++----
 6 files changed, 196 insertions(+), 148 deletions(-)

diff --git a/gdb/completer.c b/gdb/completer.c
index d3900ae2014..6a222776b48 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -1055,107 +1055,31 @@ location_completer_handle_brkchars (struct cmd_list_element *ignore,
   location_completer (ignore, tracker, text, NULL);
 }
 
-/* Helper for expression_completer which recursively adds field and
-   method names from TYPE, a struct or union type, to the OUTPUT
-   list.  */
-
-static void
-add_struct_fields (struct type *type, completion_list &output,
-		   const char *fieldname, int namelen)
-{
-  int i;
-  int computed_type_name = 0;
-  const char *type_name = NULL;
-
-  type = check_typedef (type);
-  for (i = 0; i < type->num_fields (); ++i)
-    {
-      if (i < TYPE_N_BASECLASSES (type))
-	add_struct_fields (TYPE_BASECLASS (type, i),
-			   output, fieldname, namelen);
-      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 ()));
-	    }
-	  else if (type->field (i).type ()->code () == TYPE_CODE_UNION)
-	    {
-	      /* Recurse into anonymous unions.  */
-	      add_struct_fields (type->field (i).type (),
-				 output, fieldname, namelen);
-	    }
-	}
-    }
-
-  for (i = TYPE_NFN_FIELDS (type) - 1; i >= 0; --i)
-    {
-      const char *name = TYPE_FN_FIELDLIST_NAME (type, i);
-
-      if (name && ! strncmp (name, fieldname, namelen))
-	{
-	  if (!computed_type_name)
-	    {
-	      type_name = type->name ();
-	      computed_type_name = 1;
-	    }
-	  /* Omit constructors from the completion list.  */
-	  if (!type_name || strcmp (type_name, name))
-	    output.emplace_back (xstrdup (name));
-	}
-    }
-}
-
 /* See completer.h.  */
 
 void
 complete_expression (completion_tracker &tracker,
 		     const char *text, const char *word)
 {
-  struct type *type = NULL;
-  gdb::unique_xmalloc_ptr<char> fieldname;
-  enum type_code code = TYPE_CODE_UNDEF;
+  expression_up exp;
+  std::unique_ptr<expr_completion_base> expr_completer;
 
   /* Perform a tentative parse of the expression, to see whether a
      field completion is required.  */
   try
     {
-      type = parse_expression_for_completion (text, &fieldname, &code);
+      exp = parse_expression_for_completion (text, &expr_completer);
     }
   catch (const gdb_exception_error &except)
     {
       return;
     }
 
-  if (fieldname != nullptr && type)
-    {
-      for (;;)
-	{
-	  type = check_typedef (type);
-	  if (!type->is_pointer_or_reference ())
-	    break;
-	  type = TYPE_TARGET_TYPE (type);
-	}
-
-      if (type->code () == TYPE_CODE_UNION
-	  || type->code () == TYPE_CODE_STRUCT)
-	{
-	  completion_list result;
-
-	  add_struct_fields (type, result, fieldname.get (),
-			     strlen (fieldname.get ()));
-	  tracker.add_completions (std::move (result));
-	  return;
-	}
-    }
-  else if (fieldname != nullptr && code != TYPE_CODE_UNDEF)
-    {
-      collect_symbol_completion_matches_type (tracker, fieldname.get (),
-					      fieldname.get (), code);
-      return;
-    }
+  /* Part of the parse_expression_for_completion contract.  */
+  gdb_assert ((exp == nullptr) == (expr_completer == nullptr));
+  if (expr_completer != nullptr
+      && expr_completer->complete (exp.get (), tracker))
+    return;
 
   complete_files_symbols (tracker, text, word);
 }
diff --git a/gdb/eval.c b/gdb/eval.c
index 6ced0b261e7..871f8ca0e7b 100644
--- a/gdb/eval.c
+++ b/gdb/eval.c
@@ -967,6 +967,91 @@ structop_base_operation::evaluate_funcall
 				  nullptr, expect_type);
 }
 
+/* Helper for structop_base_operation::complete which recursively adds
+   field and method names from TYPE, a struct or union type, to the
+   OUTPUT list.  */
+
+static void
+add_struct_fields (struct type *type, completion_list &output,
+		   const char *fieldname, int namelen)
+{
+  int i;
+  int computed_type_name = 0;
+  const char *type_name = NULL;
+
+  type = check_typedef (type);
+  for (i = 0; i < type->num_fields (); ++i)
+    {
+      if (i < TYPE_N_BASECLASSES (type))
+	add_struct_fields (TYPE_BASECLASS (type, i),
+			   output, fieldname, namelen);
+      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 ()));
+	    }
+	  else if (type->field (i).type ()->code () == TYPE_CODE_UNION)
+	    {
+	      /* Recurse into anonymous unions.  */
+	      add_struct_fields (type->field (i).type (),
+				 output, fieldname, namelen);
+	    }
+	}
+    }
+
+  for (i = TYPE_NFN_FIELDS (type) - 1; i >= 0; --i)
+    {
+      const char *name = TYPE_FN_FIELDLIST_NAME (type, i);
+
+      if (name && ! strncmp (name, fieldname, namelen))
+	{
+	  if (!computed_type_name)
+	    {
+	      type_name = type->name ();
+	      computed_type_name = 1;
+	    }
+	  /* Omit constructors from the completion list.  */
+	  if (!type_name || strcmp (type_name, name))
+	    output.emplace_back (xstrdup (name));
+	}
+    }
+}
+
+/* See expop.h.  */
+
+bool
+structop_base_operation::complete (struct expression *exp,
+				   completion_tracker &tracker)
+{
+  const std::string &fieldname = std::get<1> (m_storage);
+
+  value *lhs = std::get<0> (m_storage)->evaluate (nullptr, exp,
+						  EVAL_AVOID_SIDE_EFFECTS);
+  struct type *type = value_type (lhs);
+  for (;;)
+    {
+      type = check_typedef (type);
+      if (!type->is_pointer_or_reference ())
+	break;
+      type = TYPE_TARGET_TYPE (type);
+    }
+
+  if (type->code () == TYPE_CODE_UNION
+      || type->code () == TYPE_CODE_STRUCT)
+    {
+      completion_list result;
+
+      add_struct_fields (type, result, fieldname.c_str (),
+			 fieldname.length ());
+      tracker.add_completions (std::move (result));
+      return true;
+    }
+
+  return false;
+}
 
 } /* namespace expr */
 
diff --git a/gdb/expop.h b/gdb/expop.h
index 58863b8f3ab..51255b7f7cd 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1010,18 +1010,16 @@ class structop_base_operation
     return std::get<1> (m_storage);
   }
 
-  /* Used for completion.  Evaluate the LHS for type.  */
-  value *evaluate_lhs (struct expression *exp)
-  {
-    return std::get<0> (m_storage)->evaluate (nullptr, exp,
-					      EVAL_AVOID_SIDE_EFFECTS);
-  }
-
   value *evaluate_funcall (struct type *expect_type,
 			   struct expression *exp,
 			   enum noside noside,
 			   const std::vector<operation_up> &args) override;
 
+  /* 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);
+
 protected:
 
   using tuple_holding_operation::tuple_holding_operation;
diff --git a/gdb/expression.h b/gdb/expression.h
index ff3990e979c..3ba68a2db93 100644
--- a/gdb/expression.h
+++ b/gdb/expression.h
@@ -232,8 +232,26 @@ extern expression_up parse_expression (const char *,
 extern expression_up parse_expression_with_language (const char *string,
 						     enum language lang);
 
-extern struct type *parse_expression_for_completion
-    (const char *, gdb::unique_xmalloc_ptr<char> *, enum type_code *);
+
+class completion_tracker;
+
+/* Base class for expression completion.  An instance of this
+   represents a completion request from the parser.  */
+struct expr_completion_base
+{
+  /* Perform this object's completion.  EXP is the expression in which
+     the completion occurs.  TRACKER is the tracker to update with the
+     results.  Return true if completion was possible (even if no
+     completions were found), false to fall back to ordinary
+     expression completion (i.e., symbol names).  */
+  virtual bool complete (struct expression *exp,
+			 completion_tracker &tracker) = 0;
+
+  virtual ~expr_completion_base () = default;
+};
+
+extern expression_up parse_expression_for_completion
+     (const char *, std::unique_ptr<expr_completion_base> *completer);
 
 class innermost_block_tracker;
 extern expression_up parse_exp_1 (const char **, CORE_ADDR pc,
diff --git a/gdb/parse.c b/gdb/parse.c
index 23f0e66bf27..8a26ed32bcb 100644
--- a/gdb/parse.c
+++ b/gdb/parse.c
@@ -72,10 +72,11 @@ show_parserdebug (struct ui_file *file, int from_tty,
 }
 
 
-static expression_up parse_exp_in_context (const char **, CORE_ADDR,
-					   const struct block *, int,
-					   bool, innermost_block_tracker *,
-					   expr_completion_state *);
+static expression_up parse_exp_in_context
+     (const char **, CORE_ADDR,
+      const struct block *, int,
+      bool, innermost_block_tracker *,
+      std::unique_ptr<expr_completion_base> *);
 
 /* Documented at it's declaration.  */
 
@@ -171,15 +172,22 @@ find_minsym_type_and_address (minimal_symbol *msymbol,
     }
 }
 
+bool
+expr_complete_tag::complete (struct expression *exp,
+			     completion_tracker &tracker)
+{
+  collect_symbol_completion_matches_type (tracker, m_name.get (),
+					  m_name.get (), m_code);
+  return true;
+}
+
 /* See parser-defs.h.  */
 
 void
 parser_state::mark_struct_expression (expr::structop_base_operation *op)
 {
-  gdb_assert (parse_completion
-	      && (m_completion_state.expout_tag_completion_type
-		  == TYPE_CODE_UNDEF));
-  m_completion_state.expout_last_op = op;
+  gdb_assert (parse_completion && m_completion_state == nullptr);
+  m_completion_state.reset (new expr_complete_structop (op));
 }
 
 /* Indicate that the current parser invocation is completing a tag.
@@ -190,17 +198,12 @@ void
 parser_state::mark_completion_tag (enum type_code tag, const char *ptr,
 				   int length)
 {
-  gdb_assert (parse_completion
-	      && (m_completion_state.expout_tag_completion_type
-		  == TYPE_CODE_UNDEF)
-	      && m_completion_state.expout_completion_name == NULL
-	      && m_completion_state.expout_last_op == nullptr);
+  gdb_assert (parse_completion && m_completion_state == nullptr);
   gdb_assert (tag == TYPE_CODE_UNION
 	      || tag == TYPE_CODE_STRUCT
 	      || tag == TYPE_CODE_ENUM);
-  m_completion_state.expout_tag_completion_type = tag;
-  m_completion_state.expout_completion_name
-    = make_unique_xstrndup (ptr, length);
+  m_completion_state.reset
+    (new expr_complete_tag (tag, make_unique_xstrndup (ptr, length)));
 }
 
 /* See parser-defs.h.  */
@@ -433,7 +436,7 @@ parse_exp_in_context (const char **stringptr, CORE_ADDR pc,
 		      const struct block *block,
 		      int comma, bool void_context_p,
 		      innermost_block_tracker *tracker,
-		      expr_completion_state *cstate)
+		      std::unique_ptr<expr_completion_base> *completer)
 {
   const struct language_defn *lang = NULL;
 
@@ -501,7 +504,7 @@ parse_exp_in_context (const char **stringptr, CORE_ADDR pc,
 
   parser_state ps (lang, get_current_arch (), expression_context_block,
 		   expression_context_pc, comma, *stringptr,
-		   cstate != nullptr, tracker, void_context_p);
+		   completer != nullptr, tracker, void_context_p);
 
   scoped_restore_current_language lang_saver;
   set_language (lang->la_language);
@@ -525,8 +528,8 @@ parse_exp_in_context (const char **stringptr, CORE_ADDR pc,
   if (expressiondebug)
     dump_prefix_expression (result.get (), gdb_stdlog);
 
-  if (cstate != nullptr)
-    *cstate = std::move (ps.m_completion_state);
+  if (completer != nullptr)
+    *completer = std::move (ps.m_completion_state);
   *stringptr = ps.lexptr;
   return result;
 }
@@ -566,47 +569,32 @@ parse_expression_with_language (const char *string, enum language lang)
   return parse_expression (string);
 }
 
-/* Parse STRING as an expression.  If parsing ends in the middle of a
-   field reference, return the type of the left-hand-side of the
-   reference; furthermore, if the parsing ends in the field name,
-   return the field name in *NAME.  If the parsing ends in the middle
-   of a field reference, but the reference is somehow invalid, throw
-   an exception.  In all other cases, return NULL.  */
-
-struct type *
-parse_expression_for_completion (const char *string,
-				 gdb::unique_xmalloc_ptr<char> *name,
-				 enum type_code *code)
+/* Parse STRING as an expression.  If the parse is marked for
+   completion, set COMPLETER and return the expression.  In all other
+   cases, return NULL.  */
+
+expression_up
+parse_expression_for_completion
+     (const char *string,
+      std::unique_ptr<expr_completion_base> *completer)
 {
   expression_up exp;
-  expr_completion_state cstate;
 
   try
     {
-      exp = parse_exp_in_context (&string, 0, 0, 0, false, nullptr, &cstate);
+      exp = parse_exp_in_context (&string, 0, 0, 0, false, nullptr, completer);
     }
   catch (const gdb_exception_error &except)
     {
       /* Nothing, EXP remains NULL.  */
     }
 
-  if (exp == NULL)
-    return NULL;
-
-  if (cstate.expout_tag_completion_type != TYPE_CODE_UNDEF)
-    {
-      *code = cstate.expout_tag_completion_type;
-      *name = std::move (cstate.expout_completion_name);
-      return NULL;
-    }
-
-  if (cstate.expout_last_op == nullptr)
+  /* If we didn't get a completion result, be sure to also not return
+     an expression to our caller.  */
+  if (*completer == nullptr)
     return nullptr;
 
-  expr::structop_base_operation *op = cstate.expout_last_op;
-  const std::string &fld = op->get_string ();
-  *name = make_unique_xstrdup (fld.c_str ());
-  return value_type (op->evaluate_lhs (exp.get ()));
+  return exp;
 }
 
 /* Parse floating point value P of length LEN.
diff --git a/gdb/parser-defs.h b/gdb/parser-defs.h
index 6de514023b2..71381b1a725 100644
--- a/gdb/parser-defs.h
+++ b/gdb/parser-defs.h
@@ -82,20 +82,55 @@ struct expr_builder
   expression_up expout;
 };
 
-/* This is used for expression completion.  */
+/* Complete an expression that references a field, like "x->y".  */
 
-struct expr_completion_state
+struct expr_complete_structop : public expr_completion_base
 {
+  explicit expr_complete_structop (expr::structop_base_operation *op)
+    : m_op (op)
+  {
+  }
+
+  bool complete (struct expression *exp,
+		 completion_tracker &tracker) override
+  {
+    return m_op->complete (exp, tracker);
+  }
+
+private:
+
   /* The last struct expression directly before a '.' or '->'.  This
      is set when parsing and is only used when completing a field
      name.  It is nullptr if no dereference operation was found.  */
-  expr::structop_base_operation *expout_last_op = nullptr;
+  expr::structop_base_operation *m_op = nullptr;
+};
+
+/* Complete a tag name in an expression.  This is used for something
+   like "enum abc<TAB>".  */
+
+struct expr_complete_tag : public expr_completion_base
+{
+  expr_complete_tag (enum type_code code,
+		     gdb::unique_xmalloc_ptr<char> name)
+    : m_code (code),
+      m_name (std::move (name))
+  {
+    /* Parsers should enforce this statically.  */
+    gdb_assert (code == TYPE_CODE_ENUM
+		|| code == TYPE_CODE_UNION
+		|| code == TYPE_CODE_STRUCT);
+  }
+
+  bool complete (struct expression *exp,
+		 completion_tracker &tracker) override;
+
+private:
 
-  /* If we are completing a tagged type name, this will be nonzero.  */
-  enum type_code expout_tag_completion_type = TYPE_CODE_UNDEF;
+  /* The kind of tag to complete.  */
+  enum type_code m_code;
 
   /* The token for tagged type name completion.  */
-  gdb::unique_xmalloc_ptr<char> expout_completion_name;
+  gdb::unique_xmalloc_ptr<char> m_name;
 };
 
 /* An instance of this type is instantiated during expression parsing,
@@ -254,7 +289,7 @@ struct parser_state : public expr_builder
   bool parse_completion;
 
   /* Completion state is updated here.  */
-  expr_completion_state m_completion_state;
+  std::unique_ptr<expr_completion_base> m_completion_state;
 
   /* The innermost block tracker.  */
   innermost_block_tracker *block_tracker;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 5/9] Implement completion for Ada attributes
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (3 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 4/9] Refactor expression completion Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 6/9] Refactor ada-lex.l:processId Tom Tromey
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This adds a completer for Ada attributes.  Some work in the lexer is
required in order to match end-of-input correctly, as flex does not
have a general-purpose way of doing this.  (The approach taken here is
recommended in the flex manual.)
---
 gdb/ada-exp.y                           | 31 ++++++++++-
 gdb/ada-lex.l                           | 71 +++++++++++++++++++++----
 gdb/parser-defs.h                       |  8 +++
 gdb/testsuite/gdb.ada/formatted_ref.exp |  5 ++
 4 files changed, 103 insertions(+), 12 deletions(-)

diff --git a/gdb/ada-exp.y b/gdb/ada-exp.y
index a46ec991d70..c453e1896ac 100644
--- a/gdb/ada-exp.y
+++ b/gdb/ada-exp.y
@@ -393,6 +393,30 @@ pop_associations (int n)
   return result;
 }
 
+/* Expression completer for attributes.  */
+struct ada_tick_completer : public expr_completion_base
+{
+  explicit ada_tick_completer (std::string &&name)
+    : m_name (std::move (name))
+  {
+  }
+
+  bool complete (struct expression *exp,
+		 completion_tracker &tracker) override;
+
+private:
+
+  std::string m_name;
+};
+
+/* Make a new ada_tick_completer and wrap it in a unique pointer.  */
+static std::unique_ptr<expr_completion_base>
+make_tick_completer (struct stoken tok)
+{
+  return (std::unique_ptr<expr_completion_base>
+	  (new ada_tick_completer (std::string (tok.ptr, tok.length))));
+}
+
 %}
 
 %union
@@ -420,7 +444,7 @@ pop_associations (int n)
 %token <typed_val_float> FLOAT
 %token TRUEKEYWORD FALSEKEYWORD
 %token COLONCOLON
-%token <sval> STRING NAME DOT_ID 
+%token <sval> STRING NAME DOT_ID TICK_COMPLETE
 %type <bval> block
 %type <lval> arglist tick_arglist
 
@@ -449,6 +473,7 @@ pop_associations (int n)
 %right TICK_ACCESS TICK_ADDRESS TICK_FIRST TICK_LAST TICK_LENGTH
 %right TICK_MAX TICK_MIN TICK_MODULUS
 %right TICK_POS TICK_RANGE TICK_SIZE TICK_TAG TICK_VAL
+%right TICK_COMPLETE
  /* 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.  */
@@ -784,6 +809,10 @@ primary :	primary TICK_ACCESS
 			{ ada_addrof (); }
 	|	primary TICK_ADDRESS
 			{ ada_addrof (type_system_address (pstate)); }
+	|	primary TICK_COMPLETE
+			{
+			  pstate->mark_completion (make_tick_completer ($2));
+			}
 	|	primary TICK_FIRST tick_arglist
 			{
 			  operation_up arg = ada_pop ();
diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index c6ce1aec53a..6c32a9bd002 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -39,6 +39,11 @@ OPER    ([-+*/=<>&]|"<="|">="|"**"|"/="|"and"|"or"|"xor"|"not"|"mod"|"rem"|"abs"
 EXP	(e[+-]{NUM10})
 POSEXP  (e"+"?{NUM10})
 
+/* This must agree with COMPLETION_CHAR below.  See the comment there
+   for the explanation.  */
+COMPLETE "\001"
+NOT_COMPLETE [^\001]
+
 %{
 
 #include "diagnostics.h"
@@ -73,16 +78,35 @@ static void rewind_to_char (int);
    Defining YY_NO_INPUT comments it out.  */
 #define YY_NO_INPUT
 
+/* When completing, we'll return a special character at the end of the
+   input, to signal the completion position to the lexer.  This is
+   done because flex does not have a generally useful way to detect
+   EOF in a pattern.  This variable records whether the special
+   character has been emitted.  */
+static bool returned_complete = false;
+
+/* The character we use to represent the completion point.  */
+#define COMPLETE_CHAR '\001'
+
 #undef YY_INPUT
-#define YY_INPUT(BUF, RESULT, MAX_SIZE) \
-    if ( *pstate->lexptr == '\000' ) \
-      (RESULT) = YY_NULL; \
-    else \
-      { \
-        *(BUF) = *pstate->lexptr; \
-        (RESULT) = 1; \
-	pstate->lexptr += 1; \
-      }
+#define YY_INPUT(BUF, RESULT, MAX_SIZE)					\
+  if ( *pstate->lexptr == '\000' )					\
+    {									\
+      if (pstate->parse_completion && !returned_complete)		\
+	{								\
+	  returned_complete = true;					\
+	  *(BUF) = COMPLETE_CHAR;					\
+	  (RESULT) = 1;							\
+	}								\
+      else								\
+	(RESULT) = YY_NULL;						\
+    }									\
+  else									\
+    {									\
+      *(BUF) = *pstate->lexptr == COMPLETE_CHAR ? ' ' : *pstate->lexptr; \
+      (RESULT) = 1;							\
+      pstate->lexptr += 1;						\
+    }
 
 static int find_dot_all (const char *);
 
@@ -227,7 +251,7 @@ false		{ return FALSEKEYWORD; }
 
         /* ATTRIBUTES */
 
-{TICK}[a-z][a-z_]+ { BEGIN INITIAL; return processAttribute (yytext); }
+{TICK}([a-z][a-z_]*)?{COMPLETE}? { BEGIN INITIAL; return processAttribute (yytext); }
 
 	/* PUNCTUATION */
 
@@ -239,7 +263,7 @@ false		{ return FALSEKEYWORD; }
 "<="		{ return LEQ; }
 ">="		{ return GEQ; }
 
-<BEFORE_QUAL_QUOTE>"'" { BEGIN INITIAL; return '\''; }
+<BEFORE_QUAL_QUOTE>"'"/{NOT_COMPLETE} { BEGIN INITIAL; return '\''; }
 
 [-&*+./:<>=|;\[\]] { return yytext[0]; }
 
@@ -320,6 +344,7 @@ lexer_init (FILE *inp)
 {
   BEGIN INITIAL;
   paren_depth = 0;
+  returned_complete = false;
   yyrestart (inp);
 }
 
@@ -668,6 +693,16 @@ processAttribute (const char *str)
   while (isspace (*str))
     ++str;
 
+  int len = strlen (str);
+  if (len > 0 && str[len - 1] == COMPLETE_CHAR)
+    {
+      /* This is enforced by YY_INPUT.  */
+      gdb_assert (pstate->parse_completion);
+      yylval.sval.ptr = obstack_strndup (&temp_parse_space, str, len - 1);
+      yylval.sval.length = len - 1;
+      return TICK_COMPLETE;
+    }
+
   for (const auto &item : attributes)
     if (strcasecmp (str, item.name) == 0)
       return item.code;
@@ -687,6 +722,20 @@ processAttribute (const char *str)
   return *found;
 }
 
+bool
+ada_tick_completer::complete (struct expression *exp,
+			      completion_tracker &tracker)
+{
+  completion_list output;
+  for (const auto &item : attributes)
+    {
+      if (strncasecmp (item.name, m_name.c_str (), m_name.length ()) == 0)
+	output.emplace_back (xstrdup (item.name));
+    }
+  tracker.add_completions (std::move (output));
+  return true;
+}
+
 /* Back up lexptr by yyleng and then to the rightmost occurrence of
    character CH, case-folded (there must be one).  WARNING: since
    lexptr points to the next input character that Flex has not yet
diff --git a/gdb/parser-defs.h b/gdb/parser-defs.h
index 71381b1a725..3be7d6c839f 100644
--- a/gdb/parser-defs.h
+++ b/gdb/parser-defs.h
@@ -195,6 +195,14 @@ struct parser_state : public expr_builder
 
   void mark_completion_tag (enum type_code tag, const char *ptr, int length);
 
+  /* Mark for completion, using an arbitrary completer.  */
+
+  void mark_completion (std::unique_ptr<expr_completion_base> completer)
+  {
+    gdb_assert (m_completion_state == nullptr);
+    m_completion_state = std::move (completer);
+  }
+
   /* Push an operation on the stack.  */
   void push (expr::operation_up &&op)
   {
diff --git a/gdb/testsuite/gdb.ada/formatted_ref.exp b/gdb/testsuite/gdb.ada/formatted_ref.exp
index 882dbf17725..19a32658d98 100644
--- a/gdb/testsuite/gdb.ada/formatted_ref.exp
+++ b/gdb/testsuite/gdb.ada/formatted_ref.exp
@@ -82,6 +82,11 @@ proc test_p_x_addr { var addr } {
 	    }
 	}
     }
+
+    gdb_test "complete print/x $var'unres" "print/x $var'unrestricted_access"
+    gdb_test_no_output "complete print/x $var'abcd"
+    gdb_test "complete print $var'f" "print $var'first"
+
     return 0
 }
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 6/9] Refactor ada-lex.l:processId
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (4 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 5/9] Implement completion for Ada attributes Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 7/9] Remove the Ada DOT_ALL token Tom Tromey
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

processId in ada-lex.l is a bit funny -- it uses an "if" and a
"switch", and a nested loop.  This patch cleans it up a bit, changing
it to use a boolean flag and a simpler "if".
---
 gdb/ada-lex.l | 31 +++++++++++++------------------
 1 file changed, 13 insertions(+), 18 deletions(-)

diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index 6c32a9bd002..40e450bf679 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -541,33 +541,28 @@ processId (const char *name0, int len)
       return result;
     }
 
+  bool in_quotes = false;
   i = i0 = 0;
   while (i0 < len)
     {
-      if (isalnum (name0[i0]))
+      if (in_quotes)
+	name[i++] = name0[i0++];
+      else if (isalnum (name0[i0]))
 	{
 	  name[i] = tolower (name0[i0]);
 	  i += 1; i0 += 1;
 	}
-      else switch (name0[i0])
+      else if (isspace (name0[i0]))
+	i0 += 1;
+      else if (name0[i0] == '\'')
 	{
-	default:
-	  name[i] = name0[i0];
-	  i += 1; i0 += 1;
-	  break;
-	case ' ': case '\t':
-	  i0 += 1;
-	  break;
-	case '\'':
-	  do
-	    {
-	      name[i] = name0[i0];
-	      i += 1; i0 += 1;
-	    }
-	  while (i0 < len && name0[i0] != '\'');
-	  i0 += 1;
-	  break;
+	  /* Copy the starting quote, but not the ending quote.  */
+	  if (!in_quotes)
+	    name[i++] = name0[i0++];
+	  in_quotes = !in_quotes;
 	}
+      else
+	name[i++] = name0[i0++];
     }
   name[i] = '\000';
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 7/9] Remove the Ada DOT_ALL token
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (5 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 6/9] Refactor ada-lex.l:processId Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 8/9] Consolidate single-char tokens in ada-lex.l Tom Tromey
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

The Ada parser has a DOT_ALL token to represent ".all", and another
token to represent other ".<identifier>" forms.  However, for
completion it is a bit more convenient to unify these cases, so this
patch removes DOT_ALL.
---
 gdb/ada-exp.y | 18 +++++++++---------
 gdb/ada-lex.l |  2 --
 2 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/gdb/ada-exp.y b/gdb/ada-exp.y
index c453e1896ac..e94e377b904 100644
--- a/gdb/ada-exp.y
+++ b/gdb/ada-exp.y
@@ -448,8 +448,6 @@ make_tick_completer (struct stoken tok)
 %type <bval> block
 %type <lval> arglist tick_arglist
 
-%token DOT_ALL
-
 /* Special type cases, put in to allow the parser to distinguish different
    legal basetypes.  */
 %token <sval> DOLLAR_VARIABLE
@@ -477,7 +475,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 DOT_ALL
+%right '.' '(' '[' DOT_ID
 
 %token NEW OTHERS
 
@@ -506,15 +504,17 @@ exp1	:	exp
 	;
 
 /* Expressions, not including the sequencing operator.  */
-primary :	primary DOT_ALL
-			{ ada_wrap<ada_unop_ind_operation> (); }
-	;
 
 primary :	primary DOT_ID
 			{
-			  operation_up arg = ada_pop ();
-			  pstate->push_new<ada_structop_operation>
-			    (std::move (arg), copy_name ($2));
+			  if (strcmp ($2.ptr, "all") == 0)
+			    ada_wrap<ada_unop_ind_operation> ();
+			  else
+			    {
+			      operation_up arg = ada_pop ();
+			      pstate->push_new<ada_structop_operation>
+				(std::move (arg), copy_name ($2));
+			    }
 			}
 	;
 
diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index 40e450bf679..9e35776d7c2 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -289,8 +289,6 @@ false		{ return FALSEKEYWORD; }
 		    }
 		}
 
-"."{WHITE}*all  { return DOT_ALL; }
-
 "."{WHITE}*{ID} {
 	 	  yylval.sval = processId (yytext+1, yyleng-1);
 	          return DOT_ID;
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 8/9] Consolidate single-char tokens in ada-lex.l
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (6 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 7/9] Remove the Ada DOT_ALL token Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-03-07 16:04 ` [PATCH 9/9] Add context-sensitive field name completion to Ada parser Tom Tromey
  2022-04-04 18:49 ` [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

There are two rules in ada-lex.l that match single-character tokens.
This merges them.

Also, this removes '.' from the list of such tokens.  '.' is not used
in any production in ada-exp.y, and removing it here helps the
subsequent completion patches.
---
 gdb/ada-lex.l | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/gdb/ada-lex.l b/gdb/ada-lex.l
index 9e35776d7c2..ea35c7a53af 100644
--- a/gdb/ada-lex.l
+++ b/gdb/ada-lex.l
@@ -265,7 +265,7 @@ false		{ return FALSEKEYWORD; }
 
 <BEFORE_QUAL_QUOTE>"'"/{NOT_COMPLETE} { BEGIN INITIAL; return '\''; }
 
-[-&*+./:<>=|;\[\]] { return yytext[0]; }
+[-&*+{}@/:<>=|;\[\]] { return yytext[0]; }
 
 ","		{ if (paren_depth == 0 && pstate->comma_terminates)
 		    {
@@ -319,8 +319,6 @@ false		{ return FALSEKEYWORD; }
 
 "::"            { return COLONCOLON; }
 
-[{}@]		{ return yytext[0]; }
-
 	/* REGISTERS AND GDB CONVENIENCE VARIABLES */
 
 "$"({LETTER}|{DIG}|"$")*  {
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH 9/9] Add context-sensitive field name completion to Ada parser
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (7 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 8/9] Consolidate single-char tokens in ada-lex.l Tom Tromey
@ 2022-03-07 16:04 ` Tom Tromey
  2022-04-04 18:49 ` [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-03-07 16:04 UTC (permalink / raw)
  To: gdb-patches; +Cc: Tom Tromey

This updates the Ada expression parser to implement context-sensitive
field name completion.

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.
---
 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 765f0dca3c5..c0a472aef88 100644
--- a/gdb/ada-exp.h
+++ b/gdb/ada-exp.h
@@ -443,6 +443,29 @@ class ada_structop_operation
 
   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 e94e377b904..a64aa9fb591 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 871f8ca0e7b..6a5d5ec17e6 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 51255b7f7cd..0ffa5077df3 100644
--- a/gdb/expop.h
+++ b/gdb/expop.h
@@ -1018,10 +1018,18 @@ class structop_base_operation
   /* 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"]
-- 
2.34.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH 0/9] Context-sensitive completion for Ada
  2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
                   ` (8 preceding siblings ...)
  2022-03-07 16:04 ` [PATCH 9/9] Add context-sensitive field name completion to Ada parser Tom Tromey
@ 2022-04-04 18:49 ` Tom Tromey
  9 siblings, 0 replies; 11+ messages in thread
From: Tom Tromey @ 2022-04-04 18:49 UTC (permalink / raw)
  To: Tom Tromey via Gdb-patches; +Cc: Tom Tromey

>>>>> "Tom" == Tom Tromey via Gdb-patches <gdb-patches@sourceware.org> writes:

Tom> This updates the Ada parser to allow context-sensitive completion, as
Tom> is already done for C, C++, and Rust.

Tom> In addition to the usual field name completion, Ada now also allows
Tom> completion of Ada attributes.

I'm checking this in.

Tom

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2022-04-04 18:49 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-07 16:04 [PATCH 0/9] Context-sensitive completion for Ada Tom Tromey
2022-03-07 16:04 ` [PATCH 1/9] Remove null sentinel from 'attributes' Tom Tromey
2022-03-07 16:04 ` [PATCH 2/9] Fix bug in Ada attributes lexing Tom Tromey
2022-03-07 16:04 ` [PATCH 3/9] Enable "set debug parser" for Ada Tom Tromey
2022-03-07 16:04 ` [PATCH 4/9] Refactor expression completion Tom Tromey
2022-03-07 16:04 ` [PATCH 5/9] Implement completion for Ada attributes Tom Tromey
2022-03-07 16:04 ` [PATCH 6/9] Refactor ada-lex.l:processId Tom Tromey
2022-03-07 16:04 ` [PATCH 7/9] Remove the Ada DOT_ALL token Tom Tromey
2022-03-07 16:04 ` [PATCH 8/9] Consolidate single-char tokens in ada-lex.l Tom Tromey
2022-03-07 16:04 ` [PATCH 9/9] Add context-sensitive field name completion to Ada parser Tom Tromey
2022-04-04 18:49 ` [PATCH 0/9] Context-sensitive completion for Ada 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).