@fauton/cfg
Version:
A package to work with context free grammars and LL1 parsers
118 lines (117 loc) • 6.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeNullProduction = exports.findNullableVariables = exports.createProductionCombinations = void 0;
const linked_list_1 = require("@datastructures-js/linked-list");
const populateCfg_1 = require("./utils/populateCfg");
function createProductionCombinations(substitution, epsilonProducingVariable, productionRuleVariable) {
const substitutionTokens = substitution.split(' ');
const epsilonProducingVariableCount = substitutionTokens.filter((letter) => letter === epsilonProducingVariable).length;
const totalCombinations = 2 ** epsilonProducingVariableCount;
const newSubstitutions = [];
let containsEpsilon = false;
for (let index = 0; index < totalCombinations; index += 1) {
let nthVariable = 0;
const newSubstitutionTokens = [];
for (let substitutionTokenIndex = 0; substitutionTokenIndex < substitutionTokens.length; substitutionTokenIndex += 1) {
const substitutionToken = substitutionTokens[substitutionTokenIndex];
if (substitutionToken === epsilonProducingVariable) {
// If nthVariable is 4, it will generate the following combo, 10, 11, 01, 00
// 11 means both the epsilon variable will be included, 00 means none will be included
if (index & (1 << nthVariable)) {
newSubstitutionTokens.push(substitutionToken);
}
nthVariable += 1;
}
else {
newSubstitutionTokens.push(substitutionToken);
}
}
// Set the contains epsilon flag to true if we have an epsilon string
if (!newSubstitutionTokens.length) {
containsEpsilon = true;
}
else {
const newSubstitutionToken = newSubstitutionTokens.join(' ');
// Only add the substitution if we dont have any epsilon
if (newSubstitutionToken !== productionRuleVariable) {
newSubstitutions.push(newSubstitutionToken);
}
}
}
return [newSubstitutions, containsEpsilon, epsilonProducingVariableCount];
}
exports.createProductionCombinations = createProductionCombinations;
/**
* Adds epsilon to production rules that references nullable variables
* @param cfg Variables array and production rules record of cfg
*/
function findNullableVariables(cfg) {
const { productionRules, variables } = cfg;
const linkedList = new linked_list_1.LinkedList();
// A set of nullable variables which directly leads to epsilon
const nullableVariablesSet = new Set();
variables
.filter((variable) => { var _a; return (_a = productionRules[variable]) === null || _a === void 0 ? void 0 : _a.some((productionRule) => productionRule.length === 0); })
.forEach((directNullableVariable) => {
linkedList.insertFirst(directNullableVariable);
nullableVariablesSet.add(directNullableVariable);
});
while (linkedList.count()) {
const nullableVariable = linkedList.removeFirst().getValue();
// Find all the variables that indirectly references nullable variables
variables.forEach((variable) => {
// No need to check variables that are already nullable for nullability
if (!nullableVariablesSet.has(variable) && productionRules[variable]) {
// Loop through each production rules of the variable and check if any of the rules are nullable
for (let index = 0; index < productionRules[variable].length; index += 1) {
const productionRuleSubstitutions = productionRules[variable][index];
// Check if the production rule's substitutions are all nullable, then they are indirectly nullable
if (productionRuleSubstitutions
.split(' ')
.every((productionRuleSubstitution) => nullableVariablesSet.has(productionRuleSubstitution))) {
nullableVariablesSet.add(nullableVariable);
linkedList.insertFirst(variable);
break;
}
}
}
});
}
return Array.from(nullableVariablesSet);
}
exports.findNullableVariables = findNullableVariables;
/**
* Removes all the null production and returns a new transition record
* @param cfg Variables and transition record for cfg
* @returns New transition record with null production removed
*/
function removeNullProduction(inputCfg) {
const cfg = (0, populateCfg_1.populateCfg)(inputCfg);
const { productionRules } = cfg;
const nullableVariables = findNullableVariables(cfg);
nullableVariables.forEach((nullableVariable) => {
Object.entries(productionRules).forEach(([productionRuleVariable, productionRuleSubstitutions]) => {
const newSubstitutionsTokens = [];
productionRuleSubstitutions.forEach((substitution) => {
// No need to proceed further if we are inside a null string
if (substitution.length) {
// If there are no variables that produces epsilon
// No need to update the substitution
const substitutionTokens = new Set(substitution.split(' '));
// If our substitution doesn't contain any nullable variable no need to generate combinations
if (!substitutionTokens.has(nullableVariable)) {
newSubstitutionsTokens.push(substitution);
}
else {
const [productionCombinations] = createProductionCombinations(substitution, nullableVariable, productionRuleVariable);
// Generate all possible combination of the production rule, with and without the epsilon producing variable
newSubstitutionsTokens.push(...productionCombinations);
}
}
});
// Making sure that there are no duplicates
productionRules[productionRuleVariable] = Array.from(new Set(newSubstitutionsTokens));
});
});
}
exports.removeNullProduction = removeNullProduction;