Home | History | Annotate | Download | only in filesystem
      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