266 lines
8.5 KiB
JavaScript
266 lines
8.5 KiB
JavaScript
'use strict';
|
|
|
|
const regexpTree = require('regexp-tree');
|
|
|
|
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
|
|
const regexpTree__default = /*#__PURE__*/_interopDefaultCompat(regexpTree);
|
|
|
|
function build(node) {
|
|
if (node === null)
|
|
return "";
|
|
switch (node.type) {
|
|
case "CharacterClass": {
|
|
const exprs = combineContinuousSimpleChars(node.expressions);
|
|
if (exprs.length === 1) {
|
|
const first = exprs[0];
|
|
if (typeof first === "string") {
|
|
return node.negative ? `charNotIn(${first})` : `charIn(${first})`;
|
|
} else if (first.type === "Char" && first.kind === "meta" && node.negative) {
|
|
if (first.value === "\\t")
|
|
return `not.tab`;
|
|
if (first.value === "\\n")
|
|
return `not.linefeed`;
|
|
if (first.value === "\\r")
|
|
return `not.carriageReturn`;
|
|
} else {
|
|
const range = normalizeClassRange(first);
|
|
if (range === "A-Z")
|
|
return node.negative ? `not.letter.uppercase` : `letter.uppercase`;
|
|
else if (range === "a-z")
|
|
return node.negative ? `not.letter.lowercase` : `letter.lowercase`;
|
|
}
|
|
} else if (exprs.length === 2) {
|
|
if (typeof exprs[0] !== "string" && typeof exprs[1] !== "string") {
|
|
const range1 = normalizeClassRange(exprs[0]);
|
|
const range2 = normalizeClassRange(exprs[1]);
|
|
if (range1 === "A-Z" && range2 === "a-z" || range1 === "a-z" && range2 === "A-Z")
|
|
return node.negative ? `not.letter` : `letter`;
|
|
}
|
|
}
|
|
throw new Error("Unsupported for Complex charactor class");
|
|
}
|
|
case "Disjunction":
|
|
return chain(build(node.left), `or(${build(node.right)})`);
|
|
case "Assertion":
|
|
switch (node.kind) {
|
|
case "\\b":
|
|
return "wordBoundary";
|
|
case "\\B":
|
|
return "not.wordBoundary";
|
|
case "^":
|
|
return chain("", "at.lineStart()");
|
|
case "$":
|
|
return chain("", "at.lineEnd()");
|
|
case "Lookbehind":
|
|
return chain("", `${node.negative ? "notAfter" : "after"}(${build(node.assertion)})`);
|
|
case "Lookahead":
|
|
return chain("", `${node.negative ? "notBefore" : "before"}(${build(node.assertion)})`);
|
|
/* v8 ignore next 2 */
|
|
default:
|
|
throw new TypeError(`Unknown Assertion kind: ${node.kind}`);
|
|
}
|
|
case "Char":
|
|
if (node.kind === "meta") {
|
|
switch (node.value) {
|
|
case ".":
|
|
return "char";
|
|
case "\\w":
|
|
return "wordChar";
|
|
case "\\d":
|
|
return "digit";
|
|
case "\\s":
|
|
return "whitespace";
|
|
case "\\t":
|
|
return "tab";
|
|
case "\\n":
|
|
return "linefeed";
|
|
case "\\r":
|
|
return "carriageReturn";
|
|
case "\\W":
|
|
return "not.wordChar";
|
|
case "\\D":
|
|
return "not.digit";
|
|
case "\\S":
|
|
return "not.whitespace";
|
|
case "\f":
|
|
case "\v":
|
|
default:
|
|
throw new Error(`Unsupported Meta Char: ${node.value}`);
|
|
}
|
|
} else {
|
|
const char = getChar(node);
|
|
if (char === null)
|
|
throw new Error(`Unknown Char: ${node.value}`);
|
|
return `'${char}'`;
|
|
}
|
|
case "Repetition": {
|
|
const quantifier = node.quantifier;
|
|
const expr = build(node.expression);
|
|
const lazy = !quantifier.greedy;
|
|
if (lazy)
|
|
throw new Error("Unsupported for lazy quantifier");
|
|
switch (quantifier.kind) {
|
|
case "+":
|
|
return `oneOrMore(${expr})`;
|
|
case "?":
|
|
return `maybe(${expr})`;
|
|
case "*":
|
|
return chain(expr, "times.any()");
|
|
case "Range":
|
|
if (quantifier.from === quantifier.to)
|
|
return chain(expr, `times(${quantifier.from})`);
|
|
else if (!quantifier.to)
|
|
return chain(expr, `times.atLeast(${quantifier.from})`);
|
|
else if (quantifier.from === 0)
|
|
return chain(expr, `times.atMost(${quantifier.to})`);
|
|
return chain(expr, `times.between(${quantifier.from}, ${quantifier.to})`);
|
|
/* v8 ignore next 2 */
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
case "Alternative": {
|
|
const alts = combineContinuousSimpleChars(node.expressions);
|
|
const exprs = [];
|
|
for (let i = 0; i < alts.length; i++) {
|
|
const alt = alts[i];
|
|
if (typeof alt === "string") {
|
|
exprs.push(alt);
|
|
continue;
|
|
}
|
|
if (alt.type === "Assertion") {
|
|
switch (alt.kind) {
|
|
case "^": {
|
|
const next = alts[++i];
|
|
if (next === void 0)
|
|
throw new Error(`Unexpected assertion: ${JSON.stringify(alt)}`);
|
|
exprs.push(chain(next, "at.lineStart()"));
|
|
continue;
|
|
}
|
|
case "$": {
|
|
const prev = exprs.pop();
|
|
if (prev === void 0)
|
|
throw new Error(`Unexpected assertion: ${JSON.stringify(alt)}`);
|
|
exprs.push(chain(prev, "at.lineEnd()"));
|
|
continue;
|
|
}
|
|
case "Lookbehind": {
|
|
const next = alts[++i];
|
|
if (next === void 0)
|
|
throw new Error(`Unexpected assertion: ${JSON.stringify(alt)}`);
|
|
const helper = alt.negative ? "notAfter" : "after";
|
|
exprs.push(chain(next, `${helper}(${build(alt.assertion)})`));
|
|
continue;
|
|
}
|
|
case "Lookahead": {
|
|
const prev = exprs.pop();
|
|
if (prev === void 0)
|
|
throw new Error(`Unexpected assertion: ${JSON.stringify(alt)}`);
|
|
const helper = alt.negative ? "notBefore" : "before";
|
|
exprs.push(chain(prev, `${helper}(${build(alt.assertion)})`));
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (alt.type === "Backreference") {
|
|
if (alt.kind !== "name")
|
|
throw new Error(`Unsupport for number reference`);
|
|
const ref = chain(`exactly(${exprs.join(", ")})`, `and.referenceTo('${alt.reference}')`);
|
|
exprs.length = 0;
|
|
exprs.push(ref);
|
|
continue;
|
|
}
|
|
exprs.push(build(alt));
|
|
}
|
|
return exprs.join(", ");
|
|
}
|
|
case "Group":
|
|
if (node.capturing)
|
|
return chain(build(node.expression), node.name ? `as('${node.name}')` : "grouped()");
|
|
else return chain(build(node.expression));
|
|
/* v8 ignore next 2 */
|
|
case "Backreference":
|
|
return chain("", `and.referenceTo('${node.reference}')`);
|
|
}
|
|
}
|
|
function normalizeClassRange(node) {
|
|
if (node.type === "ClassRange")
|
|
return `${node.from.value}-${node.to.value}`;
|
|
}
|
|
function combineContinuousSimpleChars(expressions) {
|
|
let simpleChars = "";
|
|
const exprs = expressions.reduce(
|
|
(acc, expr) => {
|
|
const char = expr.type === "Char" ? getChar(expr) : null;
|
|
if (char !== null) {
|
|
simpleChars += char;
|
|
} else {
|
|
if (simpleChars) {
|
|
acc.push(`'${simpleChars}'`);
|
|
simpleChars = "";
|
|
}
|
|
acc.push(expr);
|
|
}
|
|
return acc;
|
|
},
|
|
[]
|
|
);
|
|
if (simpleChars)
|
|
exprs.push(`'${simpleChars}'`);
|
|
return exprs;
|
|
}
|
|
function getChar(char) {
|
|
function escapeSimpleChar(char2) {
|
|
return char2 === "'" ? "\\'" : char2;
|
|
}
|
|
switch (char.kind) {
|
|
case "simple":
|
|
return escapeSimpleChar(char.value);
|
|
case "oct":
|
|
case "decimal":
|
|
case "hex":
|
|
case "unicode":
|
|
if ("symbol" in char)
|
|
return escapeSimpleChar(char.symbol);
|
|
}
|
|
return null;
|
|
}
|
|
function chain(expr, helper) {
|
|
let _expr = "";
|
|
if (typeof expr === "string") {
|
|
if (expr === "")
|
|
_expr = "exactly('')";
|
|
else _expr = expr.startsWith("'") && expr.endsWith("'") ? `exactly(${expr})` : expr;
|
|
} else {
|
|
_expr = build(expr);
|
|
}
|
|
return helper ? `${_expr}.${helper}` : _expr;
|
|
}
|
|
function buildFlags(flags) {
|
|
if (!flags)
|
|
return "";
|
|
const readableFlags = flags.split("").map((flag) => {
|
|
return {
|
|
d: "withIndices",
|
|
i: "caseInsensitive",
|
|
g: "global",
|
|
m: "multiline",
|
|
s: "dotAll",
|
|
u: "unicode",
|
|
y: "sticky"
|
|
}[flag] || `'${flag}'`;
|
|
});
|
|
return `[${readableFlags.join(", ")}]`;
|
|
}
|
|
function convert(regex, { argsOnly = false } = {}) {
|
|
const ast = regexpTree__default.parse(regex);
|
|
if (ast.type !== "RegExp")
|
|
throw new TypeError(`Unexpected RegExp AST: ${ast.type}`);
|
|
const flags = buildFlags(ast.flags);
|
|
const args = build(ast.body) + (flags ? `, ${flags}` : "");
|
|
return argsOnly ? args : `createRegExp(${args})`;
|
|
}
|
|
|
|
exports.convert = convert;
|