papagaio 0.2.4 → 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,305 +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 = "";
1
+ // https://github.com/jardimdanificado/papagaio
9
2
 
10
- constructor() {
11
- this.#counter = { value: 0, unique: 0 };
12
- }
13
-
14
- process(input) {
15
- this.content = input;
16
- let src = input, last = null, iter = 0;
17
- const pending = () => {
18
- const rCtx = new RegExp(`\\b${this.keywords.context}\\s*\\${this.open}`, "g");
19
- const rPat = new RegExp(`\\b${this.keywords.pattern}\\s*\\${this.open}`, "g");
20
- return rCtx.test(src) || rPat.test(src);
21
- };
22
- while (src !== last && iter < this.maxRecursion) {
23
- iter++;
24
- last = src;
25
- src = this.#processContext(src);
26
- const [patterns, s2] = this.#collectPatterns(src);
27
- src = this.#applyPatterns(s2, patterns);
28
- 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;
29
13
  }
30
- 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;
31
19
  }
20
+ return src;
21
+ }
32
22
 
33
- #processContext(src) {
34
- const ctxRe = new RegExp(`\\b${this.keywords.context}\\s*\\${this.open}`, "g");
35
- let m, matches = [];
36
- while ((m = ctxRe.exec(src)) !== null)
37
- matches.push({ idx: m.index, pos: m.index + m[0].length - 1 });
38
- for (let j = matches.length - 1; j >= 0; j--) {
39
- const x = matches[j], [content, posAfter] = this.#extractBlock(src, x.pos);
40
- if (!content.trim()) {
41
- src = src.slice(0, x.idx) + src.slice(posAfter);
42
- 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++;
43
39
  }
44
- const proc = this.process(content);
45
- let left = src.substring(0, x.idx), right = src.substring(posAfter);
46
- let prefix = left.endsWith("\n") ? "\n" : "";
47
- if (prefix) left = left.slice(0, -1);
48
- src = left + prefix + proc + right;
40
+ return [src.substring(innerStart), src.length];
49
41
  }
50
- return src;
51
42
  }
52
-
53
- #extractBlock(src, openPos, openDelim = this.open, closeDelim = this.close) {
54
- let i = openPos;
55
- if (openDelim.length > 1 || closeDelim.length > 1) {
56
- if (src.substring(i, i + openDelim.length) === openDelim) {
57
- i += openDelim.length;
58
- const innerStart = i;
59
- let d = 0;
60
- while (i < src.length) {
61
- if (src.substring(i, i + openDelim.length) === openDelim) {
62
- d++;
63
- i += openDelim.length;
64
- } else if (src.substring(i, i + closeDelim.length) === closeDelim) {
65
- if (d === 0) return [src.substring(innerStart, i), i + closeDelim.length];
66
- d--;
67
- i += closeDelim.length;
68
- } else i++;
69
- }
70
- return [src.substring(innerStart), src.length];
71
- }
72
- }
73
- if (src[i] === openDelim) {
74
- i++;
75
- const innerStart = i;
76
- if (openDelim === closeDelim) {
77
- while (i < src.length && src[i] !== closeDelim) i++;
78
- return [src.substring(innerStart, i), i + 1];
79
- } else {
80
- let depth = 1;
81
- while (i < src.length && depth > 0) {
82
- if (src[i] === openDelim) depth++;
83
- else if (src[i] === closeDelim) depth--;
84
- if (depth > 0) i++;
85
- }
86
- return [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++;
87
55
  }
56
+ return [src.substring(innerStart, i), i + 1];
88
57
  }
89
- return ['', i];
90
58
  }
59
+ return ['', i];
60
+ }
91
61
 
92
- #parsePattern(pattern) {
93
- const tokens = [];
94
- let i = 0;
95
- const S = this.sigil, S2 = S + S;
96
- while (i < pattern.length) {
97
- if (pattern.startsWith(S2, i)) {
98
- tokens.push({ type: 'whitespace-optional' });
99
- i += S2.length;
100
- continue;
101
- }
102
- if (pattern.startsWith(S + 'block', i)) {
103
- 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) {
104
78
  while (j < pattern.length && /\s/.test(pattern[j])) j++;
105
- let varName = '';
106
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
107
- 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;
108
84
  while (j < pattern.length && /\s/.test(pattern[j])) j++;
109
- let openDelim = this.open;
110
- if (j < pattern.length && pattern[j] === this.open) {
111
- const [c, e] = this.#extractBlock(pattern, j);
112
- openDelim = this.#unescapeDelimiter(c.trim()) || this.open;
113
- j = e;
114
- while (j < pattern.length && /\s/.test(pattern[j])) j++;
115
- }
116
- let closeDelim = this.close;
117
- if (j < pattern.length && pattern[j] === this.open) {
118
- const [c, e] = this.#extractBlock(pattern, j);
119
- closeDelim = this.#unescapeDelimiter(c.trim()) || this.close;
120
- j = e;
121
- }
122
- tokens.push({ type: 'block', varName, openDelim, closeDelim });
123
- i = j;
124
- continue;
125
85
  }
126
- }
127
- if (pattern[i] === S) {
128
- let j = i + S.length, varName = '';
129
- while (j < pattern.length && /[A-Za-z0-9_]/.test(pattern[j])) varName += pattern[j++];
130
- if (varName) {
131
- tokens.push({ type: 'var', varName });
132
- i = j;
133
- continue;
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;
134
91
  }
135
- tokens.push({ type: 'literal', value: S });
136
- i += S.length;
92
+ tokens.push({ type: 'block', varName, openDelim, closeDelim });
93
+ i = j;
137
94
  continue;
138
95
  }
