@fauton/cfg
Version:
A package to work with context free grammars and LL1 parsers
115 lines (114 loc) • 6.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.cykParse = void 0;
const setOperations_1 = require("./utils/setOperations");
/**
* Parses a sentence given a cfg
* @param cfg Input cfg production rules and start variable
* @param sentenceTokens An array of sentence tokens
* @returns Boolean value on whether the sentence is part of the grammar and steps that the CYK algo took to resolve it
*/
function cykParse(cfg, sentenceTokens) {
const stringTokensLength = sentenceTokens.length;
const { productionRules, startVariable } = cfg;
const productionRulesEntries = Object.entries(productionRules);
const cykTableDetailed = [];
const cykTable = [];
// Initialize the cykTableDetailed first, a left sided right angled triangle with height and base equal to the length of sentence tokens
for (let i = 0; i < stringTokensLength; i += 1) {
const cykTableDetailedRows = [];
const cykTableRows = [];
for (let j = 0; j <= i; j += 1) {
// Each cell will contain a set
cykTableDetailedRows.push({
combinations: [
{
merged: [],
parts: [],
},
],
value: [],
});
cykTableRows.push('');
}
cykTable.push(cykTableRows);
cykTableDetailed.push(cykTableDetailedRows);
}
// A record that maps which nodes (T, VV) is reachable by which variables
const nodeVariablesRecord = {};
// Populating the nodeVariables record
// Loop through each of the production rules
productionRulesEntries.forEach(([productionRuleVariable, productionRuleSubstitutions]) => {
// Loop through each of the substitutions
productionRuleSubstitutions.forEach((productionRuleSubstitution) => {
// Add the substitution to the record, it it doesn't exist
if (!nodeVariablesRecord[productionRuleSubstitution]) {
nodeVariablesRecord[productionRuleSubstitution] = [];
}
// Add the production rule variable to the set
nodeVariablesRecord[productionRuleSubstitution].push(productionRuleVariable);
});
});
// filling the bottom row, with values from the record, since the bottom row will contain only direct terminals
sentenceTokens.forEach((sentenceToken, sentenceTokenIndex) => {
cykTableDetailed[stringTokensLength - 1][sentenceTokenIndex] = {
combinations: [
{
merged: nodeVariablesRecord[sentenceToken],
parts: [nodeVariablesRecord[sentenceToken]],
},
],
value: nodeVariablesRecord[sentenceToken],
};
cykTable[stringTokensLength - 1][sentenceTokenIndex] =
nodeVariablesRecord[sentenceToken].join(' ');
});
// Start from the row top of the bottom row, all the way to the first row
for (let cykTableRow = stringTokensLength - 2; cykTableRow >= 0; cykTableRow -= 1) {
// The total number of combinations that is possible from the current row
// if input is aabab and we are in row index 2, total combinations are aa+bab, aab+ab
const totalCombinations = stringTokensLength - (cykTableRow + 1); // Adding 1 as row is 0 index based;
// Loop from left most column to the right most column, since its a left right angled triangle, the number of column will be the same as the number of rows
for (let cykTableCol = 0; cykTableCol <= cykTableRow; cykTableCol += 1) {
const combinations = [];
// Each cell will contain a set of variables that can be derived from all the cross products
const variablesContainingCrossProduct = new Set();
for (let combinationNumber = 1; combinationNumber <= totalCombinations; combinationNumber += 1) {
// The combinationNumber'th bottom cell from the current cell
const bottomRowCell = cykTableDetailed[cykTableRow + combinationNumber][cykTableCol],
// The combinationNumber'th right diagonal cell from the current cell
rightDiagonalCell = cykTableDetailed[stringTokensLength - combinationNumber][stringTokensLength - cykTableRow + cykTableCol - combinationNumber];
// Get the cross product of all the variables in those two cells
const crossProduct = (0, setOperations_1.setCrossProduct)(new Set(bottomRowCell.value), new Set(rightDiagonalCell.value));
crossProduct.forEach((crossProductItem) => {
// Making sure that cross product item is present in the record
if (nodeVariablesRecord[crossProductItem]) {
// Loop through all the cross product items and add them to the above set
nodeVariablesRecord[crossProductItem].forEach((variable) => {
variablesContainingCrossProduct.add(variable);
});
}
});
combinations.push({
merged: Array.from(crossProduct),
parts: [bottomRowCell.value, rightDiagonalCell.value],
});
}
// Update the set for the corresponding cell
cykTableDetailed[cykTableRow][cykTableCol] = {
combinations,
value: Array.from(variablesContainingCrossProduct),
};
cykTable[cykTableRow][cykTableCol] = Array.from(variablesContainingCrossProduct).join(' ');
}
}
// If the first cell has the startVariable, the sentence is valid for the grammar
return {
verdict: cykTableDetailed[0][0].value.includes(startVariable),
cykTableDetailed: JSON.parse(JSON.stringify(cykTableDetailed)),
cykTable,
nodeVariablesRecord: JSON.parse(JSON.stringify(nodeVariablesRecord)),
sentenceTokens,
};
}
exports.cykParse = cykParse;