//===--------------------- filesystem/path.cpp ----------------------------===// // // The LLVM Compiler Infrastructure // // This file is dual licensed under the MIT and the University of Illinois Open // Source Licenses. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "experimental/filesystem" #include "string_view" #include "utility" namespace { namespace parser { using namespace std; using namespace std::experimental::filesystem; using string_view_t = path::__string_view; using string_view_pair = pair; using PosPtr = path::value_type const*; struct PathParser { enum ParserState : unsigned char { // Zero is a special sentinel value used by default constructed iterators. PS_BeforeBegin = 1, PS_InRootName, PS_InRootDir, PS_InFilenames, PS_InTrailingSep, PS_AtEnd }; const string_view_t Path; string_view_t RawEntry; ParserState State; private: PathParser(string_view_t P, ParserState State) noexcept : Path(P), State(State) {} public: PathParser(string_view_t P, string_view_t E, unsigned char S) : Path(P), RawEntry(E), State(static_cast(S)) { // S cannot be '0' or PS_BeforeBegin. } static PathParser CreateBegin(string_view_t P) noexcept { PathParser PP(P, PS_BeforeBegin); PP.increment(); return PP; } static PathParser CreateEnd(string_view_t P) noexcept { PathParser PP(P, PS_AtEnd); return PP; } PosPtr peek() const noexcept { auto TkEnd = getNextTokenStartPos(); auto End = getAfterBack(); return TkEnd == End ? nullptr : TkEnd; } void increment() noexcept { const PosPtr End = getAfterBack(); const PosPtr Start = getNextTokenStartPos(); if (Start == End) return makeState(PS_AtEnd); switch (State) { case PS_BeforeBegin: { PosPtr TkEnd = consumeSeparator(Start, End); // If we consumed exactly two separators we have a root name. if (TkEnd && TkEnd == Start + 2) { // FIXME Do we need to consume a name or is '//' a root name on its own? // what about '//.', '//..', '//...'? auto NameEnd = consumeName(TkEnd, End); if (NameEnd) TkEnd = NameEnd; return makeState(PS_InRootName, Start, TkEnd); } else if (TkEnd) return makeState(PS_InRootDir, Start, TkEnd); else return makeState(PS_InFilenames, Start, consumeName(Start, End)); } case PS_InRootName: return makeState(PS_InRootDir, Start, consumeSeparator(Start, End)); case PS_InRootDir: return makeState(PS_InFilenames, Start, consumeName(Start, End)); case PS_InFilenames: { PosPtr SepEnd = consumeSeparator(Start, End); if (SepEnd != End) { PosPtr TkEnd = consumeName(SepEnd, End); if (TkEnd) return makeState(PS_InFilenames, SepEnd, TkEnd); } return makeState(PS_InTrailingSep, Start, SepEnd); } case PS_InTrailingSep: return makeState(PS_AtEnd); case PS_AtEnd: _LIBCPP_UNREACHABLE(); } } void decrement() noexcept { const PosPtr REnd = getBeforeFront(); const PosPtr RStart = getCurrentTokenStartPos() - 1; switch (State) { case PS_AtEnd: { // Try to consume a trailing separator or root directory first. if (PosPtr SepEnd = consumeSeparator(RStart, REnd)) { if (SepEnd == REnd) return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir, Path.data(), RStart + 1); // Check if we're seeing the root directory separator auto PP = CreateBegin(Path); bool InRootDir = PP.State == PS_InRootName && &PP.RawEntry.back() == SepEnd; return makeState(InRootDir ? PS_InRootDir : PS_InTrailingSep, SepEnd + 1, RStart + 1); } else { PosPtr TkStart = consumeName(RStart, REnd); if (TkStart == REnd + 2 && consumeSeparator(TkStart, REnd) == REnd) return makeState(PS_InRootName, Path.data(), RStart + 1); else return makeState(PS_InFilenames, TkStart + 1, RStart + 1); } } case PS_InTrailingSep: return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1); case PS_InFilenames: { PosPtr SepEnd = consumeSeparator(RStart, REnd); if (SepEnd == REnd) return makeState((RStart == REnd + 2) ? PS_InRootName : PS_InRootDir, Path.data(), RStart + 1); PosPtr TkEnd = consumeName(SepEnd, REnd); if (TkEnd == REnd + 2 && consumeSeparator(TkEnd, REnd) == REnd) return makeState(PS_InRootDir, SepEnd + 1, RStart + 1); return makeState(PS_InFilenames, TkEnd + 1, SepEnd + 1); } case PS_InRootDir: return makeState(PS_InRootName, Path.data(), RStart + 1); case PS_InRootName: case PS_BeforeBegin: _LIBCPP_UNREACHABLE(); } } /// \brief Return a view with the "preferred representation" of the current /// element. For example trailing separators are represented as a '.' string_view_t operator*() const noexcept { switch (State) { case PS_BeforeBegin: case PS_AtEnd: return ""; case PS_InRootDir: return "/"; case PS_InTrailingSep: return "."; case PS_InRootName: case PS_InFilenames: return RawEntry; } _LIBCPP_UNREACHABLE(); } explicit operator bool() const noexcept { return State != PS_BeforeBegin && State != PS_AtEnd; } PathParser& operator++() noexcept { increment(); return *this; } PathParser& operator--() noexcept { decrement(); return *this; } private: void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept { State = NewState; RawEntry = string_view_t(Start, End - Start); } void makeState(ParserState NewState) noexcept { State = NewState; RawEntry = {}; } PosPtr getAfterBack() const noexcept { return Path.data() + Path.size(); } PosPtr getBeforeFront() const noexcept { return Path.data() - 1; } /// \brief Return a pointer to the first character after the currently /// lexed element. PosPtr getNextTokenStartPos() const noexcept { switch (State) { case PS_BeforeBegin: return Path.data(); case PS_InRootName: case PS_InRootDir: case PS_InFilenames: return &RawEntry.back() + 1; case PS_InTrailingSep: case PS_AtEnd: return getAfterBack(); } _LIBCPP_UNREACHABLE(); } /// \brief Return a pointer to the first character in the currently lexed /// element. PosPtr getCurrentTokenStartPos() const noexcept { switch (State) { case PS_BeforeBegin: case PS_InRootName: return &Path.front(); case PS_InRootDir: case PS_InFilenames: case PS_InTrailingSep: return &RawEntry.front(); case PS_AtEnd: return &Path.back() + 1; } _LIBCPP_UNREACHABLE(); } PosPtr consumeSeparator(PosPtr P, PosPtr End) const noexcept { if (P == End || *P != '/') return nullptr; const int Inc = P < End ? 1 : -1; P += Inc; while (P != End && *P == '/') P += Inc; return P; } PosPtr consumeName(PosPtr P, PosPtr End) const noexcept { if (P == End || *P == '/') return nullptr; const int Inc = P < End ? 1 : -1; P += Inc; while (P != End && *P != '/') P += Inc; return P; } }; string_view_pair separate_filename(string_view_t const & s) { if (s == "." || s == ".." || s.empty()) return string_view_pair{s, ""}; auto pos = s.find_last_of('.'); if (pos == string_view_t::npos) return string_view_pair{s, string_view{}}; return string_view_pair{s.substr(0, pos), s.substr(pos)}; } string_view_t createView(PosPtr S, PosPtr E) noexcept { return {S, static_cast(E - S) + 1}; } }} // namespace parser _LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_FILESYSTEM using parser::string_view_t; using parser::string_view_pair; using parser::PathParser; using parser::createView; /////////////////////////////////////////////////////////////////////////////// // path definitions /////////////////////////////////////////////////////////////////////////////// constexpr path::value_type path::preferred_separator; path & path::replace_extension(path const & replacement) { path p = extension(); if (not p.empty()) { __pn_.erase(__pn_.size() - p.native().size()); } if (!replacement.empty()) { if (replacement.native()[0] != '.') { __pn_ += "."; } __pn_.append(replacement.__pn_); } return *this; } /////////////////////////////////////////////////////////////////////////////// // path.decompose string_view_t path::__root_name() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) return *PP; return {}; } string_view_t path::__root_directory() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) ++PP; if (PP.State == PathParser::PS_InRootDir) return *PP; return {}; } string_view_t path::__root_path_raw() const { auto PP = PathParser::CreateBegin(__pn_); if (PP.State == PathParser::PS_InRootName) { auto NextCh = PP.peek(); if (NextCh && *NextCh == '/') { ++PP; return createView(__pn_.data(), &PP.RawEntry.back()); } return PP.RawEntry; } if (PP.State == PathParser::PS_InRootDir) return *PP; return {}; } string_view_t path::__relative_path() const { auto PP = PathParser::CreateBegin(__pn_); while (PP.State <= PathParser::PS_InRootDir) ++PP; if (PP.State == PathParser::PS_AtEnd) return {}; return createView(PP.RawEntry.data(), &__pn_.back()); } string_view_t path::__parent_path() const { if (empty()) return {}; auto PP = PathParser::CreateEnd(__pn_); --PP; if (PP.RawEntry.data() == __pn_.data()) return {}; --PP; return createView(__pn_.data(), &PP.RawEntry.back()); } string_view_t path::__filename() const { if (empty()) return {}; return *(--PathParser::CreateEnd(__pn_)); } string_view_t path::__stem() const { return parser::separate_filename(__filename()).first; } string_view_t path::__extension() const { return parser::separate_filename(__filename()).second; } //////////////////////////////////////////////////////////////////////////// // path.comparisons int path::__compare(string_view_t __s) const { auto PP = PathParser::CreateBegin(__pn_); auto PP2 = PathParser::CreateBegin(__s); while (PP && PP2) { int res = (*PP).compare(*PP2); if (res != 0) return res; ++PP; ++PP2; } if (PP.State == PP2.State && PP.State == PathParser::PS_AtEnd) return 0; if (PP.State == PathParser::PS_AtEnd) return -1; return 1; } //////////////////////////////////////////////////////////////////////////// // path.nonmembers size_t hash_value(const path& __p) noexcept { auto PP = PathParser::CreateBegin(__p.native()); size_t hash_value = 0; std::hash hasher; while (PP) { hash_value = __hash_combine(hash_value, hasher(*PP)); ++PP; } return hash_value; } //////////////////////////////////////////////////////////////////////////// // path.itr path::iterator path::begin() const { auto PP = PathParser::CreateBegin(__pn_); iterator it; it.__path_ptr_ = this; it.__state_ = PP.State; it.__entry_ = PP.RawEntry; it.__stashed_elem_.__assign_view(*PP); return it; } path::iterator path::end() const { iterator it{}; it.__state_ = PathParser::PS_AtEnd; it.__path_ptr_ = this; return it; } path::iterator& path::iterator::__increment() { static_assert(__at_end == PathParser::PS_AtEnd, ""); PathParser PP(__path_ptr_->native(), __entry_, __state_); ++PP; __state_ = PP.State; __entry_ = PP.RawEntry; __stashed_elem_.__assign_view(*PP); return *this; } path::iterator& path::iterator::__decrement() { PathParser PP(__path_ptr_->native(), __entry_, __state_); --PP; __state_ = PP.State; __entry_ = PP.RawEntry; __stashed_elem_.__assign_view(*PP); return *this; } _LIBCPP_END_NAMESPACE_EXPERIMENTAL_FILESYSTEM