@scinorandex/sparse
Version:
Yet another parser generator
125 lines • 5.95 kB
JavaScript
"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