@scinorandex/sparse
Version:
Yet another parser generator
290 lines • 14.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GeneratorResult = exports.generateStates = void 0;
const parser_1 = require("./parser");
const xContainsAllOfY = (xs, ys) => [...ys].every((x) => xs.has(x));
const EOF_STRING = "[EOF]";
function computeFirstSets(allProductions) {
const ret = new Map();
for (const production of allProductions)
ret.set(production.lhs.lexeme, new Set());
let wasEdited = true;
while (wasEdited) {
wasEdited = false;
for (const production of allProductions) {
const toBeModified = ret.get(production.lhs.lexeme);
const firstRhsToken = production.rhs[0];
if (firstRhsToken.type === "variable") {
const add = ret.get(firstRhsToken.token.lexeme);
if (add == null) {
return {
success: false,
reason: `Variable "${firstRhsToken.token.lexeme}" doesn't have a corresponding left hand side`,
token: firstRhsToken.token,
};
}
if (!xContainsAllOfY(toBeModified, add)) {
wasEdited = true;
ret.set(production.lhs.lexeme, new Set([...toBeModified, ...add]));
}
}
else if (firstRhsToken.type === "terminal") {
if (toBeModified.has(firstRhsToken.token.lexeme) == false) {
wasEdited = true;
ret.set(production.lhs.lexeme, new Set([...toBeModified, firstRhsToken.token.lexeme]));
}
}
}
}
return { success: true, value: ret };
}
function computeFollowSets(allProductions) {
const firstSetsResult = computeFirstSets(allProductions);
if (firstSetsResult.success === false)
return firstSetsResult;
const firstSets = firstSetsResult.value;
const ret = new Map();
for (const production of allProductions)
ret.set(production.lhs.lexeme, new Set());
ret.get(allProductions[0].lhs.lexeme).add(EOF_STRING);
let wasEdited = true;
while (wasEdited) {
wasEdited = false;
for (const variable of ret.keys()) {
for (const currentProduction of allProductions) {
const rhs = currentProduction.rhs;
for (let i = 0; i < rhs.length; i++) {
const currentRhsToken = rhs[i];
if (currentRhsToken.type === "variable" && currentRhsToken.token.lexeme === variable) {
if (i === rhs.length - 1) {
const toBeAdded = ret.get(currentProduction.lhs.lexeme);
const receiver = ret.get(variable);
if (!xContainsAllOfY(receiver, toBeAdded)) {
wasEdited = true;
ret.set(variable, new Set([...toBeAdded, ...receiver]));
}
}
else {
const nextRhsToken = rhs[i + 1];
if (nextRhsToken.type === "terminal") {
const existing = ret.get(variable);
if (!existing.has(nextRhsToken.token.lexeme)) {
wasEdited = true;
ret.set(variable, new Set([...existing, nextRhsToken.token.lexeme]));
}
}
else if (nextRhsToken.type === "variable") {
const firstSet = firstSets.get(nextRhsToken.token.lexeme);
if (firstSet == null)
return {
success: false,
reason: `Variable "${nextRhsToken.token.lexeme}" doesn't have a corresponding left hand side`,
token: nextRhsToken.token,
};
if (!xContainsAllOfY(ret.get(variable), firstSet)) {
wasEdited = true;
ret.set(variable, new Set([...ret.get(variable), ...firstSet]));
}
}
}
}
}
}
}
}
return { success: true, value: { firstSets, followSets: ret } };
}
const generateStates = (productions) => {
const followSetsResult = computeFollowSets(productions);
if (followSetsResult.success == false)
return followSetsResult;
const { firstSets, followSets } = followSetsResult.value;
const determineNextLookAhead = (lhs, array) => {
if (array.length === 0) {
const followSet = followSets.get(lhs.lexeme);
if (followSet != undefined)
return { success: true, value: [...followSet] };
return {
success: false,
reason: `Variable ${lhs.lexeme} not present in computed follow sets`,
token: lhs,
};
}
else {
const next = array[0];
if (next.type === "terminal")
return { success: true, value: [next.token.lexeme] };
const testing = firstSets.get(next.token.lexeme);
if (testing !== undefined)
return { success: true, value: [...testing] };
return {
success: false,
reason: `Variable ${next.token.lexeme} not present in computed first sets`,
token: next.token,
};
}
};
function generateInitialItemSet(production) {
const initialItem = { lhs: production.lhs, rhs: production.rhs, dot: 0, lookahead: [EOF_STRING] };
return expandItemSet([initialItem]);
}
function expandItemSet(items) {
const itemSet = [...items];
const unprocesssedItems = [...items];
while (unprocesssedItems.length > 0) {
const currentItem = unprocesssedItems.shift();
const after = currentItem.rhs[currentItem.dot];
if (after == null)
continue;
if (after.type === "variable") {
const newProductions = productions.filter((p) => p.lhs.lexeme === after.token.lexeme);
const rest = currentItem.rhs.slice(currentItem.dot + 1);
const lookaheadResult = determineNextLookAhead(currentItem.lhs, rest);
if (lookaheadResult.success === false)
return lookaheadResult;
const lookahead = lookaheadResult.value;
for (const newProduction of newProductions) {
const newItem = { lhs: newProduction.lhs, rhs: newProduction.rhs, dot: 0, lookahead };
const encoding = JSON.stringify(newItem);
if (itemSet.some((i) => JSON.stringify(i) === encoding) == false) {
unprocesssedItems.push(newItem);
itemSet.push(newItem);
}
}
}
}
return { success: true, value: { itemSet, kernel: items[0] } };
}
const initialItemSetResult = generateInitialItemSet(productions[0]);
if (initialItemSetResult.success === false)
return initialItemSetResult;
const initialItemSet = initialItemSetResult.value;
function generateStates(_initialState) {
const initialState = Object.assign(Object.assign({}, _initialState), { count: 0 });
const states = [initialState];
const unprocessedStates = [initialState];
const GotoTable = [];
const ActionTable = [];
while (unprocessedStates.length > 0) {
const currentState = unprocessedStates.shift();
const nextStates_Goto = new Map();
const nextStates_Shift = new Map();
calculateNextItem: for (const item of currentState.itemSet) {
const nextSymbol = item.rhs[item.dot];
if (nextSymbol == null) {
const productionToReduceTo = productions
.map((p, i) => [p, i])
.find(([p, _]) => JSON.stringify(p.rhs) === JSON.stringify(item.rhs) && p.lhs.lexeme === item.lhs.lexeme)[1];
for (const lookahead of item.lookahead) {
if (ActionTable[currentState.count] === undefined)
ActionTable[currentState.count] = new Map();
ActionTable[currentState.count].set(lookahead, { action: "reduce", value: productionToReduceTo });
}
continue calculateNextItem;
}
const newItem = Object.assign(Object.assign({}, item), { dot: item.dot + 1 });
if (nextSymbol.type === "variable") {
if (nextStates_Goto.has(nextSymbol.token.lexeme))
nextStates_Goto.get(nextSymbol.token.lexeme).push(newItem);
else
nextStates_Goto.set(nextSymbol.token.lexeme, [newItem]);
}
else if (nextSymbol.type === "terminal") {
if (nextStates_Shift.has(nextSymbol.token.lexeme))
nextStates_Shift.get(nextSymbol.token.lexeme).push(newItem);
else
nextStates_Shift.set(nextSymbol.token.lexeme, [newItem]);
}
}
for (const [gotoTransition, gotoItems] of nextStates_Goto.entries()) {
const expandedItemsResult = expandItemSet(gotoItems);
if (expandedItemsResult.success === false)
return expandedItemsResult;
const expandedItems = expandedItemsResult.value;
let existingState = states.find((s) => {
return (s.kernel.lhs === expandedItems.kernel.lhs &&
s.kernel.rhs.length === expandedItems.kernel.rhs.length &&
JSON.stringify(s.kernel.rhs) === JSON.stringify(expandedItems.kernel.rhs) &&
s.kernel.lookahead.length === expandedItems.kernel.lookahead.length &&
JSON.stringify(s.kernel.lookahead) === JSON.stringify(expandedItems.kernel.lookahead) &&
JSON.stringify(s.itemSet) === JSON.stringify(expandedItems.itemSet));
});
if (GotoTable[currentState.count] === undefined)
GotoTable[currentState.count] = new Map();
if (existingState == undefined) {
const newState = {
itemSet: expandedItems.itemSet,
kernel: expandedItems.kernel,
count: states.length,
};
states.push(newState);
unprocessedStates.push(newState);
existingState = newState;
}
GotoTable[currentState.count].set(gotoTransition, existingState.count);
}
for (const [shiftTransition, shiftItems] of nextStates_Shift.entries()) {
const expandedItemsResult = expandItemSet(shiftItems);
if (expandedItemsResult.success === false)
return expandedItemsResult;
const expandedItems = expandedItemsResult.value;
let existingState = states.find((s) => {
return (s.kernel.lhs === expandedItems.kernel.lhs &&
s.kernel.rhs.length === expandedItems.kernel.rhs.length &&
JSON.stringify(s.kernel.rhs) === JSON.stringify(expandedItems.kernel.rhs) &&
s.kernel.lookahead.length === expandedItems.kernel.lookahead.length &&
JSON.stringify(s.kernel.lookahead) === JSON.stringify(expandedItems.kernel.lookahead) &&
JSON.stringify(s.itemSet) === JSON.stringify(expandedItems.itemSet));
});
if (ActionTable[currentState.count] === undefined)
ActionTable[currentState.count] = new Map();
if (existingState == undefined) {
const newState = {
itemSet: expandedItems.itemSet,
kernel: expandedItems.kernel,
count: states.length,
};
states.push(newState);
unprocessedStates.push(newState);
existingState = newState;
}
ActionTable[currentState.count].set(shiftTransition, { action: "shift", value: existingState.count });
}
}
return { success: true, value: { states, GotoTable, ActionTable } };
}
const StatesResult = generateStates(initialItemSet);
if (StatesResult.success === false)
return StatesResult;
const { states, ActionTable, GotoTable } = StatesResult.value;
return { success: true, value: new GeneratorResult(states, ActionTable, GotoTable) };
};
exports.generateStates = generateStates;
class GeneratorResult {
constructor(states, ActionTable, GotoTable) {
this.states = states;
this.ActionTable = ActionTable;
this.GotoTable = GotoTable;
}
toTable() {
return this.ActionTable.map((actions, idx) => {
return [
...[...actions.entries()].map(([k, { action, value }]) => `${k}=${action === "reduce" ? "r" : "s"}${value}`),
...(this.GotoTable[idx] === undefined ? [] : [...this.GotoTable[idx].entries()].map(([k, v]) => `${k}=${v}`)),
].join(", ");
}).join("\n");
}
toStates() {
return this.ActionTable.map((actions, idx) => {
const tableState = new parser_1.TableState();
for (const [k, { action, value }] of actions.entries())
tableState.actions.set(k, { type: action === "reduce" ? "reduce" : "shift", value });
if (this.GotoTable[idx] !== undefined)
for (const [k, value] of this.GotoTable[idx].entries())
tableState.actions.set(k, { type: "goto", value });
return tableState;
});
}
}
exports.GeneratorResult = GeneratorResult;
//# sourceMappingURL=generator.js.map