parseit.js
Version:
Customizable parsing library for converting a list of tokens into an AST tree
187 lines • 8.81 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContentStackType = void 0;
const ParseError_1 = __importStar(require("../classes/ParseError"));
const rule_1 = require("../grammar/rule");
const util_1 = require("./util");
var ContentStackType;
(function (ContentStackType) {
ContentStackType[ContentStackType["RULE"] = 0] = "RULE";
ContentStackType[ContentStackType["BINARY"] = 1] = "BINARY";
ContentStackType[ContentStackType["BLOCK"] = 2] = "BLOCK";
})(ContentStackType = exports.ContentStackType || (exports.ContentStackType = {}));
class Parser {
tokens = [];
index = 0;
grammar;
/** The main parser object. Takes the grammar object as a constructor argument */
constructor(grammar) {
this.grammar = grammar;
}
/** Takes an array of tokens and constructs an AST tree based on the grammar rules */
parse(tokens) {
this.tokens = tokens;
this.index = 0;
const startRule = this.grammar.startRule;
const result = this.evaluateRule(startRule);
if (result instanceof ParseError_1.default)
return (0, ParseError_1.error)(result.position.index, result.position.token);
if (this.index < this.tokens.length)
return (0, ParseError_1.error)(this.index, this.currentTok); // if didn't reach the end, throw error
return result;
}
evaluateRule = (name) => {
const ruleVariations = this.grammar.getRule(name); // get rule data
let startIndex = +this.index; // copy index
let ruleVariationIndex = 0;
let errorIndex = -1;
// loop A: through all rule's variations
if (ruleVariations)
while (ruleVariationIndex < ruleVariations.length) {
const ruleVariation = ruleVariations[ruleVariationIndex];
let matched = true;
let nodeData = [];
this.index = +startIndex; // reset index to start from the (relative) beginning
// a stack for rule variation content (to make loops possible)
// each loop creates it's own stack layer and removes it when the iteration is complete
let stack = [this.stackLayer(ruleVariation.content, ContentStackType.RULE)];
let loopCompleted = false;
// loop B: through rule variation's items
while (stack.length && stack[0].index < stack[0].content.length) {
// get the current item of a variation content (in the rule)
const item = stack[0].content[stack[0].index];
// for rules
if ((0, util_1.isRule)(item) || (0, util_1.isToken)(item)) {
let shouldBreak = false;
const success = (result, skip) => skip || nodeData.push(result);
const fail = (index) => {
matched = false;
shouldBreak = true;
errorIndex = index || errorIndex;
};
this.evaluateOnePiece(item, success, fail);
if (shouldBreak)
break; // break out of loop B
// for binary loops
}
else if (item instanceof rule_1.GrammarBinaryLoop) {
// add a stack layer for the loop and set it to complete
stack.unshift(this.stackLayer(item.content, ContentStackType.BINARY));
nodeData = [(0, util_1.adaptNodeData)(nodeData, ruleVariation)];
continue;
// for block loops
}
else if (item instanceof rule_1.GrammarBlockLoop) {
// add a stack layer for the loop and set it to complete
stack.unshift(this.stackLayer(item.content, ContentStackType.BLOCK));
continue;
// for "either" selectors
}
else if (item instanceof rule_1.GrammarEither) {
let found = false;
let skip = false;
item.variants.forEach(variant => {
if (found)
return;
this.evaluateOnePiece(variant, (result) => {
found = result;
skip = variant.skip;
}, (index) => {
errorIndex = index || errorIndex;
}, ruleVariation);
});
if (found) {
if (!skip)
nodeData.push(found);
}
else {
matched = false;
break;
}
}
// advance and set loop to incomplete
stack[0].index++;
if (stack[0].type != ContentStackType.BLOCK)
loopCompleted = false;
// if reached end, remove stack layer
if (stack[0].index >= stack[0].content.length) {
stack.shift();
loopCompleted = true;
}
}
// un-skip recently skipped tokens
if (!ruleVariation.preventRollback)
this.rollback();
// if matched the variation, construct the required node and return it
if (matched || loopCompleted) {
const targetNode = (0, util_1.adaptNodeData)(nodeData, ruleVariation);
return targetNode;
}
ruleVariationIndex++;
}
errorIndex = errorIndex == -1 ? this.index : errorIndex; // if errorIndex is still -1, change it to current index
return (0, ParseError_1.error)(errorIndex, this.tokens[errorIndex]);
};
evaluateOnePiece = (item, success, fail, rule) => {
// for rules: evaluate the rule recursively
// if error — fail, else — return the result
if ((0, util_1.isRule)(item)) {
const itemResult = this.evaluateRule(item.name);
if (itemResult instanceof ParseError_1.default)
fail ? fail(itemResult.position.index) : null;
else if (itemResult)
success(itemResult, item.skip);
// for tokens: check if token matches the given item
// if not — fail, else — return the token
}
else if ((0, util_1.isToken)(item)) {
this.advance(rule);
if (!(0, util_1.tokenMatches)(this.currentTok, item))
return fail ? fail(this.index) : null;
success(this.currentTok, item.skip);
this.index++;
}
};
// skip all tokens based on ignore rules and overrides
advance(rule) {
while ((0, util_1.tokenShouldBeIgnored)(this.currentTok, this.grammar, rule))
this.index++;
}
// unskip
rollback() {
let prevToken = this.tokens[this.index - 1];
while ((0, util_1.tokenShouldBeIgnored)(prevToken, this.grammar)) {
this.index--;
prevToken = this.tokens[this.index - 1];
}
}
stackLayer(content, type) {
return { content: content, type: type, index: 0 };
}
get currentTok() { return this.tokens[this.index]; }
}
exports.default = Parser;
//# sourceMappingURL=parser.js.map