1 //===----------------------------------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
14 #include "path_parser.h"
16 _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
18 using detail::ErrorHandler
;
19 using parser::createView
;
20 using parser::PathParser
;
21 using parser::string_view_t
;
23 ///////////////////////////////////////////////////////////////////////////////
25 ///////////////////////////////////////////////////////////////////////////////
27 constexpr path::value_type
path::preferred_separator
;
29 path
& path::replace_extension(path
const& replacement
) {
32 __pn_
.erase(__pn_
.size() - p
.native().size());
34 if (!replacement
.empty()) {
35 if (replacement
.native()[0] != '.') {
36 __pn_
+= PATHSTR(".");
38 __pn_
.append(replacement
.__pn_
);
43 ///////////////////////////////////////////////////////////////////////////////
46 string_view_t
path::__root_name() const {
47 auto PP
= PathParser::CreateBegin(__pn_
);
48 if (PP
.State
== PathParser::PS_InRootName
)
53 string_view_t
path::__root_directory() const {
54 auto PP
= PathParser::CreateBegin(__pn_
);
55 if (PP
.State
== PathParser::PS_InRootName
)
57 if (PP
.State
== PathParser::PS_InRootDir
)
62 string_view_t
path::__root_path_raw() const {
63 auto PP
= PathParser::CreateBegin(__pn_
);
64 if (PP
.State
== PathParser::PS_InRootName
) {
65 auto NextCh
= PP
.peek();
66 if (NextCh
&& isSeparator(*NextCh
)) {
68 return createView(__pn_
.data(), &PP
.RawEntry
.back());
72 if (PP
.State
== PathParser::PS_InRootDir
)
77 static bool ConsumeRootName(PathParser
*PP
) {
78 static_assert(PathParser::PS_BeforeBegin
== 1 &&
79 PathParser::PS_InRootName
== 2,
80 "Values for enums are incorrect");
81 while (PP
->State
<= PathParser::PS_InRootName
)
83 return PP
->State
== PathParser::PS_AtEnd
;
86 static bool ConsumeRootDir(PathParser
* PP
) {
87 static_assert(PathParser::PS_BeforeBegin
== 1 &&
88 PathParser::PS_InRootName
== 2 &&
89 PathParser::PS_InRootDir
== 3, "Values for enums are incorrect");
90 while (PP
->State
<= PathParser::PS_InRootDir
)
92 return PP
->State
== PathParser::PS_AtEnd
;
95 string_view_t
path::__relative_path() const {
96 auto PP
= PathParser::CreateBegin(__pn_
);
97 if (ConsumeRootDir(&PP
))
99 return createView(PP
.RawEntry
.data(), &__pn_
.back());
102 string_view_t
path::__parent_path() const {
105 // Determine if we have a root path but not a relative path. In that case
108 auto PP
= PathParser::CreateBegin(__pn_
);
109 if (ConsumeRootDir(&PP
))
112 // Otherwise remove a single element from the end of the path, and return
113 // a string representing that path
115 auto PP
= PathParser::CreateEnd(__pn_
);
117 if (PP
.RawEntry
.data() == __pn_
.data())
120 return createView(__pn_
.data(), &PP
.RawEntry
.back());
124 string_view_t
path::__filename() const {
128 PathParser PP
= PathParser::CreateBegin(__pn_
);
129 if (ConsumeRootDir(&PP
))
132 return *(--PathParser::CreateEnd(__pn_
));
135 string_view_t
path::__stem() const {
136 return parser::separate_filename(__filename()).first
;
139 string_view_t
path::__extension() const {
140 return parser::separate_filename(__filename()).second
;
143 ////////////////////////////////////////////////////////////////////////////
146 enum PathPartKind
: unsigned char {
155 static PathPartKind
ClassifyPathPart(string_view_t Part
) {
157 return PK_TrailingSep
;
158 if (Part
== PATHSTR("."))
160 if (Part
== PATHSTR(".."))
162 if (Part
== PATHSTR("/"))
164 #if defined(_LIBCPP_WIN32API)
165 if (Part
== PATHSTR("\\"))
171 path
path::lexically_normal() const {
175 using PartKindPair
= pair
<string_view_t
, PathPartKind
>;
176 vector
<PartKindPair
> Parts
;
177 // Guess as to how many elements the path has to avoid reallocating.
180 // Track the total size of the parts as we collect them. This allows the
181 // resulting path to reserve the correct amount of memory.
182 size_t NewPathSize
= 0;
183 auto AddPart
= [&](PathPartKind K
, string_view_t P
) {
184 NewPathSize
+= P
.size();
185 Parts
.emplace_back(P
, K
);
187 auto LastPartKind
= [&]() {
190 return Parts
.back().second
;
193 bool MaybeNeedTrailingSep
= false;
194 // Build a stack containing the remaining elements of the path, popping off
195 // elements which occur before a '..' entry.
196 for (auto PP
= PathParser::CreateBegin(__pn_
); PP
; ++PP
) {
198 PathPartKind Kind
= ClassifyPathPart(Part
);
202 // Add all non-dot and non-dot-dot elements to the stack of elements.
204 MaybeNeedTrailingSep
= false;
208 // Only push a ".." element if there are no elements preceding the "..",
209 // or if the preceding element is itself "..".
210 auto LastKind
= LastPartKind();
211 if (LastKind
== PK_Filename
) {
212 NewPathSize
-= Parts
.back().first
.size();
214 } else if (LastKind
!= PK_RootSep
)
215 AddPart(PK_DotDot
, PATHSTR(".."));
216 MaybeNeedTrailingSep
= LastKind
== PK_Filename
;
220 case PK_TrailingSep
: {
221 MaybeNeedTrailingSep
= true;
225 __libcpp_unreachable();
228 // [fs.path.generic]p6.8: If the path is empty, add a dot.
232 // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any
233 // trailing directory-separator.
234 bool NeedTrailingSep
= MaybeNeedTrailingSep
&& LastPartKind() == PK_Filename
;
237 Result
.__pn_
.reserve(Parts
.size() + NewPathSize
+ NeedTrailingSep
);
238 for (auto& PK
: Parts
)
242 Result
/= PATHSTR("");
244 Result
.make_preferred();
248 static int DetermineLexicalElementCount(PathParser PP
) {
252 if (Elem
== PATHSTR(".."))
254 else if (Elem
!= PATHSTR(".") && Elem
!= PATHSTR(""))
260 path
path::lexically_relative(const path
& base
) const {
261 { // perform root-name/root-directory mismatch checks
262 auto PP
= PathParser::CreateBegin(__pn_
);
263 auto PPBase
= PathParser::CreateBegin(base
.__pn_
);
264 auto CheckIterMismatchAtBase
= [&]() {
265 return PP
.State
!= PPBase
.State
&&
266 (PP
.inRootPath() || PPBase
.inRootPath());
268 if (PP
.inRootName() && PPBase
.inRootName()) {
271 } else if (CheckIterMismatchAtBase())
276 if (PPBase
.inRootPath())
278 if (CheckIterMismatchAtBase())
282 // Find the first mismatching element
283 auto PP
= PathParser::CreateBegin(__pn_
);
284 auto PPBase
= PathParser::CreateBegin(base
.__pn_
);
285 while (PP
&& PPBase
&& PP
.State
== PPBase
.State
&& *PP
== *PPBase
) {
290 // If there is no mismatch, return ".".
294 // Otherwise, determine the number of elements, 'n', which are not dot or
295 // dot-dot minus the number of dot-dot elements.
296 int ElemCount
= DetermineLexicalElementCount(PPBase
);
300 // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise
301 if (ElemCount
== 0 && (PP
.atEnd() || *PP
== PATHSTR("")))
304 // return a path constructed with 'n' dot-dot elements, followed by the
305 // elements of '*this' after the mismatch.
307 // FIXME: Reserve enough room in Result that it won't have to re-allocate.
309 Result
/= PATHSTR("..");
315 ////////////////////////////////////////////////////////////////////////////
317 static int CompareRootName(PathParser
*LHS
, PathParser
*RHS
) {
318 if (!LHS
->inRootName() && !RHS
->inRootName())
321 auto GetRootName
= [](PathParser
*Parser
) -> string_view_t
{
322 return Parser
->inRootName() ? **Parser
: PATHSTR("");
324 int res
= GetRootName(LHS
).compare(GetRootName(RHS
));
325 ConsumeRootName(LHS
);
326 ConsumeRootName(RHS
);
330 static int CompareRootDir(PathParser
*LHS
, PathParser
*RHS
) {
331 if (!LHS
->inRootDir() && RHS
->inRootDir())
333 else if (LHS
->inRootDir() && !RHS
->inRootDir())
342 static int CompareRelative(PathParser
*LHSPtr
, PathParser
*RHSPtr
) {
348 if ((res
= (*LHS
).compare(*RHS
)) != 0)
356 static int CompareEndState(PathParser
*LHS
, PathParser
*RHS
) {
357 if (LHS
->atEnd() && !RHS
->atEnd())
359 else if (!LHS
->atEnd() && RHS
->atEnd())
364 int path::__compare(string_view_t __s
) const {
365 auto LHS
= PathParser::CreateBegin(__pn_
);
366 auto RHS
= PathParser::CreateBegin(__s
);
369 if ((res
= CompareRootName(&LHS
, &RHS
)) != 0)
372 if ((res
= CompareRootDir(&LHS
, &RHS
)) != 0)
375 if ((res
= CompareRelative(&LHS
, &RHS
)) != 0)
378 return CompareEndState(&LHS
, &RHS
);
381 ////////////////////////////////////////////////////////////////////////////
383 size_t hash_value(const path
& __p
) noexcept
{
384 auto PP
= PathParser::CreateBegin(__p
.native());
385 size_t hash_value
= 0;
386 hash
<string_view_t
> hasher
;
388 hash_value
= __hash_combine(hash_value
, hasher(*PP
));
394 ////////////////////////////////////////////////////////////////////////////
396 path::iterator
path::begin() const {
397 auto PP
= PathParser::CreateBegin(__pn_
);
399 it
.__path_ptr_
= this;
400 it
.__state_
= static_cast<path::iterator::_ParserState
>(PP
.State
);
401 it
.__entry_
= PP
.RawEntry
;
402 it
.__stashed_elem_
.__assign_view(*PP
);
406 path::iterator
path::end() const {
408 it
.__state_
= path::iterator::_AtEnd
;
409 it
.__path_ptr_
= this;
413 path::iterator
& path::iterator::__increment() {
414 PathParser
PP(__path_ptr_
->native(), __entry_
, __state_
);
416 __state_
= static_cast<_ParserState
>(PP
.State
);
417 __entry_
= PP
.RawEntry
;
418 __stashed_elem_
.__assign_view(*PP
);
422 path::iterator
& path::iterator::__decrement() {
423 PathParser
PP(__path_ptr_
->native(), __entry_
, __state_
);
425 __state_
= static_cast<_ParserState
>(PP
.State
);
426 __entry_
= PP
.RawEntry
;
427 __stashed_elem_
.__assign_view(*PP
);
431 #if defined(_LIBCPP_WIN32API)
432 ////////////////////////////////////////////////////////////////////////////
433 // Windows path conversions
434 size_t __wide_to_char(const wstring
&str
, char *out
, size_t outlen
) {
437 ErrorHandler
<size_t> err("__wide_to_char", nullptr);
438 UINT codepage
= AreFileApisANSI() ? CP_ACP
: CP_OEMCP
;
439 BOOL used_default
= FALSE
;
440 int ret
= WideCharToMultiByte(codepage
, 0, str
.data(), str
.size(), out
,
441 outlen
, nullptr, &used_default
);
442 if (ret
<= 0 || used_default
)
443 return err
.report(errc::illegal_byte_sequence
);
447 size_t __char_to_wide(const string
&str
, wchar_t *out
, size_t outlen
) {
450 ErrorHandler
<size_t> err("__char_to_wide", nullptr);
451 UINT codepage
= AreFileApisANSI() ? CP_ACP
: CP_OEMCP
;
452 int ret
= MultiByteToWideChar(codepage
, MB_ERR_INVALID_CHARS
, str
.data(),
453 str
.size(), out
, outlen
);
455 return err
.report(errc::illegal_byte_sequence
);
460 _LIBCPP_END_NAMESPACE_FILESYSTEM