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/README.md +115 -115
- package/bin/cli.qjs +57 -0
- package/index.html +278 -380
- package/package.json +1 -1
- package/src/papagaio.js +277 -464
- package/tests/tests.json +101 -605
- package/src/louro.js +0 -259
package/src/papagaio.js
CHANGED
|
@@ -1,507 +1,320 @@
|
|
|
1
|
-
// https://github.com/jardimdanificado/papagaio
|
|
2
|
-
|
|
3
1
|
function parsePattern(papagaio, pattern) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 (
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
320
|
-
|
|
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
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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 [
|
|
352
|
-
}
|
|
353
|
-
return ['', i];
|
|
133
|
+
return ['', i];
|
|
354
134
|
}
|
|
355
135
|
|
|
356
136
|
function collectPatterns(p, src) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
374
|
-
}
|
|
375
|
-
return [A, out];
|
|
153
|
+
return [A, out];
|
|
376
154
|
}
|
|
377
155
|
|
|
378
156
|
function extractNestedPatterns(p, replaceText) {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return [nested, out];
|
|
180
|
+
|
|
181
|
+
return [nested, out];
|
|
404
182
|
}
|
|
405
183
|
|
|
406
|
-
function
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
467
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
r +=
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
}
|