UNPKG

ll1-validator

Version:

A tool that checks if a given grammar is LL(1).

338 lines (308 loc) 11.4 kB
const parser = require('./parser'); const errors = require('./errors'); function calculateNullables(input) { const grammar = input.grammar; const nonTerminals = input.nonTerminals; const nullableRules = {}; const nullableNonTerminals = {}; let doLoop = true; let remainingCycles = input.rulesNumber; nonTerminals.forEach(l => nullableRules[l] = []); while (doLoop) { if (remainingCycles < 0) { let involvedNT = []; nonTerminals.forEach(l => { nullableRules[l].forEach((rule, index) => { if (rule === undefined) { const ruleString = grammar[l][index].map(v => v.value).join(' '); involvedNT.push(`[${l} -> ${ruleString}]`); } }); }); throw new errors.SemanticError(`Loop detected: ${involvedNT.sort().join(', ')}`); } doLoop = false; remainingCycles -= 1; nonTerminals.forEach(l => { grammar[l].forEach((rule, index) => { nullableRules[l][index] = ruleIsNullable(rule, nullableNonTerminals); if (nullableRules[l][index] === undefined) { doLoop = true; } }); }); nonTerminals.forEach(l => { if (nullableNonTerminals[l] === undefined) { nullableNonTerminals[l] = false; for (const isNullable of nullableRules[l]) { if (isNullable === true) { nullableNonTerminals[l] = true; break; } if (isNullable === undefined) { nullableNonTerminals[l] = undefined; } } } }) } return { nullableRules, nullableNonTerminals } } function ruleIsNullable(rule, nullableNonTerminals) { let currentResult = true; for (const item of rule) { if (item.type === parser.TERMINAL) { return false; } if (nullableNonTerminals[item.value] === false) { return false; } if (nullableNonTerminals[item.value] === undefined) { currentResult = undefined; } } return currentResult; } function initializeFirstSets(input) { const grammar = input.grammar; const result = {}; const nullableNonTerminals = calculateNullables(input).nullableNonTerminals; // TODO reuse precomputed values input.nonTerminals.forEach(l => { result[l] = []; grammar[l].forEach((r, index) => { result[l][index] = [[]]; for (const item of r) { if (item.type === parser.TERMINAL) { result[l][index][0].push(item.value); return; } if (item.type === parser.NONTERMINAL && !nullableNonTerminals[item.value]) { return; } } }); }); return result; } function calculateFirstSetsDependencies(input) { const grammar = input.grammar; const result = {}; const nullableNonTerminals = calculateNullables(input).nullableNonTerminals; // TODO reuse precomputed values input.nonTerminals.forEach(l => { result[l] = []; grammar[l].forEach((r, index) => { result[l][index] = new Set(); for (const item of r) { if (item.type === parser.TERMINAL) { break; } else { result[l][index].add(item.value); if (!nullableNonTerminals[item.value]) { break; } } } }); }); return result; } function getAggregateFirstSet(set, nonTerminal, index) { const result = new Set(); set[nonTerminal].forEach(item => { item[index].forEach(v => result.add(v)); }) return result; } function calculateFirstSets(input) { const firstSets = initializeFirstSets(input); const depencencies = calculateFirstSetsDependencies(input); let doLoop = true; let loopIndex = 0; while (doLoop) { doLoop = false; Object.keys(firstSets).forEach(l => { firstSets[l].forEach((item, index) => { const currentSet = item[item.length - 1]; const nextSet = Array.from(item[item.length - 1]); const dependency = depencencies[l][index]; dependency.forEach(nonTerminal => { getAggregateFirstSet(firstSets, nonTerminal, loopIndex).forEach(v => { if (!currentSet.includes(v)) { doLoop = true; nextSet.push(v); } }); }); item.push(nextSet.sort()); }); }); loopIndex++; } return firstSets; } function calculateFollowSetDependencies(input) //First run for follow sets: gets non terminals and terminals next to each non terminal { const grammar = input.grammar; const axiom = input.startSymbol; var follow_nonTerminals = {} var follow_terminals = {} input.nonTerminals.forEach(it => { follow_nonTerminals[it] = []; follow_terminals[it] = [[]]; }); follow_terminals[axiom][0].push("↙"); input.nonTerminals.forEach(l => { input.nonTerminals.forEach(f => { grammar[f].forEach(r => { var pushNext = false; //if true, the item that comes next is in the follow set of l for (const item of r) { if (pushNext) { if (item.type === parser.NONTERMINAL) { const tmp_itemInits = calculateFirstSets(input)[item.value]; tmp_itemInits.forEach(x => { const tmp_follows = x[x.length - 1]; tmp_follows.forEach(t => { if (!follow_terminals[l][0].includes(t)) { follow_terminals[l][0].push(t); } }); }); if (calculateNullables(input).nullableNonTerminals[item.value] === false) { pushNext = false; } } else if (item.type === parser.TERMINAL) { if (!follow_terminals[l][0].includes(item.value)) follow_terminals[l][0].push(item.value); pushNext = false; } } if (item.value === l) { pushNext = true; } } if (pushNext) { if (!follow_nonTerminals[l].includes(f)) follow_nonTerminals[l].push(f); //if I find l at the end, f's follows are inherited } }); }); }); return { follow_nonTerminals: follow_nonTerminals, follow_terminals: follow_terminals } } function calculateFollowSets(input) { var followsets = {} const axiom = input.startSymbol; const followSetsDep = calculateFollowSetDependencies(input) const non_terminals = followSetsDep.follow_nonTerminals; const initial_followsets = followSetsDep.follow_terminals; followsets = initial_followsets; var iteration = 0; var goahead = true; do { iteration += 1; Object.keys(non_terminals).forEach(e => { followsets[e][iteration] = followsets[e][iteration - 1].slice(); }); Object.keys(non_terminals).forEach(e => { non_terminals[e].forEach(nt => { followsets[nt][iteration - 1].forEach(fs => { if (!followsets[e][iteration].includes(fs)) followsets[e][iteration].push(fs); }); }); followsets[e][iteration].sort(); }); goahead = isDifferent(followsets, iteration); } while (goahead); return followsets; } function isDifferent(obj, iter) { var ret = false; Object.keys(obj).forEach(e => { var newRow = obj[e][iter]; var oldRow = obj[e][iter - 1]; if (newRow.length != oldRow.length) ret = true; }); return ret; } function calculateLookAheads(input) { const grammar = input.grammar; var ret = {}; const axiom = input.startSymbol; const firstSets = calculateFirstSets(input); const followSets = calculateFollowSets(input, axiom); const nullableRules = calculateNullables(input).nullableRules; input.nonTerminals.forEach(l => { ret[l] = []; grammar[l].forEach((r, index) => { ret[l][index] = []; const tmp_inits = firstSets[l][index][firstSets[l][index].length - 1]; tmp_inits.forEach(i => { ret[l][index].push(i); }); if (nullableRules[l][index]) { const tmp_follows = followSets[l][followSets[l].length - 1]; tmp_follows.forEach(f => { if (!ret[l][index].includes(f)) ret[l][index].push(f); }); } ret[l][index].sort(); }); }); return ret; } function isLL1(input) { const lookaheads = calculateLookAheads(input); var res = true; Object.keys(lookaheads).forEach(l => { const conf = calculateConflicts(l, input, lookaheads).length; if (conf > 0) { res = false; } }); return res; } function calculateConflicts(nonTerminal, input = {}, lookaheads = []) { // input and/or followsets MUST BE passed var terminals = []; var ret = []; if (lookaheads == []) { lookaheads = calculateLookAheads(input); } lookaheads[nonTerminal].forEach(r => { r.forEach(t => { if (terminals.includes(t)) { if (!ret.includes(t)) ret.push(t); } else { terminals.push(t); } }); }); return ret; } function calculateAllConflicts(input) { const lookaheads = calculateLookAheads(input); var res = {}; Object.keys(lookaheads).forEach(l => { const conf = calculateConflicts(l, input, lookaheads); res[l] = conf.slice(); }); return res; } module.exports.calculateNullables = calculateNullables; module.exports.initializeFirstSets = initializeFirstSets; module.exports.calculateFirstSetsDependencies = calculateFirstSetsDependencies; module.exports.calculateFirstSets = calculateFirstSets; module.exports.calculateFollowSets = calculateFollowSets; module.exports.calculateFollowSetDependencies = calculateFollowSetDependencies; module.exports.calculateLookAheads = calculateLookAheads; module.exports.isLL1 = isLL1; module.exports.calculateConflicts = calculateConflicts; module.exports.calculateAllConflicts = calculateAllConflicts;