aho-corasick.ts 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. const WORDEND = Symbol('wordEnd');
  2. const FAIL = Symbol('fail');
  3. type Node = Map<string, Node> & {
  4. [WORDEND]: boolean,
  5. [FAIL]: Node | undefined
  6. };
  7. const createNode = (): Node => {
  8. const node = new Map<string, Node | undefined>() as Node;
  9. node[WORDEND] = false;
  10. node[FAIL] = undefined;
  11. return node;
  12. };
  13. const deepNodeToJSON = (node: Node, wset: WeakSet<Node>) => {
  14. if (wset.has(node)) {
  15. return 'circular';
  16. }
  17. wset.add(node);
  18. const obj: Record<string, any> = {};
  19. if (node[WORDEND]) {
  20. obj['[end]'] = node[WORDEND];
  21. }
  22. node.forEach((value, key) => {
  23. obj[key] = deepNodeToJSON(value, wset);
  24. });
  25. return obj;
  26. };
  27. function createNodeInspectCustom(node: Node) {
  28. const wset = new WeakSet<Node>();
  29. return () => {
  30. try {
  31. return JSON.stringify(deepNodeToJSON(node, wset), null, 2);
  32. } catch (e) {
  33. console.error(e);
  34. return '';
  35. }
  36. };
  37. }
  38. const createKeywordFilter = (keys: string[] | Set<string>) => {
  39. const root = createNode();
  40. // Create a trie with extra fields and information
  41. const put = (key: string) => {
  42. const len = key.length;
  43. let node = root;
  44. for (let idx = 0; idx < len; idx++) {
  45. const char = key[idx];
  46. if (node.has(char)) {
  47. node = node.get(char)!;
  48. } else {
  49. const newNode = createNode();
  50. node.set(char, newNode);
  51. node = newNode;
  52. }
  53. }
  54. // If a new node is created, mark it as a word end when loop finish
  55. if (node !== root) {
  56. node[WORDEND] = true;
  57. }
  58. };
  59. keys.forEach(put);
  60. // const build = () => {
  61. const queue: Node[] = [root];
  62. while (queue.length) {
  63. const beginNode = queue.pop()!;
  64. beginNode.forEach((node, char) => {
  65. let failNode = beginNode[FAIL];
  66. while (failNode && !failNode.has(char)) {
  67. failNode = failNode[FAIL];
  68. }
  69. node[FAIL] = failNode ? failNode.get(char) : root;
  70. queue.push(node);
  71. });
  72. }
  73. // };
  74. // build();
  75. const tester = (text: string) => {
  76. let node: Node | undefined = root;
  77. for (let i = 0, textLen = text.length; i < textLen; i++) {
  78. const char = text[i];
  79. while (node && !node.has(char)) {
  80. node = node[FAIL];
  81. }
  82. node = node ? node.get(char)! : root;
  83. if (node[WORDEND]) {
  84. return true;
  85. }
  86. }
  87. return false;
  88. };
  89. tester[Bun.inspect.custom] = createNodeInspectCustom(root);
  90. return tester;
  91. };
  92. export default createKeywordFilter;