Refactor expression completion

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.
This commit is contained in:
Tom Tromey 2022-02-22 09:48:25 -07:00
parent 28c4b1ffaa
commit 1e237aba22
6 changed files with 195 additions and 147 deletions

View File

@ -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);
}

View File

@ -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 */

View File

@ -997,18 +997,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;

View File

@ -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,

View File

@ -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. */
/* Parse STRING as an expression. If the parse is marked for
completion, set COMPLETER and return the expression. 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)
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.

View File

@ -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;
};
/* If we are completing a tagged type name, this will be nonzero. */
enum type_code expout_tag_completion_type = TYPE_CODE_UNDEF;
/* 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:
/* 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;