diff --git a/packages/cspell-lib/src/SpellingDictionary/createSpellingDictionary.test.ts b/packages/cspell-lib/src/SpellingDictionary/createSpellingDictionary.test.ts index 1d43def0b75..d5d62b824bd 100644 --- a/packages/cspell-lib/src/SpellingDictionary/createSpellingDictionary.test.ts +++ b/packages/cspell-lib/src/SpellingDictionary/createSpellingDictionary.test.ts @@ -74,8 +74,8 @@ describe('Validate createSpellingDictionary', () => { word | ignoreCase | expected ${'Geschäft'} | ${false} | ${[c('Geschäft', 0)]} ${'Geschaft'} | ${false} | ${[c('Geschäft', 1)]} - ${'fone'} | ${false} | ${[c('phone', 70), c('gone', 100)]} - ${'failor'} | ${false} | ${[c('failure', 70), c('sailor', 100), c('failed', 175), c('fail', 200)]} + ${'fone'} | ${false} | ${[c('phone', 70), c('gone', 104)]} + ${'failor'} | ${false} | ${[c('failure', 70), c('sailor', 104), c('failed', 175), c('fail', 200)]} `('createSpellingDictionary with dictionaryInformation "$word" "$ignoreCase"', ({ word, ignoreCase, expected }) => { const words = sampleWords(); const options = { ...opts(), dictionaryInformation: sampleDictionaryInformation({}) }; diff --git a/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapDictionaryInfo.test.ts.snap b/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapDictionaryInfo.test.ts.snap index 4b11d2773a2..5fbdf7d32b8 100644 --- a/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapDictionaryInfo.test.ts.snap +++ b/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapDictionaryInfo.test.ts.snap @@ -13,8 +13,8 @@ Array [ "replace": 1, }, Object { - "map": "(^A)(^B)(^C)(^D)(^E)(^F)(^G)(^H)(^I)(^J)(^K)(^L)(^M)(^N)(^O)(^P)(^Q)(^R)(^S)(^T)(^U)(^V)(^W)(^X)(^Y)(^Z)(^a)(^b)(^c)(^d)(^e)(^f)(^g)(^h)(^i)(^j)(^k)(^l)(^m)(^n)(^o)(^p)(^q)(^r)(^s)(^t)(^u)(^v)(^w)(^x)(^y)(^z)", - "penalty": 4, + "map": "(^A)(^B)(^C)(^D)(^E)(^F)(^G)(^H)(^I)(^J)(^K)(^L)(^M)(^N)(^O)(^P)(^Q)(^R)(^S)(^T)(^U)(^V)(^W)(^X)(^Y)(^Z)(^a)(^b)(^c)(^d)(^e)(^f)(^g)(^h)(^i)(^j)(^k)(^l)(^m)(^n)(^o)(^p)(^q)(^r)(^s)(^t)(^u)(^v)(^w)(^x)(^y)(^z)(^)", + "penalty": 8, "replace": 96, }, Object { @@ -38,8 +38,8 @@ Array [ "replace": 1, }, Object { - "map": "(^a)(^b)(^c)(^d)(^e)", - "penalty": 4, + "map": "(^a)(^b)(^c)(^d)(^e)(^)", + "penalty": 8, "replace": 96, }, ] @@ -58,8 +58,8 @@ Array [ "replace": 1, }, Object { - "map": "(^A)(^B)(^C)(^D)(^E)(^a)(^b)(^c)(^d)(^e)", - "penalty": 4, + "map": "(^A)(^B)(^C)(^D)(^E)(^a)(^b)(^c)(^d)(^e)(^)", + "penalty": 8, "replace": 96, }, Object { diff --git a/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapHunspellInformation.test.ts.snap b/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapHunspellInformation.test.ts.snap index b63e40a1093..5bc20aef8b5 100644 --- a/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapHunspellInformation.test.ts.snap +++ b/packages/cspell-trie-lib/src/lib/mappers/__snapshots__/mapHunspellInformation.test.ts.snap @@ -15,8 +15,8 @@ Array [ "replace": 1, }, Object { - "map": "(^a)(^b)(^c)", - "penalty": 4, + "map": "(^a)(^b)(^c)(^)", + "penalty": 8, "replace": 96, }, ] @@ -66,8 +66,8 @@ Array [ "replace": 1, }, Object { - "map": "(^a)(^b)(^c)", - "penalty": 4, + "map": "(^a)(^b)(^c)(^)", + "penalty": 8, "replace": 96, }, Object { diff --git a/packages/cspell-trie-lib/src/lib/mappers/mapHunspellInformation.test.ts b/packages/cspell-trie-lib/src/lib/mappers/mapHunspellInformation.test.ts index 25784c01eb2..05ec36b8f83 100644 --- a/packages/cspell-trie-lib/src/lib/mappers/mapHunspellInformation.test.ts +++ b/packages/cspell-trie-lib/src/lib/mappers/mapHunspellInformation.test.ts @@ -112,9 +112,9 @@ describe('mapHunspellInformation', () => { line | costs | expected ${''} | ${{}} | ${undefined} ${'MAP aàâäAÀÂÄ'} | ${{}} | ${undefined} - ${'TRY abc'} | ${c({ mapCost: 1 })} | ${{ map: '(^a)(^b)(^c)', replace: 96, penalty: 4 }} - ${'TRY abc'} | ${c({ tryCharCost: 90 })} | ${{ map: '(^a)(^b)(^c)', replace: 86, penalty: 4 }} - ${'TRY abc'} | ${c({ firstLetterPenalty: 10 })} | ${{ map: '(^a)(^b)(^c)', replace: 90, penalty: 10 }} + ${'TRY abc'} | ${c({ mapCost: 1 })} | ${{ map: '(^a)(^b)(^c)(^)', replace: 96, penalty: 8 }} + ${'TRY abc'} | ${c({ tryCharCost: 90 })} | ${{ map: '(^a)(^b)(^c)(^)', replace: 86, penalty: 8 }} + ${'TRY abc'} | ${c({ firstLetterPenalty: 10 })} | ${{ map: '(^a)(^b)(^c)(^)', replace: 90, penalty: 20 }} `('affTryFirstCharacterReplace "$line" $costs', ({ line, costs, expected }) => { expect(affTryFirstCharacterReplace(line, calcCosts(costs))).toEqual(expected); }); diff --git a/packages/cspell-trie-lib/src/lib/mappers/mapToSuggestionCostDef.ts b/packages/cspell-trie-lib/src/lib/mappers/mapToSuggestionCostDef.ts index 2787bd97e75..8ac644f15d9 100644 --- a/packages/cspell-trie-lib/src/lib/mappers/mapToSuggestionCostDef.ts +++ b/packages/cspell-trie-lib/src/lib/mappers/mapToSuggestionCostDef.ts @@ -77,15 +77,16 @@ export function calcFirstCharacterReplaceDefs( } export function calcFirstCharacterReplace(cs: CharacterSetCosts, editCost: EditCostsRequired): SuggestionCostMapDef { - const mapOfFirstLetters = [ - ...pipe( - expandCharacterSet(cs.characters), - opUnique(), - opMap((letter) => `(^${letter})`) - ), - ] - .sort() - .join(''); + const mapOfFirstLetters = + [ + ...pipe( + expandCharacterSet(cs.characters), + opUnique(), + opMap((letter) => `(^${letter})`) + ), + ] + .sort() + .join('') + '(^)'; const penalty = editCost.firstLetterPenalty; // Make it a bit cheaper so it will match @@ -94,7 +95,7 @@ export function calcFirstCharacterReplace(cs: CharacterSetCosts, editCost: EditC return { map: mapOfFirstLetters, replace: cost, - penalty, + penalty: penalty * 2, }; } diff --git a/packages/cspell-trie-lib/src/lib/genSuggestionsOptions.ts b/packages/cspell-trie-lib/src/lib/suggestions/genSuggestionsOptions.ts similarity index 90% rename from packages/cspell-trie-lib/src/lib/genSuggestionsOptions.ts rename to packages/cspell-trie-lib/src/lib/suggestions/genSuggestionsOptions.ts index 5b9ab422044..a543f9d421d 100644 --- a/packages/cspell-trie-lib/src/lib/genSuggestionsOptions.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/genSuggestionsOptions.ts @@ -1,5 +1,5 @@ -import { WeightMap } from '.'; -import { CompoundWordsMethod } from './walker'; +import { WeightMap } from '..'; +import { CompoundWordsMethod } from '../walker'; export interface GenSuggestionOptionsStrict { /** @@ -18,6 +18,12 @@ export interface GenSuggestionOptionsStrict { * 3 is a good number. Above 5 can be very slow. */ changeLimit: number; + + /** + * Include the `+` compound character when returning results. + * @default false + */ + includeCompoundChar?: boolean; } export type GenSuggestionOptions = Partial; @@ -57,6 +63,7 @@ export const defaultGenSuggestionOptions: GenSuggestionOptionsStrict = { compoundMethod: CompoundWordsMethod.NONE, ignoreCase: true, changeLimit: 5, + includeCompoundChar: false, }; export const defaultSuggestionOptions: SuggestionOptionsStrict = { @@ -82,6 +89,7 @@ const keyMapOfGenSuggestionOptionsStrict: KeyMapOfGenSuggestionOptionsStrict = { changeLimit: 'changeLimit', compoundMethod: 'compoundMethod', ignoreCase: 'ignoreCase', + includeCompoundChar: 'includeCompoundChar', } as const; const keyMapOfSuggestionOptionsStrict: KeyMapOfSuggestionOptionsStrict = { diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggest-en-a-star.test.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggest-en-a-star.test.ts index 8d1e41e7c4e..2faeac715ab 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggest-en-a-star.test.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggest-en-a-star.test.ts @@ -1,5 +1,5 @@ import { readTrie } from '../../test/dictionaries.test.helper'; -import { GenSuggestionOptionsStrict } from '../genSuggestionsOptions'; +import { GenSuggestionOptionsStrict } from './genSuggestionsOptions'; import { genCompoundableSuggestions, suggest } from './suggestAStar'; import { suggestionCollector, diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggest-en.test.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggest-en.test.ts index c08964fd407..8529efafc60 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggest-en.test.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggest-en.test.ts @@ -7,7 +7,7 @@ import { clean } from '../trie-util'; import { DictionaryInformation } from '../models/DictionaryInformation'; import { mapDictionaryInformationToWeightMap, WeightMap } from '..'; import assert from 'assert'; -import { SuggestionOptions } from '../genSuggestionsOptions'; +import { SuggestionOptions } from './genSuggestionsOptions'; function getTrie() { return readTrie('@cspell/dict-en_us/cspell-ext.json'); diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggest.test.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggest.test.ts index 33447472d20..e15f82857d7 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggest.test.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggest.test.ts @@ -1,4 +1,4 @@ -import { GenSuggestionOptions, SuggestionOptions } from '../genSuggestionsOptions'; +import { GenSuggestionOptions, SuggestionOptions } from './genSuggestionsOptions'; import { parseDictionary } from '../SimpleDictionaryParser'; import { Trie } from '../trie'; import { cleanCopy } from '../utils/util'; diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggest.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggest.ts index e959acf0c9a..6f8dc52352e 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggest.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggest.ts @@ -1,4 +1,4 @@ -import { createSuggestionOptions, GenSuggestionOptions, SuggestionOptions } from '../genSuggestionsOptions'; +import { createSuggestionOptions, GenSuggestionOptions, SuggestionOptions } from './genSuggestionsOptions'; import { visualLetterMaskMap } from './orthography'; import { MaxCost, diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.test.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.test.ts index 9ff3df8b152..353bc2d46b9 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.test.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.test.ts @@ -1,4 +1,4 @@ -import { GenSuggestionOptionsStrict, SuggestionOptions } from '../genSuggestionsOptions'; +import { GenSuggestionOptionsStrict, SuggestionOptions } from './genSuggestionsOptions'; import { parseDictionary } from '../SimpleDictionaryParser'; import * as Sug from './suggestAStar'; import { SuggestionCollector, suggestionCollector, SuggestionCollectorOptions } from './suggestCollector'; diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.ts index 061d6db5c2f..841736e94fa 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggestAStar.ts @@ -3,7 +3,7 @@ import { CompoundWordsMethod, JOIN_SEPARATOR, WORD_SEPARATOR } from '../walker'; import { SuggestionGenerator, suggestionCollector, SuggestionResult } from './suggestCollector'; import { PairingHeap } from '../utils/PairingHeap'; import { visualLetterMaskMap } from './orthography'; -import { createSuggestionOptions, GenSuggestionOptionsStrict, SuggestionOptions } from '../genSuggestionsOptions'; +import { createSuggestionOptions, GenSuggestionOptionsStrict, SuggestionOptions } from './genSuggestionsOptions'; export function* genCompoundableSuggestions( root: TrieRoot, diff --git a/packages/cspell-trie-lib/src/lib/suggestions/suggestCollector.test.ts b/packages/cspell-trie-lib/src/lib/suggestions/suggestCollector.test.ts index c651f71ca26..17767ec6d7b 100644 --- a/packages/cspell-trie-lib/src/lib/suggestions/suggestCollector.test.ts +++ b/packages/cspell-trie-lib/src/lib/suggestions/suggestCollector.test.ts @@ -121,7 +121,7 @@ describe('Validate suggestCollector', () => { ${'word'} | ${[s('word', 0), s('work', 100), s('words', 100)]} ${'words'} | ${[s('words', 0), s('word', 100), s('works', 100)]} ${'joy'} | ${[s('joy', 0)]} - ${'joyo'} | ${[s('joy', 75), s('joyous', 155), s('yo-yo', 301)]} + ${'joyo'} | ${[s('joy', 75), s('joyous', 155), s('yo-yo', 305)]} ${'woudt'} | ${[s('word', 200), s('words', 200), s('would', 200), s("won't", 210)]} ${'aple'} | ${[s('apple', 55), s('apples', 155)]} ${'cafe'} | ${[s('cafe', 0), s('café', 1), s('cafés', 101)]} diff --git a/packages/cspell-trie-lib/src/lib/trie.test.ts b/packages/cspell-trie-lib/src/lib/trie.test.ts index 5df11ac4d15..c612f920545 100644 --- a/packages/cspell-trie-lib/src/lib/trie.test.ts +++ b/packages/cspell-trie-lib/src/lib/trie.test.ts @@ -1,4 +1,4 @@ -import { SuggestionOptions } from './genSuggestionsOptions'; +import { SuggestionOptions } from './suggestions/genSuggestionsOptions'; import { CompoundWordsMethod, suggestionCollector } from './index'; import { parseDictionary } from './SimpleDictionaryParser'; import { SuggestionCollectorOptions } from './suggestions/suggestCollector'; diff --git a/packages/cspell-trie-lib/src/lib/trie.ts b/packages/cspell-trie-lib/src/lib/trie.ts index d8b7fc59523..70393b9381a 100644 --- a/packages/cspell-trie-lib/src/lib/trie.ts +++ b/packages/cspell-trie-lib/src/lib/trie.ts @@ -10,7 +10,7 @@ import { isForbiddenWord, PartialFindOptions, } from './find'; -import { SuggestionOptions } from './genSuggestionsOptions'; +import { SuggestionOptions } from './suggestions/genSuggestionsOptions'; import { genSuggestions, suggest } from './suggest'; import { SuggestionCollector, SuggestionResult } from './suggestCollector'; import { PartialTrieOptions, TrieNode, TrieOptions, TrieRoot } from './TrieNode'; diff --git a/packages/cspell-trie-lib/src/lib/walker.test.ts b/packages/cspell-trie-lib/src/lib/walker/hintedWalker.test.ts similarity index 78% rename from packages/cspell-trie-lib/src/lib/walker.test.ts rename to packages/cspell-trie-lib/src/lib/walker/hintedWalker.test.ts index a16143b35e9..0d030524c4f 100644 --- a/packages/cspell-trie-lib/src/lib/walker.test.ts +++ b/packages/cspell-trie-lib/src/lib/walker/hintedWalker.test.ts @@ -1,25 +1,9 @@ -import { walker, YieldResult, hintedWalker } from './walker'; -import { orderTrie, createTriFromList } from './trie-util'; -import { parseLinesToDictionary } from './SimpleDictionaryParser'; +import type { YieldResult } from './walkerTypes'; +import { hintedWalker } from './hintedWalker'; +import { orderTrie, createTriFromList } from '../trie-util'; +import { parseLinesToDictionary } from '../SimpleDictionaryParser'; describe('Validate Util Functions', () => { - test('Tests Walker', () => { - const root = createTriFromList(sampleWords); - orderTrie(root); - const i = walker(root); - let goDeeper = true; - let ir: IteratorResult; - const result: string[] = []; - while (!(ir = i.next(goDeeper)).done) { - const { text, node } = ir.value; - if (node.f) { - result.push(text); - } - goDeeper = text.length < 4; - } - expect(result).toEqual(sampleWords.filter((a) => a.length <= 4).sort()); - }); - test('Hinted Walker', () => { const root = createTriFromList(sampleWords); orderTrie(root); diff --git a/packages/cspell-trie-lib/src/lib/walker.ts b/packages/cspell-trie-lib/src/lib/walker/hintedWalker.ts similarity index 68% rename from packages/cspell-trie-lib/src/lib/walker.ts rename to packages/cspell-trie-lib/src/lib/walker/hintedWalker.ts index ba981904fb3..5a5bf687e16 100644 --- a/packages/cspell-trie-lib/src/lib/walker.ts +++ b/packages/cspell-trie-lib/src/lib/walker/hintedWalker.ts @@ -1,78 +1,6 @@ -import { isDefined } from './trie-util'; -import { TrieNode, ChildMap, TrieRoot } from './TrieNode'; - -export const JOIN_SEPARATOR = '+'; -export const WORD_SEPARATOR = ' '; - -export interface YieldResult { - text: string; - node: TrieNode; - depth: number; -} - -export enum CompoundWordsMethod { - /** - * Do not compound words. - */ - NONE = 0, - /** - * Create word compounds separated by spaces. - */ - SEPARATE_WORDS, - /** - * Create word compounds without separation. - */ - JOIN_WORDS, -} - -export type WalkerIterator = Generator; - -/** - * Walks the Trie and yields a value at each node. - * next(goDeeper: boolean): - */ -export function* walker( - root: TrieNode, - compoundingMethod: CompoundWordsMethod = CompoundWordsMethod.NONE -): WalkerIterator { - const roots: { [index: number]: ChildMap | [string, TrieNode][] } = { - [CompoundWordsMethod.NONE]: [], - [CompoundWordsMethod.JOIN_WORDS]: [[JOIN_SEPARATOR, root]], - [CompoundWordsMethod.SEPARATE_WORDS]: [[WORD_SEPARATOR, root]], - }; - - function* children(n: TrieNode): IterableIterator<[string, TrieNode]> { - if (n.c) { - yield* n.c; - } - if (n.f) { - yield* roots[compoundingMethod]; - } - } - - let depth = 0; - const stack: { t: string; c: Iterator<[string, TrieNode]> }[] = []; - stack[depth] = { t: '', c: children(root) }; - let ir: IteratorResult<[string, TrieNode]>; - while (depth >= 0) { - let baseText = stack[depth].t; - while (!(ir = stack[depth].c.next()).done) { - const [char, node] = ir.value; - const text = baseText + char; - const goDeeper = yield { text, node, depth }; - if (goDeeper || goDeeper === undefined) { - depth++; - baseText = text; - stack[depth] = { t: text, c: children(node) }; - } - } - depth -= 1; - } -} - -export interface Hinting { - goDeeper: boolean; -} +import { isDefined } from '../trie-util'; +import { TrieNode, TrieRoot } from '../TrieNode'; +import { CompoundWordsMethod, JOIN_SEPARATOR, WORD_SEPARATOR, YieldResult } from './walkerTypes'; /** * Ask for the next result. @@ -87,6 +15,7 @@ export type HintedWalkerIterator = Generator { +export interface Hinting { + goDeeper: boolean; +} + +export function existMap(values: string[]): Record { const m: Record = Object.create(null); for (const v of values) { m[v] = true; diff --git a/packages/cspell-trie-lib/src/lib/walker/index.ts b/packages/cspell-trie-lib/src/lib/walker/index.ts new file mode 100644 index 00000000000..4a809294a33 --- /dev/null +++ b/packages/cspell-trie-lib/src/lib/walker/index.ts @@ -0,0 +1,3 @@ +export * from './walker'; +export * from './hintedWalker'; +export * from './walkerTypes'; diff --git a/packages/cspell-trie-lib/src/lib/walker/walker.test.ts b/packages/cspell-trie-lib/src/lib/walker/walker.test.ts new file mode 100644 index 00000000000..af5354282d5 --- /dev/null +++ b/packages/cspell-trie-lib/src/lib/walker/walker.test.ts @@ -0,0 +1,71 @@ +import { walker } from './walker'; +import type { YieldResult } from './walkerTypes'; +import { orderTrie, createTriFromList } from '../trie-util'; + +describe('Validate Util Functions', () => { + test('Tests Walker', () => { + const root = createTriFromList(sampleWords); + orderTrie(root); + const i = walker(root); + let goDeeper = true; + let ir: IteratorResult; + const result: string[] = []; + while (!(ir = i.next(goDeeper)).done) { + const { text, node } = ir.value; + if (node.f) { + result.push(text); + } + goDeeper = text.length < 4; + } + expect(result).toEqual(sampleWords.filter((a) => a.length <= 4).sort()); + }); +}); + +const sampleWords = [ + 'walk', + 'walked', + 'walker', + 'walking', + 'walks', + 'talk', + 'talks', + 'talked', + 'talker', + 'talking', + 'lift', + 'lifts', + 'lifted', + 'lifter', + 'lifting', + 'journal', + 'journals', + 'journalism', + 'journalist', + 'journalistic', + 'journey', + 'journeyer', + 'journeyman', + 'journeymen', + 'joust', + 'jouster', + 'jousting', + 'jovial', + 'joviality', + 'jowl', + 'jowly', + 'joy', + 'joyful', + 'joyfuller', + 'joyfullest', + 'joyfulness', + 'joyless', + 'joylessness', + 'joyous', + 'joyousness', + 'joyridden', + 'joyride', + 'joyrider', + 'joyriding', + 'joyrode', + 'joystick', +]; diff --git a/packages/cspell-trie-lib/src/lib/walker/walker.ts b/packages/cspell-trie-lib/src/lib/walker/walker.ts new file mode 100644 index 00000000000..2ad279a00c1 --- /dev/null +++ b/packages/cspell-trie-lib/src/lib/walker/walker.ts @@ -0,0 +1,45 @@ +import { TrieNode, ChildMap } from '../TrieNode'; +import { CompoundWordsMethod, WalkerIterator, WORD_SEPARATOR, JOIN_SEPARATOR } from './walkerTypes'; + +/** + * Walks the Trie and yields a value at each node. + * next(goDeeper: boolean): + */ +export function* walker( + root: TrieNode, + compoundingMethod: CompoundWordsMethod = CompoundWordsMethod.NONE +): WalkerIterator { + const roots: { [index: number]: ChildMap | [string, TrieNode][] } = { + [CompoundWordsMethod.NONE]: [], + [CompoundWordsMethod.JOIN_WORDS]: [[JOIN_SEPARATOR, root]], + [CompoundWordsMethod.SEPARATE_WORDS]: [[WORD_SEPARATOR, root]], + }; + + function* children(n: TrieNode): IterableIterator<[string, TrieNode]> { + if (n.c) { + yield* n.c; + } + if (n.f) { + yield* roots[compoundingMethod]; + } + } + + let depth = 0; + const stack: { t: string; c: Iterator<[string, TrieNode]> }[] = []; + stack[depth] = { t: '', c: children(root) }; + let ir: IteratorResult<[string, TrieNode]>; + while (depth >= 0) { + let baseText = stack[depth].t; + while (!(ir = stack[depth].c.next()).done) { + const [char, node] = ir.value; + const text = baseText + char; + const goDeeper = yield { text, node, depth }; + if (goDeeper || goDeeper === undefined) { + depth++; + baseText = text; + stack[depth] = { t: text, c: children(node) }; + } + } + depth -= 1; + } +} diff --git a/packages/cspell-trie-lib/src/lib/walker/walkerTypes.ts b/packages/cspell-trie-lib/src/lib/walker/walkerTypes.ts new file mode 100644 index 00000000000..af62f5bf43b --- /dev/null +++ b/packages/cspell-trie-lib/src/lib/walker/walkerTypes.ts @@ -0,0 +1,27 @@ +import { TrieNode } from '../TrieNode'; + +export const JOIN_SEPARATOR = '+'; +export const WORD_SEPARATOR = ' '; + +export interface YieldResult { + text: string; + node: TrieNode; + depth: number; +} + +export enum CompoundWordsMethod { + /** + * Do not compound words. + */ + NONE = 0, + /** + * Create word compounds separated by spaces. + */ + SEPARATE_WORDS, + /** + * Create word compounds without separation. + */ + JOIN_WORDS, +} + +export type WalkerIterator = Generator;