Skip to content

Commit 1622b44

Browse files
Rework SimplifyPath without using the SplitPath
1 parent bf96a8e commit 1622b44

File tree

3 files changed

+187
-46
lines changed

3 files changed

+187
-46
lines changed

Platforms/Basic/interface/BasicFileSystem.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,13 @@ struct FindFileData
141141
struct BasicFileSystem
142142
{
143143
public:
144+
static constexpr Char UnixSlash = '/';
145+
static constexpr Char WinSlash = '\\';
146+
144147
#if PLATFORM_WIN32 || PLATFORM_UNIVERSAL_WINDOWS
145-
static constexpr Char SlashSymbol = '\\';
148+
static constexpr Char SlashSymbol = WinSlash;
146149
#else
147-
static constexpr Char SlashSymbol = '/';
150+
static constexpr Char SlashSymbol = UnixSlash;
148151
#endif
149152

150153
using SearchFilesResult = std::vector<FindFileData>;
@@ -160,7 +163,7 @@ struct BasicFileSystem
160163

161164
static bool IsSlash(Char c)
162165
{
163-
return c == '/' || c == '\\';
166+
return c == UnixSlash || c == WinSlash;
164167
}
165168

166169
static void CorrectSlashes(String& Path, Char Slash = 0);

Platforms/Basic/src/BasicFileSystem.cpp

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -227,60 +227,114 @@ std::string BasicFileSystem::BuildPathFromComponents(const std::vector<String>&
227227

228228
std::string BasicFileSystem::SimplifyPath(const Char* Path, Char Slash)
229229
{
230-
if (Path == nullptr)
230+
if (Path == nullptr || Path[0] == '\0')
231231
return "";
232232

233-
if (Slash != 0)
234-
DEV_CHECK_ERR(IsSlash(Slash), "Incorrect slash symbol");
235-
else
236-
Slash = SlashSymbol;
233+
DEV_CHECK_ERR(Slash == 0 || IsSlash(Slash), "Incorrect slash symbol");
234+
Slash = IsSlash(Slash) ? Slash : SlashSymbol;
237235

238-
struct MiniStringView
239-
{
240-
MiniStringView(const char* _Start,
241-
const char* _End) :
242-
Start{_Start},
243-
End{_End}
244-
{}
236+
std::string SimplifiedPath;
237+
const char* c = Path;
245238

246-
bool operator==(const char* Str) const noexcept
239+
if (Slash == WinSlash)
240+
{
241+
// Windows path
242+
if (c[1] == ':')
247243
{
248-
const auto Len = End - Start;
249-
return strncmp(Str, Start, Len) == 0 && Str[Len] == '\0';
244+
// Windows drive letter (e.g., C:)
245+
SimplifiedPath.push_back(*(c++)); // Drive letter
246+
SimplifiedPath.push_back(*(c++)); // ':'
250247
}
251-
252-
bool operator!=(const char* str) const noexcept
248+
else if (IsSlash(c[0]) && IsSlash(c[1]))
253249
{
254-
return !(*this == str);
250+
// Windows UNC path (e.g., \\Server\Share)
251+
SimplifiedPath.push_back(Slash); // First '\'
252+
SimplifiedPath.push_back(Slash); // Second '\'
253+
c += 2;
254+
// Copy server name
255+
while (*c != '\0' && !IsSlash(*c))
256+
SimplifiedPath.push_back(*(c++));
255257
}
258+
}
259+
else
260+
{
261+
// Unix path
262+
VERIFY_EXPR(Slash == UnixSlash);
263+
if (IsSlash(*c))
264+
{
265+
// Unix absolute path (e.g., /home/user)
266+
SimplifiedPath.push_back(Slash);
267+
++c;
268+
}
269+
}
256270

257-
const char* const Start;
258-
const char* const End;
259-
};
271+
const size_t RootLen = SimplifiedPath.length();
272+
273+
Uint32 NumLeadingDirUps = 0;
274+
while (*c != '\0')
275+
{
276+
// Skip leading slashes
277+
while (IsSlash(*c))
278+
++c;
279+
280+
// Handle . and ..
281+
if (*c == '.')
282+
{
283+
if ((IsSlash(c[1]) || c[1] == '\0'))
284+
{
285+
// Skip /.
286+
c += (c[1] != '\0') ? 2 : 1;
287+
continue;
288+
}
260289

261-
const auto PathComponents = Diligent::SplitPath<MiniStringView>(Path, true);
262-
const auto NumComponents = PathComponents.size();
263-
const auto UseLeadingSlash = Slash == '/' && IsSlash(Path[0]);
290+
if (c[1] == '.' && (IsSlash(c[2]) || c[2] == '\0'))
291+
{
292+
// Handle /..
293+
c += (c[2] != '\0') ? 3 : 2;
294+
// Pop previous subdirectory unless it is a root
295+
if (SimplifiedPath.size() > RootLen)
296+
{
297+
while (SimplifiedPath.size() > RootLen)
298+
{
299+
bool WasSlash = IsSlash(SimplifiedPath.back());
300+
SimplifiedPath.pop_back();
301+
if (WasSlash)
302+
break;
303+
}
304+
}
305+
else if (RootLen == 0)
306+
{
307+
// Relative path - count leading ../
308+
++NumLeadingDirUps;
309+
}
310+
continue;
311+
}
312+
}
264313

265-
size_t Len = UseLeadingSlash ? 1 : 0;
266-
for (const auto& Cmp : PathComponents)
267-
Len += Cmp.End - Cmp.Start;
268-
if (NumComponents > 0)
269-
Len += NumComponents - 1;
314+
if (*c == '\0')
315+
break;
270316

271-
std::string SimplifiedPath;
272-
SimplifiedPath.reserve(Len);
273-
if (UseLeadingSlash)
274-
SimplifiedPath.push_back(Slash);
317+
if (!SimplifiedPath.empty() && !IsSlash(SimplifiedPath.back()))
318+
{
319+
SimplifiedPath.push_back(Slash);
320+
}
321+
322+
// Copy regular path component
323+
while (*c != '\0' && !IsSlash(*c))
324+
{
325+
SimplifiedPath.push_back(*(c++));
326+
}
327+
}
275328

276-
for (size_t i = 0; i < NumComponents; ++i)
329+
if (NumLeadingDirUps > 0)
277330
{
278-
if (i > 0)
279-
SimplifiedPath.push_back(Slash);
280-
const auto& Cmp = PathComponents[i];
281-
SimplifiedPath.append(Cmp.Start, Cmp.End);
331+
const bool IsPathEmpty = SimplifiedPath.empty();
332+
SimplifiedPath.insert(0, NumLeadingDirUps * 3 - (IsPathEmpty ? 1 : 0), '.');
333+
for (Uint32 i = 0; i < NumLeadingDirUps - (IsPathEmpty ? 1 : 0); ++i)
334+
{
335+
SimplifiedPath[i * 3 + 2] = Slash;
336+
}
282337
}
283-
VERIFY_EXPR(SimplifiedPath.length() == Len);
284338

285339
return SimplifiedPath;
286340
}

Tests/DiligentCoreTest/src/Platforms/FileSystemTest.cpp

Lines changed: 88 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,9 @@ TEST(Platforms_FileSystem, SimplifyPath)
173173
EXPECT_STREQ(FileSystem::SimplifyPath("\\", '\\').c_str(), "");
174174

175175
EXPECT_STREQ(FileSystem::SimplifyPath("//", '/').c_str(), "/");
176-
EXPECT_STREQ(FileSystem::SimplifyPath("//", '\\').c_str(), "");
176+
EXPECT_STREQ(FileSystem::SimplifyPath("//", '\\').c_str(), "\\\\"); // UNC path
177177
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\", '/').c_str(), "/");
178-
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\", '\\').c_str(), "");
178+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\", '\\').c_str(), "\\\\"); // UNC path
179179

180180
EXPECT_STREQ(FileSystem::SimplifyPath("a/", '/').c_str(), "a");
181181
EXPECT_STREQ(FileSystem::SimplifyPath("a/", '\\').c_str(), "a");
@@ -188,15 +188,20 @@ TEST(Platforms_FileSystem, SimplifyPath)
188188
EXPECT_STREQ(FileSystem::SimplifyPath("\\a", '\\').c_str(), "a");
189189

190190
EXPECT_STREQ(FileSystem::SimplifyPath("//a", '/').c_str(), "/a");
191-
EXPECT_STREQ(FileSystem::SimplifyPath("//a", '\\').c_str(), "a");
191+
EXPECT_STREQ(FileSystem::SimplifyPath("//a", '\\').c_str(), "\\\\a"); // UNC path
192192
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\a", '/').c_str(), "/a");
193-
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\a", '\\').c_str(), "a");
193+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\a", '\\').c_str(), "\\\\a"); // UNC path
194194

195195
EXPECT_STREQ(FileSystem::SimplifyPath("/a/", '/').c_str(), "/a");
196196
EXPECT_STREQ(FileSystem::SimplifyPath("/a/", '\\').c_str(), "a");
197197
EXPECT_STREQ(FileSystem::SimplifyPath("\\a/", '/').c_str(), "/a");
198198
EXPECT_STREQ(FileSystem::SimplifyPath("\\a/", '\\').c_str(), "a");
199199

200+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/", '/').c_str(), "c:");
201+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\", '/').c_str(), "c:");
202+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/", '\\').c_str(), "c:");
203+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\", '\\').c_str(), "c:");
204+
200205
EXPECT_STREQ(FileSystem::SimplifyPath("a/b", '/').c_str(), "a/b");
201206
EXPECT_STREQ(FileSystem::SimplifyPath("a\\b", '/').c_str(), "a/b");
202207
EXPECT_STREQ(FileSystem::SimplifyPath("a/b", '\\').c_str(), "a\\b");
@@ -211,6 +216,34 @@ TEST(Platforms_FileSystem, SimplifyPath)
211216
EXPECT_STREQ(FileSystem::SimplifyPath("a/./b", '\\').c_str(), "a\\b");
212217
EXPECT_STREQ(FileSystem::SimplifyPath("a\\.\\b", '\\').c_str(), "a\\b");
213218

219+
EXPECT_STREQ(FileSystem::SimplifyPath("a/.//b", '/').c_str(), "a/b");
220+
EXPECT_STREQ(FileSystem::SimplifyPath("a\\.\\\\b", '/').c_str(), "a/b");
221+
EXPECT_STREQ(FileSystem::SimplifyPath("a/.//b", '\\').c_str(), "a\\b");
222+
EXPECT_STREQ(FileSystem::SimplifyPath("a\\.\\\\b", '\\').c_str(), "a\\b");
223+
224+
EXPECT_STREQ(FileSystem::SimplifyPath("a//./b", '/').c_str(), "a/b");
225+
EXPECT_STREQ(FileSystem::SimplifyPath("a\\\\.\\b", '/').c_str(), "a/b");
226+
EXPECT_STREQ(FileSystem::SimplifyPath("a//./b", '\\').c_str(), "a\\b");
227+
EXPECT_STREQ(FileSystem::SimplifyPath("a\\\\.\\b", '\\').c_str(), "a\\b");
228+
229+
EXPECT_STREQ(FileSystem::SimplifyPath(".", '/').c_str(), "");
230+
EXPECT_STREQ(FileSystem::SimplifyPath(".", '\\').c_str(), "");
231+
232+
EXPECT_STREQ(FileSystem::SimplifyPath("./", '/').c_str(), "");
233+
EXPECT_STREQ(FileSystem::SimplifyPath(".\\", '/').c_str(), "");
234+
EXPECT_STREQ(FileSystem::SimplifyPath("./", '\\').c_str(), "");
235+
EXPECT_STREQ(FileSystem::SimplifyPath(".\\", '\\').c_str(), "");
236+
237+
EXPECT_STREQ(FileSystem::SimplifyPath("/.", '/').c_str(), "/");
238+
EXPECT_STREQ(FileSystem::SimplifyPath("\\.", '/').c_str(), "/");
239+
EXPECT_STREQ(FileSystem::SimplifyPath("/.", '\\').c_str(), "");
240+
EXPECT_STREQ(FileSystem::SimplifyPath("\\.", '\\').c_str(), "");
241+
242+
EXPECT_STREQ(FileSystem::SimplifyPath("/./", '/').c_str(), "/");
243+
EXPECT_STREQ(FileSystem::SimplifyPath("\\.\\", '/').c_str(), "/");
244+
EXPECT_STREQ(FileSystem::SimplifyPath("/./", '\\').c_str(), "");
245+
EXPECT_STREQ(FileSystem::SimplifyPath("\\.\\", '\\').c_str(), "");
246+
214247
EXPECT_STREQ(FileSystem::SimplifyPath("./a", '/').c_str(), "a");
215248
EXPECT_STREQ(FileSystem::SimplifyPath(".\\a", '/').c_str(), "a");
216249
EXPECT_STREQ(FileSystem::SimplifyPath("./a", '\\').c_str(), "a");
@@ -272,6 +305,57 @@ TEST(Platforms_FileSystem, SimplifyPath)
272305
EXPECT_STREQ(FileSystem::SimplifyPath("..\\..", '/').c_str(), "../..");
273306
EXPECT_STREQ(FileSystem::SimplifyPath("../..", '\\').c_str(), "..\\..");
274307
EXPECT_STREQ(FileSystem::SimplifyPath("..\\..", '\\').c_str(), "..\\..");
308+
309+
EXPECT_STREQ(FileSystem::SimplifyPath("/../..", '/').c_str(), "/");
310+
EXPECT_STREQ(FileSystem::SimplifyPath("\\..\\..", '/').c_str(), "/");
311+
EXPECT_STREQ(FileSystem::SimplifyPath("/../..", '\\').c_str(), "..\\..");
312+
EXPECT_STREQ(FileSystem::SimplifyPath("\\..\\..", '\\').c_str(), "..\\..");
313+
314+
EXPECT_STREQ(FileSystem::SimplifyPath("/../../a", '/').c_str(), "/a");
315+
EXPECT_STREQ(FileSystem::SimplifyPath("\\..\\..\\a", '/').c_str(), "/a");
316+
EXPECT_STREQ(FileSystem::SimplifyPath("/../../a", '\\').c_str(), "..\\..\\a");
317+
EXPECT_STREQ(FileSystem::SimplifyPath("\\..\\..\\a", '\\').c_str(), "..\\..\\a");
318+
319+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/../..", '/').c_str(), "..");
320+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\..\\..", '/').c_str(), "..");
321+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/../..", '\\').c_str(), "c:");
322+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\..\\..", '\\').c_str(), "c:");
323+
324+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/../../a", '/').c_str(), "../a");
325+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\..\\..\\a", '/').c_str(), "../a");
326+
EXPECT_STREQ(FileSystem::SimplifyPath("c:/../../a", '\\').c_str(), "c:\\a");
327+
EXPECT_STREQ(FileSystem::SimplifyPath("c:\\..\\..\\a", '\\').c_str(), "c:\\a");
328+
329+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/../..", '/').c_str(), "/");
330+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\server\\..\\..", '/').c_str(), "/");
331+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/../..", '\\').c_str(), "\\\\server");
332+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\server\\..\\..", '\\').c_str(), "\\\\server");
333+
334+
EXPECT_STREQ(FileSystem::SimplifyPath("/a/..", '/').c_str(), "/");
335+
EXPECT_STREQ(FileSystem::SimplifyPath("\\a\\..", '/').c_str(), "/");
336+
EXPECT_STREQ(FileSystem::SimplifyPath("/a/..", '\\').c_str(), "");
337+
EXPECT_STREQ(FileSystem::SimplifyPath("\\a\\..", '\\').c_str(), "");
338+
339+
// Drive-relative paths are treated as drive-rooted
340+
EXPECT_STREQ(FileSystem::SimplifyPath("c:foo", '\\').c_str(), "c:\\foo");
341+
EXPECT_STREQ(FileSystem::SimplifyPath("c:.\\foo", '\\').c_str(), "c:\\foo");
342+
EXPECT_STREQ(FileSystem::SimplifyPath("c:..\\foo", '\\').c_str(), "c:\\foo");
343+
344+
// UNC with share name
345+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share", '/').c_str(), "/server/share");
346+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share", '\\').c_str(), "\\\\server\\share");
347+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/..", '/').c_str(), "/server");
348+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/..", '\\').c_str(), "\\\\server");
349+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/../a", '/').c_str(), "/server/a");
350+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/../a", '\\').c_str(), "\\\\server\\a");
351+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/../../a", '/').c_str(), "/a");
352+
EXPECT_STREQ(FileSystem::SimplifyPath("//server/share/../../a", '\\').c_str(), "\\\\server\\a");
353+
354+
// Mixed slashes in UNC paths
355+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\server/share", '/').c_str(), "/server/share");
356+
EXPECT_STREQ(FileSystem::SimplifyPath("\\\\server/share", '\\').c_str(), "\\\\server\\share");
357+
EXPECT_STREQ(FileSystem::SimplifyPath("//server\\share/./a", '/').c_str(), "/server/share/a");
358+
EXPECT_STREQ(FileSystem::SimplifyPath("//server\\share/./a", '\\').c_str(), "\\\\server\\share\\a");
275359
}
276360

277361
TEST(Platforms_FileSystem, SplitPathList)

0 commit comments

Comments
 (0)