1 //===--------------------- filesystem/path.cpp ----------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is dual licensed under the MIT and the University of Illinois Open
6 // Source Licenses. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 #include "experimental/filesystem"
11 #include "string_view"
15 namespace { namespace parser
18 using namespace std::experimental::filesystem;
20 using string_view_t = path::__string_view;
21 using string_view_pair = pair<string_view_t, string_view_t>;
22 using PosPtr = path::value_type const*;
25 enum ParserState : unsigned char {
26 // Zero is a special sentinel value used by default constructed iterators.
35 const string_view_t Path;
36 string_view_t RawEntry;
40 PathParser(string_view_t P, ParserState State) noexcept
41 : Path(P), State(State) {}
44 PathParser(string_view_t P, string_view_t E, unsigned char S)
45 : Path(P), RawEntry(E), State(static_cast<ParserState>(S)) {
46 // S cannot be '0' or PS_BeforeBegin.
49 static PathParser CreateBegin(string_view_t P) noexcept {
50 PathParser PP(P, PS_BeforeBegin);
55 static PathParser CreateEnd(string_view_t P) noexcept {
56 PathParser PP(P, PS_AtEnd);
60 PosPtr peek() const noexcept {
61 auto End = &Path.back() + 1;
62 auto TkEnd = getNextTokenStartPos();
63 return TkEnd == End ? nullptr : TkEnd;
66 void increment() noexcept {
67 const PosPtr End = &Path.back() + 1;
68 const PosPtr Start = getNextTokenStartPos();
70 return makeState(PS_AtEnd);
73 case PS_BeforeBegin: {
74 PosPtr TkEnd = consumeSeparator(Start, End);
75 // If we consumed exactly two separators we have a root name.
76 if (TkEnd && TkEnd == Start + 2) {
77 // FIXME Do we need to consume a name or is '//' a root name on its own?
78 // what about '//.', '//..', '//...'?
79 auto NameEnd = consumeName(TkEnd, End);
82 return makeState(PS_InRootName, Start, TkEnd);
85 return makeState(PS_InRootDir, Start, TkEnd);
87 return makeState(PS_InFilenames, Start, consumeName(Start, End));
91 return makeState(PS_InRootDir, Start, consumeSeparator(Start, End));
93 return makeState(PS_InFilenames, Start, consumeName(Start, End));
95 case PS_InFilenames: {
96 PosPtr SepEnd = consumeSeparator(Start, End);
98 PosPtr TkEnd = consumeName(SepEnd, End);
100 return makeState(PS_InFilenames, SepEnd, TkEnd);
102 return makeState(PS_InTrailingSep, Start, SepEnd);
105 case PS_InTrailingSep:
106 return makeState(PS_AtEnd);
109 _LIBCPP_UNREACHABLE();
113 void decrement() noexcept {
114 const PosPtr REnd = &Path.front() - 1;
115 const PosPtr RStart = getCurrentTokenStartPos() - 1;
116 assert(RStart != REnd);
120 // Try to consume a trailing separator or root directory first.
121 if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) {
123 return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir,
124 Path.data(), RStart + 1);
125 // Check if we're seeing the root directory separator
126 auto PP = CreateBegin(Path);
127 bool InRootDir = PP.State == PS_InRootName &&
128 &PP.RawEntry.back() == SepEnd;
129 return makeState(InRootDir ? PS_InRootDir : PS_InTrailingSep,
130 SepEnd + 1, RStart + 1);
132 PosPtr TkStart = consumeName(RStart, REnd);
133 if (TkStart == REnd + 2 && consumeSeparator(TkStart, REnd) == REnd)
134 return makeState(PS_InRootName, Path.data(), RStart + 1);
136 return makeState(PS_InFilenames, TkStart + 1, RStart + 1);
139 case PS_InTrailingSep:
140 return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1);
141 case PS_InFilenames: {
142 PosPtr SepEnd = consumeSeparator(RStart, REnd);
144 return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir,
145 Path.data(), RStart + 1);
146 PosPtr TkEnd = consumeName(SepEnd, REnd);
147 if (TkEnd == REnd + 2 && consumeSeparator(TkEnd, REnd) == REnd)
148 return makeState(PS_InRootDir, SepEnd + 1, RStart + 1);
149 return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1);
152 return makeState(PS_InRootName, Path.data(), RStart + 1);
155 _LIBCPP_UNREACHABLE();
159 /// \brief Return a view with the "preferred representation" of the current
160 /// element. For example trailing separators are represented as a '.'
161 string_view_t operator*() const noexcept {
168 case PS_InTrailingSep:
174 _LIBCPP_UNREACHABLE();
177 explicit operator bool() const noexcept {
178 return State != PS_BeforeBegin && State != PS_AtEnd;
181 PathParser& operator++() noexcept {
186 PathParser& operator--() noexcept {
192 void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept {
194 RawEntry = string_view_t(Start, End - Start);
196 void makeState(ParserState NewState) noexcept {
201 /// \brief Return a pointer to the first character after the currently
203 PosPtr getNextTokenStartPos() const noexcept {
206 return &Path.front();
210 return &RawEntry.back() + 1;
211 case PS_InTrailingSep:
213 return &Path.back() + 1;
215 _LIBCPP_UNREACHABLE();
218 /// \brief Return a pointer to the first character in the currently lexed
220 PosPtr getCurrentTokenStartPos() const noexcept {
224 return &Path.front();
227 case PS_InTrailingSep:
228 return &RawEntry.front();
230 return &Path.back() + 1;
232 _LIBCPP_UNREACHABLE();
235 PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept {
236 if (P == End || *P != '/')
238 const int Inc = P < End ? 1 : -1;
240 while (P != End && *P == '/')
245 PosPtr consumeName(PosPtr P, PosPtr End) const noexcept {
246 if (P == End || *P == '/')
248 const int Inc = P < End ? 1 : -1;
250 while (P != End && *P != '/')
256 string_view_pair separate_filename(string_view_t const & s) {
257 if (s == "." || s == ".." || s.empty()) return string_view_pair{s, ""};
258 auto pos = s.find_last_of('.');
259 if (pos == string_view_t::npos) return string_view_pair{s, string_view{}};
260 return string_view_pair{s.substr(0, pos), s.substr(pos)};
263 string_view_t createView(PosPtr S, PosPtr E) noexcept {
264 return {S, static_cast<size_t>(E - S) + 1};
267 }} // namespace parser
269 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
271 using parser::string_view_t;
272 using parser::string_view_pair;
273 using parser::PathParser;
274 using parser::createView;
276 ///////////////////////////////////////////////////////////////////////////////
278 ///////////////////////////////////////////////////////////////////////////////
280 constexpr path::value_type path::preferred_separator;
282 path & path::replace_extension(path const & replacement)
284 path p = extension();
286 __pn_.erase(__pn_.size() - p.native().size());
288 if (!replacement.empty()) {
289 if (replacement.native()[0] != '.') {
292 __pn_.append(replacement.__pn_);
297 ///////////////////////////////////////////////////////////////////////////////
300 string_view_t path::__root_name() const
302 auto PP = PathParser::CreateBegin(__pn_);
303 if (PP.State == PathParser::PS_InRootName)
308 string_view_t path::__root_directory() const
310 auto PP = PathParser::CreateBegin(__pn_);
311 if (PP.State == PathParser::PS_InRootName)
313 if (PP.State == PathParser::PS_InRootDir)
318 string_view_t path::__root_path_raw() const
320 auto PP = PathParser::CreateBegin(__pn_);
321 if (PP.State == PathParser::PS_InRootName) {
322 auto NextCh = PP.peek();
323 if (NextCh && *NextCh == '/') {
325 assert(PP.State == PathParser::PS_InRootDir);
326 return createView(__pn_.data(), &PP.RawEntry.back());
330 if (PP.State == PathParser::PS_InRootDir)
335 string_view_t path::__relative_path() const
337 auto PP = PathParser::CreateBegin(__pn_);
338 while (PP.State <= PathParser::PS_InRootDir)
340 if (PP.State == PathParser::PS_AtEnd)
342 return createView(PP.RawEntry.data(), &__pn_.back());
345 string_view_t path::__parent_path() const
349 auto PP = PathParser::CreateEnd(__pn_);
351 if (PP.RawEntry.data() == __pn_.data())
354 return createView(__pn_.data(), &PP.RawEntry.back());
357 string_view_t path::__filename() const
359 if (empty()) return {};
360 return *(--PathParser::CreateEnd(__pn_));
363 string_view_t path::__stem() const
365 return parser::separate_filename(__filename()).first;
368 string_view_t path::__extension() const
370 return parser::separate_filename(__filename()).second;
373 ////////////////////////////////////////////////////////////////////////////
375 int path::__compare(string_view_t __s) const {
376 auto PP = PathParser::CreateBegin(__pn_);
377 auto PP2 = PathParser::CreateBegin(__s);
379 int res = (*PP).compare(*PP2);
380 if (res != 0) return res;
383 if (PP.State == PP2.State && PP.State == PathParser::PS_AtEnd)
385 if (PP.State == PathParser::PS_AtEnd)
390 ////////////////////////////////////////////////////////////////////////////
392 size_t hash_value(const path& __p) noexcept {
393 auto PP = PathParser::CreateBegin(__p.native());
394 size_t hash_value = 0;
395 std::hash<string_view> hasher;
397 hash_value = __hash_combine(hash_value, hasher(*PP));
403 ////////////////////////////////////////////////////////////////////////////
405 path::iterator path::begin() const
407 auto PP = PathParser::CreateBegin(__pn_);
409 it.__path_ptr_ = this;
410 it.__state_ = PP.State;
411 it.__entry_ = PP.RawEntry;
412 it.__stashed_elem_.__assign_view(*PP);
416 path::iterator path::end() const
419 it.__state_ = PathParser::PS_AtEnd;
420 it.__path_ptr_ = this;
424 path::iterator& path::iterator::__increment() {
425 static_assert(__at_end == PathParser::PS_AtEnd, "");
426 PathParser PP(__path_ptr_->native(), __entry_, __state_);
429 __entry_ = PP.RawEntry;
430 __stashed_elem_.__assign_view(*PP);
434 path::iterator& path::iterator::__decrement() {
435 PathParser PP(__path_ptr_->native(), __entry_, __state_);
438 __entry_ = PP.RawEntry;
439 __stashed_elem_.__assign_view(*PP);
443 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM