/* Entry in the cooked index

   Copyright (C) 2022-2026 Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include "dwarf2/cooked-index-entry.h"
#include "dwarf2/read.h"
#include "dwarf2/tag.h"
#include "gdbsupport/selftest.h"

/* See cooked-index-entry.h.  */

std::string
to_string (cooked_index_flag flags)
{
  static constexpr cooked_index_flag::string_mapping mapping[] = {
    MAP_ENUM_FLAG (IS_MAIN),
    MAP_ENUM_FLAG (IS_STATIC),
    MAP_ENUM_FLAG (IS_LINKAGE),
    MAP_ENUM_FLAG (IS_TYPE_DECLARATION),
    MAP_ENUM_FLAG (IS_PARENT_DEFERRED),
    MAP_ENUM_FLAG (IS_SYNTHESIZED),
    MAP_ENUM_FLAG (IS_INLINED),
  };

  return flags.to_string (mapping);
}

/* See cooked-index-entry.h.  */

int
cooked_index_entry::compare (const char *stra, const char *strb,
			     comparison_mode mode)
{
#if defined (__GNUC__) && !defined (__clang__) && __GNUC__ <= 7
  /* Work around error with gcc 7.5.0.  */
  auto munge = [] (char c) -> unsigned char
#else
  auto munge = [] (char c) constexpr -> unsigned char
#endif
    {
      /* Treat '<' as if it ended the string.  This lets something
	 like "func<t>" match "func<t<int>>".  See the "Breakpoints in
	 template functions" section in the manual.  */
      if (c == '<')
	return '\0';
      return c_tolower (c);
    };

  unsigned char a = munge (*stra);
  unsigned char b = munge (*strb);

  while (a != '\0' && b != '\0' && a == b)
    {
      a = munge (*++stra);
      b = munge (*++strb);
    }

  if (a == b)
    return 0;

  /* When completing, if STRB ends earlier than STRA, consider them as
     equal.  */
  if (mode == COMPLETE && b == '\0')
    return 0;

  return a < b ? -1 : 1;
}

#if GDB_SELF_TEST

namespace {

void
test_compare ()
{
  /* Convenience aliases.  */
  const auto mode_compare = cooked_index_entry::MATCH;
  const auto mode_sort = cooked_index_entry::SORT;
  const auto mode_complete = cooked_index_entry::COMPLETE;

  SELF_CHECK (cooked_index_entry::compare ("abcd", "abcd",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("abcd", "abcd",
					   mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("abcd", "ABCDE",
					   mode_compare) < 0);
  SELF_CHECK (cooked_index_entry::compare ("ABCDE", "abcd",
					   mode_compare) > 0);
  SELF_CHECK (cooked_index_entry::compare ("abcd", "ABCDE",
					   mode_complete) < 0);
  SELF_CHECK (cooked_index_entry::compare ("ABCDE", "abcd",
					   mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("name", "name<>",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<>", "name",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name", "name<>",
					   mode_complete) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<>", "name",
					   mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("name<arg>", "name<arg>",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg>", "name<ag>",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg>", "name<arg>",
					   mode_complete) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg>", "name<ag>",
					   mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("name<arg<more>>",
					   "name<arg<more>>",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg>",
					   "name<arg<more>>",
					   mode_compare) == 0);

  SELF_CHECK (cooked_index_entry::compare ("name", "name<arg<more>>",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg<more>>", "name",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg<more>>", "name<arg<",
					   mode_compare) == 0);
  SELF_CHECK (cooked_index_entry::compare ("name<arg<more>>", "name<arg<",
					   mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("", "abcd", mode_compare) < 0);
  SELF_CHECK (cooked_index_entry::compare ("", "abcd", mode_complete) < 0);
  SELF_CHECK (cooked_index_entry::compare ("abcd", "", mode_compare) > 0);
  SELF_CHECK (cooked_index_entry::compare ("abcd", "", mode_complete) == 0);

  SELF_CHECK (cooked_index_entry::compare ("func", "func<type>",
					   mode_sort) == 0);
  SELF_CHECK (cooked_index_entry::compare ("func<type>", "func1",
					   mode_sort) < 0);
}

} /* anonymous namespace */

#endif /* GDB_SELF_TEST */

/* See cooked-index-entry.h.  */

bool
cooked_index_entry::matches (domain_search_flags kind) const
{
  /* Just reject type declarations.  */
  if ((flags & IS_TYPE_DECLARATION) != 0)
    return false;

  return tag_matches_domain (tag, kind, lang);
}

/* See cooked-index-entry.h.  */

const char *
cooked_index_entry::full_name (struct obstack *storage,
			       cooked_index_full_name_flag name_flags,
			       const char *default_sep) const
{
  const char *local_name = ((name_flags & FOR_MAIN) != 0) ? name : canonical;

  if ((flags & IS_LINKAGE) != 0 || get_parent () == nullptr)
    return local_name;

  const char *sep = default_sep;
  switch (lang)
    {
    case language_cplus:
    case language_rust:
    case language_fortran:
      sep = "::";
      break;

    case language_ada:
      /* If GNAT emits hierarchical names (patches not in at the time
	 of writing), then we need to compute the linkage name here.
	 However for traditional GNAT, the linkage name will be in
	 'name'.  Detect this by looking for "__"; see also
	 cooked_index_shard::finalize.  */
      if ((name_flags & FOR_ADA_LINKAGE_NAME) != 0)
	{
	  if (strstr (name, "__") != nullptr)
	    return name;
	  sep = "__";
	  break;
	}
      [[fallthrough]];
    case language_go:
    case language_d:
      sep = ".";
      break;

    default:
      if (sep == nullptr)
	return local_name;
      break;
    }

  /* The FOR_ADA_LINKAGE_NAME flag should only affect Ada entries, so
     disable it here if we don't need it.  */
  if (lang != language_ada)
    name_flags &= ~FOR_ADA_LINKAGE_NAME;

  get_parent ()->write_scope (storage, sep, name_flags);
  obstack_grow0 (storage, local_name, strlen (local_name));
  return (const char *) obstack_finish (storage);
}

/* See cooked-index-entry.h.  */

void
cooked_index_entry::write_scope (struct obstack *storage,
				 const char *sep,
				 cooked_index_full_name_flag flags) const
{
  if (get_parent () != nullptr)
    get_parent ()->write_scope (storage, sep, flags);
  /* When computing the Ada linkage name, the entry might not have
     been canonicalized yet, so use the 'name'.  */
  const char *local_name = ((flags & (FOR_MAIN | FOR_ADA_LINKAGE_NAME)) != 0
			    ? name
			    : canonical);
  obstack_grow (storage, local_name, strlen (local_name));
  obstack_grow (storage, sep, strlen (sep));
}

/* See cooked_index_entry.h.  */

void
cooked_index_entry::force_set_language () const
{
  gdb_assert (lang == language_unknown);
  lang = per_cu->lang ();
}

/* See cooked-index-entry.h.  */

iteration_status
cooked_index_entry::visit_defining_cus (per_cu_callback callback) const
{
  if ((flags & IS_INLINED) == 0)
    return callback (per_cu->canonical_outermost_cu ());

  return per_cu->recursively_visit_cus (callback);
}

/* See cooked-index-entry.h.  */

INIT_GDB_FILE (dwarf2_entry)
{
#if GDB_SELF_TEST
  selftests::register_test ("cooked_index_entry::compare", test_compare);
#endif
}
