1c7d3c844SLouis Dionne //===----------------------------------------------------------------------===//
2c7d3c844SLouis Dionne //
3c7d3c844SLouis Dionne // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4c7d3c844SLouis Dionne // See https://llvm.org/LICENSE.txt for license information.
5c7d3c844SLouis Dionne // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6c7d3c844SLouis Dionne //
7c7d3c844SLouis Dionne //===----------------------------------------------------------------------===//
8c7d3c844SLouis Dionne
9c7d3c844SLouis Dionne #ifndef PATH_PARSER_H
10c7d3c844SLouis Dionne #define PATH_PARSER_H
11c7d3c844SLouis Dionne
12c7d3c844SLouis Dionne #include <__config>
13c7d3c844SLouis Dionne #include <__utility/unreachable.h>
14c7d3c844SLouis Dionne #include <cstddef>
15c7d3c844SLouis Dionne #include <filesystem>
16c7d3c844SLouis Dionne #include <utility>
17c7d3c844SLouis Dionne
18c7d3c844SLouis Dionne #include "format_string.h"
19c7d3c844SLouis Dionne
20c7d3c844SLouis Dionne _LIBCPP_BEGIN_NAMESPACE_FILESYSTEM
21c7d3c844SLouis Dionne
isSeparator(path::value_type C)2221853b96SLouis Dionne inline bool isSeparator(path::value_type C) {
23c7d3c844SLouis Dionne if (C == '/')
24c7d3c844SLouis Dionne return true;
25c7d3c844SLouis Dionne #if defined(_LIBCPP_WIN32API)
26c7d3c844SLouis Dionne if (C == '\\')
27c7d3c844SLouis Dionne return true;
28c7d3c844SLouis Dionne #endif
29c7d3c844SLouis Dionne return false;
30c7d3c844SLouis Dionne }
31c7d3c844SLouis Dionne
isDriveLetter(path::value_type C)329783f28cSLouis Dionne inline bool isDriveLetter(path::value_type C) { return (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z'); }
33c7d3c844SLouis Dionne
34c7d3c844SLouis Dionne namespace parser {
35c7d3c844SLouis Dionne
36c7d3c844SLouis Dionne using string_view_t = path::__string_view;
37c7d3c844SLouis Dionne using string_view_pair = pair<string_view_t, string_view_t>;
38c7d3c844SLouis Dionne using PosPtr = path::value_type const*;
39c7d3c844SLouis Dionne
40c7d3c844SLouis Dionne struct PathParser {
41c7d3c844SLouis Dionne enum ParserState : unsigned char {
42c7d3c844SLouis Dionne // Zero is a special sentinel value used by default constructed iterators.
43c7d3c844SLouis Dionne PS_BeforeBegin = path::iterator::_BeforeBegin,
44c7d3c844SLouis Dionne PS_InRootName = path::iterator::_InRootName,
45c7d3c844SLouis Dionne PS_InRootDir = path::iterator::_InRootDir,
46c7d3c844SLouis Dionne PS_InFilenames = path::iterator::_InFilenames,
47c7d3c844SLouis Dionne PS_InTrailingSep = path::iterator::_InTrailingSep,
48c7d3c844SLouis Dionne PS_AtEnd = path::iterator::_AtEnd
49c7d3c844SLouis Dionne };
50c7d3c844SLouis Dionne
51c7d3c844SLouis Dionne const string_view_t Path;
52c7d3c844SLouis Dionne string_view_t RawEntry;
53*731db06aSNikolas Klauser ParserState State_;
54c7d3c844SLouis Dionne
55c7d3c844SLouis Dionne private:
PathParserPathParser56*731db06aSNikolas Klauser PathParser(string_view_t P, ParserState State) noexcept : Path(P), State_(State) {}
57c7d3c844SLouis Dionne
58c7d3c844SLouis Dionne public:
PathParserPathParser59c7d3c844SLouis Dionne PathParser(string_view_t P, string_view_t E, unsigned char S)
60*731db06aSNikolas Klauser : Path(P), RawEntry(E), State_(static_cast<ParserState>(S)) {
61c7d3c844SLouis Dionne // S cannot be '0' or PS_BeforeBegin.
62c7d3c844SLouis Dionne }
63c7d3c844SLouis Dionne
CreateBeginPathParser64c7d3c844SLouis Dionne static PathParser CreateBegin(string_view_t P) noexcept {
65c7d3c844SLouis Dionne PathParser PP(P, PS_BeforeBegin);
66c7d3c844SLouis Dionne PP.increment();
67c7d3c844SLouis Dionne return PP;
68c7d3c844SLouis Dionne }
69c7d3c844SLouis Dionne
CreateEndPathParser70c7d3c844SLouis Dionne static PathParser CreateEnd(string_view_t P) noexcept {
71c7d3c844SLouis Dionne PathParser PP(P, PS_AtEnd);
72c7d3c844SLouis Dionne return PP;
73c7d3c844SLouis Dionne }
74c7d3c844SLouis Dionne
peekPathParser75c7d3c844SLouis Dionne PosPtr peek() const noexcept {
76c7d3c844SLouis Dionne auto TkEnd = getNextTokenStartPos();
77c7d3c844SLouis Dionne auto End = getAfterBack();
78c7d3c844SLouis Dionne return TkEnd == End ? nullptr : TkEnd;
79c7d3c844SLouis Dionne }
80c7d3c844SLouis Dionne
incrementPathParser81c7d3c844SLouis Dionne void increment() noexcept {
82c7d3c844SLouis Dionne const PosPtr End = getAfterBack();
83c7d3c844SLouis Dionne const PosPtr Start = getNextTokenStartPos();
84c7d3c844SLouis Dionne if (Start == End)
85c7d3c844SLouis Dionne return makeState(PS_AtEnd);
86c7d3c844SLouis Dionne
87*731db06aSNikolas Klauser switch (State_) {
88c7d3c844SLouis Dionne case PS_BeforeBegin: {
89c7d3c844SLouis Dionne PosPtr TkEnd = consumeRootName(Start, End);
90c7d3c844SLouis Dionne if (TkEnd)
91c7d3c844SLouis Dionne return makeState(PS_InRootName, Start, TkEnd);
92c7d3c844SLouis Dionne }
93c7d3c844SLouis Dionne _LIBCPP_FALLTHROUGH();
94c7d3c844SLouis Dionne case PS_InRootName: {
95c7d3c844SLouis Dionne PosPtr TkEnd = consumeAllSeparators(Start, End);
96c7d3c844SLouis Dionne if (TkEnd)
97c7d3c844SLouis Dionne return makeState(PS_InRootDir, Start, TkEnd);
98c7d3c844SLouis Dionne else
99c7d3c844SLouis Dionne return makeState(PS_InFilenames, Start, consumeName(Start, End));
100c7d3c844SLouis Dionne }
101c7d3c844SLouis Dionne case PS_InRootDir:
102c7d3c844SLouis Dionne return makeState(PS_InFilenames, Start, consumeName(Start, End));
103c7d3c844SLouis Dionne
104c7d3c844SLouis Dionne case PS_InFilenames: {
105c7d3c844SLouis Dionne PosPtr SepEnd = consumeAllSeparators(Start, End);
106c7d3c844SLouis Dionne if (SepEnd != End) {
107c7d3c844SLouis Dionne PosPtr TkEnd = consumeName(SepEnd, End);
108c7d3c844SLouis Dionne if (TkEnd)
109c7d3c844SLouis Dionne return makeState(PS_InFilenames, SepEnd, TkEnd);
110c7d3c844SLouis Dionne }
111c7d3c844SLouis Dionne return makeState(PS_InTrailingSep, Start, SepEnd);
112c7d3c844SLouis Dionne }
113c7d3c844SLouis Dionne
114c7d3c844SLouis Dionne case PS_InTrailingSep:
115c7d3c844SLouis Dionne return makeState(PS_AtEnd);
116c7d3c844SLouis Dionne
117c7d3c844SLouis Dionne case PS_AtEnd:
118c7d3c844SLouis Dionne __libcpp_unreachable();
119c7d3c844SLouis Dionne }
120c7d3c844SLouis Dionne }
121c7d3c844SLouis Dionne
decrementPathParser122c7d3c844SLouis Dionne void decrement() noexcept {
123c7d3c844SLouis Dionne const PosPtr REnd = getBeforeFront();
124c7d3c844SLouis Dionne const PosPtr RStart = getCurrentTokenStartPos() - 1;
125c7d3c844SLouis Dionne if (RStart == REnd) // we're decrementing the begin
126c7d3c844SLouis Dionne return makeState(PS_BeforeBegin);
127c7d3c844SLouis Dionne
128*731db06aSNikolas Klauser switch (State_) {
129c7d3c844SLouis Dionne case PS_AtEnd: {
130c7d3c844SLouis Dionne // Try to consume a trailing separator or root directory first.
131c7d3c844SLouis Dionne if (PosPtr SepEnd = consumeAllSeparators(RStart, REnd)) {
132c7d3c844SLouis Dionne if (SepEnd == REnd)
133c7d3c844SLouis Dionne return makeState(PS_InRootDir, Path.data(), RStart + 1);
134c7d3c844SLouis Dionne PosPtr TkStart = consumeRootName(SepEnd, REnd);
135c7d3c844SLouis Dionne if (TkStart == REnd)
136c7d3c844SLouis Dionne return makeState(PS_InRootDir, RStart, RStart + 1);
137c7d3c844SLouis Dionne return makeState(PS_InTrailingSep, SepEnd + 1, RStart + 1);
138c7d3c844SLouis Dionne } else {
139c7d3c844SLouis Dionne PosPtr TkStart = consumeRootName(RStart, REnd);
140c7d3c844SLouis Dionne if (TkStart == REnd)
141c7d3c844SLouis Dionne return makeState(PS_InRootName, TkStart + 1, RStart + 1);
142c7d3c844SLouis Dionne TkStart = consumeName(RStart, REnd);
143c7d3c844SLouis Dionne return makeState(PS_InFilenames, TkStart + 1, RStart + 1);
144c7d3c844SLouis Dionne }
145c7d3c844SLouis Dionne }
146c7d3c844SLouis Dionne case PS_InTrailingSep:
1479783f28cSLouis Dionne return makeState(PS_InFilenames, consumeName(RStart, REnd) + 1, RStart + 1);
148c7d3c844SLouis Dionne case PS_InFilenames: {
149c7d3c844SLouis Dionne PosPtr SepEnd = consumeAllSeparators(RStart, REnd);
150c7d3c844SLouis Dionne if (SepEnd == REnd)
151c7d3c844SLouis Dionne return makeState(PS_InRootDir, Path.data(), RStart + 1);
152c7d3c844SLouis Dionne PosPtr TkStart = consumeRootName(SepEnd ? SepEnd : RStart, REnd);
153c7d3c844SLouis Dionne if (TkStart == REnd) {
154c7d3c844SLouis Dionne if (SepEnd)
155c7d3c844SLouis Dionne return makeState(PS_InRootDir, SepEnd + 1, RStart + 1);
156c7d3c844SLouis Dionne return makeState(PS_InRootName, TkStart + 1, RStart + 1);
157c7d3c844SLouis Dionne }
158c7d3c844SLouis Dionne TkStart = consumeName(SepEnd, REnd);
159c7d3c844SLouis Dionne return makeState(PS_InFilenames, TkStart + 1, SepEnd + 1);
160c7d3c844SLouis Dionne }
161c7d3c844SLouis Dionne case PS_InRootDir:
162c7d3c844SLouis Dionne return makeState(PS_InRootName, Path.data(), RStart + 1);
163c7d3c844SLouis Dionne case PS_InRootName:
164c7d3c844SLouis Dionne case PS_BeforeBegin:
165c7d3c844SLouis Dionne __libcpp_unreachable();
166c7d3c844SLouis Dionne }
167c7d3c844SLouis Dionne }
168c7d3c844SLouis Dionne
169c7d3c844SLouis Dionne /// \brief Return a view with the "preferred representation" of the current
170c7d3c844SLouis Dionne /// element. For example trailing separators are represented as a '.'
171c7d3c844SLouis Dionne string_view_t operator*() const noexcept {
172*731db06aSNikolas Klauser switch (State_) {
173c7d3c844SLouis Dionne case PS_BeforeBegin:
174c7d3c844SLouis Dionne case PS_AtEnd:
175c7d3c844SLouis Dionne return PATHSTR("");
176c7d3c844SLouis Dionne case PS_InRootDir:
177c7d3c844SLouis Dionne if (RawEntry[0] == '\\')
178c7d3c844SLouis Dionne return PATHSTR("\\");
179c7d3c844SLouis Dionne else
180c7d3c844SLouis Dionne return PATHSTR("/");
181c7d3c844SLouis Dionne case PS_InTrailingSep:
182c7d3c844SLouis Dionne return PATHSTR("");
183c7d3c844SLouis Dionne case PS_InRootName:
184c7d3c844SLouis Dionne case PS_InFilenames:
185c7d3c844SLouis Dionne return RawEntry;
186c7d3c844SLouis Dionne }
187c7d3c844SLouis Dionne __libcpp_unreachable();
188c7d3c844SLouis Dionne }
189c7d3c844SLouis Dionne
190*731db06aSNikolas Klauser explicit operator bool() const noexcept { return State_ != PS_BeforeBegin && State_ != PS_AtEnd; }
191c7d3c844SLouis Dionne
192c7d3c844SLouis Dionne PathParser& operator++() noexcept {
193c7d3c844SLouis Dionne increment();
194c7d3c844SLouis Dionne return *this;
195c7d3c844SLouis Dionne }
196c7d3c844SLouis Dionne
197c7d3c844SLouis Dionne PathParser& operator--() noexcept {
198c7d3c844SLouis Dionne decrement();
199c7d3c844SLouis Dionne return *this;
200c7d3c844SLouis Dionne }
201c7d3c844SLouis Dionne
atEndPathParser202*731db06aSNikolas Klauser bool atEnd() const noexcept { return State_ == PS_AtEnd; }
203c7d3c844SLouis Dionne
inRootDirPathParser204*731db06aSNikolas Klauser bool inRootDir() const noexcept { return State_ == PS_InRootDir; }
205c7d3c844SLouis Dionne
inRootNamePathParser206*731db06aSNikolas Klauser bool inRootName() const noexcept { return State_ == PS_InRootName; }
207c7d3c844SLouis Dionne
inRootPathPathParser2089783f28cSLouis Dionne bool inRootPath() const noexcept { return inRootName() || inRootDir(); }
209c7d3c844SLouis Dionne
210c7d3c844SLouis Dionne private:
makeStatePathParser211c7d3c844SLouis Dionne void makeState(ParserState NewState, PosPtr Start, PosPtr End) noexcept {
212*731db06aSNikolas Klauser State_ = NewState;
213c7d3c844SLouis Dionne RawEntry = string_view_t(Start, End - Start);
214c7d3c844SLouis Dionne }
makeStatePathParser215c7d3c844SLouis Dionne void makeState(ParserState NewState) noexcept {
216*731db06aSNikolas Klauser State_ = NewState;
217c7d3c844SLouis Dionne RawEntry = {};
218c7d3c844SLouis Dionne }
219c7d3c844SLouis Dionne
getAfterBackPathParser220c7d3c844SLouis Dionne PosPtr getAfterBack() const noexcept { return Path.data() + Path.size(); }
221c7d3c844SLouis Dionne
getBeforeFrontPathParser222c7d3c844SLouis Dionne PosPtr getBeforeFront() const noexcept { return Path.data() - 1; }
223c7d3c844SLouis Dionne
224c7d3c844SLouis Dionne /// \brief Return a pointer to the first character after the currently
225c7d3c844SLouis Dionne /// lexed element.
getNextTokenStartPosPathParser226c7d3c844SLouis Dionne PosPtr getNextTokenStartPos() const noexcept {
227*731db06aSNikolas Klauser switch (State_) {
228c7d3c844SLouis Dionne case PS_BeforeBegin:
229c7d3c844SLouis Dionne return Path.data();
230c7d3c844SLouis Dionne case PS_InRootName:
231c7d3c844SLouis Dionne case PS_InRootDir:
232c7d3c844SLouis Dionne case PS_InFilenames:
233c7d3c844SLouis Dionne return &RawEntry.back() + 1;
234c7d3c844SLouis Dionne case PS_InTrailingSep:
235c7d3c844SLouis Dionne case PS_AtEnd:
236c7d3c844SLouis Dionne return getAfterBack();
237c7d3c844SLouis Dionne }
238c7d3c844SLouis Dionne __libcpp_unreachable();
239c7d3c844SLouis Dionne }
240c7d3c844SLouis Dionne
241c7d3c844SLouis Dionne /// \brief Return a pointer to the first character in the currently lexed
242c7d3c844SLouis Dionne /// element.
getCurrentTokenStartPosPathParser243c7d3c844SLouis Dionne PosPtr getCurrentTokenStartPos() const noexcept {
244*731db06aSNikolas Klauser switch (State_) {
245c7d3c844SLouis Dionne case PS_BeforeBegin:
246c7d3c844SLouis Dionne case PS_InRootName:
247c7d3c844SLouis Dionne return &Path.front();
248c7d3c844SLouis Dionne case PS_InRootDir:
249c7d3c844SLouis Dionne case PS_InFilenames:
250c7d3c844SLouis Dionne case PS_InTrailingSep:
251c7d3c844SLouis Dionne return &RawEntry.front();
252c7d3c844SLouis Dionne case PS_AtEnd:
253c7d3c844SLouis Dionne return &Path.back() + 1;
254c7d3c844SLouis Dionne }
255c7d3c844SLouis Dionne __libcpp_unreachable();
256c7d3c844SLouis Dionne }
257c7d3c844SLouis Dionne
258c7d3c844SLouis Dionne // Consume all consecutive separators.
consumeAllSeparatorsPathParser259c7d3c844SLouis Dionne PosPtr consumeAllSeparators(PosPtr P, PosPtr End) const noexcept {
260c7d3c844SLouis Dionne if (P == nullptr || P == End || !isSeparator(*P))
261c7d3c844SLouis Dionne return nullptr;
262c7d3c844SLouis Dionne const int Inc = P < End ? 1 : -1;
263c7d3c844SLouis Dionne P += Inc;
264c7d3c844SLouis Dionne while (P != End && isSeparator(*P))
265c7d3c844SLouis Dionne P += Inc;
266c7d3c844SLouis Dionne return P;
267c7d3c844SLouis Dionne }
268c7d3c844SLouis Dionne
269c7d3c844SLouis Dionne // Consume exactly N separators, or return nullptr.
consumeNSeparatorsPathParser270c7d3c844SLouis Dionne PosPtr consumeNSeparators(PosPtr P, PosPtr End, int N) const noexcept {
271c7d3c844SLouis Dionne PosPtr Ret = consumeAllSeparators(P, End);
272c7d3c844SLouis Dionne if (Ret == nullptr)
273c7d3c844SLouis Dionne return nullptr;
274c7d3c844SLouis Dionne if (P < End) {
275c7d3c844SLouis Dionne if (Ret == P + N)
276c7d3c844SLouis Dionne return Ret;
277c7d3c844SLouis Dionne } else {
278c7d3c844SLouis Dionne if (Ret == P - N)
279c7d3c844SLouis Dionne return Ret;
280c7d3c844SLouis Dionne }
281c7d3c844SLouis Dionne return nullptr;
282c7d3c844SLouis Dionne }
283c7d3c844SLouis Dionne
consumeNamePathParser284c7d3c844SLouis Dionne PosPtr consumeName(PosPtr P, PosPtr End) const noexcept {
285c7d3c844SLouis Dionne PosPtr Start = P;
286c7d3c844SLouis Dionne if (P == nullptr || P == End || isSeparator(*P))
287c7d3c844SLouis Dionne return nullptr;
288c7d3c844SLouis Dionne const int Inc = P < End ? 1 : -1;
289c7d3c844SLouis Dionne P += Inc;
290c7d3c844SLouis Dionne while (P != End && !isSeparator(*P))
291c7d3c844SLouis Dionne P += Inc;
292c7d3c844SLouis Dionne if (P == End && Inc < 0) {
293c7d3c844SLouis Dionne // Iterating backwards and consumed all the rest of the input.
294c7d3c844SLouis Dionne // Check if the start of the string would have been considered
295c7d3c844SLouis Dionne // a root name.
296c7d3c844SLouis Dionne PosPtr RootEnd = consumeRootName(End + 1, Start);
297c7d3c844SLouis Dionne if (RootEnd)
298c7d3c844SLouis Dionne return RootEnd - 1;
299c7d3c844SLouis Dionne }
300c7d3c844SLouis Dionne return P;
301c7d3c844SLouis Dionne }
302c7d3c844SLouis Dionne
consumeDriveLetterPathParser303c7d3c844SLouis Dionne PosPtr consumeDriveLetter(PosPtr P, PosPtr End) const noexcept {
304c7d3c844SLouis Dionne if (P == End)
305c7d3c844SLouis Dionne return nullptr;
306c7d3c844SLouis Dionne if (P < End) {
307c7d3c844SLouis Dionne if (P + 1 == End || !isDriveLetter(P[0]) || P[1] != ':')
308c7d3c844SLouis Dionne return nullptr;
309c7d3c844SLouis Dionne return P + 2;
310c7d3c844SLouis Dionne } else {
311c7d3c844SLouis Dionne if (P - 1 == End || !isDriveLetter(P[-1]) || P[0] != ':')
312c7d3c844SLouis Dionne return nullptr;
313c7d3c844SLouis Dionne return P - 2;
314c7d3c844SLouis Dionne }
315c7d3c844SLouis Dionne }
316c7d3c844SLouis Dionne
consumeNetworkRootPathParser317c7d3c844SLouis Dionne PosPtr consumeNetworkRoot(PosPtr P, PosPtr End) const noexcept {
318c7d3c844SLouis Dionne if (P == End)
319c7d3c844SLouis Dionne return nullptr;
320c7d3c844SLouis Dionne if (P < End)
321c7d3c844SLouis Dionne return consumeName(consumeNSeparators(P, End, 2), End);
322c7d3c844SLouis Dionne else
323c7d3c844SLouis Dionne return consumeNSeparators(consumeName(P, End), End, 2);
324c7d3c844SLouis Dionne }
325c7d3c844SLouis Dionne
consumeRootNamePathParser326c7d3c844SLouis Dionne PosPtr consumeRootName(PosPtr P, PosPtr End) const noexcept {
327c7d3c844SLouis Dionne #if defined(_LIBCPP_WIN32API)
328c7d3c844SLouis Dionne if (PosPtr Ret = consumeDriveLetter(P, End))
329c7d3c844SLouis Dionne return Ret;
330c7d3c844SLouis Dionne if (PosPtr Ret = consumeNetworkRoot(P, End))
331c7d3c844SLouis Dionne return Ret;
332c7d3c844SLouis Dionne #endif
333c7d3c844SLouis Dionne return nullptr;
334c7d3c844SLouis Dionne }
335c7d3c844SLouis Dionne };
336c7d3c844SLouis Dionne
separate_filename(string_view_t const & s)33721853b96SLouis Dionne inline string_view_pair separate_filename(string_view_t const& s) {
338c7d3c844SLouis Dionne if (s == PATHSTR(".") || s == PATHSTR("..") || s.empty())
339c7d3c844SLouis Dionne return string_view_pair{s, PATHSTR("")};
340c7d3c844SLouis Dionne auto pos = s.find_last_of('.');
341c7d3c844SLouis Dionne if (pos == string_view_t::npos || pos == 0)
342c7d3c844SLouis Dionne return string_view_pair{s, string_view_t{}};
343c7d3c844SLouis Dionne return string_view_pair{s.substr(0, pos), s.substr(pos)};
344c7d3c844SLouis Dionne }
345c7d3c844SLouis Dionne
createView(PosPtr S,PosPtr E)3469783f28cSLouis Dionne inline string_view_t createView(PosPtr S, PosPtr E) noexcept { return {S, static_cast<size_t>(E - S) + 1}; }
347c7d3c844SLouis Dionne
348c7d3c844SLouis Dionne } // namespace parser
349c7d3c844SLouis Dionne
350c7d3c844SLouis Dionne _LIBCPP_END_NAMESPACE_FILESYSTEM
351c7d3c844SLouis Dionne
352c7d3c844SLouis Dionne #endif // PATH_PARSER_H
353