@reidelsaltres/pureper 0.1.76 → 0.1.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PHTMLParser.d.ts","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PHTMLParser.d.ts","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,OAAO,OAAO,WAAW;IAG5B,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAcnC,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAiBpC,OAAO,CAAC,MAAM,CAAC,KAAK,CA6FlB;IACK,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;IAExC,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ/C,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG;IAWxE,OAAO,CAAC,cAAc;IAOf,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM;CAyCrE"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
class PHTMLParserRule {
|
|
2
2
|
pattern;
|
|
3
|
+
// replacer can return either a string replacement OR an object with { text, end }
|
|
4
|
+
// where `end` is a position in the source string just after the consumed range.
|
|
3
5
|
replacer;
|
|
4
6
|
constructor(pattern, replacer) {
|
|
5
7
|
this.pattern = pattern;
|
|
@@ -25,46 +27,116 @@ export default class PHTMLParser {
|
|
|
25
27
|
return null;
|
|
26
28
|
return { block: content.slice(start + 1, i - 1), end: i };
|
|
27
29
|
}
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let
|
|
33
|
-
let
|
|
34
|
-
while (
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
// Extracts a balanced parenthesis block starting at `start` (which must be '(')
|
|
31
|
+
static extractBalancedParens(content, start) {
|
|
32
|
+
if (content[start] !== '(')
|
|
33
|
+
return null;
|
|
34
|
+
let depth = 1;
|
|
35
|
+
let i = start + 1;
|
|
36
|
+
while (i < content.length && depth > 0) {
|
|
37
|
+
if (content[i] === '(')
|
|
38
|
+
depth++;
|
|
39
|
+
else if (content[i] === ')')
|
|
40
|
+
depth--;
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
if (depth !== 0)
|
|
44
|
+
return null;
|
|
45
|
+
return { block: content.slice(start + 1, i - 1), end: i };
|
|
46
|
+
}
|
|
47
|
+
// Note: @for handling moved to a rule in `rules` so it can participate in the
|
|
48
|
+
// ordered rules pipeline. The actual exec loop for the for-rule is implemented
|
|
49
|
+
// inside parse() to allow balanced-brace extraction.
|
|
50
|
+
static rules = [
|
|
51
|
+
// Rule for nested @for blocks. This rule's replacer computes the
|
|
52
|
+
// replacement for one @for occurrence when given a RegExpExecArray
|
|
53
|
+
// (it expects match.index and match.input to be present so the parser
|
|
54
|
+
// can extract the full balanced block that follows the match).
|
|
55
|
+
new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g, (parser, m, scope) => {
|
|
56
|
+
// `m` will be a RegExpExecArray-like object when used by the
|
|
57
|
+
// exec-based loop inside `parse()` so we can treat it accordingly.
|
|
58
|
+
const match = m;
|
|
37
59
|
const iterVar = match[1];
|
|
38
60
|
const iterableExpr = match[2];
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
61
|
+
const offset = Number(match[match.length - 2]); // offset position
|
|
62
|
+
const input = String(match[match.length - 1]); // source string
|
|
63
|
+
const blockStart = offset + match[0].length - 1; // position of '{'
|
|
64
|
+
const extracted = PHTMLParser.extractBalancedBlock(input, blockStart);
|
|
65
|
+
if (!extracted)
|
|
66
|
+
return match[0];
|
|
47
67
|
const inner = extracted.block;
|
|
48
|
-
|
|
49
|
-
forPattern.lastIndex = lastIndex; // Update regex position
|
|
50
|
-
const resolved = this.resolveExpression(iterableExpr, scope);
|
|
68
|
+
const resolved = parser.resolveExpression(iterableExpr, scope);
|
|
51
69
|
const arr = Array.isArray(resolved) ? resolved : [];
|
|
52
70
|
const resultParts = [];
|
|
53
71
|
for (const item of arr) {
|
|
54
72
|
const fullScope = Object.assign({}, scope, { [iterVar]: item });
|
|
55
|
-
|
|
56
|
-
const t = this.parse(inner, fullScope);
|
|
57
|
-
resultParts.push(t);
|
|
73
|
+
resultParts.push(parser.parse(inner, fullScope));
|
|
58
74
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
// return both the replacement text and the position after the full balanced block
|
|
76
|
+
return { text: resultParts.join('\n'), end: extracted.end };
|
|
77
|
+
}),
|
|
78
|
+
// Double-paren expression: @(( code )) — use exec+balanced-start extraction
|
|
79
|
+
new PHTMLParserRule(/@\(\(/g, (parser, m, scope) => {
|
|
80
|
+
const match = m;
|
|
81
|
+
const offset = Number(match[match.length - 2]);
|
|
82
|
+
const input = String(match[match.length - 1]);
|
|
83
|
+
const blockStart = offset + match[0].length - 1; // points at the inner '('
|
|
84
|
+
// extract balanced parentheses
|
|
85
|
+
const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
|
|
86
|
+
if (!extracted)
|
|
87
|
+
return match[0];
|
|
88
|
+
const code = extracted.block;
|
|
89
|
+
// Evaluate code using parser.variables + local scope
|
|
90
|
+
const ctx = Object.assign({}, parser.variables, scope || {});
|
|
91
|
+
let result;
|
|
92
|
+
try {
|
|
93
|
+
// try to evaluate as expression first
|
|
94
|
+
const fn = new Function('with(this){ return (' + code + '); }');
|
|
95
|
+
result = fn.call(ctx);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
try {
|
|
99
|
+
const fn2 = new Function('with(this){ ' + code + ' }');
|
|
100
|
+
result = fn2.call(ctx);
|
|
101
|
+
}
|
|
102
|
+
catch (e2) {
|
|
103
|
+
// On error return empty string
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// final end should include the outer ')' if present (for @(( ... )) constructs)
|
|
108
|
+
let finalEnd = extracted.end;
|
|
109
|
+
if (input[extracted.end] === ')')
|
|
110
|
+
finalEnd = extracted.end + 1;
|
|
111
|
+
return { text: parser.stringifyValue(result), end: finalEnd };
|
|
112
|
+
}),
|
|
113
|
+
// Single-paren expression: @( code ) — similar extraction, processed after double-paren rule
|
|
114
|
+
new PHTMLParserRule(/@\(/g, (parser, m, scope) => {
|
|
115
|
+
const match = m;
|
|
116
|
+
const offset = Number(match[match.length - 2]);
|
|
117
|
+
const input = String(match[match.length - 1]);
|
|
118
|
+
const blockStart = offset + match[0].length - 1; // points at '('
|
|
119
|
+
const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
|
|
120
|
+
if (!extracted)
|
|
121
|
+
return match[0];
|
|
122
|
+
const code = extracted.block;
|
|
123
|
+
const ctx = Object.assign({}, parser.variables, scope || {});
|
|
124
|
+
let result;
|
|
125
|
+
try {
|
|
126
|
+
const fn = new Function('with(this){ return (' + code + '); }');
|
|
127
|
+
result = fn.call(ctx);
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
try {
|
|
131
|
+
const fn2 = new Function('with(this){ ' + code + ' }');
|
|
132
|
+
result = fn2.call(ctx);
|
|
133
|
+
}
|
|
134
|
+
catch (e2) {
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { text: parser.stringifyValue(result), end: extracted.end };
|
|
139
|
+
}),
|
|
68
140
|
];
|
|
69
141
|
variables = {};
|
|
70
142
|
addVariable(name, value) {
|
|
@@ -90,14 +162,40 @@ export default class PHTMLParser {
|
|
|
90
162
|
return '';
|
|
91
163
|
if (typeof val === 'string')
|
|
92
164
|
return val;
|
|
93
|
-
|
|
165
|
+
const str = String(val);
|
|
166
|
+
return str;
|
|
94
167
|
}
|
|
95
168
|
parse(content, scope) {
|
|
96
|
-
|
|
97
|
-
let working = this.parseForLoops(content, scope);
|
|
98
|
-
// Then apply other rules
|
|
169
|
+
let working = content;
|
|
99
170
|
for (const rule of PHTMLParser.rules) {
|
|
100
|
-
|
|
171
|
+
const pattern = rule.pattern;
|
|
172
|
+
let result = '';
|
|
173
|
+
let lastIndex = 0;
|
|
174
|
+
let match;
|
|
175
|
+
// reset scanning position
|
|
176
|
+
pattern.lastIndex = 0;
|
|
177
|
+
while ((match = pattern.exec(working)) !== null) {
|
|
178
|
+
// append text before this match
|
|
179
|
+
result += working.slice(lastIndex, match.index);
|
|
180
|
+
// build args array similar to replace(): [...match, offset, input]
|
|
181
|
+
const args = [...match, match.index, working];
|
|
182
|
+
const out = rule.replacer(this, args, scope);
|
|
183
|
+
if (typeof out === 'string') {
|
|
184
|
+
// simple replacement: advance by matched token length
|
|
185
|
+
result += out;
|
|
186
|
+
lastIndex = match.index + match[0].length;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// object replacement provides final end index — consume until that position
|
|
190
|
+
result += out.text;
|
|
191
|
+
lastIndex = out.end;
|
|
192
|
+
}
|
|
193
|
+
// make sure regex scanning continues from the correct place
|
|
194
|
+
pattern.lastIndex = lastIndex;
|
|
195
|
+
}
|
|
196
|
+
// append tail and update working
|
|
197
|
+
result += working.slice(lastIndex);
|
|
198
|
+
working = result;
|
|
101
199
|
}
|
|
102
200
|
return working;
|
|
103
201
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PHTMLParser.js","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe;IACV,OAAO,CAAS;
|
|
1
|
+
{"version":3,"file":"PHTMLParser.js","sourceRoot":"","sources":["../../src/foundation/PHTMLParser.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
class PHTMLParserRule {
|
|
2
2
|
public pattern: RegExp;
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// replacer can return either a string replacement OR an object with { text, end }
|
|
5
|
+
// where `end` is a position in the source string just after the consumed range.
|
|
6
|
+
public replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string | { text: string; end: number };
|
|
5
7
|
|
|
6
|
-
constructor(pattern: RegExp, replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string) {
|
|
8
|
+
constructor(pattern: RegExp, replacer: (parser: PHTMLParser, match: IArguments, scope?: Record<string, any>) => string | { text: string; end: number }) {
|
|
7
9
|
this.pattern = pattern;
|
|
8
10
|
this.replacer = replacer;
|
|
9
11
|
}
|
|
@@ -25,56 +27,117 @@ export default class PHTMLParser {
|
|
|
25
27
|
return { block: content.slice(start + 1, i - 1), end: i };
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
//
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
let
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const blockStart = match.index + match[0].length - 1; // position of '{'
|
|
42
|
-
|
|
43
|
-
const extracted = PHTMLParser.extractBalancedBlock(content, blockStart);
|
|
44
|
-
if (!extracted) {
|
|
45
|
-
// Unbalanced braces, just append the match as-is and continue
|
|
46
|
-
result += match[0];
|
|
47
|
-
lastIndex = match.index + match[0].length;
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const inner = extracted.block;
|
|
52
|
-
lastIndex = extracted.end;
|
|
53
|
-
forPattern.lastIndex = lastIndex; // Update regex position
|
|
30
|
+
// Extracts a balanced parenthesis block starting at `start` (which must be '(')
|
|
31
|
+
private static extractBalancedParens(content: string, start: number): { block: string; end: number } | null {
|
|
32
|
+
if (content[start] !== '(') return null;
|
|
33
|
+
let depth = 1;
|
|
34
|
+
let i = start + 1;
|
|
35
|
+
while (i < content.length && depth > 0) {
|
|
36
|
+
if (content[i] === '(') depth++;
|
|
37
|
+
else if (content[i] === ')') depth--;
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
if (depth !== 0) return null;
|
|
41
|
+
return { block: content.slice(start + 1, i - 1), end: i };
|
|
42
|
+
}
|
|
54
43
|
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
// Note: @for handling moved to a rule in `rules` so it can participate in the
|
|
45
|
+
// ordered rules pipeline. The actual exec loop for the for-rule is implemented
|
|
46
|
+
// inside parse() to allow balanced-brace extraction.
|
|
57
47
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
private static rules: PHTMLParserRule[] = [
|
|
49
|
+
// Rule for nested @for blocks. This rule's replacer computes the
|
|
50
|
+
// replacement for one @for occurrence when given a RegExpExecArray
|
|
51
|
+
// (it expects match.index and match.input to be present so the parser
|
|
52
|
+
// can extract the full balanced block that follows the match).
|
|
53
|
+
new PHTMLParserRule(/@for\s*\(([A-Za-z0-9_$]+)\s+in\s+([A-Za-z0-9_$.]+)\s*\)\s*\{/g,
|
|
54
|
+
(parser, m, scope) => {
|
|
55
|
+
// `m` will be a RegExpExecArray-like object when used by the
|
|
56
|
+
// exec-based loop inside `parse()` so we can treat it accordingly.
|
|
57
|
+
const match = m as any as RegExpExecArray;
|
|
58
|
+
const iterVar = match[1];
|
|
59
|
+
const iterableExpr = match[2];
|
|
60
|
+
const offset = Number(match[match.length - 2]); // offset position
|
|
61
|
+
const input = String(match[match.length - 1]); // source string
|
|
62
|
+
|
|
63
|
+
const blockStart = offset + match[0].length - 1; // position of '{'
|
|
64
|
+
const extracted = PHTMLParser.extractBalancedBlock(input, blockStart);
|
|
65
|
+
if (!extracted) return match[0];
|
|
66
|
+
|
|
67
|
+
const inner = extracted.block;
|
|
68
|
+
const resolved = parser.resolveExpression(iterableExpr, scope);
|
|
69
|
+
const arr = Array.isArray(resolved) ? resolved : [];
|
|
70
|
+
|
|
71
|
+
const resultParts: string[] = [];
|
|
72
|
+
for (const item of arr) {
|
|
73
|
+
const fullScope = Object.assign({}, scope, { [iterVar]: item });
|
|
74
|
+
resultParts.push(parser.parse(inner, fullScope));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// return both the replacement text and the position after the full balanced block
|
|
78
|
+
return { text: resultParts.join('\n'), end: extracted.end };
|
|
79
|
+
}),
|
|
80
|
+
// Double-paren expression: @(( code )) — use exec+balanced-start extraction
|
|
81
|
+
new PHTMLParserRule(/@\(\(/g, (parser, m, scope) => {
|
|
82
|
+
const match = m as any as RegExpExecArray;
|
|
83
|
+
const offset = Number(match[match.length - 2]);
|
|
84
|
+
const input = String(match[match.length - 1]);
|
|
85
|
+
const blockStart = offset + match[0].length - 1; // points at the inner '('
|
|
86
|
+
|
|
87
|
+
// extract balanced parentheses
|
|
88
|
+
const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
|
|
89
|
+
if (!extracted) return match[0];
|
|
90
|
+
|
|
91
|
+
const code = extracted.block;
|
|
92
|
+
// Evaluate code using parser.variables + local scope
|
|
93
|
+
const ctx = Object.assign({}, parser.variables, scope || {});
|
|
94
|
+
let result: any;
|
|
95
|
+
try {
|
|
96
|
+
// try to evaluate as expression first
|
|
97
|
+
const fn = new Function('with(this){ return (' + code + '); }');
|
|
98
|
+
result = fn.call(ctx);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
try {
|
|
101
|
+
const fn2 = new Function('with(this){ ' + code + ' }');
|
|
102
|
+
result = fn2.call(ctx);
|
|
103
|
+
} catch (e2) {
|
|
104
|
+
// On error return empty string
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
64
107
|
}
|
|
65
|
-
result += resultParts.join('\n');
|
|
66
|
-
}
|
|
67
108
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
109
|
+
// final end should include the outer ')' if present (for @(( ... )) constructs)
|
|
110
|
+
let finalEnd = extracted.end;
|
|
111
|
+
if (input[extracted.end] === ')') finalEnd = extracted.end + 1;
|
|
112
|
+
return { text: parser.stringifyValue(result), end: finalEnd };
|
|
113
|
+
}),
|
|
114
|
+
// Single-paren expression: @( code ) — similar extraction, processed after double-paren rule
|
|
115
|
+
new PHTMLParserRule(/@\(/g, (parser, m, scope) => {
|
|
116
|
+
const match = m as any as RegExpExecArray;
|
|
117
|
+
const offset = Number(match[match.length - 2]);
|
|
118
|
+
const input = String(match[match.length - 1]);
|
|
119
|
+
const blockStart = offset + match[0].length - 1; // points at '('
|
|
120
|
+
|
|
121
|
+
const extracted = PHTMLParser.extractBalancedParens(input, blockStart);
|
|
122
|
+
if (!extracted) return match[0];
|
|
123
|
+
|
|
124
|
+
const code = extracted.block;
|
|
125
|
+
const ctx = Object.assign({}, parser.variables, scope || {});
|
|
126
|
+
let result: any;
|
|
127
|
+
try {
|
|
128
|
+
const fn = new Function('with(this){ return (' + code + '); }');
|
|
129
|
+
result = fn.call(ctx);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
try {
|
|
132
|
+
const fn2 = new Function('with(this){ ' + code + ' }');
|
|
133
|
+
result = fn2.call(ctx);
|
|
134
|
+
} catch (e2) {
|
|
135
|
+
return '';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
parser.stringifyValue(parser.resolveExpression(m[1], scope))),
|
|
76
|
-
new PHTMLParserRule(/@\(([\s\S]+?)\)/g, (parser, m, scope) =>
|
|
77
|
-
parser.stringifyValue(parser.resolveExpression(m[1], scope))),
|
|
139
|
+
return { text: parser.stringifyValue(result), end: extracted.end };
|
|
140
|
+
}),
|
|
78
141
|
];
|
|
79
142
|
public variables: Record<string, unknown> = {};
|
|
80
143
|
|
|
@@ -100,18 +163,49 @@ export default class PHTMLParser {
|
|
|
100
163
|
private stringifyValue(val: any): string {
|
|
101
164
|
if (val == null) return '';
|
|
102
165
|
if (typeof val === 'string') return val;
|
|
103
|
-
|
|
166
|
+
const str = String(val);
|
|
167
|
+
return str;
|
|
104
168
|
}
|
|
105
169
|
|
|
106
170
|
public parse(content: string, scope?: Record<string, any>): string {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Then apply other rules
|
|
171
|
+
let working = content;
|
|
172
|
+
|
|
111
173
|
for (const rule of PHTMLParser.rules) {
|
|
112
|
-
|
|
113
|
-
|
|
174
|
+
const pattern = rule.pattern;
|
|
175
|
+
let result = '';
|
|
176
|
+
let lastIndex = 0;
|
|
177
|
+
let match: RegExpExecArray | null;
|
|
178
|
+
|
|
179
|
+
// reset scanning position
|
|
180
|
+
pattern.lastIndex = 0;
|
|
181
|
+
while ((match = pattern.exec(working)) !== null) {
|
|
182
|
+
// append text before this match
|
|
183
|
+
result += working.slice(lastIndex, match.index);
|
|
184
|
+
|
|
185
|
+
// build args array similar to replace(): [...match, offset, input]
|
|
186
|
+
const args = [...match, match.index, working] as any as IArguments;
|
|
187
|
+
const out = rule.replacer(this, args, scope);
|
|
188
|
+
|
|
189
|
+
if (typeof out === 'string') {
|
|
190
|
+
// simple replacement: advance by matched token length
|
|
191
|
+
result += out;
|
|
192
|
+
lastIndex = match.index + match[0].length;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// object replacement provides final end index — consume until that position
|
|
196
|
+
result += out.text;
|
|
197
|
+
lastIndex = out.end;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// make sure regex scanning continues from the correct place
|
|
201
|
+
pattern.lastIndex = lastIndex;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// append tail and update working
|
|
205
|
+
result += working.slice(lastIndex);
|
|
206
|
+
working = result;
|
|
114
207
|
}
|
|
208
|
+
|
|
115
209
|
return working;
|
|
116
210
|
}
|
|
117
211
|
}
|