diff --git a/Build/lib/fifo.ts b/Build/lib/fifo.ts new file mode 100644 index 00000000..7f0a9091 --- /dev/null +++ b/Build/lib/fifo.ts @@ -0,0 +1,63 @@ +class Node { + next?: Node; + + constructor(public readonly value: T) {} +} + +export default class FIFO { + private head?: Node; + private tail?: Node; + public $size = 0; + + constructor() { + this.clear(); + } + + enqueue(value: T) { + const node = new Node(value); + + if (this.head) { + this.tail!.next = node; + this.tail = node; + } else { + this.head = node; + this.tail = node; + } + + this.$size++; + } + + dequeue() { + const current = this.head; + if (!current) { + return; + } + + this.head = this.head!.next; + this.$size--; + return current.value; + } + + peek() { + return this.head?.value; + } + + clear() { + this.head = undefined; + this.tail = undefined; + this.$size = 0; + } + + get size() { + return this.$size; + } + + *[Symbol.iterator]() { + let current = this.head; + + while (current) { + yield current.value; + current = current.next; + } + } +} diff --git a/Build/lib/rules/domainset.ts b/Build/lib/rules/domainset.ts index 4265ccff..9370c431 100644 --- a/Build/lib/rules/domainset.ts +++ b/Build/lib/rules/domainset.ts @@ -1,6 +1,6 @@ import { invariant } from 'foxact/invariant'; import createKeywordFilter from '../aho-corasick'; -import { buildParseDomainMap, sortDomains } from '../stable-sort-domain'; +import { buildParseDomainMap } from '../stable-sort-domain'; import { RuleOutput } from './base'; import type { SingboxSourceFormat } from '../singbox'; import { appendArrayFromSet } from '../misc'; @@ -27,7 +27,7 @@ export class DomainsetOutput extends RuleOutput { this.apexDomainMap = domainMap; this.subDomainMap = subdomainMap; } - const sorted = sortDomains(results, this.apexDomainMap, this.subDomainMap); + const sorted = results; sorted.push('this_ruleset_is_made_by_sukkaw.ruleset.skk.moe'); return sorted; diff --git a/Build/lib/trie.ts b/Build/lib/trie.ts index 65507863..62aa9bf9 100644 --- a/Build/lib/trie.ts +++ b/Build/lib/trie.ts @@ -5,6 +5,7 @@ import { fastStringArrayJoin } from './misc'; import util from 'node:util'; import { noop } from 'foxact/noop'; +import FIFO from './fifo'; type TrieNode = [ boolean, /** end */ @@ -205,6 +206,49 @@ abstract class Triebase { } while (nodeStack.length); }; + static compare(this: void, a: string, b: string) { + if (a === b) return 0; + return (a.length - b.length) || a.localeCompare(b); + } + + private walkWithSort( + onMatches: (suffix: string[], subdomain: boolean, meta: Meta) => void, + initialNode = this.$root, + initialSuffix: string[] = [] + ) { + const nodeStack = new FIFO>(); + nodeStack.enqueue(initialNode); + + // Resolving initial string (begin the start of the stack) + const suffixStack = new FIFO(); + suffixStack.enqueue(initialSuffix); + + let node: TrieNode = initialNode; + + do { + node = nodeStack.dequeue()!; + const suffix = suffixStack.dequeue()!; + + if (node[3].size) { + const keys = Array.from(node[3].keys()).sort(Triebase.compare); + + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + const childNode = node[3].get(key)!; + + // Pushing the child node to the stack for next iteration of DFS + nodeStack.enqueue(childNode); + suffixStack.enqueue([key, ...suffix]); + } + } + + // If the node is a sentinel, we push the suffix to the results + if (node[0]) { + onMatches(suffix, node[1], node[4]); + } + } while (nodeStack.size); + }; + protected getSingleChildLeaf(tokens: string[]): FindSingleChildLeafResult | null { let toPrune: TrieNode | null = null; let tokenToPrune: string | null = null; @@ -331,7 +375,7 @@ abstract class Triebase { results.push(subdomain ? '.' + d : d); }; - this.walk(handleSuffix); + this.walkWithSort(handleSuffix); return results; };