papagaio 0.4.2 → 0.5.0

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,320 @@
1
- // https://github.com/jardimdanificado/papagaio
2
-
3
1
  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;
2
+ const tokens = []; let i = 0;
3
+ const S = papagaio.symbols.sigil, S2 = S + S;
4
+ const blockKw = papagaio.symbols.block;
5
+ while (i < pattern.length) {
6
+ if (pattern.startsWith(S2 + S, i)) {
7
+ let j = i + S2.length + S.length, varName = '';
8
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
9
+ if (varName) { tokens.push({ type: 'var-ws-optional', varName }); i = j; continue; }
35
10
  }
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++;
11
+ if (pattern.startsWith(S2, i)) {
12
+ let j = i + S2.length, varName = '';
13
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
14
+ if (varName) { tokens.push({ type: 'var-ws', varName }); i = j; continue; }
15
+ tokens.push({ type: 'whitespace-optional' }); i += S2.length; continue;
73
16
  }
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;
17
+ if (pattern.startsWith(S + blockKw, i)) {
18
+ let j = i + S.length + blockKw.length;
19
+ while (j < pattern.length && /\s/.test(pattern[j])) j++;
20
+ let varName = '';
21
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
22
+ if (varName) {
23
+ while (j < pattern.length && /\s/.test(pattern[j])) j++;
24
+ let openDelim = papagaio.symbols.open;
25
+ if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
26
+ const [c, e] = extractBlock(papagaio, pattern, j);
27
+ openDelim = unescapeDelimiter(c.trim()) || papagaio.symbols.open;
28
+ j = e; while (j < pattern.length && /\s/.test(pattern[j])) j++;
29
+ }
30
+ let closeDelim = papagaio.symbols.close;
31
+ if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
32
+ const [c, e] = extractBlock(papagaio, pattern, j);
33
+ closeDelim = unescapeDelimiter(c.trim()) || papagaio.symbols.close;
34
+ j = e;
35
+ }
36
+ tokens.push({ type: 'block', varName, openDelim, closeDelim }); i = j; continue;
37
+ }
84
38
  }
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;
39
+ if (pattern[i] === S) {
40
+ let j = i + S.length, varName = '';
41
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
42
+ if (varName) { tokens.push({ type: 'var', varName }); i = j; continue; }
43
+ tokens.push({ type: 'literal', value: S }); i += S.length; continue;
106
44
  }
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;
45
+ if (/\s/.test(pattern[i])) {
46
+ while (i < pattern.length && /\s/.test(pattern[i])) i++;
47
+ tokens.push({ type: 'whitespace-optional' }); continue;
48
+ }
49
+ let literal = '';
50
+ while (i < pattern.length && !pattern.startsWith(S, i) && !/\s/.test(pattern[i])) literal += pattern[i++];
51
+ if (literal) tokens.push({ type: 'literal', value: literal });
114
52
  }
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;
53
+ return tokens;
120
54
  }
121
55
 
122
56
  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++];
195
- }
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++];
57
+ let pos = startPos, captures = {};
58
+ for (let ti = 0; ti < tokens.length; ti++) {
59
+ const token = tokens[ti];
60
+ if (token.type === 'whitespace-optional') { while (pos < src.length && /\s/.test(src[pos])) pos++; continue; }
61
+ if (token.type === 'literal') { if (!src.startsWith(token.value, pos)) return null; pos += token.value.length; continue; }
62
+ if (token.type === 'var') {
63
+ const nextToken = findNextSignificantToken(tokens, ti);
64
+ let v = '';
65
+
66
+ if (nextToken && nextToken.type === 'block') {
67
+ while (pos < src.length && !src.startsWith(nextToken.openDelim, pos) && !/\s/.test(src[pos])) {
68
+ v += src[pos++];
69
+ }
70
+ } else {
71
+ while (pos < src.length && !/\s/.test(src[pos])) v += src[pos++];
72
+ }
73
+
74
+ if (!v) return null;
75
+ captures[papagaio.symbols.sigil + token.varName] = v;
76
+ continue;
218
77
  }
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++];
78
+ if (token.type === 'var-ws' || token.type === 'var-ws-optional') {
79
+ while (pos < src.length && /\s/.test(src[pos])) pos++;
80
+ const n = findNextSignificantToken(tokens, ti);
81
+ let v = '';
82
+
83
+ if (n && n.type === 'block') {
84
+ while (pos < src.length && !src.startsWith(n.openDelim, pos) && src[pos] !== '\n') {
85
+ v += src[pos++];
86
+ }
87
+ v = v.trimEnd();
88
+ } else if (!n || ['var', 'var-ws', 'var-ws-optional'].includes(n.type)) {
89
+ while (pos < src.length && !/\s/.test(src[pos])) v += src[pos++];
90
+ } else if (n.type === 'literal') {
91
+ while (pos < src.length && !src.startsWith(n.value, pos) && src[pos] !== '\n') v += src[pos++];
92
+ v = v.trimEnd();
93
+ }
94
+
95
+ if (token.type === 'var-ws' && !v) return null;
96
+ captures[papagaio.symbols.sigil + token.varName] = v;
97
+ continue;
223
98
  }
