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/README.md +34 -39
- package/package.json +1 -1
- package/src/papagaio.js +255 -266
- package/tests/test.js +7 -5
- package/tests/tests.json +386 -74
package/src/papagaio.js
CHANGED
|
@@ -1,319 +1,308 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
232
|
-
|
|
155
|
+
} else {
|
|
156
|
+
while (pos < src.length && !/\s/.test(src[pos])) varValue += src[pos++];
|
|
233
157
|
}
|
|
234
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
193
|
+
result = result.slice(0, start) + result.slice(posAfterMatch);
|
|
260
194
|
}
|
|
195
|
+
return [patterns, result];
|
|
196
|
+
}
|
|
261
197
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
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
|
|
275
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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)(
|
|
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(
|
|
293
|
-
|
|
294
|
-
|
|
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(`${
|
|
299
|
-
.replace(new RegExp(`${
|
|
300
|
-
.replace(new RegExp(`${
|
|
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
|
-
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
clearFlag = false;
|
|
240
|
+
pos = endPos;
|
|
241
|
+
} else {
|
|
242
|
+
newSrc += src[pos];
|
|
243
|
+
pos++;
|
|
307
244
|
}
|
|
308
245
|
}
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
317
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|