From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2126) id 1A55B385840C; Mon, 4 Apr 2022 18:51:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1A55B385840C Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable From: Tom Tromey To: gdb-cvs@sourceware.org Subject: [binutils-gdb] Add context-sensitive field name completion to Ada parser X-Act-Checkin: binutils-gdb X-Git-Author: Tom Tromey X-Git-Refname: refs/heads/master X-Git-Oldrev: 484e7c5ff5fd24cfb2946fadd76b6b67bbeb4169 X-Git-Newrev: d4da1b2c1b7b85968da608dde03e054cc0b1f7ca Message-Id: <20220404185101.1A55B385840C@sourceware.org> Date: Mon, 4 Apr 2022 18:51:01 +0000 (GMT) X-BeenThere: gdb-cvs@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Gdb-cvs mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 04 Apr 2022 18:51:01 -0000 https://sourceware.org/git/gitweb.cgi?p=3Dbinutils-gdb.git;h=3Dd4da1b2c1b7b= 85968da608dde03e054cc0b1f7ca commit d4da1b2c1b7b85968da608dde03e054cc0b1f7ca Author: Tom Tromey Date: Tue Feb 22 13:12:02 2022 -0700 Add context-sensitive field name completion to Ada parser =20 This updates the Ada expression parser to implement context-sensitive field name completion. This is PR ada/28727. =20 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. =20 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. =20 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=3D28727 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: =20 enum exp_opcode opcode () const override { return STRUCTOP_STRUCT; } + + /* Set the completion prefix. */ + void set_prefix (std::string &&prefix) + { + m_prefix =3D std::move (prefix); + } + + bool complete (struct expression *exp, completion_tracker &tracker) over= ride + { + 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; }; =20 /* 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 { =20 static struct parser_state *pstate =3D NULL; =20 +/* The original expression string. */ +static const char *original_expr; + int yyparse (void); =20 static int yylex (void); @@ -82,6 +85,9 @@ static void write_object_renaming (struct parser_state *, =20 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); =20 static void write_name_assoc (struct parser_state *, struct stoken); =20 @@ -104,6 +110,8 @@ static struct type *type_boolean (struct parser_state *= ); =20 static struct type *type_system_address (struct parser_state *); =20 +static std::string find_completion_bounds (struct parser_state *); + using namespace expr; =20 /* Handle Ada type resolution for OP. DEPROCEDURE_P and CONTEXT_TYPE @@ -444,7 +452,7 @@ make_tick_completer (struct stoken tok) %token FLOAT %token TRUEKEYWORD FALSEKEYWORD %token COLONCOLON -%token STRING NAME DOT_ID TICK_COMPLETE +%token STRING NAME DOT_ID TICK_COMPLETE DOT_COMPLETE NAME_COMPLETE %type block %type arglist tick_arglist =20 @@ -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 =20 %token NEW OTHERS =20 @@ -518,6 +526,20 @@ primary : primary DOT_ID } ; =20 +primary : primary DOT_COMPLETE + { + /* This is done even for ".all", because + that might be a prefix. */ + operation_up arg =3D ada_pop (); + ada_structop_operation *str_op + =3D (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 =20 var_or_type: NAME %prec VAR { $$ =3D write_var_or_type (pstate, NULL, $1); } + | NAME_COMPLETE %prec VAR + { + $$ =3D write_var_or_type_completion (pstate, + NULL, + $1); + } | block NAME %prec VAR { $$ =3D write_var_or_type (pstate, $1, $2); } + | block NAME_COMPLETE %prec VAR + { + $$ =3D write_var_or_type_completion (pstate, + $1, + $2); + } | NAME TICK_ACCESS=20 {=20 $$ =3D write_var_or_type (pstate, NULL, $1); @@ -1109,6 +1143,7 @@ ada_parse (struct parser_state *par_state) scoped_restore pstate_restore =3D make_scoped_restore (&pstate); gdb_assert (par_state !=3D NULL); pstate =3D par_state; + original_expr =3D par_state->lexptr; =20 scoped_restore restore_yydebug =3D make_scoped_restore (&yydebug, parser_debug); @@ -1440,10 +1475,12 @@ chop_separator (const char *name) =20 /* Given that SELS is a string of the form ()*, where 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 =3D nullptr; while (*sels !=3D '\0') { const char *p =3D chop_separator (sels); @@ -1452,9 +1489,11 @@ write_selectors (struct parser_state *par_state, con= st char *sels) && (sels[0] !=3D '_' || sels[1] !=3D '_')) sels +=3D 1; operation_up arg =3D ada_pop (); - pstate->push_new - (std::move (arg), std::string (p, sels - p)); + result =3D new ada_structop_operation (std::move (arg), + std::string (p, sels - p)); + pstate->push (operation_up (result)); } + return result; } =20 /* Write a variable access (OP_VAR_VALUE) to ambiguous encoded name @@ -1701,6 +1740,72 @@ write_var_or_type (struct parser_state *par_state, =20 } =20 +/* 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 =3D 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] !=3D '.' && !isspace (end[-1]); --= end) + { + /* Nothing. */ + } + /* Now find the start of the prefix. */ + const char *ptr =3D end; + /* Here we allow '.'. */ + for (; + ptr > original_expr && (ptr[-1] =3D=3D '.' + || ptr[-1] =3D=3D '_' + || (ptr[-1] >=3D 'a' && ptr[-1] <=3D 'z') + || (ptr[-1] >=3D 'A' && ptr[-1] <=3D 'Z') + || (ptr[-1] & 0xff) >=3D 0x80); + --ptr) + { + /* Nothing. */ + } + /* ... except, skip leading spaces. */ + ptr =3D 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 =3D chop_selector (name0.ptr, name0.length); + /* If there's no separator, just defer to ordinary symbol + completion. */ + if (tail_index =3D=3D -1) + return write_var_or_type (par_state, block, name0); + + std::string copy (name0.ptr, tail_index); + struct type *type =3D 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 !=3D nullptr) + return type; + + ada_structop_operation *op =3D 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 =3D> 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 =3D false; pstate->lexptr +=3D 1; \ } =20 -static int find_dot_all (const char *); - /* Depth of parentheses. */ static int paren_depth; =20 @@ -289,12 +287,20 @@ false { return FALSEKEYWORD; } } } =20 -"."{WHITE}*{ID} { +"."{WHITE}*{ID}{COMPLETE}? { yylval.sval =3D processId (yytext+1, yyleng-1); + if (yytext[yyleng - 1] =3D=3D COMPLETE_CHAR) + return DOT_COMPLETE; return DOT_ID; } =20 -{ID}({WHITE}*"."{WHITE}*({ID}|\"{OPER}\"))*(" "*"'")? { +"."{WHITE}*{COMPLETE} { + yylval.sval.ptr =3D ""; + yylval.sval.length =3D 0; + return DOT_COMPLETE; + } + +{ID}({WHITE}*"."{WHITE}*({ID}|\"{OPER}\"))*(" "*"'"|{COMPLETE})? { int all_posn =3D find_dot_all (yytext); =20 if (all_posn =3D=3D -1 && yytext[yyleng-1] =3D=3D '\'') @@ -304,8 +310,9 @@ false { return FALSEKEYWORD; } } else if (all_posn >=3D 0) yyless (all_posn); + bool is_completion =3D yytext[yyleng - 1] =3D=3D COMPLETE_CHAR; yylval.sval =3D processId (yytext, yyleng); - return NAME; + return is_completion ? NAME_COMPLETE : NAME; } =20 =20 @@ -541,7 +548,12 @@ processId (const char *name0, int len) i =3D i0 =3D 0; while (i0 < len) { - if (in_quotes) + if (name0[i0] =3D=3D COMPLETE_CHAR) + { + /* Just ignore. */ + ++i0; + } + else if (in_quotes) name[i++] =3D 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 =20 /* 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. */ =20 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 =3D 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] !=3D '\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 () =3D=3D 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, =20 bool structop_base_operation::complete (struct expression *exp, - completion_tracker &tracker) + completion_tracker &tracker, + const char *prefix) { const std::string &fieldname =3D std::get<1> (m_storage); =20 @@ -1045,7 +1047,7 @@ structop_base_operation::complete (struct expression = *exp, completion_list result; =20 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 &track= er) + { + return complete (exp, tracker, ""); + } =20 protected: =20 + /* 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; }; =20 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" \ =20 gdb_test "ptype circle.pos.x" \ "type =3D <\[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"]