1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
x9 x9 x9 x9 x9 x9 x9 x9 x9 x54 x54 x54 x54 x54 x791 x335 x40 x16 x16 x40 x40 x40 x335 x335 x791 x38 x38 x38 x38 x38 x38 x38 x38 x791 x44 x44 x44 x791 x44 x44 x44 x44 x791 x26 x2 x2 x14 x54 |
// Imports
import type { Nullable } from "@libs/typing"
/** Tokens. */
const tokens = {
"(": ")",
"[": "]",
"{": "}",
'"': '"',
"'": "'",
"`": "`",
} as Record<PropertyKey, string>
/**
* Capture content between delimiters.
*
* ```ts
* const { captured, match } = capture("foo {{ bar }} baz")!
* console.assert(captured === "bar")
* console.assert(match === "{{ bar }}")
* ```
*
* @author Simon Lecoq (lowlighter)
* @license MIT
*/
export function capture(string: string, offset = 0): Nullable<{ a: number; b: number; match: string; captured: string; triple: boolean }> {
const stack = []
let a = NaN
let d = 2
let quoted = false
for (let i = offset; i < string.length; i++) {
// Start capturing upon meeting mustache opening
if (Number.isNaN(a)) {
if ((string[i] === "{") && (string[i + 1] === "{")) {
if (string[i + 2] === "{") {
d = 3
}
a = i
i += d - 1
}
continue
}
// Close capturing on mustache closing (stack must be empty)
if ((!stack.length) && (string[i] === "}") && (string[i + 1] === "}") && ((d === 2) || (string[i + 2] === "}"))) {
return {
a,
b: i + d,
match: string.slice(a, i + d),
captured: string.slice(a + d, i).trim(),
triple: d === 3,
}
}
// Close group on closing token
if (string[i] === tokens[stack.at(-1)!]) {
stack.pop()
continue
}
// Open group on opening token
if ((!quoted) && (string[i] in tokens)) {
stack.push(string[i])
quoted = ["'", '"', "`"].includes(string[i])
continue
}
}
// Throw on mustache unclosed
if (!Number.isNaN(a)) {
throw new SyntaxError(`Unclosed expression, unterminated expression at: ${a}\n${string.slice(a)}`)
}
return null
}
|