224
- } else if (n.type === 'literal') {
225
- while (pos < src.length && !src.startsWith(n.value, pos) && src[pos] !== '\n') {
226
- v += src[pos++];
99
+ if (token.type === 'block') {
100
+ const { varName, openDelim, closeDelim } = token;
101
+ if (!src.startsWith(openDelim, pos)) return null;
102
+ const [c, e] = extractBlock(papagaio, src, pos, openDelim, closeDelim);
103
+ captures[papagaio.symbols.sigil + varName] = c; pos = e; continue;
227
104
  }
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
105
  }
250
- }
251
- return { captures, endPos: pos };
106
+ return { captures, endPos: pos };
252
107
  }
253
108
 
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
- }
260
-
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];
306
- }
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++;
312
- }
313
- return [src.substring(s, i), i + 1];
314
- }
315
- return ['', i];
316
- }
109
+ function findNextSignificantToken(t, i) { for (let k = i + 1; k < t.length; k++) if (t[k].type !== 'whitespace-optional') return t[k]; return null; }
317
110
 
318
111
  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) {
112
+ let i = openPos;
113
+ if (openDelim.length > 1 || closeDelim.length > 1) {
326
114
  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];
115
+ i += openDelim.length; const s = i; let d = 0;
116
+ while (i < src.length) {
117
+ if (src.substring(i, i + openDelim.length) === openDelim) { d++; i += openDelim.length; }
118
+ else if (src.substring(i, i + closeDelim.length) === closeDelim) {
119
+ if (!d) return [src.substring(s, i), i + closeDelim.length];
120
+ d--; i += closeDelim.length;
121
+ } else i++;
122
+ }
123
+ return [src.substring(s), src.length];
124
+ }
344
125
  }
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++;
126
+ if (src[i] === openDelim) {
127
+ i++; const s = i;
128
+ if (openDelim === closeDelim) { while (i < src.length && src[i] !== closeDelim) i++; return [src.substring(s, i), i + 1]; }
129
+ let d = 1;
130
+ while (i < src.length && d > 0) { if (src[i] === openDelim) d++; else if (src[i] === closeDelim) d--; if (d > 0) i++; }
131
+ return [src.substring(s, i), i + 1];
350
132
  }
351
- return [src.substring(s, i), i + 1];
352
- }
353
- return ['', i];
133
+ return ['', i];
354
134
  }
355
135
 
356
136
  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;
137
+ const A = [];
138
+ const r = new RegExp(`(?:^|\\b)${escapeRegex(p.symbols.pattern)}\\s*${escapeRegex(p.symbols.open)}`, "g");
139
+ let out = src;
140
+
141
+ while (1) {
142
+ r.lastIndex = 0; const m = r.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
+ A.push({ match: mp.trim(), replace: rp.trim() });
149
+ out = out.slice(0, s) + out.slice(er); continue;
150
+ }
151
+ out = out.slice(0, s) + out.slice(em);
372
152
  }
373
- out = out.slice(0, s) + out.slice(em);
374
- }
375
- return [A, out];
153
+ return [A, out];
376
154
  }
377
155
 
378
156
  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;
157
+ const nested = [];
158
+ const r = new RegExp(`${escapeRegex(p.symbols.sigil)}${escapeRegex(p.symbols.pattern)}\\s*${escapeRegex(p.symbols.open)}`, "g");
159
+ let out = replaceText;
160
+
161
+ while (1) {
162
+ r.lastIndex = 0;
163
+ const m = r.exec(out);
164
+ if (!m) break;
165
+
166
+ const s = m.index, o = m.index + m[0].length - p.symbols.open.length;
167
+ const [mp, em] = extractBlock(p, out, o);
168
+ let k = em;
169
+
170
+ while (k < out.length && /\s/.test(out[k])) k++;
171
+
172
+ if (k < out.length && out.substring(k, k + p.symbols.open.length) === p.symbols.open) {
173
+ const [rp, er] = extractBlock(p, out, k);
174
+ nested.push({ match: mp.trim(), replace: rp.trim() });
175
+ out = out.slice(0, s) + out.slice(er);
176
+ continue;
177
+ }
178
+ out = out.slice(0, s) + out.slice(em);
399
179
  }
400
- out = out.slice(0, s) + out.slice(em);
401
- }
402
-
403
- return [nested, out];
180
+
181
+ return [nested, out];
404
182
  }
