/////////////////////////////////////////////////////////////////////////////// // // Microsoft Research Singularity // // Copyright (c) Microsoft Corporation. All rights reserved. // // File: PathUtils.sg // // Note: // // Character vector utilities. // using Microsoft.Singularity.Channels; using Microsoft.Singularity.Directory; using Microsoft.SingSharp; using System; namespace Microsoft.Singularity.Services.Fat.Fs { public class PathUtils { public const char Separator = '/'; /// Remove duplicate and trailing separators /// from character vector representing a path. A leading /// separator is preserved. public static char[]! in ExHeap CleanSeparators([Claims] char[]! in ExHeap path) { int src = 0; int dst = 0; while (src < path.Length) { bool skip = (path[src] == Separator); path[dst++] = path[src++]; if (skip) { while (src < path.Length && path[src] == Separator) { src++; } } } if (dst > 1 && path[dst - 1] == Separator) { // Chop trailing separator if not root separator dst--; } if (dst != src) { Bitter.Truncate(ref path, dst); } return path; } private static char[]! in ExHeap DropTrailingSeparator([Claims] char[]! in ExHeap path) { if (path.Length > 1 && path[path.Length - 1] == Separator) { Bitter.Truncate(ref path, path.Length - 1); } return path; } private static int GetLastSeparator(char[]! in ExHeap path) { int i = path.Length - 1; while (i >= 0 && path[i] != Separator) { i--; } return i; } private static int GetNextSeparator(char[]! in ExHeap path, int start) { while (start < path.Length) { if (path[start] == Separator) { return start; } start++; } return -1; } /// /// Splits path into top most path component and remainder. /// /// path top remainder /// ---------- ------------------------ /// "/a/b/c" "/" "a/b/c" /// "a/b/c" "a" "b/c" /// "/a" "/" "a" /// "a" "a" null /// "/" "/" null /// "//" "/" null /// "///a/b//" "/" "a/b" /// public static void SplitPathTopDown([Claims] char[]! in ExHeap path, out char[] in ExHeap top, out char[] in ExHeap remainder) requires path.Length >= 1; { path = CleanSeparators(path); if (path[0] == Separator) { if (path.Length == 1) { top = path; remainder = null; } else { remainder = Bitter.SplitOff(ref path, 1); top = path; if (remainder.Length == 0) { delete remainder; remainder = null; } } } else { int nextSep = GetNextSeparator(path, 0); if (nextSep < 0) { top = path; remainder = null; } else { // nextSep must be less than path.Length as // CleanSeparators removes trailing // separators. assert ((path.Length >= 3) && (nextSep < path.Length - 1)); remainder = Bitter.SplitOff(ref path, nextSep + 1); top = DropTrailingSeparator(path); } } } /// /// Split path into directory name and base name components. /// /// "/a/b/c" -> "/a/b" "c" /// "/a" -> "/" "a" /// "a//" -> null "a" /// "a" -> null "a" /// "/" -> "/" null /// public static void SplitPathBottomUp([Claims] char[]! in ExHeap path, out char[] in ExHeap dirname, out char[] in ExHeap basename) { path = CleanSeparators(path); int lastSep = GetLastSeparator(path); if (lastSep >= 0) { basename = Bitter.SplitOff(ref path, lastSep + 1); if (basename.Length == 0) { delete basename; basename = null; } dirname = DropTrailingSeparator(path); } else { dirname = null; basename = path; } } private static char[]! in ExHeap TrimInternal( [Claims] char[]! in ExHeap vector, bool trimStart, bool trimEnd ) { int lower = 0; int upper = vector.Length - 1; if (trimEnd) { while (upper > 0 && Char.IsWhiteSpace(vector[upper])) { upper--; } } if (trimStart) { while (lower <= upper && Char.IsWhiteSpace(vector[lower])) { lower++; } } int newLength = upper - lower + 1; if (newLength != vector.Length) { Bitter.Copy(vector, 0, newLength, vector, lower); Bitter.Truncate(ref vector, newLength); } return vector; } private static char[]! in ExHeap TrimInternal( [Claims] char[]! in ExHeap vector, char []! trimChars, bool trimStart, bool trimEnd ) { int lower = 0; int upper = vector.Length - 1; if (trimEnd) { while (upper > 0 && Array.IndexOf(trimChars, vector[upper]) >= 0) { upper--; } } if (trimStart) { while (lower <= upper && Array.IndexOf(trimChars, vector[lower]) >= 0) { lower++; } } int newLength = upper - lower + 1; if (newLength != vector.Length) { Bitter.Copy(vector, 0, newLength, vector, lower); Bitter.Truncate(ref vector, newLength); } return vector; } public static char[]! in ExHeap Trim([Claims] char[]! in ExHeap vector) { return TrimInternal(vector, true, true); } public static char[]! in ExHeap TrimStart([Claims] char[]! in ExHeap vector) { return TrimInternal(vector, true, false); } public static char[]! in ExHeap TrimEnd([Claims] char[]! in ExHeap vector) { return TrimInternal(vector, false, true); } public static char[]! in ExHeap Trim([Claims] char[]! in ExHeap vector, char[]! trimChars) { return TrimInternal(vector, trimChars, true, true); } public static char[]! in ExHeap TrimStart([Claims] char[]! in ExHeap vector, char[]! trimChars) { return TrimInternal(vector, trimChars, true, false); } public static char[]! in ExHeap TrimEnd([Claims] char[]! in ExHeap vector, char[]! trimChars) { return TrimInternal(vector, trimChars, false, true); } // -------------------------------------------------------------------- // Self-test routines private static bool CompareChars(string expected, [Claims] char[] in ExHeap received) { string other = null; if (received != null) { other = Bitter.ToString2(received); delete received; } DebugStub.Assert(expected == other); return (expected == other); } private static void TestSplitPathBottomUp() { string [][] tests = new string [][] { new string [] { "/", "/", null }, new string [] { "/abra/cadabra", "/abra", "cadabra" }, new string [] { "/the///cat//sat/on//the/mat", "/the/cat/sat/on/the", "mat" }, new string [] { "/a", "/", "a" }, new string [] { "a//", null, "a" }, new string [] { "a", null, "a" }, new string [] { "////", "/", null }, new string [] { "////a", "/", "a" }, new string [] { "////a/b///", "/a", "b" }, }; for (int i = 0; i < tests.Length; i++) { string[]! values = (!)tests[i]; DebugStub.Print("Testing {0}\n", __arglist((!)values[0])); string! spath = (!)(values[0]); char[]! in ExHeap path = Bitter.FromString2(spath); char [] in ExHeap dirname; char [] in ExHeap basename; PathUtils.SplitPathBottomUp(path, out dirname, out basename); DebugStub.Assert(CompareChars(values[1], dirname)); DebugStub.Assert(CompareChars(values[2], basename)); } } private static void TestSplitPathTopDown() { string [][] tests = new string [][] { new string [] { "/abra/cadabra", "/", "abra/cadabra" }, new string [] { "abra/cadabra", "abra", "cadabra" }, new string [] { "/the///cat//sat/on//the/mat", "/", "the/cat/sat/on/the/mat" }, new string [] { "the///cat//sat/on//the/mat", "the", "cat/sat/on/the/mat" }, new string [] { "/a", "/", "a" }, new string [] { "a", "a", null }, new string [] { "/", "/", null }, new string [] { "////", "/", null }, new string [] { "////a/b///", "/", "a/b" }, }; for (int i = 0; i < tests.Length; i++) { string[]! values = (!)tests[i]; DebugStub.Print("Testing {0}\n", __arglist((!)values[0])); string! spath = (!)(values[0]); char[]! in ExHeap path = Bitter.FromString2(spath); char [] in ExHeap top; char [] in ExHeap remainder; PathUtils.SplitPathTopDown(path, out top, out remainder); DebugStub.Assert(CompareChars(values[1], top)); DebugStub.Assert(CompareChars(values[2], remainder)); } } internal static void SelfTest() { TestSplitPathTopDown(); TestSplitPathBottomUp(); } } }