function substitutions()

in experimental/generation/generator/packages/library/src/substitutions.ts [68:164]


function substitutions(name: string, bindings: any, copies?: number, seed?: string): string {
    if (!copies) copies = 1
    if (!seed) seed = '0'

    // Normalize bindings into a simple array and setup variables
    const state = new Map<string, Variable>();
    for (const [binding, value] of Object.entries(bindings)) {
        if (Array.isArray(value)) {
            state.set(binding, {index: -1, values: (value as any).flat()})
        } else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
            state.set(binding, {index: -1, values: value ? [value] : []})
        }
    }

    // Track all generated utterances across calls in bindings
    bindings.utterances ??= new Set<string>()
    const utterances = bindings.utterances as Set<string>

    const result: string[] = []
    const rand = random(seed)
    const file = DataCache.get(name)
    if (file) {
        let lines = file.split(os.EOL)
        if (lines.length < 2) {
            // Windows uses CRLF and that is how it is checked-in, but when an npm
            // package is built it switches to just LF.
            lines = file.split('\n')
        }
        const replacer = (line: string): {newLine: string, missing: boolean} => {
            let missing = false
            const newLine = line.replace(/\${([^}*?]+)[*?]?\}/g,
                (match, key) => {
                    const val = binding(key, state, rand)
                    if (!val) {
                        missing = true
                    }
                    // For conditional tests value is empty but we set missing
                    return match.endsWith('?}') ? '' : val
                })
            return {newLine, missing}
        }
        let skipToNextComment = false
        for (const line of lines) {
            const isComment = line.startsWith('>')
            if (isComment) {
                const {newLine, missing} = replacer(line)
                skipToNextComment = missing
                if (!missing) {
                    result.push(newLine)
                }
            } else if (!skipToNextComment) {
                if (line.trim() === '') {
                    result.push(line)
                } else {
                    const all = line.match(/\${[^}*]+\*}/g) ?? []
                    for (const [key, variable] of state) {
                        variable.index = all.includes(`\${${key}*}`) ? 0 : -1
                    }
                    do {
                        let missing = false
                        for (let i = 0; i < copies; ++i) {
                            // Number of times to try for a unique result
                            let tries = 3
                            do {
                                const newline = line.replace(/\${([^}*?]+)[*?]?\}/g,
                                    (match, key) => {
                                        const val = binding(key, state, rand)
                                        if (!val) {
                                            missing = true
                                        }
                                        // For conditional tests value is empty but we set missing
                                        return match.endsWith('?}') ? '' : val
                                    })
                                if (missing) {
                                    // If missing a value, drop the line
                                    tries = 0
                                    i = copies
                                    break
                                }
                                tries = tries - 1
                                const text = plainText(newline)
                                // Ensure we generate given text only once because otherwise we can label it in multiple ways which LUIS
                                // does not allow
                                if (!utterances.has(text)) {
                                    utterances.add(text)
                                    result.push(newline)
                                    tries = 0
                                }
                            } while (tries > 0)
                        }
                    } while (!increment(state))
                }
            }
        }
    }
    return result.join(os.EOL)
}