405
183
 
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;
184
+ function extractEvalExpressions(p, text) {
185
+ const evals = [];
186
+ const S = p.symbols.sigil;
187
+ const O = p.symbols.open;
188
+ const C = p.symbols.close;
189
+ const evalKeyword = p.symbols.eval;
190
+
191
+ let i = 0;
192
+ let out = text;
193
+ let offset = 0;
194
+
195
+ while (i < text.length) {
196
+ if (text.substring(i, i + S.length) === S &&
197
+ text.substring(i + S.length, i + S.length + evalKeyword.length) === evalKeyword) {
198
+
199
+ let j = i + S.length + evalKeyword.length;
200
+
201
+ while (j < text.length && /\s/.test(text[j])) j++;
202
+
203
+ if (j < text.length && text.substring(j, j + O.length) === O) {
204
+ const startPos = i;
205
+ const blockStart = j;
206
+
207
+ const [content, endPos] = extractBlock(p, text, blockStart, O, C);
208
+
209
+ evals.push({
210
+ fullMatch: text.substring(startPos, endPos),
211
+ code: content,
212
+ startPos: startPos - offset,
213
+ endPos: endPos - offset
214
+ });
215
+
216
+ const before = out.substring(0, startPos - offset);
217
+ const after = out.substring(endPos - offset);
218
+ const placeholder = `__EVAL_${evals.length - 1}__`;
219
+ out = before + placeholder + after;
220
+
221
+ offset += (endPos - startPos) - placeholder.length;
222
+ i = endPos;
223
+ continue;
224
+ }
445
225
  }
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
- }
226
+ i++;
457
227
  }
458
- if (ok) {
459
- src = clear ? last : n;
460
- clear = false;
228
+
229
+ return [evals, out];
230
+ }
231
+
232
+ function applyEvalExpressions(p, text, evals) {
233
+ let result = text;
234
+ for (let i = evals.length - 1; i >= 0; i--) {
235
+ const placeholder = `__EVAL_${i}__`;
236
+ let evalResult;
237
+ try {
238
+ evalResult = String(Function("papagaio", "ctx", `"use strict";return(function(){${evals[i].code}})();`)(p, {}));
239
+ } catch (e) {
240
+ evalResult = "javascript error: " + e.message;
241
+ }
242
+ result = result.replace(placeholder, evalResult);
461
243
  }
462
- }
463
- return src;
244
+ return result;
464
245
  }
465
246
 
466
- function escapeRegex(s) {
467
- return s.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
247
+ function applyPatterns(p, src, pats) {
248
+ let clear = false, last = "", S = p.symbols.sigil;
249
+ for (const pat of pats) {
250
+ const t = parsePattern(p, pat.match); let n = '', pos = 0, ok = false;
251
+ while (pos < src.length) {
252
+ const m = matchPattern(p, src, t, pos);
253
+ if (m) {
254
+ ok = true; const { captures, endPos } = m;
255
+ let r = pat.replace;
256
+
257
+ const [nestedPats, cleanReplace] = extractNestedPatterns(p, r);
258
+ r = cleanReplace;
259
+
260
+ for (const [k, v] of Object.entries(captures)) {
261
+ const e = escapeRegex(k); r = r.replace(new RegExp(e + '(?![A-Za-z0-9_])', 'g'), v);
262
+ }
263
+
264
+ if (nestedPats.length > 0) r = applyPatterns(p, r, nestedPats);
265
+
266
+ p.match = src.slice(pos, endPos);
267
+
268
+ const [evals, cleanText] = extractEvalExpressions(p, r);
269
+ if (evals.length > 0) {
270
+ r = applyEvalExpressions(p, cleanText, evals);
271
+ }
272
+
273
+ n += r; last = r; pos = endPos;
274
+ } else { n += src[pos]; pos++; }
275
+ }
276
+ if (ok) { src = clear ? last : n; clear = false; }
277
+ }
278
+ return src;
468
279
  }
469
280
 
281
+ function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&'); }
282
+
470
283
  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;
284
+ let r = ''; for (let i = 0; i < s.length; i++) {
285
+ if (s[i] === '\\' && i + 1 < s.length) {
286
+ const n = s[i + 1];
287
+ if (n === '"' || n === "'" || n === '\\') { r += n; i++; }
288
+ else r += s[i];
289
+ } else r += s[i];
290
+ }
291
+ return r;
482
292
  }
483
293
 
484
294
  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;
295
+ constructor(sigil = '$', open = '{', close = '}', pattern = 'pattern', evalKeyword = 'eval', blockKeyword = 'block') {
296
+ this.recursion_limit = 512;
297
+ this.symbols = {
298
+ pattern: pattern,
299
+ open: open,
300
+ close: close,
301
+ sigil: sigil,
302
+ eval: evalKeyword,
303
+ block: blockKeyword
304
+ };
305
+ this.content = "";
306
+ }
307
+ process(input) {
308
+ this.content = input; let src = input, last = null, it = 0;
309
+ const pend = () => {
310
+ const r2 = new RegExp(`(?:^|\\b)${escapeRegex(this.symbols.pattern)}\\s*${escapeRegex(this.symbols.open)}`, "g");
311
+ return r2.test(src);
312
+ };
313
+ while (src !== last && it < this.recursion_limit) {
314
+ it++; last = src;
315
+ const [p, s2] = collectPatterns(this, src); src = applyPatterns(this, s2, p);
316
+ if (!pend()) break;
317
+ }
318
+ return this.content = src, src;
504
319
  }
505
- return this.content = src, src;
506
- }
507
320
  }