UNPKG

parseit.js

Version:

Customizable parsing library for converting a list of tokens into an AST tree

187 lines 8.81 kB
"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