UNPKG

@scinorandex/sparse

Version:

Yet another parser generator

290 lines 14.6 kB
"use strict"; 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