@fauton/cfg
Version:
A package to work with context free grammars and LL1 parsers
89 lines (88 loc) • 4.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeLeftRecursion = void 0;
const populateCfg_1 = require("./utils/populateCfg");
function replaceProduction(pastProductionVariables, productionRules, currentVariable) {
const currentVariableProductionRules = productionRules[currentVariable];
const pastProductionVariablesSet = new Set(pastProductionVariables);
let currentVariableProductionRuleIndex = 0;
while (currentVariableProductionRuleIndex !== currentVariableProductionRules.length) {
const [firstToken, ...restTokens] = currentVariableProductionRules[currentVariableProductionRuleIndex].split(" ");
if (pastProductionVariablesSet.has(firstToken)) {
const matchedVariableProductionRules = productionRules[firstToken];
const newProductionRules = matchedVariableProductionRules.map(matchedVariableProductionRule => [matchedVariableProductionRule, ...restTokens].join(" "));
currentVariableProductionRules.splice(currentVariableProductionRuleIndex, 1, ...newProductionRules);
}
else {
currentVariableProductionRuleIndex += 1;
}
}
return currentVariableProductionRules;
}
function removeLeftRecursion(inputCfg) {
const cfg = (0, populateCfg_1.populateCfg)(inputCfg);
const productionRuleEntries = Object.entries(cfg.productionRules);
let currentEntryNumber = 0;
const newEntryIndexes = new Set();
while (currentEntryNumber !== productionRuleEntries.length) {
const entry = productionRuleEntries[currentEntryNumber];
const [productionVariable] = entry;
let productionRules = entry[1];
const betas = [], alphas = [];
let index = 0;
const postIndexes = [];
// Checking indirect left recursion
while (index !== currentEntryNumber) {
if (!newEntryIndexes.has(index)) {
postIndexes.push(index);
}
index += 1;
}
productionRules = postIndexes.length !== 0 ? replaceProduction(postIndexes.map(postIndex => productionRuleEntries[postIndex][0]), cfg.productionRules, productionVariable) : productionRules;
// Check if atleast one of the production rule is left recursive
const hasDirectLeftRecursion = productionRules.some((productionRule) => productionRule.startsWith(productionVariable));
if (hasDirectLeftRecursion) {
productionRules.forEach(productionRule => {
const startsWithVariable = productionRule.startsWith(productionVariable);
// Aα
if (startsWithVariable) {
const productionRuleTokens = productionRule.split(" ");
alphas.push(productionRuleTokens.slice(1).join(" "));
}
// β
else {
betas.push(productionRule);
}
});
const newProductionVariable = `${productionVariable}'`;
const productionRulesForNewVariable = [];
alphas.forEach(alpha => {
productionRulesForNewVariable.push(`${alpha} ${newProductionVariable}`);
});
// Pushing epsilon
productionRulesForNewVariable.push("");
// Updating current production rule
const productionRulesForCurrentVariable = [];
betas.forEach(beta => {
productionRulesForCurrentVariable.push(`${beta} ${newProductionVariable}`);
});
productionRuleEntries[currentEntryNumber] = [productionVariable, productionRulesForCurrentVariable];
productionRuleEntries.splice(currentEntryNumber + 1, 0, [newProductionVariable, productionRulesForNewVariable]);
cfg.productionRules[productionVariable] = productionRulesForCurrentVariable;
// Increase it by one more since we dont want to process the new entry
currentEntryNumber += 1;
newEntryIndexes.add(currentEntryNumber);
}
currentEntryNumber += 1;
}
const productionRules = {};
// Loop through new entries and populate the new production rules
productionRuleEntries.forEach(([productionVariable, productionRulesForVariable]) => {
productionRules[productionVariable] = productionRulesForVariable;
});
return (0, populateCfg_1.populateCfg)({
productionRules,
startVariable: cfg.startVariable,
});
}
exports.removeLeftRecursion = removeLeftRecursion;