A game about forced loneliness, made by TACStudios
at master 391 lines 14 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Text.RegularExpressions; 4 5 6namespace Unity.Burst.Editor 7{ 8 internal struct SearchCriteria 9 { 10 internal string filter; 11 internal bool isCaseSensitive; 12 internal bool isWholeWords; 13 internal bool isRegex; 14 15 internal SearchCriteria(string keyword, bool caseSensitive, bool wholeWord, bool regex) 16 { 17 filter = keyword; 18 isCaseSensitive = caseSensitive; 19 isWholeWords = wholeWord; 20 isRegex = regex; 21 } 22 23 internal bool Equals(SearchCriteria obj) => 24 filter == obj.filter && isCaseSensitive == obj.isCaseSensitive && isWholeWords == obj.isWholeWords && isRegex == obj.isRegex; 25 26 public override bool Equals(object obj) => 27 obj is SearchCriteria other && Equals(other); 28 29 public override int GetHashCode() => base.GetHashCode(); 30 } 31 32 internal static class BurstStringSearch 33 { 34 /// <summary> 35 /// Gets index of line end in given string, both absolute and relative to start of line. 36 /// </summary> 37 /// <param name="str">String to search in.</param> 38 /// <param name="line">Line to get end index of.</param> 39 /// <returns>(absolute line end index of string, line end index relative to line start).</returns> 40 /// <exception cref="ArgumentOutOfRangeException"> 41 /// Argument must be greater than 0 and less than or equal to number of lines in 42 /// <paramref name="str" />. 43 /// </exception> 44 internal static (int total, int relative) GetEndIndexOfPlainLine (string str, int line) 45 { 46 var lastIdx = -1; 47 var newIdx = -1; 48 49 for (var i = 0; i <= line; i++) 50 { 51 lastIdx = newIdx; 52 newIdx = str.IndexOf('\n', lastIdx + 1); 53 54 if (newIdx == -1 && i < line) 55 { 56 throw new ArgumentOutOfRangeException(nameof(line), 57 "Argument must be greater than 0 and less than or equal to number of lines in str."); 58 } 59 } 60 lastIdx++; 61 return newIdx != -1 ? (newIdx, newIdx - lastIdx) : (str.Length - 1, str.Length - 1 - lastIdx); 62 } 63 64 /// <summary> 65 /// Gets index of line end in given string, both absolute and relative to start of line. 66 /// Adjusts the index so color tags are not included in relative index. 67 /// </summary> 68 /// <param name="str">String to search in.</param> 69 /// <param name="line">Line to find end of in string.</param> 70 /// <returns>(absolute line end index of string, line end index relative to line start adjusted for color tags).</returns> 71 internal static (int total, int relative) GetEndIndexOfColoredLine(string str, int line) 72 { 73 var (total, relative) = GetEndIndexOfPlainLine(str, line); 74 return RemoveColorTagFromIdx(str, total, relative); 75 } 76 77 /// <summary> 78 /// Adjusts index of color tags on line. 79 /// </summary> 80 /// <remarks>Assumes that <see cref="tidx"/> is index of something not a color tag.</remarks> 81 /// <param name="str">String containing the indexes.</param> 82 /// <param name="tidx">Total index of line end.</param> 83 /// <param name="ridx">Relative index of line end.</param> 84 /// <returns>(<see cref="tidx"/>, <see cref="ridx"/>) adjusted for color tags on line.</returns> 85 private static (int total, int relative) RemoveColorTagFromIdx(string str, int tidx, int ridx) 86 { 87 var lineStartIdx = tidx - ridx; 88 var colorTagFiller = 0; 89 90 var tmp = str.LastIndexOf("</color", tidx); 91 var lastWasStart = true; 92 var colorTagStart = str.LastIndexOf("<color=", tidx); 93 94 if (tmp > colorTagStart) 95 { 96 // color tag end was closest 97 lastWasStart = false; 98 colorTagStart = tmp; 99 } 100 101 while (colorTagStart != -1 && colorTagStart >= lineStartIdx) 102 { 103 var colorTagEnd = str.IndexOf('>', colorTagStart); 104 // +1 as the index is zero based. 105 colorTagFiller += colorTagEnd - colorTagStart + 1; 106 107 if (lastWasStart) 108 { 109 colorTagStart = str.LastIndexOf("</color", colorTagStart); 110 lastWasStart = false; 111 } 112 else 113 { 114 colorTagStart = str.LastIndexOf("<color=", colorTagStart); 115 lastWasStart = true; 116 } 117 } 118 return (tidx - colorTagFiller, ridx - colorTagFiller); 119 } 120 121 /// <summary> 122 /// Finds the zero indexed line number of given <see cref="matchIdx"/>. 123 /// </summary> 124 /// <param name="str">String to search in.</param> 125 /// <param name="matchIdx">Index to find line number of.</param> 126 /// <returns>Line number of given index in string.</returns> 127 internal static int FindLineNr(string str, int matchIdx) 128 { 129 var lineNr = 0; 130 var idxn = str.IndexOf('\n'); 131 132 while (idxn != -1 && idxn < matchIdx) 133 { 134 lineNr++; 135 idxn = str.IndexOf('\n', idxn + 1); 136 } 137 138 return lineNr; 139 } 140 141 /// <summary> 142 /// Finds first match of <see cref="criteria"/> in given string. 143 /// </summary> 144 /// <param name="str">String to search in.</param> 145 /// <param name="criteria">Search options.</param> 146 /// <param name="regx">Used when <see cref="criteria"/> specifies regex search.</param> 147 /// <param name="startIdx">Index to start the search at.</param> 148 /// <returns>(start index of match, length of match)</returns> 149 internal static (int idx, int length) FindMatch(string str, SearchCriteria criteria, Regex regx, int startIdx = 0) 150 { 151 var idx = -1; 152 var len = 0; 153 154 if (criteria.isRegex) 155 { 156 // regex will have the appropriate options in it if isCaseSensitive or/and isWholeWords is true. 157 var res = regx.Match(str, startIdx); 158 159 if (res.Success) (idx, len) = (res.Index, res.Length); 160 } 161 else if (criteria.isWholeWords) 162 { 163 (idx, len) = (IndexOfWholeWord(str, startIdx, criteria.filter, criteria.isCaseSensitive 164 ? StringComparison.InvariantCulture 165 : StringComparison.InvariantCultureIgnoreCase), criteria.filter.Length); 166 } 167 else 168 { 169 unsafe 170 { 171 fixed (char* source = str) 172 { 173 fixed (char* target = criteria.filter) 174 { 175 (idx, len) = ( 176 IndexOfCustom(source, str.Length, target, criteria.filter.Length, startIdx, criteria.isCaseSensitive) 177 , criteria.filter.Length); 178 } 179 } 180 } 181 } 182 183 return (idx, len); 184 } 185 186 internal static List<(int idx, int length)> FindAllMatches(string str, SearchCriteria criteria, Regex regx, 187 int startIdx = 0) 188 { 189 var retVal = new List<(int, int)>(); 190 191 if (criteria.isRegex) 192 { 193 var res = regx.Matches(str, startIdx); 194 195 foreach (Match match in res) 196 { 197 retVal.Add((match.Index, match.Length)); 198 } 199 } 200 else if (criteria.isWholeWords) 201 { 202 retVal.AddRange(IndexOfWholeWordAll(str, startIdx, criteria.filter, 203 criteria.isCaseSensitive 204 ? StringComparison.InvariantCulture 205 : StringComparison.CurrentCultureIgnoreCase)); 206 } 207 else 208 { 209 unsafe 210 { 211 fixed (char* source = str) 212 { 213 fixed (char* target = criteria.filter) 214 { 215 retVal.AddRange(FindAllIndices(source, str.Length, target, criteria.filter.Length, startIdx, criteria.isCaseSensitive)); 216 } 217 } 218 } 219 } 220 221 return retVal; 222 } 223 224 private static char ToUpper(char c) => c - 97U > 25U ? c : (char)(c - 32U); 225 226 private static unsafe int ScanForFilterInsensitive(char* str, char* filter, int flen, int i) 227 { 228 int j = 0; 229 while (j < flen && ToUpper(str[i + j]) == ToUpper(filter[j])) 230 { 231 j++; 232 } 233 return j; 234 } 235 236 private static unsafe int ScanForFilter(char* str, char* filter, int flen, int i) 237 { 238 int j = 0; 239 while (j < flen && str[i + j] == filter[j]) 240 { 241 j++; 242 } 243 return j; 244 } 245 246 private static unsafe List<(int, int)> FindAllIndices(char* str, int len, char* filter, int flen, int startIdx, bool caseSensitive) 247 { 248 var retVal = new List<(int,int)>(); 249 if (len < flen) { return retVal; } 250 251 int stop = len - flen; 252 if (caseSensitive) 253 { 254 for (int i = startIdx; i < stop; i++) 255 { 256 if (ScanForFilter(str, filter, flen, i) == flen) 257 { 258 retVal.Add((i, flen)); 259 i += flen - 1; 260 } 261 } 262 } 263 else 264 { 265 for (int i = startIdx; i < stop; i++) 266 { 267 if (ScanForFilterInsensitive(str, filter, flen, i) == flen) 268 { 269 retVal.Add((i, flen)); 270 i += flen-1; 271 } 272 } 273 } 274 return retVal; 275 } 276 277 278 /// <summary> 279 /// Finds index of first occurence of <see cref="filter"/> in <see cref="str"/>. 280 /// </summary> 281 /// <param name="str">String to search through</param> 282 /// <param name="len">Length of <see cref="str"/></param> 283 /// <param name="filter">Needle to find</param> 284 /// <param name="flen">Lenght of <see cref="filter"/></param> 285 /// <param name="startIdx">Index to start search from</param> 286 /// <param name="caseSensitive">Whether search ignore casing</param> 287 /// <returns>index of first match or -1</returns> 288 private static unsafe int IndexOfCustom(char* str, int len, char* filter, int flen, int startIdx, bool caseSensitive) 289 { 290 if (len < flen) { return -1; } 291 292 int stop = len - flen; 293 if (caseSensitive) 294 { 295 for (int i = startIdx; i < stop; i++) 296 { 297 if (ScanForFilter(str, filter, flen, i) == flen) 298 { 299 return i; 300 } 301 } 302 } 303 else 304 { 305 for (int i = startIdx; i < stop; i++) 306 { 307 if (ScanForFilterInsensitive(str, filter, flen, i) == flen) 308 { 309 return i; 310 } 311 } 312 } 313 314 return -1; 315 } 316 317 /// <summary> 318 /// Finds index of <see cref="filter"/> matching for whole words. 319 /// </summary> 320 /// <param name="str">String to search in.</param> 321 /// <param name="startIdx">Index to start search from.</param> 322 /// <param name="filter">Key to search for.</param> 323 /// <param name="opt">Options for string comparison.</param> 324 /// <returns>Index of match or -1.</returns> 325 private static int IndexOfWholeWord(string str, int startIdx, string filter, StringComparison opt) 326 { 327 const string wholeWordMatch = @"\w"; 328 329 var j = startIdx; 330 var filterLen = filter.Length; 331 var strLen = str.Length; 332 while (j < strLen && (j = str.IndexOf(filter, j, opt)) >= 0) 333 { 334 var noPrior = true; 335 if (j != 0) 336 { 337 var frontBorder = str[j - 1]; 338 noPrior = !Regex.IsMatch(frontBorder.ToString(), wholeWordMatch); 339 } 340 341 var noAfter = true; 342 if (j + filterLen != strLen) 343 { 344 var endBorder = str[j + filterLen]; 345 noAfter = !Regex.IsMatch(endBorder.ToString(), wholeWordMatch); 346 } 347 348 if (noPrior && noAfter) return j; 349 350 j++; 351 } 352 return -1; 353 } 354 355 356 private static List<(int idx, int len)> IndexOfWholeWordAll(string str, int startIdx, string filter, StringComparison opt) 357 { 358 const string wholeWordMatch = @"\w"; 359 var retVal = new List<(int, int)>(); 360 361 var j = startIdx; 362 var filterLen = filter.Length; 363 var strLen = str.Length; 364 while (j < strLen && (j = str.IndexOf(filter, j, opt)) >= 0) 365 { 366 var noPrior = true; 367 if (j != 0) 368 { 369 var frontBorder = str[j - 1]; 370 noPrior = !Regex.IsMatch(frontBorder.ToString(), wholeWordMatch); 371 } 372 373 var noAfter = true; 374 if (j + filterLen != strLen) 375 { 376 var endBorder = str[j + filterLen]; 377 noAfter = !Regex.IsMatch(endBorder.ToString(), wholeWordMatch); 378 } 379 380 if (noPrior && noAfter) 381 { 382 retVal.Add((j, filterLen)); 383 j += filterLen - 1; 384 } 385 386 j++; 387 } 388 return retVal; 389 } 390 } 391}