Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

BurstStringSearch.cs 14KB

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