139
- if (/\s/.test(pattern[i])) {
140
- let ws = '';
141
- while (i < pattern.length && /\s/.test(pattern[i])) ws += pattern[i++];
142
- tokens.push({ type: 'whitespace', value: ws });
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;
143
103
  continue;
144
104
  }
145
- let literal = '';
146
- while (i < pattern.length && !pattern.startsWith(S, i) && !/\s/.test(pattern[i])) literal += pattern[i++];
147
- if (literal) tokens.push({ type: 'literal', value: literal });
105
+ tokens.push({ type: 'literal', value: S });
106
+ i += S.length;
107
+ continue;
148
108
  }
149
- return tokens;
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;
114
+ }
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 });
150
118
  }
119
+ return tokens;
120
+ }
151
121
 
152
- #matchPattern(src, tokens, startPos = 0) {
153
- let pos = startPos;
154
- const captures = {};
155
- for (let ti = 0; ti < tokens.length; ti++) {
156
- const token = tokens[ti];
157
- if (token.type === 'whitespace-optional') {
158
- while (pos < src.length && /\s/.test(src[pos])) pos++;
159
- continue;
160
- }
161
- if (token.type === 'whitespace') {
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 === 'literal') {
167
- if (!src.startsWith(token.value, pos)) return null;
168
- pos += token.value.length;
169
- continue;
170
- }
171
- if (token.type === 'var') {
172
- const nextToken = ti + 1 < tokens.length ? tokens[ti + 1] : null;
173
- let varValue = '';
174
- if (nextToken) {
175
- if (nextToken.type === 'whitespace' || nextToken.type === 'whitespace-optional') {
176
- while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
177
- } else if (nextToken.type === 'literal') {
178
- const stopChar = nextToken.value[0];
179
- while (pos < src.length && src[pos] !== stopChar && !/\s/.test(src[pos])) varValue += src[pos++];
180
- } else if (nextToken.type === 'block') {
181
- while (pos < src.length && !src.startsWith(nextToken.openDelim, pos) && !/\s/.test(src[pos])) varValue += src[pos++];
182
- } else {
183
- while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
184
- }
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++];
185
152
  } else {
186
153
  while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
187
154
  }
188
- if (!varValue) return null;
189
- captures[this.sigil + token.varName] = varValue;
190
- continue;
191
- }
192
- if (token.type === 'block') {
193
- const { varName, openDelim, closeDelim } = token;
194
- if (!src.startsWith(openDelim, pos)) return null;
195
- const [blockContent, endPos] = this.#extractBlock(src, pos, openDelim, closeDelim);
196
- captures[this.sigil + varName] = blockContent;
197
- pos = endPos;
198
- continue;
155
+ } else {
156
+ while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
199
157
  }
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;
200
169
  }
201
- return { captures, endPos: pos };
202
170
  }
171
+ return { captures, endPos: pos };
172
+ }
203
173
 
204
- #collectPatterns(src) {
205
- const patterns = [];
206
- const patRe = new RegExp(`\\b${this.keywords.pattern}\\s*\\${this.open}`, "g");
207
- let result = src;
208
- while (true) {
209
- patRe.lastIndex = 0;
210
- const m = patRe.exec(result);
211
- if (!m) break;
212
- const start = m.index;
213
- const openPos = m.index + m[0].length - 1;
214
- const [matchPat, posAfterMatch] = this.#extractBlock(result, openPos);
215
- let k = posAfterMatch;
216
- while (k < result.length && /\s/.test(result[k])) k++;
217
- if (k < result.length && result[k] === this.open) {
218
- const [replacePat, posAfterReplace] = this.#extractBlock(result, k);
219
- patterns.push({ match: matchPat.trim(), replace: replacePat.trim() });
220
- result = result.slice(0, start) + result.slice(posAfterReplace);
221
- continue;
222
- }
223
- result = result.slice(0, start) + result.slice(posAfterMatch);
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;
224
192
  }
