1 //===--------------------- filesystem/path.cpp ----------------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is dual licensed under the MIT and the University of Illinois Open
6 // Source Licenses. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 #include "experimental/filesystem"
10 #include "string_view"
11 #include "utility"
12
13 namespace { namespace parser
14 {
15 using namespace std;
16 using namespace std::experimental::filesystem;
17
18 using string_view_t = path::__string_view;
19 using string_view_pair = pair<string_view_t, string_view_t>;
20 using PosPtr = path::value_type const*;
21
22 struct PathParser {
23 enum ParserState : unsigned char {
24 // Zero is a special sentinel value used by default constructed iterators.
25 PS_BeforeBegin = 1,
26 PS_InRootName,
27 PS_InRootDir,
28 PS_InFilenames,
29 PS_InTrailingSep,
30 PS_AtEnd
31 };
32
33 const string_view_t Path;
34 string_view_t RawEntry;
35 ParserState State;
36
37 private:
PathParser__anon40b514ad0111::parser::PathParser38 PathParser(string_view_t P, ParserState State) noexcept
39 : Path(P), State(State) {}
40
41 public:
PathParser__anon40b514ad0111::parser::PathParser42 PathParser(string_view_t P, string_view_t E, unsigned char S)
43 : Path(P), RawEntry(E), State(static_cast<ParserState>(S)) {
44 // S cannot be '0' or PS_BeforeBegin.
45 }
46
CreateBegin__anon40b514ad0111::parser::PathParser47 static PathParser CreateBegin(string_view_t P) noexcept {
48 PathParser PP(P, PS_BeforeBegin);
49 PP.increment();
50 return PP;
51 }
52
CreateEnd__anon40b514ad0111::parser::PathParser53 static PathParser CreateEnd(string_view_t P) noexcept {
54 PathParser PP(P, PS_AtEnd);
55 return PP;
56 }
57
peek__anon40b514ad0111::parser::PathParser58 PosPtr peek() const noexcept {
59 auto TkEnd = getNextTokenStartPos();
60 auto End = getAfterBack();
61 return TkEnd == End ? nullptr : TkEnd;
62 }
63
increment__anon40b514ad0111::parser::PathParser64 void increment() noexcept {
65 const PosPtr End = getAfterBack();
66 const PosPtr Start = getNextTokenStartPos();
67 if (Start == End)
68 return makeState(PS_AtEnd);
69
70 switch (State) {
71 case PS_BeforeBegin: {
72 PosPtr TkEnd = consumeSeparator(Start, End);
73 // If we consumed exactly two separators we have a root name.
74 if (TkEnd && TkEnd == Start + 2) {
75 // FIXME Do we need to consume a name or is '//' a root name on its own?
76 // what about '//.', '//..', '//...'?
77 auto NameEnd = consumeName(TkEnd, End);
78 if (NameEnd)
79 TkEnd = NameEnd;
80 return makeState(PS_InRootName, Start, TkEnd);
81 }
82 else if (TkEnd)
83 return makeState(PS_InRootDir, Start, TkEnd);
84 else
85 return makeState(PS_InFilenames, Start, consumeName(Start, End));
86 }
87
88 case PS_InRootName:
89 return makeState(PS_InRootDir, Start, consumeSeparator(Start, End));
90 case PS_InRootDir:
91 return makeState(PS_InFilenames, Start, consumeName(Start, End));
92
93 case PS_InFilenames: {
94 PosPtr SepEnd = consumeSeparator(Start, End);
95 if (SepEnd != End) {
96 PosPtr TkEnd = consumeName(SepEnd, End);
97 if (TkEnd)
98 return makeState(PS_InFilenames, SepEnd, TkEnd);
99 }
100 return makeState(PS_InTrailingSep, Start, SepEnd);
101 }
102
103 case PS_InTrailingSep:
104 return makeState(PS_AtEnd);
105
106 case PS_AtEnd:
107 _LIBCPP_UNREACHABLE();
108 }
109 }
110
decrement__anon40b514ad0111::parser::PathParser111 void decrement() noexcept {
112 const PosPtr REnd = getBeforeFront();
113 const PosPtr RStart = getCurrentTokenStartPos() - 1;
114
115 switch (State) {
116 case PS_AtEnd: {
117 // Try to consume a trailing separator or root directory first.
118 if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) {
119 if (SepEnd == REnd)
120 return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir,
121 Path.data(), RStart + 1);
122 // Check if we're seeing the root directory separator
123 auto PP = CreateBegin(Path);
124 bool InRootDir = PP.State == PS_InRootName &&
125 &PP.RawEntry.back() == SepEnd;
126 return makeState(InRootDir ? PS_InRootDir : PS_InTrailingSep,
127 SepEnd + 1, RStart + 1);
128 } else {
129 PosPtr TkStart = consumeName(RStart, REnd);
130 if (TkStart == REnd + 2 && consumeSeparator(TkStart, REnd) == REnd)
131 return makeState(PS_InRootName, Path.data(), RStart + 1);
132 else
133 return makeState(PS_InFilenames, TkStart + 1, RStart + 1);
134 }
135 }
136 case PS_InTrailingSep:
137 return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1);
138 case PS_InFilenames: {
139 PosPtr SepEnd = consumeSeparator(RStart, REnd);
140 if (SepEnd == REnd)
141 return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir,
142 Path.data(), RStart + 1);
143 PosPtr TkEnd = consumeName(SepEnd, REnd);
144 if (TkEnd == REnd + 2 && consumeSeparator(TkEnd, REnd) == REnd)
145 return makeState(PS_InRootDir, SepEnd + 1, RStart + 1);
146 return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1);
147 }
148 case PS_InRootDir:
149 return makeState(PS_InRootName, Path.data(), RStart + 1);
150 case PS_InRootName:
151 case PS_BeforeBegin:
152 _LIBCPP_UNREACHABLE();
153 }
154 }
155
156 /// \brief Return a view with the "preferred representation" of the current
157 /// element. For example trailing separators are represented as a '.'
operator *__anon40b514ad0111::parser::PathParser158 string_view_t operator*() const noexcept {
159 switch (State) {
160 case PS_BeforeBegin:
161 case PS_AtEnd:
162 return "";
163 case PS_InRootDir:
164 return "/";
165 case PS_InTrailingSep:
166 return ".";
167 case PS_InRootName:
168 case PS_InFilenames:
169 return RawEntry;
170 }
171 _LIBCPP_UNREACHABLE();
172 }
173
operator bool__anon40b514ad0111::parser::PathParser174 explicit operator bool() const noexcept {
175 return State != PS_BeforeBegin && State != PS_AtEnd;
176 }
177
operator ++__anon40b514ad0111::parser::PathParser178 PathParser& operator++() noexcept {
179 increment();
180 return *this;
181 }
182
operator --__anon40b514ad0111::parser::PathParser183 PathParser& operator--() noexcept {
184 decrement();
185 return *this;
186 }
187
188 private:
makeState__anon40b514ad0111::parser::PathParser189 void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept {
190 State = NewState;
191 RawEntry = string_view_t(Start, End - Start);
192 }
makeState__anon40b514ad0111::parser::PathParser193 void makeState(ParserState NewState) noexcept {
194 State = NewState;
195 RawEntry = {};
196 }
197
getAfterBack__anon40b514ad0111::parser::PathParser198 PosPtr getAfterBack() const noexcept {
199 return Path.data() + Path.size();
200 }
201
getBeforeFront__anon40b514ad0111::parser::PathParser202 PosPtr getBeforeFront() const noexcept {
203 return Path.data() - 1;
204 }
205
206 /// \brief Return a pointer to the first character after the currently
207 /// lexed element.
getNextTokenStartPos__anon40b514ad0111::parser::PathParser208 PosPtr getNextTokenStartPos() const noexcept {
209 switch (State) {
210 case PS_BeforeBegin:
211 return Path.data();
212 case PS_InRootName:
213 case PS_InRootDir:
214 case PS_InFilenames:
215 return &RawEntry.back() + 1;
216 case PS_InTrailingSep:
217 case PS_AtEnd:
218 return getAfterBack();
219 }
220 _LIBCPP_UNREACHABLE();
221 }
222
223 /// \brief Return a pointer to the first character in the currently lexed
224 /// element.
getCurrentTokenStartPos__anon40b514ad0111::parser::PathParser225 PosPtr getCurrentTokenStartPos() const noexcept {
226 switch (State) {
227 case PS_BeforeBegin:
228 case PS_InRootName:
229 return &Path.front();
230 case PS_InRootDir:
231 case PS_InFilenames:
232 case PS_InTrailingSep:
233 return &RawEntry.front();
234 case PS_AtEnd:
235 return &Path.back() + 1;
236 }
237 _LIBCPP_UNREACHABLE();
238 }
239
consumeSeparator__anon40b514ad0111::parser::PathParser240 PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept {
241 if (P == End || *P != '/')
242 return nullptr;
243 const int Inc = P < End ? 1 : -1;
244 P += Inc;
245 while (P != End && *P == '/')
246 P += Inc;
247 return P;
248 }
249
consumeName__anon40b514ad0111::parser::PathParser250 PosPtr consumeName(PosPtr P, PosPtr End) const noexcept {
251 if (P == End || *P == '/')
252 return nullptr;
253 const int Inc = P < End ? 1 : -1;
254 P += Inc;
255 while (P != End && *P != '/')
256 P += Inc;
257 return P;
258 }
259 };
260
separate_filename(string_view_t const & s)261 string_view_pair separate_filename(string_view_t const & s) {
262 if (s == "." || s == ".." || s.empty()) return string_view_pair{s, ""};
263 auto pos = s.find_last_of('.');
264 if (pos == string_view_t::npos)
265 return string_view_pair{s, string_view_t{}};
266 return string_view_pair{s.substr(0, pos), s.substr(pos)};
267 }
268
createView(PosPtr S,PosPtr E)269 string_view_t createView(PosPtr S, PosPtr E) noexcept {
270 return {S, static_cast<size_t>(E - S) + 1};
271 }
272
273 }} // namespace parser
274
275 _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM
276
277 using parser::string_view_t;
278 using parser::string_view_pair;
279 using parser::PathParser;
280 using parser::createView;
281
282 ///////////////////////////////////////////////////////////////////////////////
283 // path definitions
284 ///////////////////////////////////////////////////////////////////////////////
285
286 constexpr path::value_type path::preferred_separator;
287
replace_extension(path const & replacement)288 path & path::replace_extension(path const & replacement)
289 {
290 path p = extension();
291 if (not p.empty()) {
292 __pn_.erase(__pn_.size() - p.native().size());
293 }
294 if (!replacement.empty()) {
295 if (replacement.native()[0] != '.') {
296 __pn_ += ".";
297 }
298 __pn_.append(replacement.__pn_);
299 }
300 return *this;
301 }
302
303 ///////////////////////////////////////////////////////////////////////////////
304 // path.decompose
305
__root_name() const306 string_view_t path::__root_name() const
307 {
308 auto PP = PathParser::CreateBegin(__pn_);
309 if (PP.State == PathParser::PS_InRootName)
310 return *PP;
311 return {};
312 }
313
__root_directory() const314 string_view_t path::__root_directory() const
315 {
316 auto PP = PathParser::CreateBegin(__pn_);
317 if (PP.State == PathParser::PS_InRootName)
318 ++PP;
319 if (PP.State == PathParser::PS_InRootDir)
320 return *PP;
321 return {};
322 }
323
__root_path_raw() const324 string_view_t path::__root_path_raw() const
325 {
326 auto PP = PathParser::CreateBegin(__pn_);
327 if (PP.State == PathParser::PS_InRootName) {
328 auto NextCh = PP.peek();
329 if (NextCh && *NextCh == '/') {
330 ++PP;
331 return createView(__pn_.data(), &PP.RawEntry.back());
332 }
333 return PP.RawEntry;
334 }
335 if (PP.State == PathParser::PS_InRootDir)
336 return *PP;
337 return {};
338 }
339
__relative_path() const340 string_view_t path::__relative_path() const
341 {
342 auto PP = PathParser::CreateBegin(__pn_);
343 while (PP.State <= PathParser::PS_InRootDir)
344 ++PP;
345 if (PP.State == PathParser::PS_AtEnd)
346 return {};
347 return createView(PP.RawEntry.data(), &__pn_.back());
348 }
349
__parent_path() const350 string_view_t path::__parent_path() const
351 {
352 if (empty())
353 return {};
354 auto PP = PathParser::CreateEnd(__pn_);
355 --PP;
356 if (PP.RawEntry.data() == __pn_.data())
357 return {};
358 --PP;
359 return createView(__pn_.data(), &PP.RawEntry.back());
360 }
361
__filename() const362 string_view_t path::__filename() const
363 {
364 if (empty()) return {};
365 return *(--PathParser::CreateEnd(__pn_));
366 }
367
__stem() const368 string_view_t path::__stem() const
369 {
370 return parser::separate_filename(__filename()).first;
371 }
372
__extension() const373 string_view_t path::__extension() const
374 {
375 return parser::separate_filename(__filename()).second;
376 }
377
378 ////////////////////////////////////////////////////////////////////////////
379 // path.comparisons
__compare(string_view_t __s) const380 int path::__compare(string_view_t __s) const {
381 auto PP = PathParser::CreateBegin(__pn_);
382 auto PP2 = PathParser::CreateBegin(__s);
383 while (PP && PP2) {
384 int res = (*PP).compare(*PP2);
385 if (res != 0) return res;
386 ++PP; ++PP2;
387 }
388 if (PP.State == PP2.State && PP.State == PathParser::PS_AtEnd)
389 return 0;
390 if (PP.State == PathParser::PS_AtEnd)
391 return -1;
392 return 1;
393 }
394
395 ////////////////////////////////////////////////////////////////////////////
396 // path.nonmembers
hash_value(const path & __p)397 size_t hash_value(const path& __p) noexcept {
398 auto PP = PathParser::CreateBegin(__p.native());
399 size_t hash_value = 0;
400 std::hash<string_view_t> hasher;
401 while (PP) {
402 hash_value = __hash_combine(hash_value, hasher(*PP));
403 ++PP;
404 }
405 return hash_value;
406 }
407
408 ////////////////////////////////////////////////////////////////////////////
409 // path.itr
begin() const410 path::iterator path::begin() const
411 {
412 auto PP = PathParser::CreateBegin(__pn_);
413 iterator it;
414 it.__path_ptr_ = this;
415 it.__state_ = PP.State;
416 it.__entry_ = PP.RawEntry;
417 it.__stashed_elem_.__assign_view(*PP);
418 return it;
419 }
420
end() const421 path::iterator path::end() const
422 {
423 iterator it{};
424 it.__state_ = PathParser::PS_AtEnd;
425 it.__path_ptr_ = this;
426 return it;
427 }
428
__increment()429 path::iterator& path::iterator::__increment() {
430 static_assert(__at_end == PathParser::PS_AtEnd, "");
431 PathParser PP(__path_ptr_->native(), __entry_, __state_);
432 ++PP;
433 __state_ = PP.State;
434 __entry_ = PP.RawEntry;
435 __stashed_elem_.__assign_view(*PP);
436 return *this;
437 }
438
__decrement()439 path::iterator& path::iterator::__decrement() {
440 PathParser PP(__path_ptr_->native(), __entry_, __state_);
441 --PP;
442 __state_ = PP.State;
443 __entry_ = PP.RawEntry;
444 __stashed_elem_.__assign_view(*PP);
445 return *this;
446 }
447
448 _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM
449