papagaio 0.2.3 → 0.2.7

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,319 +1,308 @@
1
- export class Papagaio {
2
- maxRecursion = 512;
3
- #counter = { value: 0, unique: 0 };
4
- open = "{";
5
- close = "}";
6
- sigil = "$";
7
- keywords = { pattern: "pattern", context: "context" };
8
- content = "";
9
-
10
- constructor() {
11
- this.#counter.value = 0;
12
- this.#counter.unique = 0;
13
- }
1
+ // https://github.com/jardimdanificado/papagaio
14
2
 
15
- process(input) {
16
- this.content = input;
17
- let src = input, last = null, iter = 0;
18
- const pending = () => {
19
- const rCtx = new RegExp(`\\b${this.keywords.context}\\s*\\${this.open}`, "g");
20
- const rPat = new RegExp(`\\b${this.keywords.pattern}\\s*\\${this.open}`, "g");
21
- return rCtx.test(src) || rPat.test(src);
22
- };
23
- while (src !== last && iter < this.maxRecursion) {
24
- iter++;
25
- last = src;
26
- src = this.#procContext(src);
27
- const [patterns, s2] = this.#collectPatterns(src);
28
- src = s2;
29
- src = this.#applyPatterns(src, patterns);
30
- if (!pending()) break;
3
+ function processContext(papagaio, src) {
4
+ const ctxRe = new RegExp(`\\b${papagaio.symbols.context}\\s*\\${papagaio.symbols.open}`, "g");
5
+ let m, matches = [];
6
+ while ((m = ctxRe.exec(src)) !== null)
7
+ matches.push({ idx: m.index, pos: m.index + m[0].length - 1 });
8
+ for (let j = matches.length - 1; j >= 0; j--) {
9
+ const x = matches[j], [content, posAfter] = extractBlock(papagaio, src, x.pos);
10
+ if (!content.trim()) {
11
+ src = src.slice(0, x.idx) + src.slice(posAfter);
12
+ continue;
31
13
  }
32
- return this.content = src, src;
14
+ const proc = papagaio.process(content);
15
+ let left = src.substring(0, x.idx), right = src.substring(posAfter);
16
+ let prefix = left.endsWith("\n") ? "\n" : "";
17
+ if (prefix) left = left.slice(0, -1);
18
+ src = left + prefix + proc + right;
33
19
  }
20
+ return src;
21
+ }
34
22
 
35
- #procContext(src) {
36
- const ctxRe = new RegExp(`\\b${this.keywords.context}\\s*\\${this.open}`, "g");
37
- let m, matches = [];
38
- while ((m = ctxRe.exec(src)) !== null)
39
- matches.push({ idx: m.index, pos: m.index + m[0].length - 1 });
40
- for (let j = matches.length - 1; j >= 0; j--) {
41
- const x = matches[j], [content, posAfter] = this.#extractBlock(src, x.pos);
42
- if (!content.trim()) {
43
- src = src.slice(0, x.idx) + src.slice(posAfter);
44
- continue;
23
+ function extractBlock(papagaio, src, openPos, openDelim = papagaio.symbols.open, closeDelim = papagaio.symbols.close) {
24
+ let i = openPos;
25
+ if (openDelim.length > 1 || closeDelim.length > 1) {
26
+ if (src.substring(i, i + openDelim.length) === openDelim) {
27
+ i += openDelim.length;
28
+ const innerStart = i;
29
+ let d = 0;
30
+ while (i < src.length) {
31
+ if (src.substring(i, i + openDelim.length) === openDelim) {
32
+ d++;
33
+ i += openDelim.length;
34
+ } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
35
+ if (d === 0) return [src.substring(innerStart, i), i + closeDelim.length];
36
+ d--;
37
+ i += closeDelim.length;
38
+ } else i++;
45
39
  }
46
- const proc = this.process(content);
47
- let left = src.substring(0, x.idx), right = src.substring(posAfter);
48
- let prefix = left.endsWith("\n") ? "\n" : "";
49
- if (prefix) left = left.slice(0, -1);
50
- src = left + prefix + proc + right;
40
+ return [src.substring(innerStart), src.length];
51
41
  }
52
- return src;
53
42
  }
54
-
55
- #extractBlock(src, openPos, openDelim = this.open, closeDelim = this.close) {
56
- let i = openPos, depth = 0, innerStart = null, inStr = false, strChar = '';
57
-
58
- // Se openDelim tem múltiplos caracteres, processa diferente
59
- if (openDelim.length > 1) {
60
- // Pula o delimitador de abertura
61
- if (src.substring(i, i + openDelim.length) === openDelim) {
62
- i += openDelim.length;
63
- innerStart = i;
64
- // Procura pelo delimitador de fechamento com balanceamento
65
- let d = 0;
66
- while (i < src.length) {
67
- if (src.substring(i, i + openDelim.length) === openDelim) {
68
- d++;
69
- i += openDelim.length;
70
- } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
71
- if (d === 0) {
72
- return [src.substring(innerStart, i), i + closeDelim.length];
73
- }
74
- d--;
75
- i += closeDelim.length;
76
- } else {
77
- i++;
78
- }
79
- }
80
- return [src.substring(innerStart), src.length];
81
- }
82
- }
83
-
84
- // Para delimitadores single-char, balanceia
85
- while (i < src.length) {
86
- const ch = src[i];
87
- if (inStr) {
88
- if (ch === '\\') i += 2;
89
- else if (ch === strChar) { inStr = false; strChar = ''; i++; }
90
- else i++;
91
- continue;
92
- }
93
- if (ch === "'" || ch === "`") {
94
- inStr = true;
95
- strChar = ch;
96
- i++;
97
- continue;
98
- }
99
- if (ch === openDelim) {
100
- depth++;
101
- if (innerStart === null) innerStart = i + 1;
102
- } else if (ch === closeDelim) {
103
- depth--;
104
- if (depth === 0) return [innerStart !== null ? src.substring(innerStart, i) : '', i + 1];
43
+ if (src[i] === openDelim) {
44
+ i++;
45
+ const innerStart = i;
46
+ if (openDelim === closeDelim) {
47
+ while (i < src.length && src[i] !== closeDelim) i++;
48
+ return [src.substring(innerStart, i), i + 1];
49
+ } else {
50
+ let depth = 1;
51
+ while (i < src.length && depth > 0) {
52
+ if (src[i] === openDelim) depth++;
53
+ else if (src[i] === closeDelim) depth--;
54
+ if (depth > 0) i++;
105
55
  }
106
- i++;
56
+ return [src.substring(innerStart, i), i + 1];
107
57
  }
108
- return [innerStart !== null ? src.substring(innerStart) : '', src.length];
109
58
  }
59
+ return ['', i];
60
+ }
110
61
 
111
- #patternToRegex(pattern) {
112
- let regex = '', i = 0;
113
- const S = this.sigil, S2 = S + S;
114
- while (i < pattern.length) {
115
- if (pattern.startsWith(S2, i)) {
116
- regex += '\\s*';
117
- i += S2.length;
118
- continue;
119
- }
120
- if (pattern.startsWith(S + 'block', i)) {
121
- let j = i + S.length + 'block'.length;
62
+ function parsePattern(papagaio, pattern) {
63
+ const tokens = [];
64
+ let i = 0;
65
+ const S = papagaio.symbols.sigil, S2 = S + S;
66
+ while (i < pattern.length) {
67
+ if (pattern.startsWith(S2, i)) {
68
+ tokens.push({ type: 'whitespace-optional' });
69
+ i += S2.length;
70
+ continue;
71
+ }
72
+ if (pattern.startsWith(S + 'block', i)) {
73
+ let j = i + S.length + 'block'.length;
74
+ while (j < pattern.length && /\s/.test(pattern[j])) j++;
75
+ let varName = '';
76
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
77
+ if (varName) {
122
78
  while (j < pattern.length && /\s/.test(pattern[j])) j++;
123
-
124
- let varName = '';
125
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) {
126
- varName += pattern[j++];
127
- }
128
-
129
- if (varName) {
79
+ let openDelim = papagaio.symbols.open;
80
+ if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
81
+ const [c, e] = extractBlock(papagaio, pattern, j);
82
+ openDelim = unescapeDelimiter(c.trim()) || papagaio.symbols.open;
83
+ j = e;
130
84
  while (j < pattern.length && /\s/.test(pattern[j])) j++;
131
-
132
- // Extrair delimitador de abertura - com balanceamento
133
- let openDelim = this.open;
134
- if (j < pattern.length && pattern[j] === this.open) {
135
- const [c, e] = this.#extractBlock(pattern, j);
136
- openDelim = c.trim() || this.open;
137
- j = e;
138
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
139
- }
140
-
141
- // Extrair delimitador de fechamento - com balanceamento
142
- let closeDelim = this.close;
143
- if (j < pattern.length && pattern[j] === this.open) {
144
- const [c, e] = this.#extractBlock(pattern, j, this.open, this.close);
145
- closeDelim = c.trim() || this.close;
146
- j = e;
147
- }
148
-
149
- const eoMask = this.#escapeRegex(openDelim);
150
- const ecMask = this.#escapeRegex(closeDelim);
151
- // Use greedy match to allow nested delimiters to be captured (will match up to the last closing delimiter)
152
- regex += `${eoMask}([\\s\\S]*)${ecMask}`;
153
- i = j;
154
- continue;
155
85
  }
156
- }
157
- if (pattern[i] === S) {
158
- let j = i + S.length, varName = '';
159
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
160
- if (varName) {
161
- regex += '(\\S+)';
162
- i = j;
163
- } else {
164
- regex += this.#escapeRegex(S);
165
- i += S.length;
86
+ let closeDelim = papagaio.symbols.close;
87
+ if (j < pattern.length && pattern[j] === papagaio.symbols.open) {
88
+ const [c, e] = extractBlock(papagaio, pattern, j);
89
+ closeDelim = unescapeDelimiter(c.trim()) || papagaio.symbols.close;
90
+ j = e;
166
91
  }
92
+ tokens.push({ type: 'block', varName, openDelim, closeDelim });
93
+ i = j;
167
94
  continue;
168
95
  }
169
- if (/\s/.test(pattern[i])) {
170
- regex += '\\s+';
171
- while (i < pattern.length && /\s/.test(pattern[i])) i++;
96
+ }
97
+ if (pattern[i] === S) {
98
+ let j = i + S.length, varName = '';
99
+ while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
100
+ if (varName) {
101
+ tokens.push({ type: 'var', varName });
102
+ i = j;
172
103
  continue;
173
104
  }
174
- const ch = pattern[i];
175
- regex += /[.*+?^${}()|[\]\\]/.test(ch) ? '\\' + ch : ch;
176
- i++;
105
+ tokens.push({ type: 'literal', value: S });
106
+ i += S.length;
107
+ continue;
108
+ }
109
+ if (/\s/.test(pattern[i])) {
110
+ let ws = '';
111
+ while (i < pattern.length && /\s/.test(pattern[i])) ws += pattern[i++];
112
+ tokens.push({ type: 'whitespace', value: ws });
113
+ continue;
177
114
  }
178
- return new RegExp(regex, 'g');
115
+ let literal = '';
116
+ while (i < pattern.length && !pattern.startsWith(S, i) && !/\s/.test(pattern[i])) literal += pattern[i++];
117
+ if (literal) tokens.push({ type: 'literal', value: literal });
179
118
  }
119
+ return tokens;
120
+ }
180
121
 
181
- #extractVarNames(pattern) {
182
- const vars = [], seen = new Set(), S = this.sigil, S2 = S + S;
183
- let i = 0;
184
- while (i < pattern.length) {
185
- if (pattern.startsWith(S + 'block', i)) {
186
- let j = i + S.length + 'block'.length;
187
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
188
-
189
- // Extrair nome como identificador simples
190
- let varName = '';
191
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) {
192
- varName += pattern[j++];
193
- }
194
-
195
- if (varName && !seen.has(varName)) {
196
- vars.push(S + varName);
197
- seen.add(varName);
198
- }
199
-
200
- if (varName) {
201
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
202
-
203
- // Pular delimitador de abertura
204
- if (j < pattern.length && pattern[j] === this.open) {
205
- const [, ne] = this.#extractBlock(pattern, j);
206
- j = ne;
207
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
208
- }
209
-
210
- // Pular delimitador de fechamento
211
- if (j < pattern.length && pattern[j] === this.open) {
212
- const [, ne] = this.#extractBlock(pattern, j);
213
- j = ne;
214
- }
215
- }
216
-
217
- i = j;
218
- continue;
219
- }
220
- if (pattern.startsWith(S2, i)) {
221
- i += S2.length;
222
- continue;
223
- }
224
- if (pattern.startsWith(S, i)) {
225
- let j = i + S.length, varName = '';
226
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
227
- if (varName && !seen.has(varName)) {
228
- vars.push(S + varName);
229
- seen.add(varName);
122
+ function matchPattern(papagaio, src, tokens, startPos = 0) {
123
+ let pos = startPos;
124
+ const captures = {};
125
+ for (let ti = 0; ti < tokens.length; ti++) {
126
+ const token = tokens[ti];
127
+ if (token.type === 'whitespace-optional') {
128
+ while (pos < src.length && /\s/.test(src[pos])) pos++;
129
+ continue;
130
+ }
131
+ if (token.type === 'whitespace') {
132
+ if (pos >= src.length || !/\s/.test(src[pos])) return null;
133
+ while (pos < src.length && /\s/.test(src[pos])) pos++;
134
+ continue;
135
+ }
136
+ if (token.type === 'literal') {
137
+ if (!src.startsWith(token.value, pos)) return null;
138
+ pos += token.value.length;
139
+ continue;
140
+ }
141
+ if (token.type === 'var') {
142
+ const nextToken = ti + 1 < tokens.length ? tokens[ti + 1] : null;
143
+ let varValue = '';
144
+ if (nextToken) {
145
+ if (nextToken.type === 'whitespace' || nextToken.type === 'whitespace-optional') {
146
+ while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
147
+ } else if (nextToken.type === 'literal') {
148
+ const stopChar = nextToken.value[0];
149
+ while (pos < src.length && src[pos] !== stopChar && !/\s/.test(src[pos])) varValue += src[pos++];
150
+ } else if (nextToken.type === 'block') {
151
+ while (pos < src.length && !src.startsWith(nextToken.openDelim, pos) && !/\s/.test(src[pos])) varValue += src[pos++];
152
+ } else {
153
+ while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
230
154
  }
231
- i = j;
232
- continue;
155
+ } else {
156
+ while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
233
157
  }
234
- i++;
158
+ if (!varValue) return null;
159
+ captures[papagaio.symbols.sigil + token.varName] = varValue;
160
+ continue;
161
+ }
162
+ if (token.type === 'block') {
163
+ const { varName, openDelim, closeDelim } = token;
164
+ if (!src.startsWith(openDelim, pos)) return null;
165
+ const [blockContent, endPos] = extractBlock(papagaio, src, pos, openDelim, closeDelim);
166
+ captures[papagaio.symbols.sigil + varName] = blockContent;
167
+ pos = endPos;
168
+ continue;
235
169
  }
236
- return vars;
237
170
  }
171
+ return { captures, endPos: pos };
172
+ }
238
173
 
239
- #collectPatterns(src) {
240
- const patterns = [], patRe = new RegExp(`\\b${this.keywords.pattern}\\s*\\${this.open}`, "g");
241
- let result = src, i = 0;
242
- while (i < result.length) {
243
- patRe.lastIndex = i;
244
- const m = patRe.exec(result);
245
- if (!m) break;
246
- const start = m.index, openPos = m.index + m[0].length - 1;
247
- const [matchPat, posAfterMatch] = this.#extractBlock(result, openPos);
248
- let k = posAfterMatch;
249
- while (k < result.length && /\s/.test(result[k])) k++;
250
- if (k < result.length && result[k] === this.open) {
251
- const [replacePat, posAfterReplace] = this.#extractBlock(result, k);
252
- patterns.push({ match: matchPat.trim(), replace: replacePat.trim() });
253
- result = result.slice(0, start) + result.slice(posAfterReplace);
254
- i = start;
255
- continue;
256
- }
257
- i = start + 1;
174
+ function collectPatterns(papagaio, src) {
175
+ const patterns = [];
176
+ const patRe = new RegExp(`\\b${papagaio.symbols.pattern}\\s*\\${papagaio.symbols.open}`, "g");
177
+ let result = src;
178
+ while (true) {
179
+ patRe.lastIndex = 0;
180
+ const m = patRe.exec(result);
181
+ if (!m) break;
182
+ const start = m.index;
183
+ const openPos = m.index + m[0].length - 1;
184
+ const [matchPat, posAfterMatch] = extractBlock(papagaio, result, openPos);
185
+ let k = posAfterMatch;
186
+ while (k < result.length && /\s/.test(result[k])) k++;
187
+ if (k < result.length && result[k] === papagaio.symbols.open) {
188
+ const [replacePat, posAfterReplace] = extractBlock(papagaio, result, k);
189
+ patterns.push({ match: matchPat.trim(), replace: replacePat.trim() });
190
+ result = result.slice(0, start) + result.slice(posAfterReplace);
191
+ continue;
258
192
  }
259
- return [patterns, result];
193
+ result = result.slice(0, start) + result.slice(posAfterMatch);
260
194
  }
195
+ return [patterns, result];
196
+ }
261
197
 
262
- #applyPatterns(src, patterns) {
263
- let clearFlag = false, lastResult = "", S = this.sigil;
264
- for (const pat of patterns) {
265
- // Aplicar uma única vez
266
- const regex = this.#patternToRegex(pat.match);
267
- const varNames = this.#extractVarNames(pat.match);
268
- src = src.replace(regex, (...args) => {
269
- const fullMatch = args[0];
270
- const captures = args.slice(1, -2);
271
- const matchStart = args[args.length - 2];
272
- const matchEnd = matchStart + fullMatch.length;
198
+ function applyPatterns(papagaio, src, patterns) {
199
+ let clearFlag = false, lastResult = "", S = papagaio.symbols.sigil;
200
+ for (const pat of patterns) {
201
+ const tokens = parsePattern(papagaio, pat.match);
202
+ let newSrc = '';
203
+ let pos = 0, matched = false;
204
+ while (pos < src.length) {
205
+ const matchResult = matchPattern(papagaio, src, tokens, pos);
206
+ if (matchResult) {
207
+ matched = true;
208
+ const { captures, endPos } = matchResult;
273
209
  let result = pat.replace;
274
- const varMap = {};
275
- for (let i = 0; i < varNames.length; i++)
276
- varMap[varNames[i]] = captures[i] || '';
277
- for (const [k, v] of Object.entries(varMap)) {
278
- const keyEsc = this.#escapeRegex(k);
210
+ for (const [k, v] of Object.entries(captures)) {
211
+ const keyEsc = escapeRegex(k);
279
212
  result = result.replace(new RegExp(keyEsc + '(?![A-Za-z0-9_])', 'g'), v);
280
213
  }
281
- result = result.replace(new RegExp(`${this.#escapeRegex(S)}unique\\b`, 'g'),
282
- () => this.#genUnique());
214
+
215
+ const uniqueId = papagaio.unique_id++;
216
+ result = result.replace(new RegExp(`${escapeRegex(S)}unique\\b`, 'g'), () => String(uniqueId));
283
217
  result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
284
218
  try {
285
219
  const wrapped = `"use strict"; return (function() { ${code} })();`;
286
- return String(Function("papagaio", "ctx", wrapped)(this, {}));
220
+ return String(Function("papagaio", "ctx", wrapped)(papagaio, {}));
287
221
  } catch {
288
222
  return "";
289
223
  }
290
224
  });
291
225
  const S2 = S + S;
292
- result = result.replace(new RegExp(this.#escapeRegex(S2), 'g'), '');
293
- if (new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g').test(result)) {
294
- result = result.replace(new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g'), '');
226
+ result = result.replace(new RegExp(escapeRegex(S2), 'g'), '');
227
+
228
+ if (new RegExp(`${escapeRegex(S)}clear\\b`, 'g').test(result)) {
229
+ result = result.replace(new RegExp(`${escapeRegex(S)}clear\\b\\s?`, 'g'), '');
295
230
  clearFlag = true;
296
231
  }
232
+
233
+ const matchStart = pos, matchEnd = endPos;
297
234
  result = result
298
- .replace(new RegExp(`${this.#escapeRegex(S)}prefix\\b`, 'g'), src.slice(0, matchStart))
299
- .replace(new RegExp(`${this.#escapeRegex(S)}suffix\\b`, 'g'), src.slice(matchEnd))
300
- .replace(new RegExp(`${this.#escapeRegex(S)}match\\b`, 'g'), fullMatch);
235
+ .replace(new RegExp(`${escapeRegex(S)}prefix\\b`, 'g'), src.slice(0, matchStart))
236
+ .replace(new RegExp(`${escapeRegex(S)}suffix\\b`, 'g'), src.slice(matchEnd))
237
+ .replace(new RegExp(`${escapeRegex(S)}match\\b`, 'g'), src.slice(matchStart, matchEnd));
238
+ newSrc += result;
301
239
  lastResult = result;
302
- return result;
303
- });
304
- if (clearFlag) {
305
- src = lastResult;
306
- clearFlag = false;
240
+ pos = endPos;
241
+ } else {
242
+ newSrc += src[pos];
243
+ pos++;
307
244
  }
308
245
  }
309
- return src;
246
+ if (matched) {
247
+ src = clearFlag ? lastResult : newSrc;
248
+ clearFlag = false;
249
+ }
310
250
  }
251
+ return src;
252
+ }
253
+
254
+ function escapeRegex(str) {
255
+ return str.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
256
+ }
311
257
 
312
- #genUnique() {
313
- return "u" + (this.#counter.unique++).toString(36);
258
+ function unescapeDelimiter(str) {
259
+ let result = '';
260
+ for (let i = 0; i < str.length; i++) {
261
+ if (str[i] === '\\' && i + 1 < str.length) {
262
+ const next = str[i + 1];
263
+ if (next === '"' || next === "'" || next === '\\') {
264
+ result += next;
265
+ i++;
266
+ } else {
267
+ result += str[i];
268
+ }
269
+ } else {
270
+ result += str[i];
271
+ }
272
+ }
273
+ return result;
274
+ }
275
+
276
+ export class Papagaio {
277
+ constructor() {
278
+ this.recursion_limit = 512;
279
+ this.unique_id = 0;
280
+ this.symbols = {
281
+ pattern: "pattern",
282
+ context: "context",
283
+ open: "{",
284
+ close: "}",
285
+ sigil: "$"
286
+ };
287
+ this.content = "";
314
288
  }
315
289
 
316
- #escapeRegex(str) {
317
- return str.replace(/[.*+?^${}()|[\]\\\"']/g, '\\$&');
290
+ process(input) {
291
+ this.content = input;
292
+ let src = input, last = null, iter = 0;
293
+ const pending = () => {
294
+ const rCtx = new RegExp(`\\b${this.symbols.context}\\s*\\${this.symbols.open}`, "g");
295
+ const rPat = new RegExp(`\\b${this.symbols.pattern}\\s*\\${this.symbols.open}`, "g");
296
+ return rCtx.test(src) || rPat.test(src);
297
+ };
298
+ while (src !== last && iter < this.recursion_limit) {
299
+ iter++;
300
+ last = src;
301
+ src = processContext(this, src);
302
+ const [patterns, s2] = collectPatterns(this, src);
303
+ src = applyPatterns(this, s2, patterns);
304
+ if (!pending()) break;
305
+ }
306
+ return this.content = src, src;
318
307
  }
319
308
  }
package/tests/test.js CHANGED
@@ -20,8 +20,6 @@ const testsPath = path.join(__dirname, 'tests.json');
20
20
  const testsData = JSON.parse(fs.readFileSync(testsPath, 'utf-8'));
21
21
  const tests = testsData.tests;
22
22
 
23
- const p = new Papagaio();
24
-
25
23
  console.log(`${colors.cyan}[TEST] PAPAGAIO - TEST RUNNER${colors.reset}\n`);
26
24
  console.log('='.repeat(80));
27
25
 
@@ -30,22 +28,25 @@ let failed = 0;
30
28
  const failedTests = [];
31
29
 
32
30
  for (const test of tests) {
31
+ // Criar nova instância de Papagaio para cada teste
32
+ const p = new Papagaio();
33
+
33
34
  try {
34
35
  const result = p.process(test.code).trim();
35
- const success = result.includes(test.shouldContain);
36
+ const success = result.includes(test.expected);
36
37
 
37
38
  if (success) {
38
39
  console.log(`${colors.green}[PASS]${colors.reset} [${test.id}] ${test.name}`);
39
40
  passed++;
40
41
  } else {
41
42
  console.log(`${colors.red}[FAIL]${colors.reset} [${test.id}] ${test.name}`);
42
- console.log(` ${colors.yellow}Expected:${colors.reset} "${test.shouldContain}"`);
43
+ console.log(` ${colors.yellow}Expected:${colors.reset} "${test.expected}"`);
43
44
  console.log(` ${colors.yellow}Got:${colors.reset} "${result.substring(0, 80)}${result.length > 80 ? '...' : ''}"`);
44
45
  failed++;
45
46
  failedTests.push({
46
47
  id: test.id,
47
48
  name: test.name,
48
- expected: test.shouldContain,
49
+ expected: test.expected,
49
50
  got: result.substring(0, 150)
50
51
  });
51
52
  }
@@ -93,6 +94,7 @@ const report = {
93
94
 
94
95
  const reportPath = path.join(__dirname, 'test-report.json');
95
96
  fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
97
+
96
98
  console.log(`\n${colors.cyan}[INFO]${colors.reset} Report saved at: ${reportPath}`);
97
99
 
98
100
  process.exit(failed > 0 ? 1 : 0);