X-Git-Url: http://drtracing.org/?a=blobdiff_plain;f=gdb%2Flinespec.c;h=480180869fa4fe126f150ae51b868302415844d7;hb=51abb421302bdd86946827727aebc878b5c756e3;hp=0216bf123e68624268e4100a58d2b64e6dc09da2;hpb=a245927022bc4351fafd9e6275e217021ec93e08;p=deliverable%2Fbinutils-gdb.git diff --git a/gdb/linespec.c b/gdb/linespec.c index 0216bf123e..480180869f 100644 --- a/gdb/linespec.c +++ b/gdb/linespec.c @@ -46,6 +46,35 @@ #include "location.h" #include "common/function-view.h" +/* An enumeration of the various things a user might attempt to + complete for a linespec location. */ + +enum class linespec_complete_what +{ + /* Nothing, no possible completion. */ + NOTHING, + + /* A function/method name. Due to ambiguity between + + (gdb) b source[TAB] + source_file.c + source_function + + this can also indicate a source filename, iff we haven't seen a + separate source filename component, as in "b source.c:function". */ + FUNCTION, + + /* A label symbol. E.g., break file.c:function:LABEL. */ + LABEL, + + /* An expression. E.g., "break foo if EXPR", or "break *EXPR". */ + EXPRESSION, + + /* A linespec keyword ("if"/"thread"/"task"). + E.g., "break func threa". */ + KEYWORD, +}; + typedef struct symbol *symbolp; DEF_VEC_P (symbolp); @@ -144,7 +173,7 @@ struct linespec_state /* The 'canonical' value passed to decode_line_full, or NULL. */ struct linespec_result *canonical; - /* Canonical strings that mirror the symtabs_and_lines result. */ + /* Canonical strings that mirror the std::vector result. */ struct linespec_canonical_name *canonical_names; /* This is a set of address_entry objects which is used to prevent @@ -271,6 +300,29 @@ struct ls_parser /* The result of the parse. */ struct linespec result; #define PARSER_RESULT(PPTR) (&(PPTR)->result) + + /* What the parser believes the current word point should complete + to. */ + linespec_complete_what complete_what; + + /* The completion word point. The parser advances this as it skips + tokens. At some point the input string will end or parsing will + fail, and then we attempt completion at the captured completion + word point, interpreting the string at completion_word as + COMPLETE_WHAT. */ + const char *completion_word; + + /* If the current token was a quoted string, then this is the + quoting character (either " or '). */ + int completion_quote_char; + + /* If the current token was a quoted string, then this points at the + end of the quoted string. */ + const char *completion_quote_end; + + /* If parsing for completion, then this points at the completion + tracker. Otherwise, this is NULL. */ + struct completion_tracker *completion_tracker; }; typedef struct ls_parser linespec_parser; @@ -289,9 +341,9 @@ static void initialize_defaults (struct symtab **default_symtab, CORE_ADDR linespec_expression_to_pc (const char **exp_ptr); -static struct symtabs_and_lines decode_objc (struct linespec_state *self, - linespec_p ls, - const char *arg); +static std::vector decode_objc (struct linespec_state *self, + linespec_p ls, + const char *arg); static VEC (symtab_ptr) *symtabs_from_filename (const char *, struct program_space *pspace); @@ -327,20 +379,20 @@ static VEC (symtab_ptr) * collect_symtabs_from_filename (const char *file, struct program_space *pspace); -static void decode_digits_ordinary (struct linespec_state *self, - linespec_p ls, - int line, - struct symtabs_and_lines *sals, - struct linetable_entry **best_entry); +static std::vector decode_digits_ordinary + (struct linespec_state *self, + linespec_p ls, + int line, + linetable_entry **best_entry); -static void decode_digits_list_mode (struct linespec_state *self, - linespec_p ls, - struct symtabs_and_lines *values, - struct symtab_and_line val); +static std::vector decode_digits_list_mode + (struct linespec_state *self, + linespec_p ls, + struct symtab_and_line val); static void minsym_found (struct linespec_state *self, struct objfile *objfile, struct minimal_symbol *msymbol, - struct symtabs_and_lines *result); + std::vector *result); static int compare_symbols (const void *a, const void *b); @@ -543,6 +595,30 @@ find_parameter_list_end (const char *input) return p; } +/* If the [STRING, STRING_LEN) string ends with what looks like a + keyword, return the keyword start offset in STRING. Return -1 + otherwise. */ + +static size_t +string_find_incomplete_keyword_at_end (const char * const *keywords, + const char *string, size_t string_len) +{ + const char *end = string + string_len; + const char *p = end; + + while (p > string && *p != ' ') + --p; + if (p > string) + { + p++; + size_t len = end - p; + for (size_t i = 0; keywords[i] != NULL; ++i) + if (strncmp (keywords[i], p, len) == 0) + return p - string; + } + + return -1; +} /* Lex a string from the input in PARSER. */ @@ -590,13 +666,31 @@ linespec_lexer_lex_string (linespec_parser *parser) /* Skip to the ending quote. */ end = skip_quote_char (PARSER_STREAM (parser), quote_char); - /* Error if the input did not terminate properly. */ - if (end == NULL) - error (_("unmatched quote")); + /* This helps the completer mode decide whether we have a + complete string. */ + parser->completion_quote_char = quote_char; + parser->completion_quote_end = end; - /* Skip over the ending quote and mark the length of the string. */ - PARSER_STREAM (parser) = (char *) ++end; - LS_TOKEN_STOKEN (token).length = PARSER_STREAM (parser) - 2 - start; + /* Error if the input did not terminate properly, unless in + completion mode. */ + if (end == NULL) + { + if (parser->completion_tracker == NULL) + error (_("unmatched quote")); + + /* In completion mode, we'll try to complete the incomplete + token. */ + token.type = LSTOKEN_STRING; + while (*PARSER_STREAM (parser) != '\0') + PARSER_STREAM (parser)++; + LS_TOKEN_STOKEN (token).length = PARSER_STREAM (parser) - 1 - start; + } + else + { + /* Skip over the ending quote and mark the length of the string. */ + PARSER_STREAM (parser) = (char *) ++end; + LS_TOKEN_STOKEN (token).length = PARSER_STREAM (parser) - 2 - start; + } } else { @@ -674,14 +768,50 @@ linespec_lexer_lex_string (linespec_parser *parser) else if (*PARSER_STREAM (parser) == '<' || *PARSER_STREAM (parser) == '(') { - const char *p; + /* Don't interpret 'operator<' / 'operator<<' as a + template parameter list though. */ + if (*PARSER_STREAM (parser) == '<' + && (PARSER_STATE (parser)->language->la_language + == language_cplus) + && (PARSER_STREAM (parser) - start) >= CP_OPERATOR_LEN) + { + const char *p = PARSER_STREAM (parser); + + while (p > start && isspace (p[-1])) + p--; + if (p - start >= CP_OPERATOR_LEN) + { + p -= CP_OPERATOR_LEN; + if (strncmp (p, CP_OPERATOR_STR, CP_OPERATOR_LEN) == 0 + && (p == start + || !(isalnum (p[-1]) || p[-1] == '_'))) + { + /* This is an operator name. Keep going. */ + ++(PARSER_STREAM (parser)); + if (*PARSER_STREAM (parser) == '<') + ++(PARSER_STREAM (parser)); + continue; + } + } + } - p = find_parameter_list_end (PARSER_STREAM (parser)); - if (p != NULL) + const char *p = find_parameter_list_end (PARSER_STREAM (parser)); + PARSER_STREAM (parser) = p; + + /* Don't loop around to the normal \0 case above because + we don't want to misinterpret a potential keyword at + the end of the token when the string isn't + "()<>"-balanced. This handles "b + function(thread" in completion mode. */ + if (*p == '\0') { - PARSER_STREAM (parser) = p; - continue; + LS_TOKEN_STOKEN (token).ptr = start; + LS_TOKEN_STOKEN (token).length + = PARSER_STREAM (parser) - start; + return token; } + else + continue; } /* Commas are terminators, but not if they are part of an operator name. */ @@ -799,13 +929,48 @@ linespec_lexer_lex_one (linespec_parser *parser) } /* Consume the current token and return the next token in PARSER's - input stream. */ + input stream. Also advance the completion word for completion + mode. */ static linespec_token linespec_lexer_consume_token (linespec_parser *parser) { + gdb_assert (parser->lexer.current.type != LSTOKEN_EOI); + + bool advance_word = (parser->lexer.current.type != LSTOKEN_STRING + || *PARSER_STREAM (parser) != '\0'); + + /* If we're moving past a string to some other token, it must be the + quote was terminated. */ + if (parser->completion_quote_char) + { + gdb_assert (parser->lexer.current.type == LSTOKEN_STRING); + + /* If the string was the last (non-EOI) token, we're past the + quote, but remember that for later. */ + if (*PARSER_STREAM (parser) != '\0') + { + parser->completion_quote_char = '\0'; + parser->completion_quote_end = NULL;; + } + } + parser->lexer.current.type = LSTOKEN_CONSUMED; - return linespec_lexer_lex_one (parser); + linespec_lexer_lex_one (parser); + + if (parser->lexer.current.type == LSTOKEN_STRING) + { + /* Advance the completion word past a potential initial + quote-char. */ + parser->completion_word = LS_TOKEN_STOKEN (parser->lexer.current).ptr; + } + else if (advance_word) + { + /* Advance the completion word past any whitespace. */ + parser->completion_word = PARSER_STREAM (parser); + } + + return parser->lexer.current; } /* Return the next token without consuming the current token. */ @@ -816,26 +981,21 @@ linespec_lexer_peek_token (linespec_parser *parser) linespec_token next; const char *saved_stream = PARSER_STREAM (parser); linespec_token saved_token = parser->lexer.current; + int saved_completion_quote_char = parser->completion_quote_char; + const char *saved_completion_quote_end = parser->completion_quote_end; + const char *saved_completion_word = parser->completion_word; next = linespec_lexer_consume_token (parser); PARSER_STREAM (parser) = saved_stream; parser->lexer.current = saved_token; + parser->completion_quote_char = saved_completion_quote_char; + parser->completion_quote_end = saved_completion_quote_end; + parser->completion_word = saved_completion_word; return next; } /* Helper functions. */ -/* Add SAL to SALS. */ - -static void -add_sal_to_sals_basic (struct symtabs_and_lines *sals, - struct symtab_and_line *sal) -{ - ++sals->nelts; - sals->sals = XRESIZEVEC (struct symtab_and_line, sals->sals, sals->nelts); - sals->sals[sals->nelts - 1] = *sal; -} - /* Add SAL to SALS, and also update SELF->CANONICAL_NAMES to reflect the new sal, if needed. If not NULL, SYMNAME is the name of the symbol to use when constructing the new canonical name. @@ -845,19 +1005,20 @@ add_sal_to_sals_basic (struct symtabs_and_lines *sals, static void add_sal_to_sals (struct linespec_state *self, - struct symtabs_and_lines *sals, + std::vector *sals, struct symtab_and_line *sal, const char *symname, int literal_canonical) { - add_sal_to_sals_basic (sals, sal); + sals->push_back (*sal); if (self->canonical) { struct linespec_canonical_name *canonical; self->canonical_names = XRESIZEVEC (struct linespec_canonical_name, - self->canonical_names, sals->nelts); - canonical = &self->canonical_names[sals->nelts - 1]; + self->canonical_names, + sals->size ()); + canonical = &self->canonical_names[sals->size () - 1]; if (!literal_canonical && sal->symtab) { symtab_to_fullname (sal->symtab); @@ -1112,7 +1273,7 @@ find_methods (struct type *t, const char *name, /* Find an instance of the character C in the string S that is outside of all parenthesis pairs, single-quoted strings, and double-quoted strings. Also, ignore the char within a template name, like a ',' - within foo. */ + within foo, while considering C++ operator') && depth > 0) depth--; + else if (*scan == 'o' && !quoted && depth == 0) + { + /* Handle C++ operator names. */ + if (strncmp (scan, CP_OPERATOR_STR, CP_OPERATOR_LEN) == 0) + { + scan += CP_OPERATOR_LEN; + if (*scan == c) + return scan; + while (isspace (*scan)) + { + ++scan; + if (*scan == c) + return scan; + } + if (*scan == '\0') + break; + + switch (*scan) + { + /* Skip over one less than the appropriate number of + characters: the for loop will skip over the last + one. */ + case '<': + if (scan[1] == '<') + { + scan++; + if (*scan == c) + return scan; + } + break; + case '>': + if (scan[1] == '>') + { + scan++; + if (*scan == c) + return scan; + } + break; + } + } + } } return 0; @@ -1193,7 +1395,7 @@ canonical_to_fullform (const struct linespec_canonical_name *canonical) static void filter_results (struct linespec_state *self, - struct symtabs_and_lines *result, + std::vector *result, VEC (const_char_ptr) *filters) { int i; @@ -1201,12 +1403,9 @@ filter_results (struct linespec_state *self, for (i = 0; VEC_iterate (const_char_ptr, filters, i, name); ++i) { - struct linespec_sals lsal; - int j; - - memset (&lsal, 0, sizeof (lsal)); + linespec_sals lsal; - for (j = 0; j < result->nelts; ++j) + for (size_t j = 0; j < result->size (); ++j) { const struct linespec_canonical_name *canonical; char *fullform; @@ -1217,15 +1416,15 @@ filter_results (struct linespec_state *self, cleanup = make_cleanup (xfree, fullform); if (strcmp (name, fullform) == 0) - add_sal_to_sals_basic (&lsal.sals, &result->sals[j]); + lsal.sals.push_back ((*result)[j]); do_cleanups (cleanup); } - if (lsal.sals.nelts > 0) + if (!lsal.sals.empty ()) { lsal.canonical = xstrdup (name); - VEC_safe_push (linespec_sals, self->canonical->sals, &lsal); + self->canonical->lsals.push_back (std::move (lsal)); } } @@ -1236,13 +1435,13 @@ filter_results (struct linespec_state *self, static void convert_results_to_lsals (struct linespec_state *self, - struct symtabs_and_lines *result) + std::vector *result) { struct linespec_sals lsal; lsal.canonical = NULL; - lsal.sals = *result; - VEC_safe_push (linespec_sals, self->canonical->sals, &lsal); + lsal.sals = std::move (*result); + self->canonical->lsals.push_back (std::move (lsal)); } /* A structure that contains two string representations of a struct @@ -1290,7 +1489,7 @@ decode_line_2_compare_items (const void *ap, const void *bp) static void decode_line_2 (struct linespec_state *self, - struct symtabs_and_lines *result, + std::vector *result, const char *select_mode) { char *args; @@ -1303,12 +1502,12 @@ decode_line_2 (struct linespec_state *self, gdb_assert (select_mode != multiple_symbols_all); gdb_assert (self->canonical != NULL); - gdb_assert (result->nelts >= 1); + gdb_assert (!result->empty ()); old_chain = make_cleanup (VEC_cleanup (const_char_ptr), &filters); /* Prepare ITEMS array. */ - items_count = result->nelts; + items_count = result->size (); items = XNEWVEC (struct decode_line_2_item, items_count); make_cleanup (xfree, items); for (i = 0; i < items_count; ++i) @@ -1522,6 +1721,17 @@ source_file_not_found_error (const char *name) throw_error (NOT_FOUND_ERROR, _("No source file named %s."), name); } +/* Unless at EIO, save the current stream position as completion word + point, and consume the next token. */ + +static linespec_token +save_stream_and_consume_token (linespec_parser *parser) +{ + if (linespec_lexer_peek_token (parser).type != LSTOKEN_EOI) + parser->completion_word = PARSER_STREAM (parser); + return linespec_lexer_consume_token (parser); +} + /* See description in linespec.h. */ struct line_offset @@ -1549,6 +1759,26 @@ linespec_parse_line_offset (const char *string) return line_offset; } +/* In completion mode, if the user is still typing the number, there's + no possible completion to offer. But if there's already input past + the number, setup to expect NEXT. */ + +static void +set_completion_after_number (linespec_parser *parser, + linespec_complete_what next) +{ + if (*PARSER_STREAM (parser) == ' ') + { + parser->completion_word = skip_spaces_const (PARSER_STREAM (parser) + 1); + parser->complete_what = next; + } + else + { + parser->completion_word = PARSER_STREAM (parser); + parser->complete_what = linespec_complete_what::NOTHING; + } +} + /* Parse the basic_spec in PARSER's input. */ static void @@ -1564,11 +1794,20 @@ linespec_parse_basic (linespec_parser *parser) token = linespec_lexer_lex_one (parser); /* If it is EOI or KEYWORD, issue an error. */ - if (token.type == LSTOKEN_KEYWORD || token.type == LSTOKEN_EOI) - unexpected_linespec_error (parser); + if (token.type == LSTOKEN_KEYWORD) + { + parser->complete_what = linespec_complete_what::NOTHING; + unexpected_linespec_error (parser); + } + else if (token.type == LSTOKEN_EOI) + { + unexpected_linespec_error (parser); + } /* If it is a LSTOKEN_NUMBER, we have an offset. */ else if (token.type == LSTOKEN_NUMBER) { + set_completion_after_number (parser, linespec_complete_what::KEYWORD); + /* Record the line offset and get the next token. */ name = copy_token_string (token); cleanup = make_cleanup (xfree, name); @@ -1580,7 +1819,10 @@ linespec_parse_basic (linespec_parser *parser) /* If the next token is a comma, stop parsing and return. */ if (token.type == LSTOKEN_COMMA) - return; + { + parser->complete_what = linespec_complete_what::NOTHING; + return; + } /* If the next token is anything but EOI or KEYWORD, issue an error. */ @@ -1593,12 +1835,58 @@ linespec_parse_basic (linespec_parser *parser) /* Next token must be LSTOKEN_STRING. */ if (token.type != LSTOKEN_STRING) - unexpected_linespec_error (parser); + { + parser->complete_what = linespec_complete_what::NOTHING; + unexpected_linespec_error (parser); + } /* The current token will contain the name of a function, method, or label. */ - name = copy_token_string (token); - cleanup = make_cleanup (xfree, name); + name = copy_token_string (token); + cleanup = make_cleanup (free_current_contents, &name); + + if (parser->completion_tracker != NULL) + { + /* If the function name ends with a ":", then this may be an + incomplete "::" scope operator instead of a label separator. + E.g., + "b klass:" + which should expand to: + "b klass::method()" + + Do a tentative completion assuming the later. If we find + completions, advance the stream past the colon token and make + it part of the function name/token. */ + + if (!parser->completion_quote_char + && strcmp (PARSER_STREAM (parser), ":") == 0) + { + completion_tracker tmp_tracker; + const char *source_filename + = PARSER_EXPLICIT (parser)->source_filename; + + linespec_complete_function (tmp_tracker, + parser->completion_word, + source_filename); + + if (tmp_tracker.have_completions ()) + { + PARSER_STREAM (parser)++; + LS_TOKEN_STOKEN (token).length++; + + xfree (name); + name = savestring (parser->completion_word, + (PARSER_STREAM (parser) + - parser->completion_word)); + } + } + + PARSER_EXPLICIT (parser)->function_name = name; + discard_cleanups (cleanup); + } + else + { + /* XXX Reindent before pushing. */ /* Try looking it up as a function/method. */ find_linespec_symbols (PARSER_STATE (parser), @@ -1658,11 +1946,19 @@ linespec_parse_basic (linespec_parser *parser) return; } } + } + + int previous_qc = parser->completion_quote_char; /* Get the next token. */ token = linespec_lexer_consume_token (parser); - if (token.type == LSTOKEN_COLON) + if (token.type == LSTOKEN_EOI) + { + if (previous_qc && !parser->completion_quote_char) + parser->complete_what = linespec_complete_what::KEYWORD; + } + else if (token.type == LSTOKEN_COLON) { /* User specified a label or a lineno. */ token = linespec_lexer_consume_token (parser); @@ -1671,17 +1967,56 @@ linespec_parse_basic (linespec_parser *parser) { /* User specified an offset. Record the line offset and get the next token. */ + set_completion_after_number (parser, linespec_complete_what::KEYWORD); + name = copy_token_string (token); cleanup = make_cleanup (xfree, name); PARSER_EXPLICIT (parser)->line_offset = linespec_parse_line_offset (name); do_cleanups (cleanup); - /* Ge the next token. */ + /* Get the next token. */ token = linespec_lexer_consume_token (parser); } + else if (token.type == LSTOKEN_EOI && parser->completion_tracker != NULL) + { + parser->complete_what = linespec_complete_what::LABEL; + } else if (token.type == LSTOKEN_STRING) { + parser->complete_what = linespec_complete_what::LABEL; + + /* If we have text after the label separated by whitespace + (e.g., "b func():lab i"), don't consider it part of + the label. In completion mode that should complete to + "if", in normal mode, the 'i' should be treated as + garbage. */ + if (parser->completion_quote_char == '\0') + { + const char *ptr = LS_TOKEN_STOKEN (token).ptr; + for (size_t i = 0; i < LS_TOKEN_STOKEN (token).length; i++) + { + if (ptr[i] == ' ') + { + LS_TOKEN_STOKEN (token).length = i; + PARSER_STREAM (parser) = skip_spaces_const (ptr + i + 1); + break; + } + } + } + + if (parser->completion_tracker != NULL) + { + if (PARSER_STREAM (parser)[-1] == ' ') + { + parser->completion_word = PARSER_STREAM (parser); + parser->complete_what = linespec_complete_what::KEYWORD; + } + } + else + { + /* XXX Reindent before pushing. */ + /* Grab a copy of the label's name and look it up. */ name = copy_token_string (token); cleanup = make_cleanup (xfree, name); @@ -1704,8 +2039,10 @@ linespec_parse_basic (linespec_parser *parser) name); } + } + /* Check for a line offset. */ - token = linespec_lexer_consume_token (parser); + token = save_stream_and_consume_token (parser); if (token.type == LSTOKEN_COLON) { /* Get the next token. */ @@ -1785,18 +2122,12 @@ canonicalize_linespec (struct linespec_state *state, const linespec_p ls) /* Given a line offset in LS, construct the relevant SALs. */ -static struct symtabs_and_lines +static std::vector create_sals_line_offset (struct linespec_state *self, linespec_p ls) { - struct symtabs_and_lines values; - struct symtab_and_line val; int use_default = 0; - init_sal (&val); - values.sals = NULL; - values.nelts = 0; - /* This is where we need to make sure we have good defaults. We must guarantee that this section of code is never executed when we are called with just a function name, since @@ -1821,6 +2152,7 @@ create_sals_line_offset (struct linespec_state *self, use_default = 1; } + symtab_and_line val; val.line = ls->explicit_loc.line_offset.offset; switch (ls->explicit_loc.line_offset.sign) { @@ -1844,27 +2176,22 @@ create_sals_line_offset (struct linespec_state *self, break; /* No need to adjust val.line. */ } + std::vector values; if (self->list_mode) - decode_digits_list_mode (self, ls, &values, val); + values = decode_digits_list_mode (self, ls, val); else { struct linetable_entry *best_entry = NULL; int *filter; const struct block **blocks; - struct cleanup *cleanup; - struct symtabs_and_lines intermediate_results; int i, j; - intermediate_results.sals = NULL; - intermediate_results.nelts = 0; - - decode_digits_ordinary (self, ls, val.line, &intermediate_results, - &best_entry); - if (intermediate_results.nelts == 0 && best_entry != NULL) - decode_digits_ordinary (self, ls, best_entry->line, - &intermediate_results, &best_entry); - - cleanup = make_cleanup (xfree, intermediate_results.sals); + std::vector intermediate_results + = decode_digits_ordinary (self, ls, val.line, &best_entry); + if (intermediate_results.empty () && best_entry != NULL) + intermediate_results = decode_digits_ordinary (self, ls, + best_entry->line, + &best_entry); /* For optimized code, the compiler can scatter one source line across disjoint ranges of PC values, even when no duplicate @@ -1876,24 +2203,24 @@ create_sals_line_offset (struct linespec_state *self, above, we see if there are other PCs that are in the same block. If yes, the other PCs are filtered out. */ - filter = XNEWVEC (int, intermediate_results.nelts); - make_cleanup (xfree, filter); - blocks = XNEWVEC (const struct block *, intermediate_results.nelts); + filter = XNEWVEC (int, intermediate_results.size ()); + struct cleanup *cleanup = make_cleanup (xfree, filter); + blocks = XNEWVEC (const struct block *, intermediate_results.size ()); make_cleanup (xfree, blocks); - for (i = 0; i < intermediate_results.nelts; ++i) + for (i = 0; i < intermediate_results.size (); ++i) { - set_current_program_space (intermediate_results.sals[i].pspace); + set_current_program_space (intermediate_results[i].pspace); filter[i] = 1; - blocks[i] = block_for_pc_sect (intermediate_results.sals[i].pc, - intermediate_results.sals[i].section); + blocks[i] = block_for_pc_sect (intermediate_results[i].pc, + intermediate_results[i].section); } - for (i = 0; i < intermediate_results.nelts; ++i) + for (i = 0; i < intermediate_results.size (); ++i) { if (blocks[i] != NULL) - for (j = i + 1; j < intermediate_results.nelts; ++j) + for (j = i + 1; j < intermediate_results.size (); ++j) { if (blocks[j] == blocks[i]) { @@ -1903,7 +2230,7 @@ create_sals_line_offset (struct linespec_state *self, } } - for (i = 0; i < intermediate_results.nelts; ++i) + for (i = 0; i < intermediate_results.size (); ++i) if (filter[i]) { struct symbol *sym = (blocks[i] @@ -1911,18 +2238,18 @@ create_sals_line_offset (struct linespec_state *self, : NULL); if (self->funfirstline) - skip_prologue_sal (&intermediate_results.sals[i]); + skip_prologue_sal (&intermediate_results[i]); /* Make sure the line matches the request, not what was found. */ - intermediate_results.sals[i].line = val.line; - add_sal_to_sals (self, &values, &intermediate_results.sals[i], + intermediate_results[i].line = val.line; + add_sal_to_sals (self, &values, &intermediate_results[i], sym ? SYMBOL_NATURAL_NAME (sym) : NULL, 0); } do_cleanups (cleanup); } - if (values.nelts == 0) + if (values.empty ()) { if (ls->explicit_loc.source_filename) throw_error (NOT_FOUND_ERROR, _("No line %d in file \"%s\"."), @@ -1937,17 +2264,16 @@ create_sals_line_offset (struct linespec_state *self, /* Convert the given ADDRESS into SaLs. */ -static struct symtabs_and_lines +static std::vector convert_address_location_to_sals (struct linespec_state *self, CORE_ADDR address) { - struct symtab_and_line sal; - struct symtabs_and_lines sals = {NULL, 0}; - - sal = find_pc_line (address, 0); + symtab_and_line sal = find_pc_line (address, 0); sal.pc = address; sal.section = find_pc_overlay (address); sal.explicit_pc = 1; + + std::vector sals; add_sal_to_sals (self, &sals, &sal, core_addr_to_string (address), 1); return sals; @@ -1955,10 +2281,10 @@ convert_address_location_to_sals (struct linespec_state *self, /* Create and return SALs from the linespec LS. */ -static struct symtabs_and_lines +static std::vector convert_linespec_to_sals (struct linespec_state *state, linespec_p ls) { - struct symtabs_and_lines sals = {NULL, 0}; + std::vector sals; if (ls->labels.label_symbols != NULL) { @@ -2048,7 +2374,7 @@ convert_linespec_to_sals (struct linespec_state *state, linespec_p ls) canonicalize_linespec (state, ls); - if (sals.nelts > 0 && state->canonical != NULL) + if (!sals.empty () && state->canonical != NULL) state->canonical->pre_expanded = 1; return sals; @@ -2124,7 +2450,7 @@ convert_explicit_location_to_linespec (struct linespec_state *self, /* Convert the explicit location EXPLICIT_LOC into SaLs. */ -static struct symtabs_and_lines +static std::vector convert_explicit_location_to_sals (struct linespec_state *self, linespec_p result, const struct explicit_location *explicit_loc) @@ -2187,11 +2513,10 @@ convert_explicit_location_to_sals (struct linespec_state *self, /* Parse the linespec in ARG. */ -static struct symtabs_and_lines +static std::vector parse_linespec (linespec_parser *parser, const char *arg) { linespec_token token; - struct symtabs_and_lines values; struct gdb_exception file_exception = exception_none; struct cleanup *cleanup; @@ -2199,7 +2524,8 @@ parse_linespec (linespec_parser *parser, const char *arg) IDEs to work around bugs in the previous parser by quoting the entire linespec, so we attempt to deal with this nicely. */ parser->is_quote_enclosed = 0; - if (!is_ada_operator (arg) + if (parser->completion_tracker == NULL + && !is_ada_operator (arg) && strchr (linespec_quote_characters, *arg) != NULL) { const char *end; @@ -2216,20 +2542,35 @@ parse_linespec (linespec_parser *parser, const char *arg) parser->lexer.saved_arg = arg; parser->lexer.stream = arg; + parser->completion_word = arg; + parser->complete_what = linespec_complete_what::FUNCTION; /* Initialize the default symtab and line offset. */ initialize_defaults (&PARSER_STATE (parser)->default_symtab, &PARSER_STATE (parser)->default_line); /* Objective-C shortcut. */ - values = decode_objc (PARSER_STATE (parser), PARSER_RESULT (parser), arg); - if (values.sals != NULL) - return values; + if (parser->completion_tracker == NULL) + { + std::vector values + = decode_objc (PARSER_STATE (parser), PARSER_RESULT (parser), arg); + if (!values.empty ()) + return values; + } + else + { + /* "-"/"+" is either an objc selector, or a number. There's + nothing to complete the latter to, so just let the caller + complete on functions, which finds objc selectors, if there's + any. */ + if ((arg[0] == '-' || arg[0] == '+') && arg[1] == '\0') + return {}; + } /* Start parsing. */ /* Get the first token. */ - token = linespec_lexer_lex_one (parser); + token = linespec_lexer_consume_token (parser); /* It must be either LSTOKEN_STRING or LSTOKEN_NUMBER. */ if (token.type == LSTOKEN_STRING && *LS_TOKEN_STOKEN (token).ptr == '$') @@ -2237,7 +2578,8 @@ parse_linespec (linespec_parser *parser, const char *arg) char *var; /* A NULL entry means to use GLOBAL_DEFAULT_SYMTAB. */ - VEC_safe_push (symtab_ptr, PARSER_RESULT (parser)->file_symtabs, NULL); + if (parser->completion_tracker == NULL) + VEC_safe_push (symtab_ptr, PARSER_RESULT (parser)->file_symtabs, NULL); /* User specified a convenience variable or history value. */ var = copy_token_string (token); @@ -2256,8 +2598,16 @@ parse_linespec (linespec_parser *parser, const char *arg) goto convert_to_sals; } } + else if (token.type == LSTOKEN_EOI && parser->completion_tracker != NULL) + { + /* Let the default linespec_complete_what::FUNCTION kick in. */ + unexpected_linespec_error (parser); + } else if (token.type != LSTOKEN_STRING && token.type != LSTOKEN_NUMBER) - unexpected_linespec_error (parser); + { + parser->complete_what = linespec_complete_what::NOTHING; + unexpected_linespec_error (parser); + } /* Shortcut: If the next token is not LSTOKEN_COLON, we know that this token cannot represent a filename. */ @@ -2305,8 +2655,9 @@ parse_linespec (linespec_parser *parser, const char *arg) } } /* If the next token is not EOI, KEYWORD, or COMMA, issue an error. */ - else if (token.type != LSTOKEN_EOI && token.type != LSTOKEN_KEYWORD - && token.type != LSTOKEN_COMMA) + else if (parser->completion_tracker == NULL + && (token.type != LSTOKEN_EOI && token.type != LSTOKEN_KEYWORD + && token.type != LSTOKEN_COMMA)) { /* TOKEN is the _next_ token, not the one currently in the parser. Consuming the token will give the correct error message. */ @@ -2322,7 +2673,8 @@ parse_linespec (linespec_parser *parser, const char *arg) /* Parse the rest of the linespec. */ linespec_parse_basic (parser); - if (PARSER_RESULT (parser)->function_symbols == NULL + if (parser->completion_tracker == NULL + && PARSER_RESULT (parser)->function_symbols == NULL && PARSER_RESULT (parser)->labels.label_symbols == NULL && PARSER_EXPLICIT (parser)->line_offset.sign == LINE_OFFSET_UNKNOWN && PARSER_RESULT (parser)->minimal_symbols == NULL) @@ -2343,13 +2695,23 @@ parse_linespec (linespec_parser *parser, const char *arg) if necessary. */ token = linespec_lexer_lex_one (parser); if (token.type != LSTOKEN_EOI && token.type != LSTOKEN_KEYWORD) - PARSER_STREAM (parser) = LS_TOKEN_STOKEN (token).ptr; + unexpected_linespec_error (parser); + else if (token.type == LSTOKEN_KEYWORD) + { + /* Setup the completion word past the keyword. Lexing never + advances past a keyword automatically, so skip it + manually. */ + parser->completion_word + = skip_spaces_const (skip_to_space_const (PARSER_STREAM (parser))); + parser->complete_what = linespec_complete_what::EXPRESSION; + } /* Convert the data in PARSER_RESULT to SALs. */ - values = convert_linespec_to_sals (PARSER_STATE (parser), + if (parser->completion_tracker == NULL) + return convert_linespec_to_sals (PARSER_STATE (parser), PARSER_RESULT (parser)); - return values; + return {}; } @@ -2485,6 +2847,67 @@ linespec_complete_function (completion_tracker &tracker, collect_symbol_completion_matches (tracker, mode, function, function); } +/* Helper for complete_linespec to simplify it. SOURCE_FILENAME is + only meaningful if COMPONENT is FUNCTION. */ + +static void +complete_linespec_component (linespec_parser *parser, + completion_tracker &tracker, + const char *text, + linespec_complete_what component, + const char *source_filename) +{ + if (component == linespec_complete_what::KEYWORD) + { + complete_on_enum (tracker, linespec_keywords, text, text); + } + else if (component == linespec_complete_what::EXPRESSION) + { + const char *word + = advance_to_expression_complete_word_point (tracker, text); + complete_expression (tracker, text, word); + } + else if (component == linespec_complete_what::FUNCTION) + { + completion_list fn_list; + + linespec_complete_function (tracker, text, source_filename); + if (source_filename == NULL) + { + /* Haven't seen a source component, like in "b + file.c:function[TAB]". Maybe this wasn't a function, but + a filename instead, like "b file.[TAB]". */ + fn_list = complete_source_filenames (text); + } + + /* If we only have a single filename completion, append a ':' for + the user, since that's the only thing that can usefully follow + the filename. */ + if (fn_list.size () == 1 && !tracker.have_completions ()) + { + char *fn = fn_list[0].release (); + + /* If we also need to append a quote char, it needs to be + appended before the ':'. Append it now, and make ':' the + new "quote" char. */ + if (tracker.quote_char ()) + { + char quote_char_str[2] = { tracker.quote_char () }; + + fn = reconcat (fn, fn, quote_char_str, (char *) NULL); + tracker.set_quote_char (':'); + } + else + fn = reconcat (fn, fn, ":", (char *) NULL); + fn_list[0].reset (fn); + + /* Tell readline to skip appending a space. */ + tracker.set_suppress_append_ws (true); + } + tracker.add_completions (std::move (fn_list)); + } +} + /* Helper for linespec_complete_label. Find labels that match LABEL_NAME in the function symbols listed in the PARSER, and add them to the tracker. */ @@ -2548,14 +2971,209 @@ linespec_complete_label (completion_tracker &tracker, do_cleanups (cleanup); } +/* See description in linespec.h. */ + +void +linespec_complete (completion_tracker &tracker, const char *text) +{ + linespec_parser parser; + struct cleanup *cleanup; + const char *orig = text; + + linespec_parser_new (&parser, 0, current_language, NULL, NULL, 0, NULL); + cleanup = make_cleanup (linespec_parser_delete, &parser); + parser.lexer.saved_arg = text; + PARSER_STREAM (&parser) = text; + + parser.completion_tracker = &tracker; + PARSER_STATE (&parser)->is_linespec = 1; + + /* Parse as much as possible. parser.completion_word will hold + furthest completion point we managed to parse to. */ + TRY + { + parse_linespec (&parser, text); + } + CATCH (except, RETURN_MASK_ERROR) + { + } + END_CATCH + + if (parser.completion_quote_char != '\0' + && parser.completion_quote_end != NULL + && parser.completion_quote_end[1] == '\0') + { + /* If completing a quoted string with the cursor right at + terminating quote char, complete the completion word without + interpretation, so that readline advances the cursor one + whitespace past the quote, even if there's no match. This + makes these cases behave the same: + + before: "b function()" + after: "b function() " + + before: "b 'function()'" + after: "b 'function()' " + + and trusts the user in this case: + + before: "b 'not_loaded_function_yet()'" + after: "b 'not_loaded_function_yet()' " + */ + parser.complete_what = linespec_complete_what::NOTHING; + parser.completion_quote_char = '\0'; + + gdb::unique_xmalloc_ptr text_copy + (xstrdup (parser.completion_word)); + tracker.add_completion (std::move (text_copy)); + } + + tracker.set_quote_char (parser.completion_quote_char); + + if (parser.complete_what == linespec_complete_what::LABEL) + { + parser.complete_what = linespec_complete_what::NOTHING; + + const char *func_name = PARSER_EXPLICIT (&parser)->function_name; + + VEC (symbolp) *function_symbols; + VEC (bound_minimal_symbol_d) *minimal_symbols; + find_linespec_symbols (PARSER_STATE (&parser), + PARSER_RESULT (&parser)->file_symtabs, + func_name, + &function_symbols, &minimal_symbols); + + PARSER_RESULT (&parser)->function_symbols = function_symbols; + PARSER_RESULT (&parser)->minimal_symbols = minimal_symbols; + + complete_label (tracker, &parser, parser.completion_word); + } + else if (parser.complete_what == linespec_complete_what::FUNCTION) + { + /* While parsing/lexing, we didn't know whether the completion + word completes to a unique function/source name already or + not. + + E.g.: + "b function() " + may need to complete either to: + "b function() const" + or to: + "b function() if/thread/task" + + Or, this: + "b foo t" + may need to complete either to: + "b foo template_fun()" + with "foo" being the template function's return type, or to: + "b foo thread/task" + + Or, this: + "b file" + may need to complete either to a source file name: + "b file.c" + or this, also a filename, but a unique completion: + "b file.c:" + or to a function name: + "b file_function" + + Address that by completing assuming source or function, and + seeing if we find a completion that matches exactly the + completion word. If so, then it must be a function (see note + below) and we advance the completion word to the end of input + and switch to KEYWORD completion mode. + + Note: if we find a unique completion for a source filename, + then it won't match the completion word, because the LCD will + contain a trailing ':'. And if we're completing at or after + the ':', then complete_linespec_component won't try to + complete on source filenames. */ + + const char *text = parser.completion_word; + const char *word = parser.completion_word; + + complete_linespec_component (&parser, tracker, + parser.completion_word, + linespec_complete_what::FUNCTION, + PARSER_EXPLICIT (&parser)->source_filename); + + parser.complete_what = linespec_complete_what::NOTHING; + + if (tracker.quote_char ()) + { + /* The function/file name was not close-quoted, so this + can't be a keyword. Note: complete_linespec_component + may have swapped the original quote char for ':' when we + get here, but that still indicates the same. */ + } + else if (!tracker.have_completions ()) + { + size_t key_start; + size_t wordlen = strlen (parser.completion_word); + + key_start + = string_find_incomplete_keyword_at_end (linespec_keywords, + parser.completion_word, + wordlen); + + if (key_start != -1 + || (wordlen > 0 + && parser.completion_word[wordlen - 1] == ' ')) + { + parser.completion_word += key_start; + parser.complete_what = linespec_complete_what::KEYWORD; + } + } + else if (tracker.completes_to_completion_word (word)) + { + /* Skip the function and complete on keywords. */ + parser.completion_word += strlen (word); + parser.complete_what = linespec_complete_what::KEYWORD; + tracker.discard_completions (); + } + } + + tracker.advance_custom_word_point_by (parser.completion_word - orig); + + complete_linespec_component (&parser, tracker, + parser.completion_word, + parser.complete_what, + PARSER_EXPLICIT (&parser)->source_filename); + + /* If we're past the "filename:function:label:offset" linespec, and + didn't find any match, then assume the user might want to create + a pending breakpoint anyway and offer the keyword + completions. */ + if (!parser.completion_quote_char + && (parser.complete_what == linespec_complete_what::FUNCTION + || parser.complete_what == linespec_complete_what::LABEL + || parser.complete_what == linespec_complete_what::NOTHING) + && !tracker.have_completions ()) + { + const char *end + = parser.completion_word + strlen (parser.completion_word); + + if (end > orig && end[-1] == ' ') + { + tracker.advance_custom_word_point_by (end - parser.completion_word); + + complete_linespec_component (&parser, tracker, end, + linespec_complete_what::KEYWORD, + NULL); + } + } + + do_cleanups (cleanup); +} + /* A helper function for decode_line_full and decode_line_1 to - turn LOCATION into symtabs_and_lines. */ + turn LOCATION into std::vector. */ -static struct symtabs_and_lines +static std::vector event_location_to_sals (linespec_parser *parser, const struct event_location *location) { - struct symtabs_and_lines result = {NULL, 0}; + std::vector result; switch (event_location_type (location)) { @@ -2631,7 +3249,6 @@ decode_line_full (const struct event_location *location, int flags, const char *select_mode, const char *filter) { - struct symtabs_and_lines result; struct cleanup *cleanups; VEC (const_char_ptr) *filters = NULL; linespec_parser parser; @@ -2653,19 +3270,20 @@ decode_line_full (const struct event_location *location, int flags, scoped_restore_current_program_space restore_pspace; - result = event_location_to_sals (&parser, location); + std::vector result = event_location_to_sals (&parser, + location); state = PARSER_STATE (&parser); - gdb_assert (result.nelts == 1 || canonical->pre_expanded); + gdb_assert (result.size () == 1 || canonical->pre_expanded); canonical->pre_expanded = 1; /* Arrange for allocated canonical names to be freed. */ - if (result.nelts > 0) + if (!result.empty ()) { int i; make_cleanup (xfree, state->canonical_names); - for (i = 0; i < result.nelts; ++i) + for (i = 0; i < result.size (); ++i) { gdb_assert (state->canonical_names[i].suffix != NULL); make_cleanup (xfree, state->canonical_names[i].suffix); @@ -2699,13 +3317,12 @@ decode_line_full (const struct event_location *location, int flags, /* See linespec.h. */ -struct symtabs_and_lines +std::vector decode_line_1 (const struct event_location *location, int flags, struct program_space *search_pspace, struct symtab *default_symtab, int default_line) { - struct symtabs_and_lines result; linespec_parser parser; struct cleanup *cleanups; @@ -2716,7 +3333,8 @@ decode_line_1 (const struct event_location *location, int flags, scoped_restore_current_program_space restore_pspace; - result = event_location_to_sals (&parser, location); + std::vector result = event_location_to_sals (&parser, + location); do_cleanups (cleanups); return result; @@ -2724,23 +3342,20 @@ decode_line_1 (const struct event_location *location, int flags, /* See linespec.h. */ -struct symtabs_and_lines +std::vector decode_line_with_current_source (char *string, int flags) { - struct symtabs_and_lines sals; - struct symtab_and_line cursal; - if (string == 0) error (_("Empty line specification.")); /* We use whatever is set as the current source line. We do not try and get a default source symtab+line or it will recursively call us! */ - cursal = get_current_source_symtab_and_line (); + symtab_and_line cursal = get_current_source_symtab_and_line (); event_location_up location = string_to_event_location (&string, current_language); - sals = decode_line_1 (location.get (), flags, NULL, - cursal.symtab, cursal.line); + std::vector sals + = decode_line_1 (location.get (), flags, NULL, cursal.symtab, cursal.line); if (*string) error (_("Junk at end of line specification: %s"), string); @@ -2750,23 +3365,21 @@ decode_line_with_current_source (char *string, int flags) /* See linespec.h. */ -struct symtabs_and_lines +std::vector decode_line_with_last_displayed (char *string, int flags) { - struct symtabs_and_lines sals; - if (string == 0) error (_("Empty line specification.")); event_location_up location = string_to_event_location (&string, current_language); - if (last_displayed_sal_is_valid ()) - sals = decode_line_1 (location.get (), flags, NULL, - get_last_displayed_symtab (), - get_last_displayed_line ()); - else - sals = decode_line_1 (location.get (), flags, NULL, - (struct symtab *) NULL, 0); + std::vector sals + = (last_displayed_sal_is_valid () + ? decode_line_1 (location.get (), flags, NULL, + get_last_displayed_symtab (), + get_last_displayed_line ()) + : decode_line_1 (location.get (), flags, NULL, + (struct symtab *) NULL, 0)); if (*string) error (_("Junk at end of line specification: %s"), string); @@ -2822,12 +3435,11 @@ linespec_expression_to_pc (const char **exp_ptr) than one method that could represent the selector, then use some of the existing C++ code to let the user choose one. */ -static struct symtabs_and_lines +static std::vector decode_objc (struct linespec_state *self, linespec_p ls, const char *arg) { struct collect_info info; VEC (const_char_ptr) *symbol_names = NULL; - struct symtabs_and_lines values; const char *new_argptr; struct cleanup *cleanup = make_cleanup (VEC_cleanup (const_char_ptr), &symbol_names); @@ -2838,18 +3450,17 @@ decode_objc (struct linespec_state *self, linespec_p ls, const char *arg) make_cleanup (VEC_cleanup (symtab_ptr), &info.file_symtabs); info.result.symbols = NULL; info.result.minimal_symbols = NULL; - values.nelts = 0; - values.sals = NULL; new_argptr = find_imps (arg, &symbol_names); if (VEC_empty (const_char_ptr, symbol_names)) { do_cleanups (cleanup); - return values; + return {}; } add_all_symbol_names_from_pspace (&info, NULL, symbol_names); + std::vector values; if (!VEC_empty (symbolp, info.result.symbols) || !VEC_empty (bound_minimal_symbol_d, info.result.minimal_symbols)) { @@ -3563,10 +4174,9 @@ find_label_symbols (struct linespec_state *self, /* A helper for create_sals_line_offset that handles the 'list_mode' case. */ -static void +static std::vector decode_digits_list_mode (struct linespec_state *self, linespec_p ls, - struct symtabs_and_lines *values, struct symtab_and_line val) { int ix; @@ -3574,6 +4184,8 @@ decode_digits_list_mode (struct linespec_state *self, gdb_assert (self->list_mode); + std::vector values; + for (ix = 0; VEC_iterate (symtab_ptr, ls->file_symtabs, ix, elt); ++ix) { @@ -3590,23 +4202,25 @@ decode_digits_list_mode (struct linespec_state *self, val.pc = 0; val.explicit_line = 1; - add_sal_to_sals (self, values, &val, NULL, 0); + add_sal_to_sals (self, &values, &val, NULL, 0); } + + return values; } /* A helper for create_sals_line_offset that iterates over the symtabs, adding lines to the VEC. */ -static void +static std::vector decode_digits_ordinary (struct linespec_state *self, linespec_p ls, int line, - struct symtabs_and_lines *sals, struct linetable_entry **best_entry) { int ix; struct symtab *elt; + std::vector sals; for (ix = 0; VEC_iterate (symtab_ptr, ls->file_symtabs, ix, elt); ++ix) { std::vector pcs; @@ -3619,16 +4233,16 @@ decode_digits_ordinary (struct linespec_state *self, pcs = find_pcs_for_symtab_line (elt, line, best_entry); for (CORE_ADDR pc : pcs) { - struct symtab_and_line sal; - - init_sal (&sal); + symtab_and_line sal; sal.pspace = SYMTAB_PSPACE (elt); sal.symtab = elt; sal.line = line; sal.pc = pc; - add_sal_to_sals_basic (sals, &sal); + sals.push_back (std::move (sal)); } } + + return sals; } @@ -3698,7 +4312,7 @@ linespec_parse_variable (struct linespec_state *self, const char *variable) static void minsym_found (struct linespec_state *self, struct objfile *objfile, struct minimal_symbol *msymbol, - struct symtabs_and_lines *result) + std::vector *result) { struct gdbarch *gdbarch = get_objfile_arch (objfile); CORE_ADDR pc; @@ -4001,7 +4615,7 @@ symbol_to_sal (struct symtab_and_line *result, { if (SYMBOL_CLASS (sym) == LOC_LABEL && SYMBOL_VALUE_ADDRESS (sym) != 0) { - init_sal (result); + *result = {}; result->symtab = symbol_symtab (sym); result->line = SYMBOL_LINE (sym); result->pc = SYMBOL_VALUE_ADDRESS (sym); @@ -4016,7 +4630,7 @@ symbol_to_sal (struct symtab_and_line *result, else if (SYMBOL_LINE (sym) != 0) { /* We know its line number. */ - init_sal (result); + *result = {}; result->symtab = symbol_symtab (sym); result->line = SYMBOL_LINE (sym); result->pspace = SYMTAB_PSPACE (result->symtab); @@ -4029,15 +4643,8 @@ symbol_to_sal (struct symtab_and_line *result, linespec_result::~linespec_result () { - int i; - struct linespec_sals *lsal; - - for (i = 0; VEC_iterate (linespec_sals, sals, i, lsal); ++i) - { - xfree (lsal->canonical); - xfree (lsal->sals.sals); - } - VEC_free (linespec_sals, sals); + for (linespec_sals &lsal : lsals) + xfree (lsal.canonical); } /* Return the quote characters permitted by the linespec parser. */