UNPKG

cm-tarnation

Version:

An alternative parser for CodeMirror 6

177 lines 5.87 kB
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { Wrapping } from "../constants"; import { Node } from "./node"; /** Represents a leaf or branch of a tree of matches found by a grammar. */ export class Matched { state; node; total; from; captures; wrapping; constructor( /** The current {@link GrammarState}. */ state, /** The match's {@link Node} type. */ node, /** The entire matched string. */ total, /** The start of the match. */ from, /** The children contained by this match's {@link Node}. */ captures, /** * The wrapping mode of the match. There are three modes: * * - FULL: The {@link Node} in this match contains the entirety of the branch. * - BEGIN: The {@link Node} in this match begins the branch. * - END: The {@link Node} in this match ends the branch. */ wrapping = Wrapping.FULL) { this.state = state; this.node = node; this.total = total; this.from = from; this.captures = captures; this.wrapping = wrapping; this.length = total.length; } /** Changes the starting offset of the match. */ offset(offset) { if (this.captures) { for (let i = 0; i < this.captures.length; i++) { const child = this.captures[i]; child.offset(child.from - this.from + offset); } } this.from = offset; } /** * Wraps this `Matched` with another one. * * @param node - The node of the `Matched` to wrap with. * @param wrap - The wrapping mode, if different. */ wrap(node, wrap = this.wrapping) { return new Matched(this.state, node, this.total, this.from, [this], wrap); } /** * Pushes an opening or closing node to one side of this matches captures. * * @param node - The node to push. * @param side - The side to push to. * @param wrap - The wrapping mode of the node. Can't be `FULL`. */ push(node, side, wrap = this.wrapping) { if (wrap === Wrapping.FULL) throw new Error("Cannot push onto a FULL match"); this.captures ??= []; let pos = side === -1 ? this.from : this.from + this.length; const match = new Matched(this.state, node, "", pos, undefined, wrap); if (side === -1) this.captures.unshift(match); else this.captures.push(match); } /** Returns this match represented as a raw {@link MatchOutput}. */ output() { let captures = null; if (this.captures) { captures = []; for (let i = 0; i < this.captures.length; i++) { captures.push(this.captures[i].total); } } return { total: this.total, captures, length: this.length }; } /** Internal method for compiling. */ _compile() { if (!this.captures) return compileLeaf(this); // verbose approach for performance const tokens = []; for (let i = 0; i < this.captures.length; i++) { const compiled = this.captures[i]._compile(); // wasn't emitted if (!compiled) continue; // leaf if (!isGrammarTokenList(compiled)) tokens.push(compiled); // branch else { for (let i = 0; i < compiled.length; i++) { tokens.push(compiled[i]); } } } return compileTree(this, tokens); } /** * Compiles this match into a list of tokens. Always returns a list, even * if this match represents a leaf. */ compile() { const compiled = this._compile(); if (isGrammarTokenList(compiled)) return compiled; else if (compiled) return [compiled]; else return []; } } function isGrammarTokenList(token) { if (token.length === 0) return true; return Array.isArray(token[0]); } /** Compiles a {@link Matched} as a leaf. */ function compileLeaf(match) { if (match.wrapping !== Wrapping.FULL && match.node === Node.None) { throw new Error("Cannot compile a null leaf with a non-full wrapping"); } // prettier-ignore switch (match.wrapping) { case Wrapping.FULL: return [ match.node === Node.None ? null : match.node.id, match.from, match.from + match.length ]; case Wrapping.BEGIN: return [ null, match.from, match.from + match.length, [match.node.id] ]; case Wrapping.END: return [ null, match.from, match.from + match.length, undefined, [match.node.id] ]; } } /** * Compiles a {@link Matched} as a tree, with the given token list being its * already compiled children. */ function compileTree(match, tokens) { if (match.node === Node.None) return tokens; const first = tokens[0]; const last = tokens[tokens.length - 1]; if (match.wrapping === Wrapping.FULL || match.wrapping === Wrapping.BEGIN) { first[3] ??= []; first[3].unshift(match.node.id); } if (match.wrapping === Wrapping.FULL || match.wrapping === Wrapping.END) { last[4] ??= []; last[4].push(match.node.id); } return tokens; } //# sourceMappingURL=matched.js.map