//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <__config>
#include <filesystem>
#include <vector>
#include "error.h"
#include "path_parser.h"
_LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
using detail::ErrorHandler;
using parser::createView;
using parser::PathParser;
using parser::string_view_t;
///////////////////////////////////////////////////////////////////////////////
// path definitions
///////////////////////////////////////////////////////////////////////////////
constexpr path::value_type path::preferred_separator;
path& path::replace_extension(path const& replacement) {
path p = extension();
if (not p.empty()) {
__pn_.erase(__pn_.size() - p.native().size());
}
if (!replacement.empty()) {
if (replacement.native()[0] != '.') {
__pn_ += PATHSTR(".");
}
__pn_.append(replacement.__pn_);
}
return *this;
}
///////////////////////////////////////////////////////////////////////////////
// path.decompose
string_view_t path::__root_name() const {
auto PP = PathParser::CreateBegin(__pn_);
if (PP.State_ == PathParser::PS_InRootName)
return *PP;
return {};
}
string_view_t path::__root_directory() const {
auto PP = PathParser::CreateBegin(__pn_);
if (PP.State_ == PathParser::PS_InRootName)
++PP;
if (PP.State_ == PathParser::PS_InRootDir)
return *PP;
return {};
}
string_view_t path::__root_path_raw() const {
auto PP = PathParser::CreateBegin(__pn_);
if (PP.State_ == PathParser::PS_InRootName) {
auto NextCh = PP.peek();
if (NextCh && isSeparator(*NextCh)) {
++PP;
return createView(__pn_.data(), &PP.RawEntry.back());
}
return PP.RawEntry;
}
if (PP.State_ == PathParser::PS_InRootDir)
return *PP;
return {};
}
static bool ConsumeRootName(PathParser* PP) {
static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2, "Values for enums are incorrect");
while (PP->State_ <= PathParser::PS_InRootName)
++(*PP);
return PP->State_ == PathParser::PS_AtEnd;
}
static bool ConsumeRootDir(PathParser* PP) {
static_assert(PathParser::PS_BeforeBegin == 1 && PathParser::PS_InRootName == 2 && PathParser::PS_InRootDir == 3,
"Values for enums are incorrect");
while (PP->State_ <= PathParser::PS_InRootDir)
++(*PP);
return PP->State_ == PathParser::PS_AtEnd;
}
string_view_t path::__relative_path() const {
auto PP = PathParser::CreateBegin(__pn_);
if (ConsumeRootDir(&PP))
return {};
return createView(PP.RawEntry.data(), &__pn_.back());
}
string_view_t path::__parent_path() const {
if (empty())
return {};
// Determine if we have a root path but not a relative path. In that case
// return *this.
{
auto PP = PathParser::CreateBegin(__pn_);
if (ConsumeRootDir(&PP))
return __pn_;
}
// Otherwise remove a single element from the end of the path, and return
// a string representing that path
{
auto PP = PathParser::CreateEnd(__pn_);
--PP;
if (PP.RawEntry.data() == __pn_.data())
return {};
--PP;
return createView(__pn_.data(), &PP.RawEntry.back());
}
}
string_view_t path::__filename() const {
if (empty())
return {};
{
PathParser PP = PathParser::CreateBegin(__pn_);
if (ConsumeRootDir(&PP))
return {};
}
return *(--PathParser::CreateEnd(__pn_));
}
string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; }
string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; }
////////////////////////////////////////////////////////////////////////////
// path.gen
enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };
static PathPartKind ClassifyPathPart(string_view_t Part) {
if (Part.empty())
return PK_TrailingSep;
if (Part == PATHSTR("."))
return PK_Dot;
if (Part == PATHSTR(".."))
return PK_DotDot;
if (Part == PATHSTR("/"))
return PK_RootSep;
#if defined(_LIBCPP_WIN32API)
if (Part == PATHSTR("\\"))
return PK_RootSep;
#endif
return PK_Filename;
}
path path::lexically_normal() const {
if (__pn_.empty())
return *this;
using PartKindPair = pair<string_view_t, PathPartKind>;
vector<PartKindPair> Parts;
// Guess as to how many elements the path has to avoid reallocating.
Parts.reserve(32);
// Track the total size of the parts as we collect them. This allows the
// resulting path to reserve the correct amount of memory.
size_t NewPathSize = 0;
auto AddPart = [&](PathPartKind K, string_view_t P) {
NewPathSize += P.size();
Parts.emplace_back(P, K);
};
auto LastPartKind = [&]() {
if (Parts.empty())
return PK_None;
return Parts.back().second;
};
bool MaybeNeedTrailingSep = false;
// Build a stack containing the remaining elements of the path, popping off
// elements which occur before a '..' entry.
for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {
auto Part = *PP;
PathPartKind Kind = ClassifyPathPart(Part);
switch (Kind) {
case PK_Filename:
case PK_RootSep: {
// Add all non-dot and non-dot-dot elements to the stack of elements.
AddPart(Kind, Part);
MaybeNeedTrailingSep = false;
break;
}
case PK_DotDot: {
// Only push a ".." element if there are no elements preceding the "..",
// or if the preceding element is itself "..".
auto LastKind = LastPartKind();
if (LastKind == PK_Filename) {
NewPathSize -= Parts.back().first.size();
Parts.pop_back();
} else if (LastKind != PK_RootSep)
AddPart(PK_DotDot, PATHSTR(".."));
MaybeNeedTrailingSep = LastKind == PK_Filename;
break;
}
case PK_Dot:
case PK_TrailingSep: {
MaybeNeedTrailingSep = true;
break;
}
case PK_None:
__libcpp_unreachable();
}
}
// [fs.path.generic]p6.8: If the path is empty, add a dot.
if (Parts.empty())
return PATHSTR(".");
// [fs.path.generic]p6.7: If the last filename is dot-dot, remove any
// trailing directory-separator.
bool NeedTrailingSep = MaybeNeedTrailingSep && LastPartKind() == PK_Filename;
path Result;
Result.__pn_.reserve(Parts.size() + NewPathSize + NeedTrailingSep);
for (auto& PK : Parts)
Result /= PK.first;
if (NeedTrailingSep)
Result /= PATHSTR("");
Result.make_preferred();
return Result;
}
static int DetermineLexicalElementCount(PathParser PP) {
int Count = 0;
for (; PP; ++PP) {
auto Elem = *PP;
if (Elem == PATHSTR(".."))
--Count;
else if (Elem != PATHSTR(".") && Elem != PATHSTR(""))
++Count;
}
return Count;
}
path path::lexically_relative(const path& base) const {
{ // perform root-name/root-directory mismatch checks
auto PP = PathParser::CreateBegin(__pn_);
auto PPBase = PathParser::CreateBegin(base.__pn_);
auto CheckIterMismatchAtBase = [&]() {
return PP.State_ != PPBase.State_ && (PP.inRootPath() || PPBase.inRootPath());
};
if (PP.inRootName() && PPBase.inRootName()) {
if (*PP != *PPBase)
return {};
} else if (CheckIterMismatchAtBase())
return {};
if (PP.inRootPath())
++PP;
if (PPBase.inRootPath())
++PPBase;
if (CheckIterMismatchAtBase())
return {};
}
// Find the first mismatching element
auto PP = PathParser::CreateBegin(__pn_);
auto PPBase = PathParser::CreateBegin(base.__pn_);
while (PP && PPBase && PP.State_ == PPBase.State_ && (*PP == *PPBase || PP.inRootDir())) {
++PP;
++PPBase;
}
// If there is no mismatch, return ".".
if (!PP && !PPBase)
return ".";
// Otherwise, determine the number of elements, 'n', which are not dot or
// dot-dot minus the number of dot-dot elements.
int ElemCount = DetermineLexicalElementCount(PPBase);
if (ElemCount < 0)
return {};
// if n == 0 and (a == end() || a->empty()), returns path("."); otherwise
if (ElemCount == 0 && (PP.atEnd() || *PP == PATHSTR("")))
return PATHSTR(".");
// return a path constructed with 'n' dot-dot elements, followed by the
// elements of '*this' after the mismatch.
path Result;
// FIXME: Reserve enough room in Result that it won't have to re-allocate.
while (ElemCount--)
Result /= PATHSTR("..");
for (; PP; ++PP)
Result /= *PP;
return Result;
}
////////////////////////////////////////////////////////////////////////////
// path.comparisons
static int CompareRootName(PathParser* LHS, PathParser* RHS) {
if (!LHS->inRootName() && !RHS->inRootName())
return 0;
auto GetRootName = [](PathParser* Parser) -> string_view_t { return Parser->inRootName() ? **Parser : PATHSTR(""); };
int res = GetRootName(LHS).compare(GetRootName(RHS));
ConsumeRootName(LHS);
ConsumeRootName(RHS);
return res;
}
static int CompareRootDir(PathParser* LHS, PathParser* RHS) {
if (!LHS->inRootDir() && RHS->inRootDir())
return -1;
else if (LHS->inRootDir() && !RHS->inRootDir())
return 1;
else {
ConsumeRootDir(LHS);
ConsumeRootDir(RHS);
return 0;
}
}
static int CompareRelative(PathParser* LHSPtr, PathParser* RHSPtr) {
auto& LHS = *LHSPtr;
auto& RHS = *RHSPtr;
int res;
while (LHS && RHS) {
if ((res = (*LHS).compare(*RHS)) != 0)
return res;
++LHS;
++RHS;
}
return 0;
}
static int CompareEndState(PathParser* LHS, PathParser* RHS) {
if (LHS->atEnd() && !RHS->atEnd())
return -1;
else if (!LHS->atEnd() && RHS->atEnd())
return 1;
return 0;
}
int path::__compare(string_view_t __s) const {
auto LHS = PathParser::CreateBegin(__pn_);
auto RHS = PathParser::CreateBegin(__s);
int res;
if ((res = CompareRootName(&LHS, &RHS)) != 0)
return res;
if ((res = CompareRootDir(&LHS, &RHS)) != 0)
return res;
if ((res = CompareRelative(&LHS, &RHS)) != 0)
return res;
return CompareEndState(&LHS, &RHS);
}
////////////////////////////////////////////////////////////////////////////
// path.nonmembers
size_t hash_value(const path& __p) noexcept {
auto PP = PathParser::CreateBegin(__p.native());
size_t hash_value = 0;
hash<string_view_t> hasher;
while (PP) {
string_view_t Part = PP.inRootDir() ? PATHSTR("/") : *PP;
hash_value = __hash_combine(hash_value, hasher(Part));
++PP;
}
return hash_value;
}
////////////////////////////////////////////////////////////////////////////
// path.itr
path::iterator path::begin() const {
auto PP = PathParser::CreateBegin(__pn_);
iterator it;
it.__path_ptr_ = this;
it.__state_ = static_cast<path::iterator::_ParserState>(PP.State_);
it.__entry_ = PP.RawEntry;
it.__stashed_elem_.__assign_view(*PP);
return it;
}
path::iterator path::end() const {
iterator it{};
it.__state_ = path::iterator::_AtEnd;
it.__path_ptr_ = this;
return it;
}
path::iterator& path::iterator::__increment() {
PathParser PP(__path_ptr_->native(), __entry_, __state_);
++PP;
__state_ = static_cast<_ParserState>(PP.State_);
__entry_ = PP.RawEntry;
__stashed_elem_.__assign_view(*PP);
return *this;
}
path::iterator& path::iterator::__decrement() {
PathParser PP(__path_ptr_->native(), __entry_, __state_);
--PP;
__state_ = static_cast<_ParserState>(PP.State_);
__entry_ = PP.RawEntry;
__stashed_elem_.__assign_view(*PP);
return *this;
}
#if defined(_LIBCPP_WIN32API)
////////////////////////////////////////////////////////////////////////////
// Windows path conversions
size_t __wide_to_char(const wstring& str, char* out, size_t outlen) {
if (str.empty())
return 0;
ErrorHandler<size_t> err("__wide_to_char", nullptr);
UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
BOOL used_default = FALSE;
int ret = WideCharToMultiByte(codepage, 0, str.data(), str.size(), out, outlen, nullptr, &used_default);
if (ret <= 0 || used_default)
return err.report(errc::illegal_byte_sequence);
return ret;
}
size_t __char_to_wide(const string& str, wchar_t* out, size_t outlen) {
if (str.empty())
return 0;
ErrorHandler<size_t> err("__char_to_wide", nullptr);
UINT codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP;
int ret = MultiByteToWideChar(codepage, MB_ERR_INVALID_CHARS, str.data(), str.size(), out, outlen);
if (ret <= 0)
return err.report(errc::illegal_byte_sequence);
return ret;
}
#endif
_LIBCPP_END_NAMESPACE_FILESYSTEM