text: #include "pch.h"
  
  #include <vcpkg/base/checks.h>
  #include <vcpkg/base/parse.h>
  #include <vcpkg/base/util.h>
  
  #include <vcpkg/packagespec.h>
  #include <vcpkg/paragraphparser.h>
  
  namespace vcpkg
  {
      std::string FeatureSpec::to_string() const
      {
          std::string ret;
          this->to_string(ret);
          return ret;
      }
      void FeatureSpec::to_string(std::string& out) const
      {
          if (feature().empty()) return spec().to_string(out);
          Strings::append(out, name(), '[', feature(), "]:", triplet());
      }
  
      std::vector<FeatureSpec> FullPackageSpec::to_feature_specs(const std::vector<std::string>& default_features,
                                                                 const std::vector<std::string>& all_features) const
      {
          std::vector<FeatureSpec> feature_specs;
  
          if (Util::find(features, "*") != features.end())
          {
              feature_specs.emplace_back(package_spec, "core");
              for (const std::string& feature : all_features)
              {
                  feature_specs.emplace_back(package_spec, feature);
              }
          }
          else
          {
              bool core = false;
              for (const std::string& feature : features)
              {
                  feature_specs.emplace_back(package_spec, feature);
  
                  if (!core)
                  {
                      core = feature == "core";
                  }
              }
  
              if (!core)
              {
                  feature_specs.emplace_back(package_spec, "core");
  
                  for (const std::string& def : default_features)
                  {
                      feature_specs.emplace_back(package_spec, def);
                  }
              }
          }
  
          return feature_specs;
      }
  
      ExpectedS<FullPackageSpec> FullPackageSpec::from_string(const std::string& spec_as_string, Triplet default_triplet)
      {
          return parse_qualified_specifier(spec_as_string)
              .then([&](ParsedQualifiedSpecifier&& p) -> ExpectedS<FullPackageSpec> {
                  if (p.platform)
                      return "Error: platform specifier not allowed in this context: " + spec_as_string + "\n";
                  auto triplet = p.triplet ? Triplet::from_canonical_name(std::move(*p.triplet.get())) : default_triplet;
                  return FullPackageSpec({p.name, triplet}, p.features.value_or({}));
              });
      }
  
      std::vector<PackageSpec> PackageSpec::to_package_specs(const std::vector<std::string>& ports, Triplet triplet)
      {
          return Util::fmap(ports, [&](const std::string& spec_as_string) -> PackageSpec {
              return {spec_as_string, triplet};
          });
      }
  
      const std::string& PackageSpec::name() const { return this->m_name; }
  
      Triplet PackageSpec::triplet() const { return this->m_triplet; }
  
      std::string PackageSpec::dir() const { return Strings::format("%s_%s", this->m_name, this->m_triplet); }
  
      std::string PackageSpec::to_string() const { return Strings::format("%s:%s", this->name(), this->triplet()); }
      void PackageSpec::to_string(std::string& s) const { Strings::append(s, this->name(), ':', this->triplet()); }
  
      bool operator==(const PackageSpec& left, const PackageSpec& right)
      {
          return left.name() == right.name() && left.triplet() == right.triplet();
      }
  
      bool operator!=(const PackageSpec& left, const PackageSpec& right) { return !(left == right); }
  
      ExpectedS<Features> Features::from_string(const std::string& name)
      {
          return parse_qualified_specifier(name).then([&](ParsedQualifiedSpecifier&& pqs) -> ExpectedS<Features> {
              if (pqs.triplet) return "Error: triplet not allowed in this context: " + name + "\n";
              if (pqs.platform) return "Error: platform specifier not allowed in this context: " + name + "\n";
              return Features{pqs.name, pqs.features.value_or({})};
          });
      }
  
      static bool is_package_name_char(char32_t ch)
      {
          return Parse::ParserBase::is_lower_alpha(ch) || Parse::ParserBase::is_ascii_digit(ch) || ch == '-';
      }
  
      static bool is_feature_name_char(char32_t ch)
      {
          // TODO: we do not intend underscores to be valid, however there is currently a feature using them
          // (libwebp[vwebp_sdl]).
          // TODO: we need to rename this feature, then remove underscores from this list.
          return is_package_name_char(ch) || ch == '_';
      }
  
      ExpectedS<ParsedQualifiedSpecifier> parse_qualified_specifier(StringView input)
      {
          auto parser = Parse::ParserBase(input, "<unknown>");
          auto maybe_pqs = parse_qualified_specifier(parser);
          if (!parser.at_eof()) parser.add_error("expected eof");
          if (auto e = parser.get_error()) return e->format();
          return std::move(maybe_pqs).value_or_exit(VCPKG_LINE_INFO);
      }
  
      Optional<std::string> parse_feature_name(Parse::ParserBase& parser)
      {
          using Parse::ParserBase;
          auto ret = parser.match_zero_or_more(is_feature_name_char).to_string();
          auto ch = parser.cur();
  
          // ignores the feature name vwebp_sdl as a back-compat thing
          const bool has_underscore = std::find(ret.begin(), ret.end(), '_') != ret.end() && ret != "vwebp_sdl";
          if (has_underscore || ParserBase::is_upper_alpha(ch))
          {
              parser.add_error("invalid character in feature name (must be lowercase, digits, '-')");
              return nullopt;
          }
  
          if (ret.empty())
          {
              parser.add_error("expected feature name (must be lowercase, digits, '-')");
              return nullopt;
          }
          return ret;
      }
      Optional<std::string> parse_package_name(Parse::ParserBase& parser)
      {
          using Parse::ParserBase;
          auto ret = parser.match_zero_or_more(is_package_name_char).to_string();
          auto ch = parser.cur();
          if (ParserBase::is_upper_alpha(ch) || ch == '_')
          {
              parser.add_error("invalid character in package name (must be lowercase, digits, '-')");
              return nullopt;
          }
          if (ret.empty())
          {
              parser.add_error("expected package name (must be lowercase, digits, '-')");
              return nullopt;
          }
          return ret;
      }
  
      Optional<ParsedQualifiedSpecifier> parse_qualified_specifier(Parse::ParserBase& parser)
      {
          using Parse::ParserBase;
          ParsedQualifiedSpecifier ret;
          auto name = parse_package_name(parser);
          if (auto n = name.get())
              ret.name = std::move(*n);
          else
              return nullopt;
          auto ch = parser.cur();
          if (ch == '[')
          {
              std::vector<std::string> features;
              do
              {
                  parser.next();
                  parser.skip_tabs_spaces();
                  if (parser.cur() == '*')
                  {
                      features.push_back("*");
                      parser.next();
                  }
                  else
                  {
                      auto feature = parse_feature_name(parser);
                      if (auto f = feature.get())
                          features.push_back(std::move(*f));
                      else
                          return nullopt;
                  }
                  auto skipped_space = parser.skip_tabs_spaces();
                  ch = parser.cur();
                  if (ch == ']')
                  {
                      ch = parser.next();
                      break;
                  }
                  else if (ch == ',')
                  {
                      continue;
                  }
                  else
                  {
                      if (skipped_space.size() > 0 || Parse::ParserBase::is_lineend(parser.cur()))
                          parser.add_error("expected ',' or ']' in feature list");
                      else
                          parser.add_error("invalid character in feature name (must be lowercase, digits, '-', or '*')");
                      return nullopt;
                  }
              } while (true);
              ret.features = std::move(features);
          }
          if (ch == ':')
          {
              parser.next();
              ret.triplet = parser.match_zero_or_more(is_package_name_char).to_string();
              if (ret.triplet.get()->empty())
              {
                  parser.add_error("expected triplet name (must be lowercase, digits, '-')");
                  return nullopt;
              }
          }
          parser.skip_tabs_spaces();
          ch = parser.cur();
          if (ch == '(')
          {
              auto loc = parser.cur_loc();
              std::string platform_string;
              int depth = 1;
              while (depth > 0 && (ch = parser.next()) != 0)
              {
                  if (ch == '(') ++depth;
                  if (ch == ')') --depth;
              }
              if (depth > 0)
              {
                  parser.add_error("unmatched open braces in platform specifier", loc);
                  return nullopt;
              }
              platform_string.append((++loc.it).pointer_to_current(), parser.it().pointer_to_current());
              auto platform_opt = PlatformExpression::parse_platform_expression(
                  platform_string, PlatformExpression::MultipleBinaryOperators::Allow);
              if (auto platform = platform_opt.get())
              {
                  ret.platform = std::move(*platform);
              }
              else
              {
                  parser.add_error(platform_opt.error(), loc);
              }
              parser.next();
          }
          // This makes the behavior of the parser more consistent -- otherwise, it will skip tabs and spaces only if
          // there isn't a qualifier.
          parser.skip_tabs_spaces();
          return ret;
      }
  }
  