papagaio 0.4.2 → 0.5.2

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.
package/src/papagaio.js CHANGED
@@ -1,507 +1,265 @@
1
- // https://github.com/jardimdanificado/papagaio
2
-
3
- function parsePattern(papagaio, pattern) {
4
- const tokens = [], S = papagaio.symbols.sigil, S2 = S + S;
5
- let i = 0;
6
- const isWhitespaceChar = c => /\s/.test(c);
7
- const getWhitespaceType = c => c === ' ' ? 'space' : c === '\t' ? 'tab' : c === '\n' ? 'newline' : c === '\r' ? 'carriage-return' : 'other';
8
- while (i < pattern.length) {
9
- if (pattern.startsWith(S + S + S, i) && i + 3 < pattern.length && isWhitespaceChar(pattern[i + 3])) {
10
- tokens.push({ type: 'any-ws-required', wsChar: pattern[i + 3] });
11
- i += 4;
12
- continue;
13
- }
14
- if (pattern.startsWith(S + S + S + S, i) && i + 4 < pattern.length && isWhitespaceChar(pattern[i + 4])) {
15
- tokens.push({ type: 'any-ws-optional', wsChar: pattern[i + 4] });
16
- i += 5;
17
- continue;
18
- }
19
- if (pattern.startsWith(S2, i)) {
20
- let j = i + S2.length, varName = '';
21
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
22
- if (varName) {
23
- if (j < pattern.length && isWhitespaceChar(pattern[j])) {
24
- tokens.push({ type: 'var-ws', varName, wsTrailing: getWhitespaceType(pattern[j]), wsChar: pattern[j] });
25
- i = j + 1;
26
- continue;
27
- } else if (j < pattern.length && pattern[j] === S) {
28
- tokens.push({ type: 'var-ws', varName, wsTrailing: 'optional', wsChar: null });
29
- i = j + 1;
30
- continue;
31
- } else {
32
- tokens.push({ type: 'var-ws', varName });
33
- i = j;
34
- continue;
1
+ function parsePattern(p, pat) {
2
+ const t = [], S = p.symbols.sigil, O = p.symbols.open;
3
+ let i = 0;
4
+
5
+ while (i < pat.length) {
6
+ if (pat.startsWith(S + p.symbols.regex, i)) {
7
+ let j = i + S.length + p.symbols.regex.length;
8
+ while (j < pat.length && /\s/.test(pat[j])) j++;
9
+ let v = '';
10
+ while (j < pat.length && /[A-Za-z0-9_]/.test(pat[j])) v += pat[j++];
11
+ if (v) {
12
+ while (j < pat.length && /\s/.test(pat[j])) j++;
13
+ if (pat[j] === O) {
14
+ const [rx, e] = extractBlock(p, pat, j);
15
+ t.push({ type: 'regex', varName: v, regex: rx.trim() });
16
+ i = e; continue;
17
+ }
18
+ }
35
19
  }
36
- }
37
- }
38
- if (pattern.startsWith(S2, i) && i + 2 < pattern.length && isWhitespaceChar(pattern[i + 2])) {
39
- tokens.push({ type: 'ws-optional', wsType: getWhitespaceType(pattern[i + 2]), wsChar: pattern[i + 2] });
40
- i += 3;
41
- continue;
42
- }
43
- if (pattern.startsWith(S2, i)) {
44
- tokens.push({ type: 'whitespace-optional' });
45
- i += S2.length;
46
- continue;
47
- }
48
- if (pattern[i] === S && i + 1 < pattern.length && isWhitespaceChar(pattern[i + 1])) {
49
- tokens.push({ type: 'ws-required', wsType: getWhitespaceType(pattern[i + 1]), wsChar: pattern[i + 1] });
50
- i += 2;
51
- continue;
52
- }
53
- if (pattern.startsWith(S + 'block', i)) {
54
- let j = i + S.length + 5;
55
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
56
- let varName = '';
57
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
58
- if (varName) {
59
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
60
- let openDelim = papagaio.symbols.open, closeDelim = papagaio.symbols.close;
61
- let openDelimIsWs = false, closeDelimIsWs = false;
62
- if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
63
- const [c, e] = extractBlock(papagaio, pattern, j);
64
- const trimmed = c.trim();
65
- if (trimmed === '') {
66
- openDelimIsWs = true;
67
- let wsStart = j + papagaio.symbols.open.length, wsEnd = wsStart;
68
- while (wsEnd < pattern.length && pattern[wsEnd] !== papagaio.symbols.close) wsEnd++;
69
- openDelim = pattern.substring(wsStart, wsEnd);
70
- } else openDelim = unescapeDelimiter(trimmed) || papagaio.symbols.open;
71
- j = e;
72
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
20
+ if (pat.startsWith(S + p.symbols.block, i)) {
21
+ let j = i + S.length + p.symbols.block.length;
22
+ while (j < pat.length && /\s/.test(pat[j])) j++;
23
+ let v = '';
24
+ while (j < pat.length && /[A-Za-z0-9_]/.test(pat[j])) v += pat[j++];
25
+ if (v) {
26
+ while (j < pat.length && /\s/.test(pat[j])) j++;
27
+ let od = O, cd = p.symbols.close;
28
+ if (pat[j] === O) {
29
+ const [c, e] = extractBlock(p, pat, j);
30
+ od = unescapeDelim(c.trim()) || O;
31
+ j = e; while (j < pat.length && /\s/.test(pat[j])) j++;
32
+ }
33
+ if (pat[j] === O) {
34
+ const [c, e] = extractBlock(p, pat, j);
35
+ cd = unescapeDelim(c.trim()) || cd;
36
+ j = e;
37
+ }
38
+ t.push({ type: 'block', varName: v, open: od, close: cd });
39
+ i = j; continue;
40
+ }
73
41
  }
74
- if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
75
- const [c, e] = extractBlock(papagaio, pattern, j);
76
- const trimmed = c.trim();
77
- if (trimmed === '') {
78
- closeDelimIsWs = true;
79
- let wsStart = j + papagaio.symbols.open.length, wsEnd = wsStart;
80
- while (wsEnd < pattern.length && pattern[wsEnd] !== papagaio.symbols.close) wsEnd++;
81
- closeDelim = pattern.substring(wsStart, wsEnd);
82
- } else closeDelim = unescapeDelimiter(trimmed) || papagaio.symbols.close;
83
- j = e;
42
+ if (pat[i] === S) {
43
+ let j = i + S.length, v = '';
44
+ while (j < pat.length && /[A-Za-z0-9_]/.test(pat[j])) v += pat[j++];
45
+ if (v) {
46
+ const optional = pat[j] === '?';
47
+ if (optional) j++;
48
+ t.push({ type: 'var', varName: v, optional });
49
+ i = j;
50
+ continue;
51
+ }
52
+ t.push({ type: 'lit', value: S }); i += S.length; continue;
84
53
  }
85
- tokens.push({ type: 'block', varName, openDelim, closeDelim, openDelimIsWs, closeDelimIsWs });
86
- i = j;
87
- continue;
88
- }
89
- }
90
- if (pattern[i] === S) {
91
- let j = i + S.length, varName = '';
92
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
93
- if (varName) {
94
- if (j < pattern.length && isWhitespaceChar(pattern[j])) {
95
- tokens.push({ type: 'var', varName, wsTrailing: getWhitespaceType(pattern[j]), wsChar: pattern[j] });
96
- i = j + 1;
97
- continue;
98
- } else if (j < pattern.length && pattern[j] === S) {
99
- tokens.push({ type: 'var', varName, wsTrailing: 'optional', wsChar: null });
100
- i = j + 1;
101
- continue;
102
- } else {
103
- tokens.push({ type: 'var', varName });
104
- i = j;
105
- continue;
54
+ if (/\s/.test(pat[i])) {
55
+ while (i < pat.length && /\s/.test(pat[i])) i++;
56
+ t.push({ type: 'ws' }); continue;
106
57
  }
107
- }
108
- }
109
- if (isWhitespaceChar(pattern[i])) {
110
- let ws = '';
111
- while (i < pattern.length && isWhitespaceChar(pattern[i])) ws += pattern[i++];
112
- tokens.push({ type: 'literal-ws', value: ws });
113
- continue;
58
+ let lit = '';
59
+ while (i < pat.length && pat[i] !== S && !/\s/.test(pat[i])) lit += pat[i++];
60
+ if (lit) t.push({ type: 'lit', value: lit });
114
61
  }
115
- let literal = '';
116
- while (i < pattern.length && !pattern.startsWith(S, i) && !isWhitespaceChar(pattern[i])) literal += pattern[i++];
117
- if (literal) tokens.push({ type: 'literal', value: literal });
118
- }
119
- return tokens;
62
+ return t;
120
63
  }
121
64
 
122
- function matchPattern(papagaio, src, tokens, startPos = 0) {
123
- let pos = startPos, captures = {};
124
- const matchWhitespaceType = (str, idx, wsType) => {
125
- if (idx >= str.length) return { matched: '', newPos: idx };
126
- if (wsType === 'space' && str[idx] === ' ') {
127
- let j = idx;
128
- while (j < str.length && str[j] === ' ') j++;
129
- return { matched: str.slice(idx, j), newPos: j };
130
- }
131
- if (wsType === 'tab' && str[idx] === '\t') {
132
- let j = idx;
133
- while (j < str.length && str[j] === '\t') j++;
134
- return { matched: str.slice(idx, j), newPos: j };
135
- }
136
- if (wsType === 'newline' && str[idx] === '\n') {
137
- let j = idx;
138
- while (j < str.length && str[j] === '\n') j++;
139
- return { matched: str.slice(idx, j), newPos: j };
140
- }
141
- return { matched: '', newPos: idx };
142
- };
143
- for (let ti = 0; ti < tokens.length; ti++) {
144
- const token = tokens[ti];
145
- if (token.type === 'literal-ws') {
146
- if (!src.startsWith(token.value, pos)) return null;
147
- pos += token.value.length;
148
- continue;
149
- }
150
- if (token.type === 'ws-required') {
151
- const { matched, newPos } = matchWhitespaceType(src, pos, token.wsType);
152
- if (!matched) return null;
153
- pos = newPos;
154
- continue;
155
- }
156
- if (token.type === 'ws-optional') {
157
- const { newPos } = matchWhitespaceType(src, pos, token.wsType);
158
- pos = newPos;
159
- continue;
160
- }
161
- if (token.type === 'any-ws-required') {
162
- if (pos >= src.length || !/\s/.test(src[pos])) return null;
163
- while (pos < src.length && /\s/.test(src[pos])) pos++;
164
- continue;
165
- }
166
- if (token.type === 'any-ws-optional') {
167
- while (pos < src.length && /\s/.test(src[pos])) pos++;
168
- continue;
169
- }
170
- if (token.type === 'whitespace-optional') {
171
- while (pos < src.length && /\s/.test(src[pos])) pos++;
172
- continue;
173
- }
174
- if (token.type === 'literal') {
175
- if (!src.startsWith(token.value, pos)) return null;
176
- pos += token.value.length;
177
- continue;
178
- }
179
- if (token.type === 'var') {
180
- let v = '';
181
- const nextToken = findNextSignificantToken(tokens, ti);
182
-
183
- // Se o próximo token é um block, captura até o delimitador de abertura
184
- if (nextToken && nextToken.type === 'block') {
185
- while (pos < src.length && !src.startsWith(nextToken.openDelim, pos) && !/\s/.test(src[pos])) {
186
- v += src[pos++];
187
- }
188
- } else if (nextToken && nextToken.type === 'literal') {
189
- while (pos < src.length && !src.startsWith(nextToken.value, pos) && !/\s/.test(src[pos])) {
190
- v += src[pos++];
191
- }
192
- } else {
193
- while (pos < src.length && !/\s/.test(src[pos])) {
194
- v += src[pos++];
65
+ function matchPattern(p, src, tokens, pos = 0) {
66
+ let cap = {};
67
+ for (let ti = 0; ti < tokens.length; ti++) {
68
+ const tok = tokens[ti];
69
+ if (tok.type === 'ws') { while (pos < src.length && /\s/.test(src[pos])) pos++; continue; }
70
+ if (tok.type === 'lit') { if (!src.startsWith(tok.value, pos)) return null; pos += tok.value.length; continue; }
71
+ if (tok.type === 'regex') {
72
+ try {
73
+ let regex = p._regexCache.get(tok.regex);
74
+ if (!regex) {
75
+ regex = new RegExp(tok.regex);
76
+ p._regexCache.set(tok.regex, regex);
77
+ }
78
+ const m = src.slice(pos).match(regex);
79
+ if (!m || m.index !== 0) return null;
80
+ cap[p.symbols.sigil + tok.varName] = m[0];
81
+ pos += m[0].length;
82
+ } catch { return null; }
83
+ continue;
195
84
  }
196
- }
197
-
198
- if (token.wsTrailing && token.wsTrailing !== 'optional') {
199
- const { newPos } = matchWhitespaceType(src, pos, token.wsTrailing);
200
- pos = newPos;
201
- } else if (token.wsTrailing === 'optional') {
202
- const { newPos } = matchWhitespaceType(src, pos, 'space');
203
- pos = newPos;
204
- }
205
- if (!v) return null;
206
- captures[papagaio.symbols.sigil + token.varName] = v;
207
- continue;
208
- }
209
- if (token.type === 'var-ws') {
210
- while (pos < src.length && /\s/.test(src[pos])) pos++;
211
- const n = findNextSignificantToken(tokens, ti);
212
- let v = '';
213
-
214
- // Se o próximo token é um block, captura até o delimitador de abertura
215
- if (n && n.type === 'block') {
216
- while (pos < src.length && !src.startsWith(n.openDelim, pos) && src[pos] !== '\n') {
217
- v += src[pos++];
218
- }
219
- v = v.trimEnd();
220
- } else if (!n || ['var', 'var-ws'].includes(n.type)) {
221
- while (pos < src.length && !/\s/.test(src[pos])) {
222
- v += src[pos++];
85
+ if (tok.type === 'var') {
86
+ while (pos < src.length && /\s/.test(src[pos])) pos++;
87
+ const nx = findNext(tokens, ti);
88
+ let v = '';
89
+ if (nx?.type === 'block') {
90
+ while (pos < src.length && !src.startsWith(nx.open, pos) && src[pos] !== '\n') v += src[pos++];
91
+ v = v.trimEnd();
92
+ } else if (nx?.type === 'lit') {
93
+ while (pos < src.length && !src.startsWith(nx.value, pos) && src[pos] !== '\n') v += src[pos++];
94
+ v = v.trimEnd();
95
+ } else {
96
+ while (pos < src.length && !/\s/.test(src[pos])) v += src[pos++];
97
+ }
98
+ if (!v && !tok.optional) return null;
99
+ cap[p.symbols.sigil + tok.varName] = v;
100
+ continue;
223
101
  }
224
- } else if (n.type === 'literal') {
225
- while (pos < src.length && !src.startsWith(n.value, pos) && src[pos] !== '\n') {
226
- v += src[pos++];
102
+ if (tok.type === 'block') {
103
+ if (!src.startsWith(tok.open, pos)) return null;
104
+ const [c, e] = extractBlock(p, src, pos, tok.open, tok.close);
105
+ cap[p.symbols.sigil + tok.varName] = c; pos = e; continue;
227
106
  }
228
- v = v.trimEnd();
229
- }
230
-
231
- if (token.wsTrailing && token.wsTrailing !== 'optional') {
232
- const { newPos } = matchWhitespaceType(src, pos, token.wsTrailing);
233
- pos = newPos;
234
- } else if (token.wsTrailing === 'optional') {
235
- const { newPos } = matchWhitespaceType(src, pos, 'space');
236
- pos = newPos;
237
- }
238
- if (!v) return null;
239
- captures[papagaio.symbols.sigil + token.varName] = v;
240
- continue;
241
- }
242
- if (token.type === 'block') {
243
- const { varName, openDelim, closeDelim, openDelimIsWs, closeDelimIsWs } = token;
244
- if (!src.startsWith(openDelim, pos)) return null;
245
- const [c, e] = extractBlockWithWsDelimiter(papagaio, src, pos, openDelim, closeDelim, openDelimIsWs, closeDelimIsWs);
246
- captures[papagaio.symbols.sigil + varName] = c;
247
- pos = e;
248
- continue;
249
107
  }
250
- }
251
- return { captures, endPos: pos };
108
+ return { captures: cap, endPos: pos };
252
109
  }
253
110
 
254
- function findNextSignificantToken(t, i) {
255
- for (let k = i + 1; k < t.length; k++) {
256
- if (!['whitespace-optional', 'ws-optional', 'ws-required', 'any-ws-optional', 'any-ws-required'].includes(t[k].type)) return t[k];
257
- }
258
- return null;
259
- }
111
+ function findNext(t, i) { for (let k = i + 1; k < t.length; k++) if (t[k].type !== 'ws') return t[k]; return null; }
260
112
 
261
- function extractBlockWithWsDelimiter(p, src, openPos, openDelim, closeDelim, openDelimIsWs, closeDelimIsWs) {
262
- let i = openPos;
263
- if (openDelimIsWs || closeDelimIsWs) {
264
- if (src.substring(i, i + openDelim.length) === openDelim) {
265
- i += openDelim.length;
266
- const s = i;
267
- let d = 0;
268
- while (i < src.length) {
269
- if (src.substring(i, i + openDelim.length) === openDelim) {
270
- d++;
271
- i += openDelim.length;
272
- } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
273
- if (!d) return [src.substring(s, i), i + closeDelim.length];
274
- d--;
275
- i += closeDelim.length;
276
- } else i++;
277
- }
278
- return [src.substring(s), src.length];
279
- }
280
- return ['', i];
281
- }
282
- if (openDelim.length > 1 || closeDelim.length > 1) {
283
- if (src.substring(i, i + openDelim.length) === openDelim) {
284
- i += openDelim.length;
285
- const s = i;
286
- let d = 0;
287
- while (i < src.length) {
288
- if (src.substring(i, i + openDelim.length) === openDelim) {
289
- d++;
290
- i += openDelim.length;
291
- } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
292
- if (!d) return [src.substring(s, i), i + closeDelim.length];
293
- d--;
294
- i += closeDelim.length;
295
- } else i++;
296
- }
297
- return [src.substring(s), src.length];
298
- }
299
- }
300
- if (src[i] === openDelim) {
301
- i++;
302
- const s = i;
303
- if (openDelim === closeDelim) {
304
- while (i < src.length && src[i] !== closeDelim) i++;
305
- return [src.substring(s, i), i + 1];
113
+ function extractBlock(p, src, i, od = p.symbols.open, cd = p.symbols.close) {
114
+ if (od.length > 1 || cd.length > 1) {
115
+ if (src.substring(i, i + od.length) === od) {
116
+ i += od.length; const s = i; let d = 0;
117
+ while (i < src.length) {
118
+ if (src.substring(i, i + od.length) === od) { d++; i += od.length; }
119
+ else if (src.substring(i, i + cd.length) === cd) {
120
+ if (!d) return [src.substring(s, i), i + cd.length];
121
+ d--; i += cd.length;
122
+ } else i++;
123
+ }
124
+ return [src.substring(s), src.length];
125
+ }
306
126
  }
307
- let d = 1;
308
- while (i < src.length && d > 0) {
309
- if (src[i] === openDelim) d++;
310
- else if (src[i] === closeDelim) d--;
311
- if (d > 0) i++;
127
+ if (src[i] === od) {
128
+ i++; const s = i;
129
+ if (od === cd) { while (i < src.length && src[i] !== cd) i++; return [src.substring(s, i), i + 1]; }
130
+ let d = 1;
131
+ while (i < src.length && d > 0) { if (src[i] === od) d++; else if (src[i] === cd) d--; if (d > 0) i++; }
132
+ return [src.substring(s, i), i + 1];
312
133
  }
313
- return [src.substring(s, i), i + 1];
314
- }
315
- return ['', i];
134
+ return ['', i];
316
135
  }
317
136
 
318
- function extractBlock(p, src, openPos, openDelim = p.symbols.open, closeDelim = p.symbols.close) {
319
- let i = openPos;
320
- if (openDelim.length > 1 || closeDelim.length > 1) {
321
- if (src.substring(i, i + openDelim.length) === openDelim) {
322
- i += openDelim.length;
323
- const s = i;
324
- let d = 0;
325
- while (i < src.length) {
326
- if (src.substring(i, i + openDelim.length) === openDelim) {
327
- d++;
328
- i += openDelim.length;
329
- } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
330
- if (!d) return [src.substring(s, i), i + closeDelim.length];
331
- d--;
332
- i += closeDelim.length;
333
- } else i++;
334
- }
335
- return [src.substring(s), src.length];
336
- }
337
- }
338
- if (src[i] === openDelim) {
339
- i++;
340
- const s = i;
341
- if (openDelim === closeDelim) {
342
- while (i < src.length && src[i] !== closeDelim) i++;
343
- return [src.substring(s, i), i + 1];
344
- }
345
- let d = 1;
346
- while (i < src.length && d > 0) {
347
- if (src[i] === openDelim) d++;
348
- else if (src[i] === closeDelim) d--;
349
- if (d > 0) i++;
137
+ function collectPats(p, src) {
138
+ const arr = [];
139
+ const rx = new RegExp(`(?:^|\\b)${esc(p.symbols.pattern)}\\s*${esc(p.symbols.open)}`, "g");
140
+ let out = src;
141
+ while (1) {
142
+ rx.lastIndex = 0; const m = rx.exec(out); if (!m) break;
143
+ const s = m.index, o = m.index + m[0].length - p.symbols.open.length;
144
+ const [mp, em] = extractBlock(p, out, o); let k = em;
145
+ while (k < out.length && /\s/.test(out[k])) k++;
146
+ if (k < out.length && out.substring(k, k + p.symbols.open.length) === p.symbols.open) {
147
+ const [rp, er] = extractBlock(p, out, k);
148
+ arr.push({ m: mp.trim(), r: rp.trim() });
149
+ out = out.slice(0, s) + out.slice(er); continue;
150
+ }
151
+ out = out.slice(0, s) + out.slice(em);
350
152
  }
351
- return [src.substring(s, i), i + 1];
352
- }
353
- return ['', i];
153
+ return [arr, out];
354
154
  }
355
155
 
356
- function collectPatterns(p, src) {
357
- const A = [], r = new RegExp(`\\b${p.symbols.pattern}\\s*\\${p.symbols.open}`, "g");
358
- let out = src;
359
- while (1) {
360
- r.lastIndex = 0;
361
- const m = r.exec(out);
362
- if (!m) break;
363
- const s = m.index, o = m.index + m[0].length - 1;
364
- const [mp, em] = extractBlock(p, out, o);
365
- let k = em;
366
- while (k < out.length && /\s/.test(out[k])) k++;
367
- if (k < out.length && out[k] === p.symbols.open) {
368
- const [rp, er] = extractBlock(p, out, k);
369
- A.push({ match: mp.trim(), replace: rp.trim() });
370
- out = out.slice(0, s) + out.slice(er);
371
- continue;
156
+ function extractNested(p, txt) {
157
+ const n = [];
158
+ const rx = new RegExp(`${esc(p.symbols.sigil)}${esc(p.symbols.pattern)}\\s*${esc(p.symbols.open)}`, "g");
159
+ let out = txt;
160
+ while (1) {
161
+ rx.lastIndex = 0; const m = rx.exec(out); if (!m) break;
162
+ const s = m.index, o = m.index + m[0].length - p.symbols.open.length;
163
+ const [mp, em] = extractBlock(p, out, o); let k = em;
164
+ while (k < out.length && /\s/.test(out[k])) k++;
165
+ if (k < out.length && out.substring(k, k + p.symbols.open.length) === p.symbols.open) {
166
+ const [rp, er] = extractBlock(p, out, k);
167
+ n.push({ m: mp.trim(), r: rp.trim() });
168
+ out = out.slice(0, s) + out.slice(er); continue;
169
+ }
170
+ out = out.slice(0, s) + out.slice(em);
372
171
  }
373
- out = out.slice(0, s) + out.slice(em);
374
- }
375
- return [A, out];
172
+ return [n, out];
376
173
  }
377
174
 
378
- function extractNestedPatterns(p, replaceText) {
379
- const nested = [];
380
- const r = new RegExp(`\\${p.symbols.sigil}${escapeRegex(p.symbols.pattern)}\\s*\\${p.symbols.open}`, "g");
381
- let out = replaceText;
382
-
383
- while (1) {
384
- r.lastIndex = 0;
385
- const m = r.exec(out);
386
- if (!m) break;
387
-
388
- const s = m.index, o = m.index + m[0].length - 1;
389
- const [mp, em] = extractBlock(p, out, o);
390
- let k = em;
391
-
392
- while (k < out.length && /\s/.test(out[k])) k++;
393
-
394
- if (k < out.length && out[k] === p.symbols.open) {
395
- const [rp, er] = extractBlock(p, out, k);
396
- nested.push({ match: mp.trim(), replace: rp.trim() });
397
- out = out.slice(0, s) + out.slice(er);
398
- continue;
175
+ function extractEvals(p, txt) {
176
+ const ev = [], S = p.symbols.sigil, O = p.symbols.open;
177
+ let i = 0, out = txt, off = 0;
178
+ while (i < txt.length) {
179
+ if (txt.substring(i, i + S.length) === S && txt.substring(i + S.length, i + S.length + p.symbols.eval.length) === p.symbols.eval) {
180
+ let j = i + S.length + p.symbols.eval.length;
181
+ while (j < txt.length && /\s/.test(txt[j])) j++;
182
+ if (j < txt.length && txt.substring(j, j + O.length) === O) {
183
+ const sp = i, bp = j;
184
+ const [c, ep] = extractBlock(p, txt, bp);
185
+ ev.push({ code: c, sp: sp - off, ep: ep - off });
186
+ const ph = `__E${ev.length - 1}__`;
187
+ out = out.substring(0, sp - off) + ph + out.substring(ep - off);
188
+ off += (ep - sp) - ph.length; i = ep; continue;
189
+ }
190
+ }
191
+ i++;
399
192
  }
400
- out = out.slice(0, s) + out.slice(em);
401
- }
402
-
403
- return [nested, out];
193
+ return [ev, out];
404
194
  }
405
195
 
406
- function applyPatterns(p, src, pats) {
407
- let clear = false, last = "", S = p.symbols.sigil;
408
- for (const pat of pats) {
409
- const t = parsePattern(p, pat.match);
410
- let n = '', pos = 0, ok = false;
411
- while (pos < src.length) {
412
- const m = matchPattern(p, src, t, pos);
413
- if (m) {
414
- ok = true;
415
- const { captures, endPos } = m;
416
- let r = pat.replace;
417
-
418
- // Extrai e processa padrões aninhados ($pattern)
419
- const [nestedPats, cleanReplace] = extractNestedPatterns(p, r);
420
- r = cleanReplace;
421
-
422
- for (const [k, v] of Object.entries(captures)) {
423
- const e = escapeRegex(k);
424
- r = r.replace(new RegExp(e + '(?![A-Za-z0-9_])', 'g'), v);
425
- }
426
-
427
- // Aplica padrões aninhados ao resultado
428
- if (nestedPats.length > 0) {
429
- r = applyPatterns(p, r, nestedPats);
430
- }
431
-
432
- const uid = p.unique_id++;
433
- r = r.replace(new RegExp(`${escapeRegex(S)}unique\\b`, 'g'), () => String(uid));
434
- r = r.replace(/\$eval\{([^}]*)\}/g, (_, c) => {
435
- try {
436
- return String(Function("papagaio", "ctx", `"use strict";return(function(){${c}})();`)(p, {}));
437
- } catch {
438
- return "";
439
- }
440
- });
441
- r = r.replace(new RegExp(escapeRegex(S + S), 'g'), '');
442
- if (new RegExp(`${escapeRegex(S)}clear\\b`, 'g').test(r)) {
443
- r = r.replace(new RegExp(`${escapeRegex(S)}clear\\b\\s?`, 'g'), '');
444
- clear = true;
445
- }
446
- const ms = pos, me = endPos;
447
- r = r.replace(new RegExp(`${escapeRegex(S)}prefix\\b`, 'g'), src.slice(0, ms))
448
- .replace(new RegExp(`${escapeRegex(S)}suffix\\b`, 'g'), src.slice(me))
449
- .replace(new RegExp(`${escapeRegex(S)}match\\b`, 'g'), src.slice(ms, me));
450
- n += r;
451
- last = r;
452
- pos = endPos;
453
- } else {
454
- n += src[pos];
455
- pos++;
456
- }
457
- }
458
- if (ok) {
459
- src = clear ? last : n;
460
- clear = false;
461
- }
462
- }
463
- return src;
196
+ function applyEvals(p, txt, ev) {
197
+ let r = txt;
198
+ for (let i = ev.length - 1; i >= 0; i--) {
199
+ const ph = `__E${i}__`;
200
+ let res;
201
+ try { res = String(Function("papagaio", "ctx", `"use strict";return(function(){${ev[i].code}})();`)(p, {})); }
202
+ catch (e) { res = "error: " + e.message; }
203
+ r = r.replace(ph, res);
204
+ }
205
+ return r;
464
206
  }
465
207
 
466
- function escapeRegex(s) {
467
- return s.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
208
+ function applyPats(p, src, pats) {
209
+ let last = "", S = p.symbols.sigil;
210
+ for (const pat of pats) {
211
+ const tok = parsePattern(p, pat.m);
212
+ let n = '', pos = 0, ok = false;
213
+ while (pos < src.length) {
214
+ const m = matchPattern(p, src, tok, pos);
215
+ if (m) {
216
+ ok = true;
217
+ let r = pat.r;
218
+ const [nested, clean] = extractNested(p, r);
219
+ r = clean;
220
+ for (const [k, v] of Object.entries(m.captures)) {
221
+ r = r.replace(new RegExp(esc(k) + '(?![A-Za-z0-9_])', 'g'), v);
222
+ }
223
+ if (nested.length) r = applyPats(p, r, nested);
224
+ p.match = src.slice(pos, m.endPos);
225
+ const [ev, ct] = extractEvals(p, r);
226
+ if (ev.length) r = applyEvals(p, ct, ev);
227
+ n += r; last = r; pos = m.endPos;
228
+ } else { n += src[pos]; pos++; }
229
+ }
230
+ if (ok) src = n;
231
+ }
232
+ return src;
468
233
  }
469
234
 
470
- function unescapeDelimiter(s) {
471
- let r = '';
472
- for (let i = 0; i < s.length; i++) {
473
- if (s[i] === '\\' && i + 1 < s.length) {
474
- const n = s[i + 1];
475
- if (n === '"' || n === "'" || n === '\\') {
476
- r += n;
477
- i++;
478
- } else r += s[i];
479
- } else r += s[i];
480
- }
481
- return r;
235
+ function esc(s) { return s.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&'); }
236
+ function unescapeDelim(s) {
237
+ let r = '';
238
+ for (let i = 0; i < s.length; i++) {
239
+ if (s[i] === '\\' && i + 1 < s.length && (s[i+1] === '"' || s[i+1] === "'" || s[i+1] === '\\')) { r += s[i+1]; i++; }
240
+ else r += s[i];
241
+ }
242
+ return r;
482
243
  }
483
244
 
484
245
  export class Papagaio {
485
- constructor(sigil = "$", open = "{", close = "}", pattern = "pattern") {
486
- this.recursion_limit = 512;
487
- this.unique_id = 0;
488
- this.symbols = { sigil: sigil, open: open, close: close, pattern: pattern};
489
- this.content = "";
490
- }
491
- process(input) {
492
- this.content = input;
493
- let src = input, last = null, it = 0;
494
- const pend = () => {
495
- const r2 = new RegExp(`\\b${this.symbols.pattern}\\s*\\${this.symbols.open}`, "g");
496
- return r2.test(src);
497
- };
498
- while (src !== last && it < this.recursion_limit) {
499
- it++;
500
- last = src;
501
- const [p, s2] = collectPatterns(this, src);
502
- src = applyPatterns(this, s2, p);
503
- if (!pend()) break;
246
+ constructor(sigil = '$', open = '{', close = '}', pattern = 'pattern', evalKeyword = 'eval', blockKeyword = 'block', regexKeyword = 'regex') {
247
+ this.recursion_limit = 512;
248
+ this.symbols = { pattern, open, close, sigil, eval: evalKeyword, block: blockKeyword, regex: regexKeyword };
249
+ this.content = "";
250
+ this.match = "";
251
+ this._regexCache = new Map();
252
+ }
253
+ process(input) {
254
+ this.content = input;
255
+ let src = input, last = null, it = 0;
256
+ const pending = () => new RegExp(`(?:^|\\b)${esc(this.symbols.pattern)}\\s*${esc(this.symbols.open)}`, "g").test(src);
257
+ while (src !== last && it < this.recursion_limit) {
258
+ it++; last = src;
259
+ const [p, s2] = collectPats(this, src);
260
+ src = applyPats(this, s2, p);
261
+ if (!pending()) break;
262
+ }
263
+ return this.content = src, src;
504
264
  }
505
- return this.content = src, src;
506
- }
507
265
  }