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: 38 PathParser(string_view_t P, ParserState State) noexcept 39 : Path(P), State(State) {} 40 41 public: 42 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 47 static PathParser CreateBegin(string_view_t P) noexcept { 48 PathParser PP(P, PS_BeforeBegin); 49 PP.increment(); 50 return PP; 51 } 52 53 static PathParser CreateEnd(string_view_t P) noexcept { 54 PathParser PP(P, PS_AtEnd); 55 return PP; 56 } 57 58 PosPtr peek() const noexcept { 59 auto TkEnd = getNextTokenStartPos(); 60 auto End = getAfterBack(); 61 return TkEnd == End ? nullptr : TkEnd; 62 } 63 64 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 111 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 '.' 158 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 174 explicit operator bool() const noexcept { 175 return State != PS_BeforeBegin && State != PS_AtEnd; 176 } 177 178 PathParser& operator++() noexcept { 179 increment(); 180 return *this; 181 } 182 183 PathParser& operator--() noexcept { 184 decrement(); 185 return *this; 186 } 187 188 private: 189 void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept { 190 State = NewState; 191 RawEntry = string_view_t(Start, End - Start); 192 } 193 void makeState(ParserState NewState) noexcept { 194 State = NewState; 195 RawEntry = {}; 196 } 197 198 PosPtr getAfterBack() const noexcept { 199 return Path.data() + Path.size(); 200 } 201 202 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. 208 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. 225 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 240 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 250 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 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 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 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 306 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 314 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 324 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 340 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 350 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 362 string_view_t path::__filename() const 363 { 364 if (empty()) return {}; 365 return *(--PathParser::CreateEnd(__pn_)); 366 } 367 368 string_view_t path::__stem() const 369 { 370 return parser::separate_filename(__filename()).first; 371 } 372 373 string_view_t path::__extension() const 374 { 375 return parser::separate_filename(__filename()).second; 376 } 377 378 //////////////////////////////////////////////////////////////////////////// 379 // path.comparisons 380 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 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 410 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 421 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 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 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