UNPKG

cm-tarnation

Version:

An alternative parser for CodeMirror 6

218 lines 7.28 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 { NodeProp } from "@lezer/common"; /** * Performs a binary search through an array. * * The comparator function should return -1 if undershooting the desired * value, +1 if overshooting, and 0 if the value was found. * * The comparator can also short-circuit the search by returning true or * false. Returning true is like returning a 0 (target found), but * returning false induces a null return. */ export function search(haystack, target, comparator, { min = 0, max = haystack.length - 1, precise = true } = {}) { if (haystack.length === 0) return null; let index = -1; while (min <= max) { index = min + ((max - min) >>> 1); const cmp = comparator(haystack[index], target); if (cmp === true || cmp === 0) return { element: haystack[index], index }; if (cmp === false) return null; if (cmp < 0) min = index + 1; else if (cmp > 0) max = index - 1; } if (index === -1) return null; if (!precise) return { element: null, index }; return null; } /** Class that implements the Lezer `Input` interface using a normal string. */ export class StringInput { string; constructor(string) { this.string = string; } get length() { return this.string.length; } chunk(from) { return this.string.slice(from); } lineChunks = false; read(from, to) { return this.string.slice(from, to); } } /** * Safely compiles a regular expression. * * @example * * ```ts * // returns null if features aren't supported (e.g. Safari) * const regex = re`/(?<=\d)\w+/d` * ``` */ export function re(str, forceFlags = "") { const input = typeof str === "string" ? str : str.raw[0]; const split = /^!?\/([^]+)\/([^]*)$/.exec(input); if (!split || !split[1]) return null; let [, src = "", flags = ""] = split; if (forceFlags) flags = dedupe([...flags, ...forceFlags]).join(""); try { return new RegExp(src, flags); } catch (err) { console.warn("cm-tarnation: Recovered from failed RegExp construction"); console.warn("cm-tarnation: RegExp source:", input); console.warn(err); return null; } } /** * Tests if the given string is a "RegExp string", as in it's in the format * of a native `RegExp` statement. */ export function isRegExpString(str) { const split = /^!?\/([^]+)\/([^]*)$/.exec(str); if (!split || !split[1]) return false; return true; } /** Returns if the given `RegExp` has any remembered capturing groups. */ export function hasCapturingGroups(regexp) { // give an alternative that always matches const always = new RegExp(`|${regexp.source}`); // ... which means we can use it to get a successful match, // regardless of the original regex. this is a bit of a hack, // but we can use this to detect capturing groups. return always.exec("").length > 1; } /** * Creates a lookbehind function from a `RegExp`. This function can only * test for a pattern's (non) existence, so no matches or capturing groups * are returned. * * @param pattern - A `RegExp` to be used as a pattern. * @param negative - Negates the pattern. */ export function createLookbehind(pattern, negative) { // can't be sticky, global, or multiline const flags = pattern.flags.replaceAll(/[ygm]/g, ""); // regexp that can only match at the end of a string const regex = new RegExp(`(?:${pattern.source})$`, flags); return (str, pos) => { const clipped = str.slice(0, pos); const result = regex.test(clipped); return negative ? !result : result; }; } /** * A special per-node `NodeProp` used for describing nodes where a nested * parser will be embedded. */ export const EmbeddedParserProp = new NodeProp(); /** * Returns a completely concatenated `Int32Array` from a list of arrays. * * @param arrays - Arrays to concatenate. * @param length - If you know the length of the final array, you can pass * it here to avoid having the function calculate it. */ export function concatInt32Arrays(arrays, length) { let total = length ?? 0; if (!total) { for (let i = 0; i < arrays.length; i++) { total += arrays[i].length; } } const result = new Int32Array(total); let offset = 0; for (let i = 0; i < arrays.length; i++) { result.set(arrays[i], offset); offset += arrays[i].length; } return result; } /** * Deduplicates an array. Does not mutate the original array. * * @param arr - The array to deduplicate. * @param insert - Additional values to insert into the array, if desired. */ export function dedupe(arr, ...insert) { return [...new Set([...arr, ...insert])]; } /** Performance measuring utility. */ export function perfy() { const start = performance.now(); return () => { return parseFloat((performance.now() - start).toFixed(4)); }; } /** Removes all properties assigned to `undefined` in an object. */ export function removeUndefined(obj) { // this wacky approach is faster as it avoids an iterator const keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { if (obj[keys[i]] === undefined) delete obj[keys[i]]; } return obj; } /** Takes a string and escapes any `RegExp` sensitive characters. */ export function escapeRegExp(str) { return str.replace(/[.*+?^${}()|\[\]\\]/g, "\\$&"); } /** Creates a simple pseudo-random ID, with an optional prefix attached. */ export function createID(prefix = "") { const suffix = Math.abs(hash(Math.random() * 100 + prefix)); return `${prefix}-${suffix}`; } /** Converts a string into an array of codepoints. */ export function toPoints(str) { const codes = []; for (let i = 0; i < str.length; i++) { codes.push(str.codePointAt(i)); } return codes; } /** * Checks an array of codepoints against a codepoint array or a string, * starting from a given position. */ export function pointsMatch(points, str, pos) { if (typeof str === "string") { for (let i = 0; i < points.length; i++) { if (points[i] !== str.codePointAt(pos + i)) return false; } } else { for (let i = 0; i < points.length; i++) { if (points[i] !== str[pos + i]) return false; } } return true; } // https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0#gistcomment-2694461 /** Very quickly generates a (non-secure) hash from the given string. */ export function hash(s) { let h = 0; for (let i = 0; i < s.length; i++) { h = (Math.imul(31, h) + s.charCodeAt(i)) | 0; } return h; } //# sourceMappingURL=util.js.map