| // Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /// This library contains helper methods and generated methods for effecient |
| /// string searching using a combination of BMH and KMP. |
| /// |
| /// Method bodies - including the "magic tables" are generated and will be |
| /// recreated when the tool |
| /// `pkg/analyzer/tool/generators/string_search_util_generator.dart` |
| /// is run. |
| /// |
| /// With the implementation it can only search for ascii characters and |
| /// the generator will crash if trying to generate for non-ascii characters. |
| /// Matching against a string with non-ascii characters is fine. |
| /// This is not considered a defect, but a tradeoff. |
| /// |
| /// Adding new things to search for can be done by adding a new class with |
| /// new static members with an annotation like |
| /// `@_GeneratedSearchData('myNeedle')` |
| library; |
| |
| /// Searches for [needle] in [haystack] starting from [offset]. |
| /// |
| /// Expects [bmhTable] and [kmpTable] to be correctly precomputed const lists. |
| /// |
| /// This is a combination of Boyer–Moore–Horspool (BMH) |
| /// (https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm) |
| /// and Knuth–Morris–Pratt (KMP) |
| /// (https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm) |
| /// where BMH often has sub-linear performance by skipping (up to) needle-size |
| /// chunks when the last character doesn't match (but is worse-case O(n*m)) and |
| /// KMP runs in worst-case O(n) (with n being the length of [haystack] and m |
| /// being the length of [needle]). |
| int _combinedBmhAndKmp( |
| List<int> bmhTable, |
| List<int> kmpTable, |
| String haystack, |
| String needle, |
| int offset, |
| ) { |
| int skip = offset; |
| int lastNeedleChar = needle.codeUnitAt(needle.length - 1); |
| outerLoop: |
| while (haystack.length - skip >= needle.length) { |
| // BMH: Try to match the last char: If it doesn't match we can skip |
| // something. |
| int haystackChar = haystack.codeUnitAt(skip + needle.length - 1); |
| while (lastNeedleChar != haystackChar) { |
| if (haystackChar < bmhTable.length) { |
| skip += bmhTable[haystackChar]; |
| } else { |
| skip += needle.length; |
| } |
| int nextIndex = skip + needle.length - 1; |
| if (nextIndex >= haystack.length) return -1; |
| haystackChar = haystack.codeUnitAt(nextIndex); |
| } |
| |
| // Matches on the last char. We switch to KMP. |
| int k = 0; |
| while (skip < haystack.length) { |
| if (needle.codeUnitAt(k) == haystack.codeUnitAt(skip)) { |
| skip++; |
| k++; |
| if (k == needle.length) { |
| return skip - k; |
| } |
| } else { |
| k = kmpTable[k]; |
| if (k < 0) { |
| skip++; |
| // Here KMP would set k to 0 and thus start over searching from |
| // scratch at position skip in haystack. We instead continue with BMH. |
| continue outerLoop; |
| } |
| } |
| } |
| } |
| return -1; |
| } |
| |
| class DartdocDirectiveSearchHelper { |
| // Don't instantiate. |
| DartdocDirectiveSearchHelper._(); |
| |
| @_GeneratedSearchData('{@endtemplate}', addNeedleLength: true) |
| static int nextAtEndtemplate(String haystack, int offset) { |
| const String needle = '{@endtemplate}'; |
| const List<int> bmhTable = [ |
| // Format hack. |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 12, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 14, 14, 14, 14, 14, 14, 14, |
| 14, 3, 14, 14, 9, 1, 14, 14, |
| 14, 14, 14, 14, 4, 6, 10, 14, |
| 5, 14, 14, 14, 2, 14, 14, 14, |
| 14, 14, 14, 13, 14, 14, 14, 14, |
| ]; |
| const List<int> kmpTable = [ |
| // Format hack. |
| -1, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, |
| ]; |
| int result = _combinedBmhAndKmp( |
| bmhTable, |
| kmpTable, |
| haystack, |
| needle, |
| offset, |
| ); |
| if (result >= 0) result += needle.length; |
| return result; |
| } |
| |
| @_GeneratedSearchData('{@template', addNeedleLength: true) |
| static int nextAtTemplate(String haystack, int offset) { |
| const String needle = '{@template'; |
| const List<int> bmhTable = [ |
| // Format hack. |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 8, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 10, 10, 10, 10, 10, 10, 10, |
| 10, 2, 10, 10, 10, 6, 10, 10, |
| 10, 10, 10, 10, 3, 5, 10, 10, |
| 4, 10, 10, 10, 1, 10, 10, 10, |
| 10, 10, 10, 9, 10, 10, 10, 10, |
| ]; |
| const List<int> kmpTable = [ |
| // Format hack. |
| -1, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, |
| ]; |
| int result = _combinedBmhAndKmp( |
| bmhTable, |
| kmpTable, |
| haystack, |
| needle, |
| offset, |
| ); |
| if (result >= 0) result += needle.length; |
| return result; |
| } |
| } |
| |
| class _GeneratedSearchData { |
| final String needle; |
| final bool addNeedleLength; |
| |
| const _GeneratedSearchData(this.needle, {this.addNeedleLength = false}); |
| } |