All files dictionary.ts

64.29% Statements 27/42
42.86% Branches 6/14
66.67% Functions 4/6
72.41% Lines 21/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163    4x                                                                                                                           4x       58x                                     58x           116x 58x               163x 95x 95x 95x               4x                           4x                                       4x 1x     1x   2x 1x 1x 1x     1x   4x  
import { Pattern, ScopeName, Word } from './types'
 
import { Scope } from './scope'
import { Suggestion } from './suggestion'
 
 
/**
 * <p align="center"> 
 *   <img src="https://web.archive.org/web/20000828135714/http://www.geocities.com:80/HotSprings/4530/turnbook.gif">
 *   <img src="https://web.archive.org/web/20000828135714/http://www.geocities.com:80/HotSprings/4530/turnbook.gif">
 *   <img src="https://web.archive.org/web/20000828135714/http://www.geocities.com:80/HotSprings/4530/turnbook.gif">
 * </p>
 *
 * A `Dictionary` is used to define, add/remove patterns to/from, and generate
 * suggestions from suggestion scopes. As someone using this library, the 
 * `Dictionary` class will likely be the primary interface between your code
 * and the functionality `autosuggestion` has to offer.
 *
 * Generally speaking, a `Dictionary` is a collection of named [[Trie|scopes]] containing [[Pattern|patterns]]
 * (words or phrases) which can be easily matched with partially completed input patterns 
 * <sup><a href="#fn-dictionary-1">1</a></sup>. For example,
 * if we want to generate suggestions based on a list of colors, we can
 * ```javascript
 * // create a new dictionary
 * const myDictionary = new Dictionary()
 *
 * // define a named context with some colors
 * myDictionary.define('myColors', ['red', 'rose', 'pink'])
 * ```
 * now, we can generate suggestions given input,
 * ```javascript
 * const inputText = 'r'
 *
 * // match input text with potential completions in the 'myColors' context
 * const colorSuggestions = myDictionary.suggest(inputText, 'myColors')
 * // outputs: red, rose
 * ```
 *
 * <p align="center"> 
 *   <img height="50px" src=" https://web.archive.org/web/20091020080355/http://hk.geocities.com/fatkeehk1/mov-book.gif">
 *   <img height="50px" src=" https://web.archive.org/web/20091020080355/http://hk.geocities.com/fatkeehk1/mov-book.gif">
 *   <img height="50px" src=" https://web.archive.org/web/20091020080355/http://hk.geocities.com/fatkeehk1/mov-book.gif">
 * </p>
 *
 * If we don't know all the patterns for a scope upfront, we can dynamically add,
 * ```javascript
 * myDictionary.add('myColors', ['salmon', 'violet', 'avocado'])
 * ```
 * or remove,
 * ```javascript
 * myDictionary.remove('myColors', ['red', 'salmon'])
 * ```
 * ---
 *
 * <div style="font-style:italic;font-size:small">
 *  <p id="fn-dictionary-1">
 *    [1] Each named scope is a special type of <a href="https://en.wikipedia.org/wiki/Trie">trie</a>
 *        indexed by a string-valued name. What makes this type of trie special is that each node not
 *        only points to the set of next character candidates, but also points to the set of next 
 *        word candidates. This makes it, essentially, a trie of tries allowing us to perform phrase-based
 *        matches in addition to word-based matches.
 *  </p>
 * </div>
 */
export class Dictionary {
    /**
     * A map of scope names to corresponding scopes.
     */
    public scopes: Map<ScopeName, Scope> = new Map()
 
    /**
     * Configures how many consecutive [[Lookup|lookups]] to resolve given a 
     * suggestion with a lookup [[Term|term]] which immediately follows the input pattern.
     *
     * For example, if there exists the following pattern
     * ```javascript
     * // assume the scopes for the 'color', 'size', & 'shape' scope groups have been defined.
     * one two <color> <size> <shape>
     * ```
     * then, given an input of `['one', 'two']` & `lookahead=2`, the resulting suggestions would be,
     * ```javascript
     * one two green big <shape>
     * one two red big <shape>
     * one two green small <shape>
     * ...
     * ```
     */
    public lookahead: number = 0
 
    /**
     * Constructs a `Dictionary`.
     * @param lookahead sets [[Dictionary.lookahead|lookahead]] parameter.
     */
    constructor(Elookahead: number = 0) {
        this.lookahead = lookahead
    }
 
    /**
     * Defines a new named scope and populates it with optionally provided patterns.
     * @param scopeName the new scope name.
     * @param patterns optional initial scope patterns.
     */
    define(scopeName: ScopeName, patterns: Pattern[] = []): Scope {
        const scope: Scope = new Scope(this, patterns)
        this.scopes.set(scopeName, scope)
        return scope
    }
 
    /**
     * Adds patterns to an existing scope.
     * @param scopeName an existing scope name.
     * @param patterns array of patterns to add.
     */
    add(scopeName: ScopeName, patterns: Pattern[]) {
        // TODO make it optional to pass in a Word | Lookup | Pattern | Pattern []
        const scope = this.scopes.get(scopeName)
        if (!scope) return
        for (const pattern of patterns) {
            scope.add(pattern)
        }
    }
 
    /**
     * Removes patterns from an existing scope.
     * @param scopeName an existing scope name.
     * @param patterns array of patterns to remove.
     */
    remove(scopeName: ScopeName, patterns: Pattern[]) {
        // TODO make it optional to pass in a Word | Lookup | Pattern | Pattern []
        const scope = this.scopes.get(scopeName)
        if (!scope) return
        for (const pattern of patterns) {
            scope.remove(pattern)
        }
    }
 
    /**
     * Generates suggestions from specified scopes given an array of input tokens.
     *
     * This method expects that the input tokens have already been tokenized by the
     * calling code. In particular, multi-word inputs, which are expected to be matched
     * against phrase-based patterns which have been added to a scope, must be broken up
     * into a array of words (rather than just an raw string with words separated by spaces).
     *
     * @param tokens a single input word token or array of input word tokens.
     * @param scopes the scopes to search. Given none, all scopes are searched.
     */
    suggest(tokens: Word | Word[], EscopeNames: ScopeName[] = []): Suggestion[] {
        let suggestions: Suggestion[] = []
 
        // if no scope names have been provided, search all scopes.
        Eif (scopeNames.length === 0) scopeNames = Array.from(this.scopes.keys())
 
        for (const scopeName of scopeNames) {
            const scope = this.scopes.get(scopeName)
            Iif (!scope) continue
            suggestions = suggestions.concat(scope.suggest(tokens, this.lookahead))
        }
 
        return suggestions
    }
}