225
- return [patterns, result];
193
+ result = result.slice(0, start) + result.slice(posAfterMatch);
226
194
  }
195
+ return [patterns, result];
196
+ }
227
197
 
228
- #applyPatterns(src, patterns) {
229
- let clearFlag = false, lastResult = "", S = this.sigil;
230
- for (const pat of patterns) {
231
- const tokens = this.#parsePattern(pat.match);
232
- let newSrc = '';
233
- let pos = 0, matched = false;
234
- while (pos < src.length) {
235
- const matchResult = this.#matchPattern(src, tokens, pos);
236
- if (matchResult) {
237
- matched = true;
238
- const { captures, endPos } = matchResult;
239
- let result = pat.replace;
240
- for (const [k, v] of Object.entries(captures)) {
241
- const keyEsc = this.#escapeRegex(k);
242
- result = result.replace(new RegExp(keyEsc + '(?![A-Za-z0-9_])', 'g'), v);
243
- }
244
- result = result.replace(new RegExp(`${this.#escapeRegex(S)}unique\\b`, 'g'), () => this.#genUnique());
245
- result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
246
- try {
247
- const wrapped = `"use strict"; return (function() { ${code} })();`;
248
- return String(Function("papagaio", "ctx", wrapped)(this, {}));
249
- } catch {
250
- return "";
251
- }
252
- });
253
- const S2 = S + S;
254
- result = result.replace(new RegExp(this.#escapeRegex(S2), 'g'), '');
255
- if (new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g').test(result)) {
256
- result = result.replace(new RegExp(`${this.#escapeRegex(S)}clear\\b`, 'g'), '');
257
- clearFlag = true;
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;
209
+ let result = pat.replace;
210
+ for (const [k, v] of Object.entries(captures)) {
211
+ const keyEsc = escapeRegex(k);
212
+ result = result.replace(new RegExp(keyEsc + '(?![A-Za-z0-9_])', 'g'), v);
213
+ }
214
+
215
+ const uniqueId = papagaio.unique_id++;
216
+ result = result.replace(new RegExp(`${escapeRegex(S)}unique\\b`, 'g'), () => String(uniqueId));
217
+ result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
218
+ try {
219
+ const wrapped = `"use strict"; return (function() { ${code} })();`;
220
+ return String(Function("papagaio", "ctx", wrapped)(papagaio, {}));
221
+ } catch {
222
+ return "";
258
223
  }
259
- const matchStart = pos, matchEnd = endPos;
260
- result = result
261
- .replace(new RegExp(`${this.#escapeRegex(S)}prefix\\b`, 'g'), src.slice(0, matchStart))
262
- .replace(new RegExp(`${this.#escapeRegex(S)}suffix\\b`, 'g'), src.slice(matchEnd))
263
- .replace(new RegExp(`${this.#escapeRegex(S)}match\\b`, 'g'), src.slice(matchStart, matchEnd));
264
- newSrc += result;
265
- lastResult = result;
266
- pos = endPos;
267
- } else {
268
- newSrc += src[pos];
269
- pos++;
224
+ });
225
+ const S2 = S + S;
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'), '');
230
+ clearFlag = true;
270
231
  }
271
- }
272
- if (matched) {
273
- src = clearFlag ? lastResult : newSrc;
274
- clearFlag = false;
232
+
233
+ const matchStart = pos, matchEnd = endPos;
234
+ result = result
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;
239
+ lastResult = result;
240
+ pos = endPos;
241
+ } else {
242
+ newSrc += src[pos];
243
+ pos++;
275
244
  }
276
245
  }
277
- return src;
278
- }
279
-
280
- #genUnique() {
281
- return "u" + (this.#counter.unique++).toString(36);
246
+ if (matched) {
247
+ src = clearFlag ? lastResult : newSrc;
248
+ clearFlag = false;
249
+ }
282
250
  }
251
+ return src;
252
+ }
283
253
 
284
- #escapeRegex(str) {
285
- return str.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
286
- }
254
+ function escapeRegex(str) {
255
+ return str.replace(/[.*+?^${}()|[\]\\""']/g, '\\$&');
256
+ }
287
257
 
288
- #unescapeDelimiter(str) {
289
- let result = '';
290
- for (let i = 0; i < str.length; i++) {
291
- if (str[i] === '\\' && i + 1 < str.length) {
292
- const next = str[i + 1];
293
- if (next === '"' || next === "'" || next === '\\') {
294
- result += next;
295
- i++;
296
- } else {
297
- result += str[i];
298
- }
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++;
299
266
  } else {
300
267
  result += str[i];
301
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 = "";
288
+ }
289
+
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;
302
305
  }
303
- return result;
306
+ return this.content = src, src;
304
307
  }
305
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);