gx
chenyc
2025-06-12 7b72ac13a83764a662159d4a49b7fffb90476ecb
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
/**
 * @license
 * Copyright 2018 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =============================================================================
 */
import { computeStrides, isString, rightPad, sizeFromShape } from './util';
// Maximum number of values before we decide to show ellipsis.
const FORMAT_LIMIT_NUM_VALS = 20;
// Number of first and last values to show when displaying a, b,...,y, z.
const FORMAT_NUM_FIRST_LAST_VALS = 3;
// Number of significant digits to show.
const FORMAT_NUM_SIG_DIGITS = 7;
export function tensorToString(vals, shape, dtype, verbose) {
    const strides = computeStrides(shape);
    const padPerCol = computeMaxSizePerColumn(vals, shape, dtype, strides);
    const rank = shape.length;
    const valsLines = subTensorToString(vals, shape, dtype, strides, padPerCol);
    const lines = ['Tensor'];
    if (verbose) {
        lines.push(`  dtype: ${dtype}`);
        lines.push(`  rank: ${rank}`);
        lines.push(`  shape: [${shape}]`);
        lines.push(`  values:`);
    }
    lines.push(valsLines.map(l => '    ' + l).join('\n'));
    return lines.join('\n');
}
function computeMaxSizePerColumn(vals, shape, dtype, strides) {
    const n = sizeFromShape(shape);
    const numCols = strides[strides.length - 1];
    const padPerCol = new Array(numCols).fill(0);
    const rank = shape.length;
    const valuesOrTuples = dtype === 'complex64' ? createComplexTuples(vals) : vals;
    if (rank > 1) {
        for (let row = 0; row < n / numCols; row++) {
            const offset = row * numCols;
            for (let j = 0; j < numCols; j++) {
                padPerCol[j] = Math.max(padPerCol[j], valToString(valuesOrTuples[offset + j], 0, dtype).length);
            }
        }
    }
    return padPerCol;
}
function valToString(val, pad, dtype) {
    let valStr;
    if (Array.isArray(val)) {
        valStr = `${parseFloat(val[0].toFixed(FORMAT_NUM_SIG_DIGITS))} + ` +
            `${parseFloat(val[1].toFixed(FORMAT_NUM_SIG_DIGITS))}j`;
    }
    else if (isString(val)) {
        valStr = `'${val}'`;
    }
    else if (dtype === 'bool') {
        valStr = boolNumToString(val);
    }
    else {
        valStr = parseFloat(val.toFixed(FORMAT_NUM_SIG_DIGITS)).toString();
    }
    return rightPad(valStr, pad);
}
function boolNumToString(v) {
    return v === 0 ? 'false' : 'true';
}
function subTensorToString(vals, shape, dtype, strides, padPerCol, isLast = true) {
    const storagePerElement = dtype === 'complex64' ? 2 : 1;
    const size = shape[0];
    const rank = shape.length;
    if (rank === 0) {
        if (dtype === 'complex64') {
            const complexTuple = createComplexTuples(vals);
            return [valToString(complexTuple[0], 0, dtype)];
        }
        if (dtype === 'bool') {
            return [boolNumToString(vals[0])];
        }
        return [vals[0].toString()];
    }
    if (rank === 1) {
        if (size > FORMAT_LIMIT_NUM_VALS) {
            const firstValsSize = FORMAT_NUM_FIRST_LAST_VALS * storagePerElement;
            let firstVals = Array.from(vals.slice(0, firstValsSize));
            let lastVals = Array.from(vals.slice((size - FORMAT_NUM_FIRST_LAST_VALS) * storagePerElement, size * storagePerElement));
            if (dtype === 'complex64') {
                firstVals = createComplexTuples(firstVals);
                lastVals = createComplexTuples(lastVals);
            }
            return [
                '[' +
                    firstVals.map((x, i) => valToString(x, padPerCol[i], dtype))
                        .join(', ') +
                    ', ..., ' +
                    lastVals
                        .map((x, i) => valToString(x, padPerCol[size - FORMAT_NUM_FIRST_LAST_VALS + i], dtype))
                        .join(', ') +
                    ']'
            ];
        }
        const displayVals = dtype === 'complex64' ? createComplexTuples(vals) :
            Array.from(vals);
        return [
            '[' +
                displayVals.map((x, i) => valToString(x, padPerCol[i], dtype))
                    .join(', ') +
                ']'
        ];
    }
    // The array is rank 2 or more.
    const subshape = shape.slice(1);
    const substrides = strides.slice(1);
    const stride = strides[0] * storagePerElement;
    const lines = [];
    if (size > FORMAT_LIMIT_NUM_VALS) {
        for (let i = 0; i < FORMAT_NUM_FIRST_LAST_VALS; i++) {
            const start = i * stride;
            const end = start + stride;
            lines.push(...subTensorToString(vals.slice(start, end), subshape, dtype, substrides, padPerCol, false /* isLast */));
        }
        lines.push('...');
        for (let i = size - FORMAT_NUM_FIRST_LAST_VALS; i < size; i++) {
            const start = i * stride;
            const end = start + stride;
            lines.push(...subTensorToString(vals.slice(start, end), subshape, dtype, substrides, padPerCol, i === size - 1 /* isLast */));
        }
    }
    else {
        for (let i = 0; i < size; i++) {
            const start = i * stride;
            const end = start + stride;
            lines.push(...subTensorToString(vals.slice(start, end), subshape, dtype, substrides, padPerCol, i === size - 1 /* isLast */));
        }
    }
    const sep = rank === 2 ? ',' : '';
    lines[0] = '[' + (size > 0 ? lines[0] + sep : '');
    for (let i = 1; i < lines.length - 1; i++) {
        lines[i] = ' ' + lines[i] + sep;
    }
    let newLineSep = ',\n';
    for (let i = 2; i < rank; i++) {
        newLineSep += '\n';
    }
    lines[lines.length - 1] =
        ' ' + lines[lines.length - 1] + ']' + (isLast ? '' : newLineSep);
    return lines;
}
function createComplexTuples(vals) {
    const complexTuples = [];
    for (let i = 0; i < vals.length; i += 2) {
        complexTuples.push([vals[i], vals[i + 1]]);
    }
    return complexTuples;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVuc29yX2Zvcm1hdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3RmanMtY29yZS9zcmMvdGVuc29yX2Zvcm1hdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFHSCxPQUFPLEVBQUMsY0FBYyxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsYUFBYSxFQUFDLE1BQU0sUUFBUSxDQUFDO0FBRXpFLDhEQUE4RDtBQUM5RCxNQUFNLHFCQUFxQixHQUFHLEVBQUUsQ0FBQztBQUNqQyx5RUFBeUU7QUFDekUsTUFBTSwwQkFBMEIsR0FBRyxDQUFDLENBQUM7QUFDckMsd0NBQXdDO0FBQ3hDLE1BQU0scUJBQXFCLEdBQUcsQ0FBQyxDQUFDO0FBRWhDLE1BQU0sVUFBVSxjQUFjLENBQzFCLElBQXlCLEVBQUUsS0FBZSxFQUFFLEtBQWUsRUFDM0QsT0FBZ0I7SUFDbEIsTUFBTSxPQUFPLEdBQUcsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RDLE1BQU0sU0FBUyxHQUFHLHVCQUF1QixDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZFLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUM7SUFDMUIsTUFBTSxTQUFTLEdBQUcsaUJBQWlCLENBQUMsSUFBSSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQzVFLE1BQU0sS0FBSyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDekIsSUFBSSxPQUFPLEVBQUU7UUFDWCxLQUFLLENBQUMsSUFBSSxDQUFDLFlBQVksS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNoQyxLQUFLLENBQUMsSUFBSSxDQUFDLFdBQVcsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUM5QixLQUFLLENBQUMsSUFBSSxDQUFDLGFBQWEsS0FBSyxHQUFHLENBQUMsQ0FB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QUFDLENBQUM7UUFDbkIsR0FBRyxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNyRSxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUM7QUFFRCxTQUFTLG1CQUFtQixDQUFDLElBQ1U7SUFDckMsTUFBTSxhQUFhLEdBQTRCLEVBQUUsQ0FBQztJQUNsRCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQ3ZDLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBcUIsQ0FBQyxDQUFDO0tBQ2hFO0lBQ0QsT0FBTyxhQUFhLENBQUM7QUFDdkIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGxpY2Vuc2VcbiAqIENvcHlyaWdodCAyMDE4IEdvb2dsZSBMTEMuIEFsbCBSaWdodHMgUmVzZXJ2ZWQuXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgQXBhY2hlIExpY2Vuc2UsIFZlcnNpb24gMi4wICh0aGUgXCJMaWNlbnNlXCIpO1xuICogeW91IG1heSBub3QgdXNlIHRoaXMgZmlsZSBleGNlcHQgaW4gY29tcGxpYW5jZSB3aXRoIHRoZSBMaWNlbnNlLlxuICogWW91IG1heSBvYnRhaW4gYSBjb3B5IG9mIHRoZSBMaWNlbnNlIGF0XG4gKlxuICogaHR0cDovL3d3dy5hcGFjaGUub3JnL2xpY2Vuc2VzL0xJQ0VOU0UtMi4wXG4gKlxuICogVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZVxuICogZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIExpY2Vuc2UgaXMgZGlzdHJpYnV0ZWQgb24gYW4gXCJBUyBJU1wiIEJBU0lTLFxuICogV0lUSE9VVCBXQVJSQU5USUVTIE9SIENPTkRJVElPTlMgT0YgQU5ZIEtJTkQsIGVpdGhlciBleHByZXNzIG9yIGltcGxpZWQuXG4gKiBTZWUgdGhlIExpY2Vuc2UgZm9yIHRoZSBzcGVjaWZpYyBsYW5ndWFnZSBnb3Zlcm5pbmcgcGVybWlzc2lvbnMgYW5kXG4gKiBsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS5cbiAqID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gKi9cblxuaW1wb3J0IHtEYXRhVHlwZSwgVHlwZWRBcnJheX0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQge2NvbXB1dGVTdHJpZGVzLCBpc1N0cmluZywgcmlnaHRQYWQsIHNpemVGcm9tU2hhcGV9IGZyb20gJy4vdXRpbCc7XG5cbi8vIE1heGltdW0gbnVtYmVyIG9mIHZhbHVlcyBiZWZvcmUgd2UgZGVjaWRlIHRvIHNob3cgZWxsaXBzaXMuXG5jb25zdCBGT1JNQVRfTElNSVRfTlVNX1ZBTFMgPSAyMDtcbi8vIE51bWJlciBvZiBmaXJzdCBhbmQgbGFzdCB2YWx1ZXMgdG8gc2hvdyB3aGVuIGRpc3BsYXlpbmcgYSwgYiwuLi4seSwgei5cbmNvbnN0IEZPUk1BVF9OVU1fRklSU1RfTEFTVF9WQUxTID0gMztcbi8vIE51bWJlciBvZiBzaWduaWZpY2FudCBkaWdpdHMgdG8gc2hvdy5cbmNvbnN0IEZPUk1BVF9OVU1fU0lHX0RJR0lUUyA9IDc7XG5cbmV4cG9ydCBmdW5jdGlvbiB0ZW5zb3JUb1N0cmluZyhcbiAgICB2YWxzOiBUeXBlZEFycmF5fHN0cmluZ1tdLCBzaGFwZTogbnVtYmVyW10sIGR0eXBlOiBEYXRhVHlwZSxcbiAgICB2ZXJib3NlOiBib29sZWFuKSB7XG4gIGNvbnN0IHN0cmlkZXMgPSBjb21wdXRlU3RyaWRlcyhzaGFwZSk7XG4gIGNvbnN0IHBhZFBlckNvbCA9IGNvbXB1dGVNYXhTaXplUGVyQ29sdW1uKHZhbHMsIHNoYXBlLCBkdHlwZSwgc3RyaWRlcyk7XG4gIGNvbnN0IHJhbmsgPSBzaGFwZS5sZW5ndGg7XG4gIGNvbnN0IHZhbHNMaW5lcyA9IHN1YlRlbnNvclRvU3RyaW5nKHZhbHMsIHNoYXBlLCBkdHlwZSwgc3RyaWRlcywgcGFkUGVyQ29sKTtcbiAgY29uc3QgbGluZXMgPSBbJ1RlbnNvciddO1xuICBpZiAodmVyYm9zZSkge1xuICAgIGxpbmVzLnB1c2goYCAgZHR5cGU6ICR7ZHR5cGV9YCk7XG4gICAgbGluZXMucHVzaChgICByYW5rOiAke3Jhbmt9YCk7XG4gICAgbGluZXMucHVzaChgICBzaGFwZTogWyR7c2hhcGV9XWApO1xuICAgIGxpbmVzLnB1c2goYCAgdmFsdWVzOmApO1xuICB9XG4gIGxpbmVzLnB1c2godmFsc0xpbmVzLm1hcChsID0+ICcgICAgJyArIGwpLmpvaW4oJ1xcbicpKTtcbiAgcmV0dXJuIGxpbmVzLmpvaW4oJ1xcbicpO1xufVxuXG5mdW5jdGlvbiBjb21wdXRlTWF4U2l6ZVBlckNvbHVtbihcbiAgICB2YWxzOiBUeXBlZEFycmF5fHN0cmluZ1tdLCBzaGFwZTogbnVtYmVyW10sIGR0eXBlOiBEYXRhVHlwZSxcbiAgICBzdHJpZGVzOiBudW1iZXJbXSk6IG51bWJlcltdIHtcbiAgY29uc3QgbiA9IHNpemVGcm9tU2hhcGUoc2hhcGUpO1xuICBjb25zdCBudW1Db2xzID0gc3RyaWRlc1tzdHJpZGVzLmxlbmd0aCAtIDFdO1xuICBjb25zdCBwYWRQZXJDb2wgPSBuZXcgQXJyYXkobnVtQ29scykuZmlsbCgwKTtcbiAgY29uc3QgcmFuayA9IHNoYXBlLmxlbmd0aDtcbiAgY29uc3QgdmFsdWVzT3JUdXBsZXMgPVxuICAgICAgZHR5cGUgPT09ICdjb21wbGV4NjQnID8gY3JlYXRlQ29tcGxleFR1cGxlcyh2YWxzKSA6IHZhbHM7XG5cbiAgaWYgKHJhbmsgPiAxKSB7XG4gICAgZm9yIChsZXQgcm93ID0gMDsgcm93IDwgbiAvIG51bUNvbHM7IHJvdysrKSB7XG4gICAgICBjb25zdCBvZmZzZXQgPSByb3cgKiBudW1Db2xzO1xuICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBudW1Db2xzOyBqKyspIHtcbiAgICAgICAgcGFkUGVyQ29sW2pdID0gTWF0aC5tYXgoXG4gICAgICAgICAgICBwYWRQZXJDb2xbal0sXG4gICAgICAgICAgICB2YWxUb1N0cmluZyh2YWx1ZXNPclR1cGxlc1tvZmZzZXQgKyBqXSwgMCwgZHR5cGUpLmxlbmd0aCk7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIHJldHVybiBwYWRQZXJDb2w7XG59XG5cbmZ1bmN0aW9uIHZhbFRvU3RyaW5nKFxuICAgIHZhbDogbnVtYmVyfHN0cmluZ3xbbnVtYmVyLCBudW1iZXJdLCBwYWQ6IG51bWJlciwgZHR5cGU6IERhdGFUeXBlKSB7XG4gIGxldCB2YWxTdHI6IHN0cmluZztcbiAgaWYgKEFycmF5LmlzQXJyYXkodmFsKSkge1xuICAgIHZhbFN0ciA9IGAke3BhcnNlRmxvYXQodmFsWzBdLnRvRml4ZWQoRk9STUFUX05VTV9TSUdfRElHSVRTKSl9ICsgYCArXG4gICAgICAgIGAke3BhcnNlRmxvYXQodmFsWzFdLnRvRml4ZWQoRk9STUFUX05VTV9TSUdfRElHSVRTKSl9amA7XG4gIH0gZWxzZSBpZiAoaXNTdHJpbmcodmFsKSkge1xuICAgIHZhbFN0ciA9IGAnJHt2YWx9J2A7XG4gIH0gZWxzZSBpZiAoZHR5cGUgPT09ICdib29sJykge1xuICAgIHZhbFN0ciA9IGJvb2xOdW1Ub1N0cmluZyh2YWwpO1xuICB9IGVsc2Uge1xuICAgIHZhbFN0ciA9IHBhcnNlRmxvYXQodmFsLnRvRml4ZWQoRk9STUFUX05VTV9TSUdfRElHSVRTKSkudG9TdHJpbmcoKTtcbiAgfVxuXG4gIHJldHVybiByaWdodFBhZCh2YWxTdHIsIHBhZCk7XG59XG5cbmZ1bmN0aW9uIGJvb2xOdW1Ub1N0cmluZyh2OiBudW1iZXIpOiBzdHJpbmcge1xuICByZXR1cm4gdiA9PT0gMCA/ICdmYWxzZScgOiAndHJ1ZSc7XG59XG5cbmZ1bmN0aW9uIHN1YlRlbnNvclRvU3RyaW5nKFxuICAgIHZhbHM6IFR5cGVkQXJyYXl8c3RyaW5nW10sIHNoYXBlOiBudW1iZXJbXSwgZHR5cGU6IERhdGFUeXBlLFxuICAgIHN0cmlkZXM6IG51bWJlcltdLCBwYWRQZXJDb2w6IG51bWJlcltdLCBpc0xhc3QgPSB0cnVlKTogc3RyaW5nW10ge1xuICBjb25zdCBzdG9yYWdlUGVyRWxlbWVudCA9IGR0eXBlID09PSAnY29tcGxleDY0JyA/IDIgOiAxO1xuXG4gIGNvbnN0IHNpemUgPSBzaGFwZVswXTtcbiAgY29uc3QgcmFuayA9IHNoYXBlLmxlbmd0aDtcbiAgaWYgKHJhbmsgPT09IDApIHtcbiAgICBpZiAoZHR5cGUgPT09ICdjb21wbGV4NjQnKSB7XG4gICAgICBjb25zdCBjb21wbGV4VHVwbGUgPSBjcmVhdGVDb21wbGV4VHVwbGVzKHZhbHMpO1xuICAgICAgcmV0dXJuIFt2YWxUb1N0cmluZyhjb21wbGV4VHVwbGVbMF0sIDAsIGR0eXBlKV07XG4gICAgfVxuICAgIGlmIChkdHlwZSA9PT0gJ2Jvb2wnKSB7XG4gICAgICByZXR1cm4gW2Jvb2xOdW1Ub1N0cmluZyh2YWxzWzBdIGFzIG51bWJlcildO1xuICAgIH1cbiAgICByZXR1cm4gW3ZhbHNbMF0udG9TdHJpbmcoKV07XG4gIH1cblxuICBpZiAocmFuayA9PT0gMSkge1xuICAgIGlmIChzaXplID4gRk9STUFUX0xJTUlUX05VTV9WQUxTKSB7XG4gICAgICBjb25zdCBmaXJzdFZhbHNTaXplID0gRk9STUFUX05VTV9GSVJTVF9MQVNUX1ZBTFMgKiBzdG9yYWdlUGVyRWxlbWVudDtcblxuICAgICAgbGV0IGZpcnN0VmFscyA9IEFycmF5LmZyb208bnVtYmVyfHN0cmluZ3xbbnVtYmVyLCBudW1iZXJdPihcbiAgICAgICAgICB2YWxzLnNsaWNlKDAsIGZpcnN0VmFsc1NpemUpKTtcbiAgICAgIGxldCBsYXN0VmFscyA9IEFycmF5LmZyb208bnVtYmVyfHN0cmluZ3xbbnVtYmVyLCBudW1iZXJdPih2YWxzLnNsaWNlKFxuICAgICAgICAgIChzaXplIC0gRk9STUFUX05VTV9GSVJTVF9MQVNUX1ZBTFMpICogc3RvcmFnZVBlckVsZW1lbnQsXG4gICAgICAgICAgc2l6ZSAqIHN0b3JhZ2VQZXJFbGVtZW50KSk7XG4gICAgICBpZiAoZHR5cGUgPT09ICdjb21wbGV4NjQnKSB7XG4gICAgICAgIGZpcnN0VmFscyA9IGNyZWF0ZUNvbXBsZXhUdXBsZXMoZmlyc3RWYWxzKTtcbiAgICAgICAgbGFzdFZhbHMgPSBjcmVhdGVDb21wbGV4VHVwbGVzKGxhc3RWYWxzKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBbXG4gICAgICAgICdbJyArXG4gICAgICAgIGZpcnN0VmFscy5tYXAoKHgsIGkpID0+IHZhbFRvU3RyaW5nKHgsIHBhZFBlckNvbFtpXSwgZHR5cGUpKVxuICAgICAgICAgICAgLmpvaW4oJywgJykgK1xuICAgICAgICAnLCAuLi4sICcgK1xuICAgICAgICBsYXN0VmFsc1xuICAgICAgICAgICAgLm1hcChcbiAgICAgICAgICAgICAgICAoeCwgaSkgPT4gdmFsVG9TdHJpbmcoXG4gICAgICAgICAgICAgICAgICAgIHgsIHBhZFBlckNvbFtzaXplIC0gRk9STUFUX05VTV9GSVJTVF9MQVNUX1ZBTFMgKyBpXSwgZHR5cGUpKVxuICAgICAgICAgICAgLmpvaW4oJywgJykgK1xuICAgICAgICAnXSdcbiAgICAgIF07XG4gICAgfVxuICAgIGNvbnN0IGRpc3BsYXlWYWxzOiBBcnJheTxudW1iZXJ8c3RyaW5nfFtudW1iZXIsIG51bWJlcl0+ID1cbiAgICAgICAgZHR5cGUgPT09ICdjb21wbGV4NjQnID8gY3JlYXRlQ29tcGxleFR1cGxlcyh2YWxzKSA6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFycmF5LmZyb208bnVtYmVyfHN0cmluZz4odmFscyk7XG5cbiAgICByZXR1cm4gW1xuICAgICAgJ1snICtcbiAgICAgIGRpc3BsYXlWYWxzLm1hcCgoeCwgaSkgPT4gdmFsVG9TdHJpbmcoeCwgcGFkUGVyQ29sW2ldLCBkdHlwZSkpXG4gICAgICAgICAgLmpvaW4oJywgJykgK1xuICAgICAgJ10nXG4gICAgXTtcbiAgfVxuXG4gIC8vIFRoZSBhcnJheSBpcyByYW5rIDIgb3IgbW9yZS5cbiAgY29uc3Qgc3Vic2hhcGUgPSBzaGFwZS5zbGljZSgxKTtcbiAgY29uc3Qgc3Vic3RyaWRlcyA9IHN0cmlkZXMuc2xpY2UoMSk7XG4gIGNvbnN0IHN0cmlkZSA9IHN0cmlkZXNbMF0gKiBzdG9yYWdlUGVyRWxlbWVudDtcbiAgY29uc3QgbGluZXM6IHN0cmluZ1tdID0gW107XG4gIGlmIChzaXplID4gRk9STUFUX0xJTUlUX05VTV9WQUxTKSB7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBGT1JNQVRfTlVNX0ZJUlNUX0xBU1RfVkFMUzsgaSsrKSB7XG4gICAgICBjb25zdCBzdGFydCA9IGkgKiBzdHJpZGU7XG4gICAgICBjb25zdCBlbmQgPSBzdGFydCArIHN0cmlkZTtcbiAgICAgIGxpbmVzLnB1c2goLi4uc3ViVGVuc29yVG9TdHJpbmcoXG4gICAgICAgICAgdmFscy5zbGljZShzdGFydCwgZW5kKSwgc3Vic2hhcGUsIGR0eXBlLCBzdWJzdHJpZGVzLCBwYWRQZXJDb2wsXG4gICAgICAgICAgZmFsc2UgLyogaXNMYXN0ICovKSk7XG4gICAgfVxuICAgIGxpbmVzLnB1c2goJy4uLicpO1xuICAgIGZvciAobGV0IGkgPSBzaXplIC0gRk9STUFUX05VTV9GSVJTVF9MQVNUX1ZBTFM7IGkgPCBzaXplOyBpKyspIHtcbiAgICAgIGNvbnN0IHN0YXJ0ID0gaSAqIHN0cmlkZTtcbiAgICAgIGNvbnN0IGVuZCA9IHN0YXJ0ICsgc3RyaWRlO1xuICAgICAgbGluZXMucHVzaCguLi5zdWJUZW5zb3JUb1N0cmluZyhcbiAgICAgICAgICB2YWxzLnNsaWNlKHN0YXJ0LCBlbmQpLCBzdWJzaGFwZSwgZHR5cGUsIHN1YnN0cmlkZXMsIHBhZFBlckNvbCxcbiAgICAgICAgICBpID09PSBzaXplIC0gMSAvKiBpc0xhc3QgKi8pKTtcbiAgICB9XG4gIH0gZWxzZSB7XG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBzaXplOyBpKyspIHtcbiAgICAgIGNvbnN0IHN0YXJ0ID0gaSAqIHN0cmlkZTtcbiAgICAgIGNvbnN0IGVuZCA9IHN0YXJ0ICsgc3RyaWRlO1xuICAgICAgbGluZXMucHVzaCguLi5zdWJUZW5zb3JUb1N0cmluZyhcbiAgICAgICAgICB2YWxzLnNsaWNlKHN0YXJ0LCBlbmQpLCBzdWJzaGFwZSwgZHR5cGUsIHN1YnN0cmlkZXMsIHBhZFBlckNvbCxcbiAgICAgICAgICBpID09PSBzaXplIC0gMSAvKiBpc0xhc3QgKi8pKTtcbiAgICB9XG4gIH1cbiAgY29uc3Qgc2VwID0gcmFuayA9PT0gMiA/ICcsJyA6ICcnO1xuICBsaW5lc1swXSA9ICdbJyArIChzaXplID4gMCA/IGxpbmVzWzBdICsgc2VwIDogJycpO1xuICBmb3IgKGxldCBpID0gMTsgaSA8IGxpbmVzLmxlbmd0aCAtIDE7IGkrKykge1xuICAgIGxpbmVzW2ldID0gJyAnICsgbGluZXNbaV0gKyBzZXA7XG4gIH1cbiAgbGV0IG5ld0xpbmVTZXAgPSAnLFxcbic7XG4gIGZvciAobGV0IGkgPSAyOyBpIDwgcmFuazsgaSsrKSB7XG4gICAgbmV3TGluZVNlcCArPSAnXFxuJztcbiAgfVxuICBsaW5lc1tsaW5lcy5sZW5ndGggLSAxXSA9XG4gICAgICAnICcgKyBsaW5lc1tsaW5lcy5sZW5ndGggLSAxXSArICddJyArIChpc0xhc3QgPyAnJyA6IG5ld0xpbmVTZXApO1xuICByZXR1cm4gbGluZXM7XG59XG5cbmZ1bmN0aW9uIGNyZWF0ZUNvbXBsZXhUdXBsZXModmFsczogQXJyYXk8e30+fFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUeXBlZEFycmF5KTogQXJyYXk8W251bWJlciwgbnVtYmVyXT4ge1xuICBjb25zdCBjb21wbGV4VHVwbGVzOiBBcnJheTxbbnVtYmVyLCBudW1iZXJdPiA9IFtdO1xuICBmb3IgKGxldCBpID0gMDsgaSA8IHZhbHMubGVuZ3RoOyBpICs9IDIpIHtcbiAgICBjb21wbGV4VHVwbGVzLnB1c2goW3ZhbHNbaV0sIHZhbHNbaSArIDFdXSBhcyBbbnVtYmVyLCBudW1iZXJdKTtcbiAgfVxuICByZXR1cm4gY29tcGxleFR1cGxlcztcbn1cbiJdfQ==