Skip to content

Commit

Permalink
Change captureTransfers shape
Browse files Browse the repository at this point in the history
  • Loading branch information
slevithan committed Jan 31, 2025
1 parent bb0d966 commit 1b26a32
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 35 deletions.
40 changes: 20 additions & 20 deletions spec/recursion-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,94 +165,94 @@ describe('recursion', () => {
describe('with capture transfers', () => {
it('should transfer with global recursion', () => {
expect(recursion('(a)(?R=2)?(b)', {
captureTransfers: new Map([[1, 2]]),
captureTransfers: new Map([[1, [2]]]),
})).toEqual({
pattern: '(a)(?:(a)(?:)?(b))?(b)',
captureTransfers: new Map([[1, 4]]),
captureTransfers: new Map([[1, [4]]]),
hiddenCaptures: [2, 3],
});
});

it('should transfer to capture that precedes the recursion', () => {
expect(recursion(r`()(()(a)()\g<2&R=2>?b)`, {
captureTransfers: new Map([[1, 4]]),
captureTransfers: new Map([[1, [4]]]),
hiddenCaptures: [4],
})).toEqual({
pattern: '()(()(a)()(?:()(a)()(?:)?b)?b)',
captureTransfers: new Map([[1, 7]]),
captureTransfers: new Map([[1, [7]]]),
hiddenCaptures: [4, 6, 7, 8],
});
expect(recursion(r`()(a\g<2&R=2>?()(b)())`, {
captureTransfers: new Map([[1, 4]]),
captureTransfers: new Map([[1, [4]]]),
hiddenCaptures: [4],
})).toEqual({
pattern: '()(a(?:a(?:)?()(b)())?()(b)())',
captureTransfers: new Map([[1, 7]]),
captureTransfers: new Map([[1, [7]]]),
hiddenCaptures: [7, 3, 4, 5], // unsorted
});
});

it('should transfer to capture of the recursed group', () => {
expect(recursion(r`((a)\g<1&R=2>?(b))`, {
captureTransfers: new Map([[1, 3]]),
captureTransfers: new Map([[1, [3]]]),
})).toEqual({
pattern: '((a)(?:(a)(?:)?(b))?(b))',
captureTransfers: new Map([[1, 5]]),
captureTransfers: new Map([[1, [5]]]),
hiddenCaptures: [3, 4],
});
});

it('should transfer across multiple recursions', () => {
// Capture in left contents of recursions
expect(recursion(r`(?<r>(a)\g<r&R=2>?b) ((a)\g<3&R=2>?b)`, {
captureTransfers: new Map([[1, 3], ['r', 3], [2, 4]]),
captureTransfers: new Map([[1, [3]], [2, [4]]]),
})).toEqual({
pattern: '(?<r>(a)(?:(a)(?:)?b)?b) ((a)(?:(a)(?:)?b)?b)',
captureTransfers: new Map([[1, 4], ['r', 4], [2, 6]]),
captureTransfers: new Map([[1, [4]], [2, [6]]]),
hiddenCaptures: [3, 6],
});
// Capture in right contents of recursions
expect(recursion(r`(?<r>a\g<r&R=2>?(b)) (a\g<3&R=2>?(b))`, {
captureTransfers: new Map([[1, 3], ['r', 3], [2, 4]]),
captureTransfers: new Map([[1, [3]], [2, [4]]]),
})).toEqual({
pattern: '(?<r>a(?:a(?:)?(b))?(b)) (a(?:a(?:)?(b))?(b))',
captureTransfers: new Map([[1, 4], ['r', 4], [3, 6]]),
captureTransfers: new Map([[1, [4]], [3, [6]]]),
hiddenCaptures: [2, 5],
});
// Capture in left and right contents of recursions
expect(recursion(r`(?<r>(a)\g<r&R=2>?(b)) ((a)\g<4&R=2>?(b))`, {
captureTransfers: new Map([[1, 4], ['r', 4], [2, 5], [3, 6]]),
captureTransfers: new Map([[1, [4]], [2, [5]], [3, [6]]]),
})).toEqual({
pattern: '(?<r>(a)(?:(a)(?:)?(b))?(b)) ((a)(?:(a)(?:)?(b))?(b))',
captureTransfers: new Map([[1, 6], ['r', 6], [2, 8], [5, 10]]),
captureTransfers: new Map([[1, [6]], [2, [8]], [5, [10]]]),
hiddenCaptures: [3, 4, 8, 9],
});
// Triple recursion with capture transfer to middle (Oniguruma: `\g<a> (?<a>a\g<b>?b) (?<b>c\g<a>?d)`)
expect(recursion(r`(a(c\g<1&R=2>?d)?b) (?<a>a(c\g<3&R=2>?d)?b) (?<b>c(a\g<5&R=2>?b)?d)`, {
captureTransfers: new Map([[3, 6], ['a', 6]]),
captureTransfers: new Map([[3, [6]]]),
hiddenCaptures: [1, 2, 4, 6],
})).toEqual({
pattern: '(a(c(?:a(c(?:)?d)?b)?d)?b) (?<a>a(c(?:a(c(?:)?d)?b)?d)?b) (?<b>c(a(?:c(a(?:)?b)?d)?b)?d)',
captureTransfers: new Map([[4, 9],['a', 9]]),
captureTransfers: new Map([[4, [9]]]),
hiddenCaptures: [1, 2, 5, 8, 3, 6, 9], // unsorted
});
// Same as above but with depth 3
expect(recursion(r`(a(c\g<1&R=3>?d)?b) (?<a>a(c\g<3&R=3>?d)?b) (?<b>c(a\g<5&R=3>?b)?d)`, {
captureTransfers: new Map([[3, 6], ['a', 6]]),
captureTransfers: new Map([[3, [6]]]),
hiddenCaptures: [1, 2, 4, 6],
})).toEqual({
pattern: '(a(c(?:a(c(?:a(c(?:)?d)?b)?d)?b)?d)?b) (?<a>a(c(?:a(c(?:a(c(?:)?d)?b)?d)?b)?d)?b) (?<b>c(a(?:c(a(?:c(a(?:)?b)?d)?b)?d)?b)?d)',
captureTransfers: new Map([[5, 12],['a', 12]]),
captureTransfers: new Map([[5, [12]]]),
hiddenCaptures: [1, 2, 6, 10, 3, 4, 7, 8, 11, 12], // unsorted
});
});

it('should transfer between captures following recursion', () => {
expect(recursion(r`((2)\g<1&R=2>?) (3) (4)`, {
captureTransfers: new Map([[3, 4]]),
captureTransfers: new Map([[3, [4]]]),
})).toEqual({
pattern: '((2)(?:(2)(?:)?)?) (3) (4)',
captureTransfers: new Map([[4, 5]]),
captureTransfers: new Map([[4, [5]]]),
hiddenCaptures: [3],
});
});
Expand Down
33 changes: 18 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ const overlappingRecursionMsg = 'Cannot use multiple overlapping recursions';
@param {string} pattern
@param {{
flags?: string;
captureTransfers?: Map<number | string, number>;
captureTransfers?: Map<number, Array<number>>;
hiddenCaptures?: Array<number>;
mode?: 'plugin' | 'external';
}} [data]
@returns {{
pattern: string;
captureTransfers: Map<number | string, number>;
captureTransfers: Map<number, Array<number>>;
hiddenCaptures: Array<number>;
}}
*/
Expand Down Expand Up @@ -314,33 +314,36 @@ function incrementIfAtLeast(arr, threshold) {
}

/**
@param {Map<number | string, number>} captureTransfers
@param {Map<number, Array<number>>} captureTransfers
@param {number} numCapturesPassed
@param {string} left
@param {number} reps
@param {number} numCapturesAddedInExpansion
@param {number} numAddedHiddenCapturesPreExpansion
@returns {Map<number | string, number>}
@returns {Map<number, Array<number>>}
*/
function mapCaptureTransfers(captureTransfers, numCapturesPassed, left, reps, numCapturesAddedInExpansion, numAddedHiddenCapturesPreExpansion) {
if (captureTransfers.size && numCapturesAddedInExpansion) {
let numCapturesInLeft = 0;
forEachUnescaped(left, captureDelim, () => numCapturesInLeft++, Context.DEFAULT);
const recursionDelimCaptureNum = numCapturesPassed - numCapturesInLeft + numAddedHiddenCapturesPreExpansion; // 0 for global
// 0 for global recursion
const recursionDelimCaptureNum = numCapturesPassed - numCapturesInLeft + numAddedHiddenCapturesPreExpansion;
const newCaptureTransfers = new Map();
captureTransfers.forEach((/** @type {number} */ from, /** @type {number | string} */ to) => {
// `to` can be a group number or name
captureTransfers.forEach((from, to) => {
if (to > (numCapturesPassed + numAddedHiddenCapturesPreExpansion)) {
to += numCapturesAddedInExpansion;
}
if (from > recursionDelimCaptureNum) {
from += (
// if capture is on left side of expanded group
from <= (recursionDelimCaptureNum + numCapturesInLeft) ?
numCapturesInLeft * reps :
numCapturesAddedInExpansion
);
}
from = from.map(f => {
if (f > recursionDelimCaptureNum) {
f += (
// if the capture is on the left side of the expanded subpattern
f <= (recursionDelimCaptureNum + numCapturesInLeft) ?
numCapturesInLeft * reps :
numCapturesAddedInExpansion
);
}
return f;
});
newCaptureTransfers.set(to, from);
});
return newCaptureTransfers;
Expand Down

0 comments on commit 1b26a32

Please sign in to comment.