127 lines
3.7 KiB
JavaScript
127 lines
3.7 KiB
JavaScript
/**
|
|
* Performs expandSinglePhrase() on array
|
|
*/
|
|
export const expandPhrases = (phrases) => phrases.flatMap(expandSinglePhrase);
|
|
/**
|
|
* Expands a syntax description like
|
|
*
|
|
* "CREATE [OR REPLACE] [TEMP|TEMPORARY] TABLE"
|
|
*
|
|
* into an array of all possible combinations like:
|
|
*
|
|
* [ "CREATE TABLE",
|
|
* "CREATE TEMP TABLE",
|
|
* "CREATE TEMPORARY TABLE",
|
|
* "CREATE OR REPLACE TABLE",
|
|
* "CREATE OR REPLACE TEMP TABLE",
|
|
* "CREATE OR REPLACE TEMPORARY TABLE" ]
|
|
*
|
|
* The [] and {} parenthesis can also be nested like
|
|
*
|
|
* "FOR [OF {UNIQUE | MANDATORY} TABLES]"
|
|
*
|
|
* resulting in:
|
|
*
|
|
* [ "FOR",
|
|
* "FOR OF UNIQUE TABLES",
|
|
* "FOR OF MANDATORY TABLES" ]
|
|
*/
|
|
export const expandSinglePhrase = (phrase) => buildCombinations(parsePhrase(phrase)).map(stripExtraWhitespace);
|
|
const stripExtraWhitespace = (text) => text.replace(/ +/g, ' ').trim();
|
|
const parsePhrase = (text) => ({
|
|
type: 'mandatory_block',
|
|
items: parseAlteration(text, 0)[0],
|
|
});
|
|
const parseAlteration = (text, index, expectClosing) => {
|
|
const alterations = [];
|
|
while (text[index]) {
|
|
const [term, newIndex] = parseConcatenation(text, index);
|
|
alterations.push(term);
|
|
index = newIndex;
|
|
if (text[index] === '|') {
|
|
index++;
|
|
}
|
|
else if (text[index] === '}' || text[index] === ']') {
|
|
if (expectClosing !== text[index]) {
|
|
throw new Error(`Unbalanced parenthesis in: ${text}`);
|
|
}
|
|
index++;
|
|
return [alterations, index];
|
|
}
|
|
else if (index === text.length) {
|
|
if (expectClosing) {
|
|
throw new Error(`Unbalanced parenthesis in: ${text}`);
|
|
}
|
|
return [alterations, index];
|
|
}
|
|
else {
|
|
throw new Error(`Unexpected "${text[index]}"`);
|
|
}
|
|
}
|
|
return [alterations, index];
|
|
};
|
|
const parseConcatenation = (text, index) => {
|
|
const items = [];
|
|
while (true) {
|
|
const [term, newIndex] = parseTerm(text, index);
|
|
if (term) {
|
|
items.push(term);
|
|
index = newIndex;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
return items.length === 1 ? [items[0], index] : [{ type: 'concatenation', items }, index];
|
|
};
|
|
const parseTerm = (text, index) => {
|
|
if (text[index] === '{') {
|
|
return parseMandatoryBlock(text, index + 1);
|
|
}
|
|
else if (text[index] === '[') {
|
|
return parseOptionalBlock(text, index + 1);
|
|
}
|
|
else {
|
|
let word = '';
|
|
while (text[index] && /[A-Za-z0-9_ ]/.test(text[index])) {
|
|
word += text[index];
|
|
index++;
|
|
}
|
|
return [word, index];
|
|
}
|
|
};
|
|
const parseMandatoryBlock = (text, index) => {
|
|
const [items, newIndex] = parseAlteration(text, index, '}');
|
|
return [{ type: 'mandatory_block', items }, newIndex];
|
|
};
|
|
const parseOptionalBlock = (text, index) => {
|
|
const [items, newIndex] = parseAlteration(text, index, ']');
|
|
return [{ type: 'optional_block', items }, newIndex];
|
|
};
|
|
const buildCombinations = (node) => {
|
|
if (typeof node === 'string') {
|
|
return [node];
|
|
}
|
|
else if (node.type === 'concatenation') {
|
|
return node.items.map(buildCombinations).reduce(stringCombinations, ['']);
|
|
}
|
|
else if (node.type === 'mandatory_block') {
|
|
return node.items.flatMap(buildCombinations);
|
|
}
|
|
else if (node.type === 'optional_block') {
|
|
return ['', ...node.items.flatMap(buildCombinations)];
|
|
}
|
|
else {
|
|
throw new Error(`Unknown node type: ${node}`);
|
|
}
|
|
};
|
|
const stringCombinations = (xs, ys) => {
|
|
const results = [];
|
|
for (const x of xs) {
|
|
for (const y of ys) {
|
|
results.push(x + y);
|
|
}
|
|
}
|
|
return results;
|
|
};
|
|
//# sourceMappingURL=expandPhrases.js.map
|