papagaio 0.1.8 → 0.2.3
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/README.md +227 -206
- package/{cli.js → bin/cli.js} +2 -2
- package/index.html +1 -1
- package/package.json +8 -7
- package/src/papagaio.js +319 -0
- package/tests/test.js +98 -0
- package/tests/tests.json +406 -0
- package/papagaio.js +0 -659
package/papagaio.js
DELETED
|
@@ -1,659 +0,0 @@
|
|
|
1
|
-
// ============================================
|
|
2
|
-
// papagaio - a easy to use preprocessor
|
|
3
|
-
// ============================================
|
|
4
|
-
|
|
5
|
-
export class Papagaio {
|
|
6
|
-
maxRecursion = 512;
|
|
7
|
-
|
|
8
|
-
// Private state
|
|
9
|
-
#counterState = { value: 0, unique: 0 };
|
|
10
|
-
|
|
11
|
-
// Public configuration
|
|
12
|
-
delimiters = [["{", "}"]];
|
|
13
|
-
sigil = "$";
|
|
14
|
-
keywords = {
|
|
15
|
-
pattern: "pattern",
|
|
16
|
-
macro: "macro",
|
|
17
|
-
eval: "eval",
|
|
18
|
-
scope: "scope"
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Public state - processing state
|
|
22
|
-
content = "";
|
|
23
|
-
#matchContent = "";
|
|
24
|
-
#scopeContent = "";
|
|
25
|
-
#evalContent = "";
|
|
26
|
-
|
|
27
|
-
constructor() {
|
|
28
|
-
this.#resetCounterState();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ============================================
|
|
32
|
-
// PUBLIC API
|
|
33
|
-
// ============================================
|
|
34
|
-
|
|
35
|
-
process(input) {
|
|
36
|
-
this.content = input;
|
|
37
|
-
|
|
38
|
-
let src = input;
|
|
39
|
-
let last = null;
|
|
40
|
-
let iter = 0;
|
|
41
|
-
|
|
42
|
-
const open = this.#getDefaultOpen(); // delimitador atual
|
|
43
|
-
const close = this.#getDefaultClose();
|
|
44
|
-
|
|
45
|
-
// regex para detectar blocos papagaio remanescentes
|
|
46
|
-
const pending = () => {
|
|
47
|
-
const rEval = new RegExp(`\\b${this.keywords.eval}\\s*\\${open}`, "g");
|
|
48
|
-
const rScope = new RegExp(`\\b${this.keywords.scope}\\s*\\${open}`, "g");
|
|
49
|
-
const rPattern = new RegExp(`\\b${this.keywords.pattern}\\s*\\${open}`, "g");
|
|
50
|
-
const rMacro = new RegExp(`\\b${this.keywords.macro}\\s+[A-Za-z_][A-Za-z0-9_]*\\s*\\${open}`, "g");
|
|
51
|
-
return rEval.test(src)
|
|
52
|
-
|| rScope.test(src)
|
|
53
|
-
|| rPattern.test(src)
|
|
54
|
-
|| rMacro.test(src);
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// fixpoint loop
|
|
58
|
-
while (src !== last && iter < this.maxRecursion) {
|
|
59
|
-
iter++;
|
|
60
|
-
last = src;
|
|
61
|
-
|
|
62
|
-
// --- pipeline padrão ---
|
|
63
|
-
src = this.#processScopeBlocks(src);
|
|
64
|
-
src = this.#processEvalBlocks(src);
|
|
65
|
-
|
|
66
|
-
const [macros, s1] = this.#collectMacros(src);
|
|
67
|
-
src = s1;
|
|
68
|
-
|
|
69
|
-
const [patterns, s2] = this.#collectPatterns(src);
|
|
70
|
-
src = s2;
|
|
71
|
-
|
|
72
|
-
src = this.#applyPatterns(src, patterns);
|
|
73
|
-
src = this.#expandMacros(src, macros);
|
|
74
|
-
|
|
75
|
-
// --- se sobrou bloco papagaio → roda de novo ---
|
|
76
|
-
if (!pending()) break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
this.content = src;
|
|
80
|
-
return src;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ============================================
|
|
84
|
-
// PRIVATE METHODS
|
|
85
|
-
// ============================================
|
|
86
|
-
|
|
87
|
-
#resetCounterState() {
|
|
88
|
-
this.#counterState.value = 0;
|
|
89
|
-
this.#counterState.unique = 0;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
#genUnique() {
|
|
93
|
-
return "u" + (this.#counterState.unique++).toString(36);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#findClosingDelim(open) {
|
|
97
|
-
for (const [o, c] of this.delimiters) {
|
|
98
|
-
if (o === open) return c;
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
#isRegisteredOpen(ch) {
|
|
104
|
-
return this.delimiters.some(([o, _]) => o === ch);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
#getDefaultOpen() {
|
|
108
|
-
return this.delimiters[0][0];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#getDefaultClose() {
|
|
112
|
-
return this.delimiters[0][1];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
#extractBlock(src, openpos, open = null, close = null) {
|
|
116
|
-
if (!open) open = this.#getDefaultOpen();
|
|
117
|
-
if (!close) close = this.#getDefaultClose();
|
|
118
|
-
|
|
119
|
-
let i = openpos;
|
|
120
|
-
let depth = 0;
|
|
121
|
-
let startInner = null;
|
|
122
|
-
let inString = false;
|
|
123
|
-
let strChar = '';
|
|
124
|
-
|
|
125
|
-
while (i < src.length) {
|
|
126
|
-
let ch = src[i];
|
|
127
|
-
|
|
128
|
-
if (inString) {
|
|
129
|
-
if (ch === '\\') { i += 2; continue; }
|
|
130
|
-
if (ch === strChar) { inString = false; strChar = ''; }
|
|
131
|
-
i++;
|
|
132
|
-
continue;
|
|
133
|
-
} else {
|
|
134
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
135
|
-
inString = true;
|
|
136
|
-
strChar = ch;
|
|
137
|
-
i++;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (ch === open) {
|
|
143
|
-
depth++;
|
|
144
|
-
if (startInner === null) startInner = i + 1;
|
|
145
|
-
} else if (ch === close) {
|
|
146
|
-
depth--;
|
|
147
|
-
if (depth === 0) {
|
|
148
|
-
const inner = startInner !== null ? src.substring(startInner, i) : '';
|
|
149
|
-
return [inner, i + 1];
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
i++;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const inner = startInner !== null ? src.substring(startInner) : '';
|
|
157
|
-
return [inner, src.length];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
#patternToRegex(pattern) {
|
|
161
|
-
let regex = '';
|
|
162
|
-
let i = 0;
|
|
163
|
-
|
|
164
|
-
const S = this.sigil;
|
|
165
|
-
const S2 = this.sigil + this.sigil;
|
|
166
|
-
const open = this.#getDefaultOpen();
|
|
167
|
-
const close = this.#getDefaultClose();
|
|
168
|
-
|
|
169
|
-
while (i < pattern.length) {
|
|
170
|
-
if (pattern.startsWith(S2, i)) {
|
|
171
|
-
regex += '\\s*';
|
|
172
|
-
i += S2.length;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (this.#isRegisteredOpen(pattern[i]) && pattern.startsWith(S, i + 1)) {
|
|
177
|
-
const openDelim = pattern[i];
|
|
178
|
-
const closeDelim = this.#findClosingDelim(openDelim);
|
|
179
|
-
|
|
180
|
-
let j = i + 1 + S.length;
|
|
181
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;
|
|
182
|
-
|
|
183
|
-
if (j < pattern.length && pattern[j] === closeDelim) {
|
|
184
|
-
const escapedOpen = this.#escapeRegex(openDelim);
|
|
185
|
-
const escapedClose = this.#escapeRegex(closeDelim);
|
|
186
|
-
const innerRegex = this.#buildBalancedBlockRegex(openDelim, closeDelim);
|
|
187
|
-
|
|
188
|
-
regex += `${escapedOpen}(${innerRegex})${escapedClose}`;
|
|
189
|
-
i = j + 1;
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (pattern[i] === S) {
|
|
195
|
-
let j = i + S.length;
|
|
196
|
-
let varName = '';
|
|
197
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) {
|
|
198
|
-
varName += pattern[j];
|
|
199
|
-
j++;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (varName && pattern.slice(j, j + 3) === '...') {
|
|
203
|
-
j += 3;
|
|
204
|
-
let token = '';
|
|
205
|
-
while (j < pattern.length && /\S/.test(pattern[j])) {
|
|
206
|
-
token += pattern[j];
|
|
207
|
-
j++;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Aqui convertemos o token em uma parte de regex:
|
|
211
|
-
// - qualquer ocorrência de $$ (S2) vira \s*
|
|
212
|
-
// - o restante é escapado apropriadamente
|
|
213
|
-
let tokenRegex = '';
|
|
214
|
-
if (token.length === 0) {
|
|
215
|
-
tokenRegex = ''; // sem token → apenas captura sem terminador
|
|
216
|
-
} else {
|
|
217
|
-
// dividir pelo S2 e escapar cada pedaço literal
|
|
218
|
-
const parts = token.split(S2);
|
|
219
|
-
tokenRegex = parts.map(p => this.#escapeRegex(p)).join('\\s*');
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// captura não-gulosa para o ... seguida do token interpretado
|
|
223
|
-
regex += `((?:.|\\r|\\n)*?)${tokenRegex}`;
|
|
224
|
-
i = j;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (varName) {
|
|
229
|
-
regex += '(\\S+)';
|
|
230
|
-
i = j;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
regex += this.#escapeRegex(S);
|
|
235
|
-
i += S.length;
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (/\s/.test(pattern[i])) {
|
|
240
|
-
regex += '\\s+';
|
|
241
|
-
while (i < pattern.length && /\s/.test(pattern[i])) i++;
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const char = pattern[i];
|
|
246
|
-
regex += /[.*+?^${}()|[\]\\]/.test(char) ? '\\' + char : char;
|
|
247
|
-
i++;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return new RegExp(regex, 'g');
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
#buildBalancedBlockRegex(open, close) {
|
|
255
|
-
const escapedOpen = open === '(' ? '\\(' : (open === '[' ? '\\[' : open === '{' ? '\\{' : open === '<' ? '\\<' : open);
|
|
256
|
-
const escapedClose = close === ')' ? '\\)' : (close === ']' ? '\\]' : close === '}' ? '\\}' : close === '>' ? '\\>' : close);
|
|
257
|
-
|
|
258
|
-
return `(?:[^${escapedOpen}${escapedClose}\\\\]|\\\\.|${escapedOpen}(?:[^${escapedOpen}${escapedClose}\\\\]|\\\\.)*${escapedClose})*`;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
#extractVarNames(pattern) {
|
|
262
|
-
const vars = [];
|
|
263
|
-
const seen = new Set();
|
|
264
|
-
const S = this.sigil;
|
|
265
|
-
let i = 0;
|
|
266
|
-
|
|
267
|
-
while (i < pattern.length) {
|
|
268
|
-
if (this.#isRegisteredOpen(pattern[i]) && pattern.startsWith(S, i + 1)) {
|
|
269
|
-
const openDelim = pattern[i];
|
|
270
|
-
const closeDelim = this.#findClosingDelim(openDelim);
|
|
271
|
-
|
|
272
|
-
let j = i + 1 + S.length;
|
|
273
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) j++;
|
|
274
|
-
|
|
275
|
-
if (j < pattern.length && pattern[j] === closeDelim) {
|
|
276
|
-
const varName = pattern.slice(i + 1 + S.length, j);
|
|
277
|
-
|
|
278
|
-
if (!seen.has(varName)) {
|
|
279
|
-
vars.push(S + varName);
|
|
280
|
-
seen.add(varName);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
i = j + 1;
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (pattern.startsWith(S, i)) {
|
|
289
|
-
let j = i + S.length;
|
|
290
|
-
let varName = '';
|
|
291
|
-
|
|
292
|
-
while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) {
|
|
293
|
-
varName += pattern[j];
|
|
294
|
-
j++;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (varName && pattern.slice(j, j + 3) === '...') {
|
|
298
|
-
j += 3;
|
|
299
|
-
let token = '';
|
|
300
|
-
while (j < pattern.length && /\S/.test(pattern[j])) {
|
|
301
|
-
token += pattern[j];
|
|
302
|
-
j++;
|
|
303
|
-
}
|
|
304
|
-
if (!seen.has(varName)) {
|
|
305
|
-
vars.push(S + varName);
|
|
306
|
-
seen.add(varName);
|
|
307
|
-
}
|
|
308
|
-
i = j;
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (varName && !seen.has(varName)) {
|
|
313
|
-
vars.push(S + varName);
|
|
314
|
-
seen.add(varName);
|
|
315
|
-
}
|
|
316
|
-
i = j;
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
i++;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return vars;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
#collectMacros(src) {
|
|
327
|
-
const macros = {};
|
|
328
|
-
const open = this.#getDefaultOpen();
|
|
329
|
-
const macroRegex = new RegExp(`\\b${this.keywords.macro}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\${open}`, "g");
|
|
330
|
-
|
|
331
|
-
let match;
|
|
332
|
-
const matches = [];
|
|
333
|
-
|
|
334
|
-
while ((match = macroRegex.exec(src)) !== null) {
|
|
335
|
-
matches.push({
|
|
336
|
-
name: match[1],
|
|
337
|
-
matchStart: match.index,
|
|
338
|
-
openPos: match.index + match[0].length - 1
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
for (let j = matches.length - 1; j >= 0; j--) {
|
|
343
|
-
const m = matches[j];
|
|
344
|
-
const [body, posAfter] = this.#extractBlock(src, m.openPos);
|
|
345
|
-
macros[m.name] = body;
|
|
346
|
-
|
|
347
|
-
let left = src.substring(0, m.matchStart);
|
|
348
|
-
let right = src.substring(posAfter);
|
|
349
|
-
src = this.#collapseLocalNewlines(left, right);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return [macros, src];
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
#patternDepthAt(src, pos) {
|
|
356
|
-
const open = this.keywords.pattern;
|
|
357
|
-
let depth = 0;
|
|
358
|
-
|
|
359
|
-
// scaneia até 'pos' contando quantos pattern{ e } ocorreram
|
|
360
|
-
let i = 0;
|
|
361
|
-
while (i < pos) {
|
|
362
|
-
// identificação de pattern{
|
|
363
|
-
if (src.startsWith(this.keywords.pattern, i)) {
|
|
364
|
-
let j = i + this.keywords.pattern.length;
|
|
365
|
-
while (j < src.length && /\s/.test(src[j])) j++;
|
|
366
|
-
|
|
367
|
-
if (src[j] === "{") {
|
|
368
|
-
depth++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
if (src[i] === "}") {
|
|
373
|
-
if (depth > 0) depth--;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
i++;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return depth;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
#collectPatterns(src) {
|
|
384
|
-
const patterns = [];
|
|
385
|
-
const open = this.#getDefaultOpen();
|
|
386
|
-
const close = this.#getDefaultClose();
|
|
387
|
-
const patternRegex = new RegExp(`\\b${this.keywords.pattern}\\s*\\{`, "g");
|
|
388
|
-
|
|
389
|
-
let resultSrc = src;
|
|
390
|
-
let out = "";
|
|
391
|
-
let i = 0;
|
|
392
|
-
|
|
393
|
-
while (i < resultSrc.length) {
|
|
394
|
-
patternRegex.lastIndex = i;
|
|
395
|
-
const m = patternRegex.exec(resultSrc);
|
|
396
|
-
if (!m) break;
|
|
397
|
-
|
|
398
|
-
const start = m.index;
|
|
399
|
-
|
|
400
|
-
// Antes de aceitar, precisamos saber se estamos dentro de outro pattern
|
|
401
|
-
const depth = this.#patternDepthAt(resultSrc, start);
|
|
402
|
-
|
|
403
|
-
if (depth > 0) {
|
|
404
|
-
// pattern interno: pula, não coleta, não remove
|
|
405
|
-
i = start + 1;
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Coletar pattern de nível global
|
|
410
|
-
const openPos = m.index + m[0].length - 1;
|
|
411
|
-
const [matchPattern, posAfterMatch] = this.#extractBlock(resultSrc, openPos);
|
|
412
|
-
|
|
413
|
-
let k = posAfterMatch;
|
|
414
|
-
while (k < resultSrc.length && /\s/.test(resultSrc[k])) k++;
|
|
415
|
-
|
|
416
|
-
if (k < resultSrc.length && resultSrc[k] === open) {
|
|
417
|
-
const [replacePattern, posAfterReplace] = this.#extractBlock(resultSrc, k);
|
|
418
|
-
|
|
419
|
-
patterns.push({
|
|
420
|
-
match: matchPattern.trim(),
|
|
421
|
-
replace: replacePattern.trim()
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
// Remove o bloco completo do src
|
|
425
|
-
resultSrc =
|
|
426
|
-
resultSrc.slice(0, start) +
|
|
427
|
-
resultSrc.slice(posAfterReplace);
|
|
428
|
-
|
|
429
|
-
// Continua logo após o ponto removido
|
|
430
|
-
i = start;
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
i = start + 1;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return [patterns, resultSrc];
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
#applyPatterns(src, patterns) {
|
|
441
|
-
let globalClearFlag = false;
|
|
442
|
-
let lastResult = "";
|
|
443
|
-
const S = this.sigil;
|
|
444
|
-
|
|
445
|
-
for (const pattern of patterns) {
|
|
446
|
-
let changed = true;
|
|
447
|
-
let iterations = 0;
|
|
448
|
-
|
|
449
|
-
while (changed && iterations < this.maxRecursion) {
|
|
450
|
-
changed = false;
|
|
451
|
-
iterations++;
|
|
452
|
-
|
|
453
|
-
const regex = this.#patternToRegex(pattern.match);
|
|
454
|
-
const varNames = this.#extractVarNames(pattern.match);
|
|
455
|
-
|
|
456
|
-
src = src.replace(regex, (...args) => {
|
|
457
|
-
changed = true;
|
|
458
|
-
const fullMatch = args[0];
|
|
459
|
-
const captures = args.slice(1, -2);
|
|
460
|
-
const matchStart = args[args.length - 2];
|
|
461
|
-
const matchEnd = matchStart + fullMatch.length;
|
|
462
|
-
|
|
463
|
-
const varMap = {};
|
|
464
|
-
for (let i = 0; i < varNames.length; i++) {
|
|
465
|
-
varMap[varNames[i]] = captures[i] || '';
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
this.#matchContent = fullMatch;
|
|
469
|
-
|
|
470
|
-
const _pre = src.slice(0, matchStart);
|
|
471
|
-
const _post = src.slice(matchEnd);
|
|
472
|
-
|
|
473
|
-
let result = pattern.replace;
|
|
474
|
-
|
|
475
|
-
for (const [key, val] of Object.entries(varMap)) {
|
|
476
|
-
const escaped = this.#escapeRegex(key);
|
|
477
|
-
result = result.replace(new RegExp(escaped + '(?![A-Za-z0-9_])', 'g'), val);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
result = result.replace(new RegExp(`${this.#escapeRegex(S)}unique\\b`, 'g'),
|
|
481
|
-
() => this.#genUnique()
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
const S2 = S + S;
|
|
485
|
-
result = result.replace(new RegExp(this.#escapeRegex(S2), 'g'), '');
|
|
486
|
-
|
|
487
|
-
const clearRe = new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g');
|
|
488
|
-
if (clearRe.test(result)) {
|
|
489
|
-
result = result.replace(clearRe, '');
|
|
490
|
-
globalClearFlag = true;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
result = result
|
|
494
|
-
.replace(new RegExp(`${this.#escapeRegex(S)}pre\\b`, 'g'), _pre)
|
|
495
|
-
.replace(new RegExp(`${this.#escapeRegex(S)}post\\b`, 'g'), _post)
|
|
496
|
-
.replace(new RegExp(`${this.#escapeRegex(S)}match\\b`, 'g'), fullMatch);
|
|
497
|
-
|
|
498
|
-
lastResult = result;
|
|
499
|
-
return result;
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
if (globalClearFlag) {
|
|
503
|
-
src = lastResult;
|
|
504
|
-
globalClearFlag = false;
|
|
505
|
-
changed = true;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return src;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
#expandMacros(src, macros) {
|
|
514
|
-
const S = this.sigil;
|
|
515
|
-
|
|
516
|
-
for (const name of Object.keys(macros)) {
|
|
517
|
-
const body = macros[name];
|
|
518
|
-
let changed = true;
|
|
519
|
-
let iterations = 0;
|
|
520
|
-
|
|
521
|
-
while (changed && iterations < this.maxRecursion) {
|
|
522
|
-
changed = false;
|
|
523
|
-
iterations++;
|
|
524
|
-
|
|
525
|
-
const callRegex = new RegExp(`\\b${this.#escapeRegex(name)}\\s*\\(`, 'g');
|
|
526
|
-
|
|
527
|
-
let match;
|
|
528
|
-
const matches = [];
|
|
529
|
-
|
|
530
|
-
while ((match = callRegex.exec(src)) !== null) {
|
|
531
|
-
matches.push({
|
|
532
|
-
matchStart: match.index,
|
|
533
|
-
openPos: match.index + match[0].length - 1
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
for (let j = matches.length - 1; j >= 0; j--) {
|
|
538
|
-
const m = matches[j];
|
|
539
|
-
const [argsStr, posAfter] = this.#extractBlock(src, m.openPos, '(', ')');
|
|
540
|
-
const vals = argsStr.split(',').map(v => v.trim());
|
|
541
|
-
|
|
542
|
-
let exp = body;
|
|
543
|
-
|
|
544
|
-
// Substituição flexível de $0, $1, $2 etc, mesmo dentro de palavras
|
|
545
|
-
for (let k = vals.length; k >= 0; k--) {
|
|
546
|
-
const sigil = k === 0 ? S + '0' : S + k;
|
|
547
|
-
const pattern = new RegExp(this.#escapeRegex(sigil) + '(?![0-9])', 'g');
|
|
548
|
-
const replacement = k === 0 ? name : (vals[k - 1] !== undefined ? vals[k - 1] : '');
|
|
549
|
-
exp = exp.replace(pattern, replacement);
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
let left = src.substring(0, m.matchStart);
|
|
553
|
-
let right = src.substring(posAfter);
|
|
554
|
-
src = left + exp + right;
|
|
555
|
-
changed = true;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
return src;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
#escapeRegex(str) {
|
|
564
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
#collapseLocalNewlines(left, right) {
|
|
568
|
-
left = left.replace(/\n+$/, '\n');
|
|
569
|
-
right = right.replace(/^\n+/, '\n');
|
|
570
|
-
|
|
571
|
-
if (left.endsWith('\n') && right.startsWith('\n')) {
|
|
572
|
-
right = right.replace(/^\n+/, '\n');
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (left === '' && right.startsWith('\n')) {
|
|
576
|
-
right = right.replace(/^\n+/, '');
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
return left + right;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
#processEvalBlocks(src) {
|
|
583
|
-
const open = this.#getDefaultOpen();
|
|
584
|
-
const evalRegex = new RegExp(`\\b${this.keywords.eval}\\s*\\${open}`, "g");
|
|
585
|
-
|
|
586
|
-
let match;
|
|
587
|
-
const matches = [];
|
|
588
|
-
|
|
589
|
-
while ((match = evalRegex.exec(src)) !== null) {
|
|
590
|
-
matches.push({
|
|
591
|
-
matchStart: match.index,
|
|
592
|
-
openPos: match.index + match[0].length - 1
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
for (let j = matches.length - 1; j >= 0; j--) {
|
|
597
|
-
const m = matches[j];
|
|
598
|
-
|
|
599
|
-
const [content, posAfter] = this.#extractBlock(src, m.openPos);
|
|
600
|
-
this.#evalContent = content;
|
|
601
|
-
|
|
602
|
-
let out = "";
|
|
603
|
-
try {
|
|
604
|
-
// O conteúdo é o corpo de uma função autoinvocada
|
|
605
|
-
const wrappedCode = `"use strict"; return (function() { ${content} })();`;
|
|
606
|
-
|
|
607
|
-
out = String(
|
|
608
|
-
Function("papagaio", "ctx", wrappedCode)(this, {})
|
|
609
|
-
);
|
|
610
|
-
} catch (e) {
|
|
611
|
-
out = "";
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
let left = src.substring(0, m.matchStart);
|
|
615
|
-
let right = src.substring(posAfter);
|
|
616
|
-
src = left + out + right;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
return src;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
#processScopeBlocks(src) {
|
|
623
|
-
const open = this.#getDefaultOpen();
|
|
624
|
-
const scopeRegex = new RegExp(`\\b${this.keywords.scope}\\s*\\${open}`, "g");
|
|
625
|
-
|
|
626
|
-
let match;
|
|
627
|
-
const matches = [];
|
|
628
|
-
|
|
629
|
-
while ((match = scopeRegex.exec(src)) !== null) {
|
|
630
|
-
matches.push({
|
|
631
|
-
matchStart: match.index,
|
|
632
|
-
openPos: match.index + match[0].length - 1
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
for (let j = matches.length - 1; j >= 0; j--) {
|
|
637
|
-
const m = matches[j];
|
|
638
|
-
const [content, posAfter] = this.#extractBlock(src, m.openPos);
|
|
639
|
-
|
|
640
|
-
this.#scopeContent = content;
|
|
641
|
-
const processedContent = this.process(content);
|
|
642
|
-
|
|
643
|
-
let left = src.substring(0, m.matchStart);
|
|
644
|
-
let right = src.substring(posAfter);
|
|
645
|
-
|
|
646
|
-
let prefix = "";
|
|
647
|
-
if (left.endsWith("\n")) {
|
|
648
|
-
prefix = "\n";
|
|
649
|
-
left = left.slice(0, -1);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
src = left + prefix + processedContent + right;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
return src;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|