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 _LIBCPP_DIAGNOSTIC_PUSH
28 _LIBCPP_CLANG_DIAGNOSTIC_IGNORED("-Wdeprecated")
29 constexpr path::value_type
path::preferred_separator
;
30 _LIBCPP_DIAGNOSTIC_POP
32 path
& path::replace_extension(path
const& replacement
) {
35 __pn_
.erase(__pn_
.size() - p
.native().size());
37 if (!replacement
.empty()) {
38 if (replacement
.native()[0] != '.') {
39 __pn_
+= PATHSTR(".");
41 __pn_
.append(replacement
.__pn_
);
46 ///////////////////////////////////////////////////////////////////////////////
49 string_view_t
path::__root_name() const {
50 auto PP
= PathParser::CreateBegin(__pn_
);
51 if (PP
.State_
== PathParser::PS_InRootName
)
56 string_view_t
path::__root_directory() const {
57 auto PP
= PathParser::CreateBegin(__pn_
);
58 if (PP
.State_
== PathParser::PS_InRootName
)
60 if (PP
.State_
== PathParser::PS_InRootDir
)
65 string_view_t
path::__root_path_raw() const {
66 auto PP
= PathParser::CreateBegin(__pn_
);
67 if (PP
.State_
== PathParser::PS_InRootName
) {
68 auto NextCh
= PP
.peek();
69 if (NextCh
&& isSeparator(*NextCh
)) {
71 return createView(__pn_
.data(), &PP
.RawEntry
.back());
75 if (PP
.State_
== PathParser::PS_InRootDir
)
80 static bool ConsumeRootName(PathParser
* PP
) {
81 static_assert(PathParser::PS_BeforeBegin
== 1 && PathParser::PS_InRootName
== 2, "Values for enums are incorrect");
82 while (PP
->State_
<= PathParser::PS_InRootName
)
84 return PP
->State_
== PathParser::PS_AtEnd
;
87 static bool ConsumeRootDir(PathParser
* PP
) {
88 static_assert(PathParser::PS_BeforeBegin
== 1 && PathParser::PS_InRootName
== 2 && PathParser::PS_InRootDir
== 3,
89 "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 { return parser::separate_filename(__filename()).first
; }
137 string_view_t
path::__extension() const { return parser::separate_filename(__filename()).second
; }
139 ////////////////////////////////////////////////////////////////////////////
142 enum PathPartKind
: unsigned char { PK_None
, PK_RootSep
, PK_Filename
, PK_Dot
, PK_DotDot
, PK_TrailingSep
};
144 static PathPartKind
ClassifyPathPart(string_view_t Part
) {
146 return PK_TrailingSep
;
147 if (Part
== PATHSTR("."))
149 if (Part
== PATHSTR(".."))
151 if (Part
== PATHSTR("/"))
153 #if defined(_LIBCPP_WIN32API)
154 if (Part
== PATHSTR("\\"))
160 path
path::lexically_normal() const {
164 using PartKindPair
= pair
<string_view_t
, PathPartKind
>;
165 vector
<PartKindPair
> Parts
;
166 // Guess as to how many elements the path has to avoid reallocating.
169 // Track the total size of the parts as we collect them. This allows the
170 // resulting path to reserve the correct amount of memory.
171 size_t NewPathSize
= 0;
172 auto AddPart
= [&](PathPartKind K
, string_view_t P
) {
173 NewPathSize
+= P
.size();
174 Parts
.emplace_back(P
, K
);
176 auto LastPartKind
= [&]() {
179 return Parts
.back().second
;
182 bool MaybeNeedTrailingSep
= false;
183 // Build a stack containing the remaining elements of the path, popping off
184 // elements which occur before a '..' entry.
185 for (auto PP
= PathParser::CreateBegin(__pn_
); PP
; ++PP
) {
187 PathPartKind Kind
= ClassifyPathPart(Part
);
191 // Add all non-dot and non-dot-dot elements to the stack of elements.
193 MaybeNeedTrailingSep
= false;
197 // Only push a ".." element if there are no elements preceding the "..",
198 // or if the preceding element is itself "..".
199 auto LastKind
= LastPartKind();
200 if (LastKind
== PK_Filename
) {
201 NewPathSize
-= Parts
.back().first
.size();
203 } else if (LastKind
!= PK_RootSep
)
204 AddPart(PK_DotDot
, PATHSTR(".."));
205 MaybeNeedTrailingSep
= LastKind
== PK_Filename
;
209 case PK_TrailingSep
: {
210 MaybeNeedTrailingSep
= true;
214 __libcpp_unreachable();
217 // [fs.path.generic]p6.8: If the path is empty, add a dot.
221 // [fs.path.generic]p6.7: If the last filename is dot-dot, remove any
222 // trailing directory-separator.
223 bool NeedTrailingSep
= MaybeNeedTrailingSep
&& LastPartKind() == PK_Filename
;
226 Result
.__pn_
.reserve(Parts
.size() + NewPathSize
+ NeedTrailingSep
);
227 for (auto& PK
: Parts
)
231 Result
/= PATHSTR("");
233 Result
.make_preferred();
237 static int DetermineLexicalElementCount(PathParser PP
) {
241 if (Elem
== PATHSTR(".."))
243 else if (Elem
!= PATHSTR(".") && Elem
!= PATHSTR(""))
249 path
path::lexically_relative(const path
& base
) const {
250 { // perform root-name/root-directory mismatch checks
251 auto PP
= PathParser::CreateBegin(__pn_
);
252 auto PPBase
= PathParser::CreateBegin(base
.__pn_
);
253 auto CheckIterMismatchAtBase
= [&]() {
254 return PP
.State_
!= PPBase
.State_
&& (PP
.inRootPath() || PPBase
.inRootPath());
256 if (PP
.inRootName() && PPBase
.inRootName()) {
259 } else if (CheckIterMismatchAtBase())
264 if (PPBase
.inRootPath())
266 if (CheckIterMismatchAtBase())
270 // Find the first mismatching element
271 auto PP
= PathParser::CreateBegin(__pn_
);
272 auto PPBase
= PathParser::CreateBegin(base
.__pn_
);
273 while (PP
&& PPBase
&& PP
.State_
== PPBase
.State_
&& (*PP
== *PPBase
|| PP
.inRootDir())) {
278 // If there is no mismatch, return ".".
282 // Otherwise, determine the number of elements, 'n', which are not dot or
283 // dot-dot minus the number of dot-dot elements.
284 int ElemCount
= DetermineLexicalElementCount(PPBase
);
288 // if n == 0 and (a == end() || a->empty()), returns path("."); otherwise
289 if (ElemCount
== 0 && (PP
.atEnd() || *PP
== PATHSTR("")))
292 // return a path constructed with 'n' dot-dot elements, followed by the
293 // elements of '*this' after the mismatch.
295 // FIXME: Reserve enough room in Result that it won't have to re-allocate.
297 Result
/= PATHSTR("..");
303 ////////////////////////////////////////////////////////////////////////////
305 static int CompareRootName(PathParser
* LHS
, PathParser
* RHS
) {
306 if (!LHS
->inRootName() && !RHS
->inRootName())
309 auto GetRootName
= [](PathParser
* Parser
) -> string_view_t
{ return Parser
->inRootName() ? **Parser
: PATHSTR(""); };
310 int res
= GetRootName(LHS
).compare(GetRootName(RHS
));
311 ConsumeRootName(LHS
);
312 ConsumeRootName(RHS
);
316 static int CompareRootDir(PathParser
* LHS
, PathParser
* RHS
) {
317 if (!LHS
->inRootDir() && RHS
->inRootDir())
319 else if (LHS
->inRootDir() && !RHS
->inRootDir())
328 static int CompareRelative(PathParser
* LHSPtr
, PathParser
* RHSPtr
) {
334 if ((res
= (*LHS
).compare(*RHS
)) != 0)
342 static int CompareEndState(PathParser
* LHS
, PathParser
* RHS
) {
343 if (LHS
->atEnd() && !RHS
->atEnd())
345 else if (!LHS
->atEnd() && RHS
->atEnd())
350 int path::__compare(string_view_t __s
) const {
351 auto LHS
= PathParser::CreateBegin(__pn_
);
352 auto RHS
= PathParser::CreateBegin(__s
);
355 if ((res
= CompareRootName(&LHS
, &RHS
)) != 0)
358 if ((res
= CompareRootDir(&LHS
, &RHS
)) != 0)
361 if ((res
= CompareRelative(&LHS
, &RHS
)) != 0)
364 return CompareEndState(&LHS
, &RHS
);
367 ////////////////////////////////////////////////////////////////////////////
369 size_t hash_value(const path
& __p
) noexcept
{
370 auto PP
= PathParser::CreateBegin(__p
.native());
371 size_t hash_value
= 0;
372 hash
<string_view_t
> hasher
;
374 string_view_t Part
= PP
.inRootDir() ? PATHSTR("/") : *PP
;
375 hash_value
= __hash_combine(hash_value
, hasher(Part
));
381 ////////////////////////////////////////////////////////////////////////////
383 path::iterator
path::begin() const {
384 auto PP
= PathParser::CreateBegin(__pn_
);
386 it
.__path_ptr_
= this;
387 it
.__state_
= static_cast<path::iterator::_ParserState
>(PP
.State_
);
388 it
.__entry_
= PP
.RawEntry
;
389 it
.__stashed_elem_
.__assign_view(*PP
);
393 path::iterator
path::end() const {
395 it
.__state_
= path::iterator::_AtEnd
;
396 it
.__path_ptr_
= this;
400 path::iterator
& path::iterator::__increment() {
401 PathParser
PP(__path_ptr_
->native(), __entry_
, __state_
);
403 __state_
= static_cast<_ParserState
>(PP
.State_
);
404 __entry_
= PP
.RawEntry
;
405 __stashed_elem_
.__assign_view(*PP
);
409 path::iterator
& path::iterator::__decrement() {
410 PathParser
PP(__path_ptr_
->native(), __entry_
, __state_
);
412 __state_
= static_cast<_ParserState
>(PP
.State_
);
413 __entry_
= PP
.RawEntry
;
414 __stashed_elem_
.__assign_view(*PP
);
418 #if defined(_LIBCPP_WIN32API)
419 ////////////////////////////////////////////////////////////////////////////
420 // Windows path conversions
421 size_t __wide_to_char(const wstring
& str
, char* out
, size_t outlen
) {
424 ErrorHandler
<size_t> err("__wide_to_char", nullptr);
425 UINT codepage
= AreFileApisANSI() ? CP_ACP
: CP_OEMCP
;
426 BOOL used_default
= FALSE
;
427 int ret
= WideCharToMultiByte(codepage
, 0, str
.data(), str
.size(), out
, outlen
, nullptr, &used_default
);
428 if (ret
<= 0 || used_default
)
429 return err
.report(errc::illegal_byte_sequence
);
433 size_t __char_to_wide(const string
& str
, wchar_t* out
, size_t outlen
) {
436 ErrorHandler
<size_t> err("__char_to_wide", nullptr);
437 UINT codepage
= AreFileApisANSI() ? CP_ACP
: CP_OEMCP
;
438 int ret
= MultiByteToWideChar(codepage
, MB_ERR_INVALID_CHARS
, str
.data(), str
.size(), out
, outlen
);
440 return err
.report(errc::illegal_byte_sequence
);
445 _LIBCPP_END_NAMESPACE_FILESYSTEM