UNPKG

@scinorandex/sparse

Version:

Yet another parser generator

125 lines 5.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LR1ParserGraveError = exports.Sparse = exports.TableState = void 0; const generator_1 = require("./generator"); const Stack_1 = require("./utils/Stack"); class TableState { constructor() { this.actions = new Map(); } getTerminalAction(terminal) { return this.actions.get(`[${terminal}]`); } getVariableAction(variable) { return this.actions.get(variable); } } exports.TableState = TableState; class Sparse { constructor(options) { this.options = options; } static tryFromProductions(options) { const statesResult = (0, generator_1.generateStates)(options.productions); if (statesResult.success === false) return statesResult; const states = statesResult.value; const sparse = new Sparse({ productions: options.productions, states: states.toStates(), toStringifiedTokenType: options.toStringifiedTokenType, }); return { success: true, value: sparse }; } static fromProductions(options) { const result = Sparse.tryFromProductions(options); if (result.success === false) throw new Error(`Encountered error "${result.reason}" at ${result.token.line}:${result.token.column}`); return result.value; } generate(lexer, options) { var _a; return new LR1Parser(this.options, options.reducer, (_a = options.recover) !== null && _a !== void 0 ? _a : null, lexer); } } exports.Sparse = Sparse; class LR1Parser { constructor(options, reducer, recover, lexer) { this.options = options; this.reducer = reducer; this.recover = recover; this.lexer = lexer; this.statesStack = new Stack_1.Stack([0]); this.symbolsStack = new Stack_1.Stack(); this.exceptions = []; } parse() { var _a; const { productions, states, toStringifiedTokenType } = this.options; while (true) { let currentState = this.statesStack.peek(); let token = this.lexer.peekNextToken(); let action = states[currentState].getTerminalAction(toStringifiedTokenType(token.type)); if (action == null) { if (this.recover === null) throw new LR1ParserGraveError(`Invalid syntax. Expected ${[...states[currentState].actions.keys()].join(",")} but got ${toStringifiedTokenType(token.type)}`, token); const recoveryResult = this.recover({ lexer: this.lexer, states, statesStack: this.statesStack, symbolsStack: this.symbolsStack, addError: (reason) => this.exceptions.push({ message: reason, token: this.lexer.peekNextToken() }), crash: (reason) => ({ success: false, reason }), finish: (options) => ({ success: true, token: options === null || options === void 0 ? void 0 : options.newToken }), isSafe: () => states[this.statesStack.peek()].actions.has(`[${toStringifiedTokenType(this.lexer.peekNextToken().type)}]`), }); if (recoveryResult.success === false) throw new LR1ParserGraveError(recoveryResult.reason, token); token = (_a = recoveryResult.token) !== null && _a !== void 0 ? _a : this.lexer.peekNextToken(); currentState = this.statesStack.peek(); action = states[currentState].getTerminalAction(toStringifiedTokenType(token.type)); } const fixedAction = action; if (fixedAction.type === "shift") { token = this.lexer.getNextToken(); this.statesStack.push(fixedAction.value); this.symbolsStack.push({ type: "token", token: token }); } else if (fixedAction.type === "reduce") { if (fixedAction.value === 0) break; const production = productions[fixedAction.value]; const popped = []; for (let i = 0; i < production.rhs.length; i++) { popped.unshift(this.symbolsStack.pop()); this.statesStack.pop(); } try { this.symbolsStack.push({ type: "node", node: this.reducer(popped, fixedAction.value) }); } catch (err) { const input = JSON.stringify(popped, null, 2); throw new LR1ParserGraveError(`Error while performing reduction for production: ${fixedAction.value}. Reduction input: ${input}`, token); } const topNode = this.statesStack.peek(); const gotoAction = states[topNode].getVariableAction(production.lhs.lexeme); if (gotoAction == undefined) throw new LR1ParserGraveError(`No GOTO action for for state: ${topNode} and production: ${production.lhs}. Actions: Reduce by production ${fixedAction.value}`, token); else if (gotoAction.type !== "goto") throw new LR1ParserGraveError(`Expected GOTO action for state: ${topNode} and production: ${production.lhs}. Actions: Reduce by production ${fixedAction.value}. Received: ${gotoAction.type}`, token); this.statesStack.push(gotoAction.value); } } const top = this.symbolsStack.peek(); return { result: top.type === "node" ? top.node : null, errors: this.exceptions }; } } class LR1ParserGraveError extends Error { constructor(reason, currentToken, err) { super(reason); this.reason = reason; this.currentToken = currentToken; } } exports.LR1ParserGraveError = LR1ParserGraveError; //# sourceMappingURL=parser.js.map