papagaio 0.6.2 → 0.7.1
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 +131 -31
- package/examples/simple.html +1 -1
- package/examples/wasm.papagaio +2 -2
- package/mobile.html +209 -0
- package/package.json +1 -1
- package/src/papagaio.js +52 -49
- package/tests/tests.json +17 -17
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ papagaio.symbols = {
|
|
|
22
22
|
close: "}", // closing delimiter (multi-char supported)
|
|
23
23
|
sigil: "$", // variable marker
|
|
24
24
|
eval: "eval", // eval keyword
|
|
25
|
-
block: "
|
|
26
|
-
regex: "regex"
|
|
25
|
+
block: "recursive", // block keyword (recursive nesting)
|
|
26
|
+
regex: "regex", // regex keyword
|
|
27
|
+
blockseq: "sequential" // blockseq keyword (sequential blocks)
|
|
27
28
|
};
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -64,7 +65,7 @@ hello world
|
|
|
64
65
|
Output: `[hello] [world]`
|
|
65
66
|
|
|
66
67
|
```
|
|
67
|
-
$pattern {$name $
|
|
68
|
+
$pattern {$name ${(}{)}content} {$name: $content}
|
|
68
69
|
greeting (hello world)
|
|
69
70
|
```
|
|
70
71
|
Output: `greeting: hello world`
|
|
@@ -126,48 +127,104 @@ Output: `Month 03 in 2024`
|
|
|
126
127
|
|
|
127
128
|
## Blocks
|
|
128
129
|
|
|
129
|
-
|
|
130
|
+
Papagaio supports two types of block capture: **nested** and **adjacent**.
|
|
130
131
|
|
|
131
|
-
###
|
|
132
|
+
### Nested Blocks - `${open}{close}varName`
|
|
133
|
+
|
|
134
|
+
Captures content between delimiters with full nesting support. Nested delimiters are handled recursively.
|
|
135
|
+
|
|
136
|
+
#### Basic Syntax
|
|
132
137
|
```
|
|
133
|
-
$
|
|
138
|
+
${opening_delimiter}{closing_delimiter}varName
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
|
|
141
|
+
#### Examples
|
|
142
|
+
|
|
143
|
+
**Basic Recursive Block:**
|
|
137
144
|
```
|
|
138
|
-
$pattern {$name $
|
|
145
|
+
$pattern {$name ${(}{)}content} {[$content]}
|
|
139
146
|
data (hello world)
|
|
140
147
|
```
|
|
141
148
|
Output: `[hello world]`
|
|
142
149
|
|
|
143
|
-
|
|
150
|
+
**Custom Delimiters:**
|
|
144
151
|
```
|
|
145
|
-
$pattern {$
|
|
152
|
+
$pattern {${<<}{>>}data} {DATA: $data}
|
|
146
153
|
<<json stuff>>
|
|
147
154
|
```
|
|
148
155
|
Output: `DATA: json stuff`
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
**Multi-Character Delimiters:**
|
|
151
158
|
```
|
|
152
|
-
$pattern {$
|
|
159
|
+
$pattern {${```}{```}code} {<pre>$code</pre>}
|
|
153
160
|
```markdown
|
|
154
161
|
# Title
|
|
155
162
|
```
|
|
156
163
|
Output: `<pre># Title</pre>`
|
|
157
164
|
|
|
158
|
-
|
|
165
|
+
**Default Delimiters (empty blocks):**
|
|
166
|
+
```
|
|
167
|
+
$pattern {${}{}data} {[$data]}
|
|
168
|
+
{hello world}
|
|
169
|
+
```
|
|
170
|
+
Output: `[hello world]`
|
|
171
|
+
*(Uses default `{` and `}` when delimiters are empty)*
|
|
172
|
+
|
|
173
|
+
**Nested Blocks:**
|
|
174
|
+
```
|
|
175
|
+
$pattern {${(}{)}outer} {[$outer]}
|
|
176
|
+
(outer (inner (deep)))
|
|
177
|
+
```
|
|
178
|
+
Output: `[outer (inner (deep))]`
|
|
179
|
+
|
|
180
|
+
### Adjancent Blocks - `$${open}{close}varName`
|
|
181
|
+
|
|
182
|
+
Captures multiple adjacent blocks with the same delimiters and concatenates their content (separated by spaces).
|
|
183
|
+
|
|
184
|
+
#### Basic Syntax
|
|
159
185
|
```
|
|
160
|
-
|
|
161
|
-
(first), [second]
|
|
186
|
+
$${opening_delimiter}{closing_delimiter}varName
|
|
162
187
|
```
|
|
163
|
-
Output: `first|second`
|
|
164
188
|
|
|
165
|
-
|
|
189
|
+
#### Examples
|
|
190
|
+
|
|
191
|
+
**Basic Adjacent Block:**
|
|
192
|
+
```
|
|
193
|
+
$pattern {$${[}{]}items} {Items: $items}
|
|
194
|
+
[first][second][third]
|
|
195
|
+
```
|
|
196
|
+
Output: `Items: first second third`
|
|
197
|
+
|
|
198
|
+
**Adjacent with Custom Delimiters:**
|
|
199
|
+
```
|
|
200
|
+
$pattern {$${<}{>}tags} {Tags: $tags}
|
|
201
|
+
<html><body><div>
|
|
202
|
+
```
|
|
203
|
+
Output: `Tags: html body div`
|
|
204
|
+
|
|
205
|
+
**Default Delimiters:**
|
|
166
206
|
```
|
|
167
|
-
$pattern {
|
|
168
|
-
|
|
207
|
+
$pattern {$${}{}data} {Result: $data}
|
|
208
|
+
{a}{b}{c}
|
|
169
209
|
```
|
|
170
|
-
Output: `
|
|
210
|
+
Output: `Result: a b c`
|
|
211
|
+
|
|
212
|
+
**Mixed Usage:**
|
|
213
|
+
```
|
|
214
|
+
$pattern {${(}{)}nested, $${[}{]}seq} {Nested: $nested | Seq: $seq}
|
|
215
|
+
(a (b c)), [x][y][z]
|
|
216
|
+
```
|
|
217
|
+
Output: `Nested: a (b c) | Seq: x y z`
|
|
218
|
+
|
|
219
|
+
### Block Comparison
|
|
220
|
+
|
|
221
|
+
| Feature | Nested `${}{}var` | Adjacent `$${}{}var` |
|
|
222
|
+
|---------|---------------------|------------------------|
|
|
223
|
+
| Purpose | Capture nested content | Capture adjacent blocks |
|
|
224
|
+
| Input | `[a [b [c]]]` | `[a][b][c]` |
|
|
225
|
+
| Output | `a [b [c]]` | `a b c` |
|
|
226
|
+
| Nesting | Handled recursively | Not nested, sequential |
|
|
227
|
+
| Spacing | Preserves internal structure | Joins with spaces |
|
|
171
228
|
|
|
172
229
|
---
|
|
173
230
|
|
|
@@ -274,17 +331,18 @@ Output: `10`
|
|
|
274
331
|
* Variables automatically skip leading whitespace
|
|
275
332
|
* Trailing whitespace is trimmed when variables appear before literals
|
|
276
333
|
|
|
334
|
+
### Block Matching
|
|
335
|
+
* `${open}{close}name` = nested block capture
|
|
336
|
+
* `$${open}{close}name` = adjacent block capture (captures adjacent blocks)
|
|
337
|
+
* Supports multi-character delimiters of any length
|
|
338
|
+
* Empty delimiters `${}{}name` or `$${}{}name` use defaults from `symbols.open` and `symbols.close`
|
|
339
|
+
* Sequential blocks are joined with spaces in the captured variable
|
|
340
|
+
|
|
277
341
|
### Pattern Matching
|
|
278
342
|
* `$pattern {match} {replace}` = pattern scoped to current context
|
|
279
343
|
* Patterns inherit from parent scopes hierarchically
|
|
280
344
|
* Each `process()` call starts with a clean slate (no persistence)
|
|
281
345
|
|
|
282
|
-
### Block Matching
|
|
283
|
-
* `$block name {open}{close}` captures delimited regions
|
|
284
|
-
* Supports nested delimiters of any length
|
|
285
|
-
* Multi-character delimiters fully supported (e.g., `{>>>}{<<<}`)
|
|
286
|
-
* Blocks support arbitrary nesting depth
|
|
287
|
-
|
|
288
346
|
---
|
|
289
347
|
|
|
290
348
|
## Multi-Character Delimiter Support
|
|
@@ -305,7 +363,7 @@ Output: `[hello]`
|
|
|
305
363
|
|
|
306
364
|
### In Blocks
|
|
307
365
|
```
|
|
308
|
-
$pattern<<<$
|
|
366
|
+
$pattern<<<${<<}{>>}data>>> <<<$data>>>
|
|
309
367
|
<<content>>
|
|
310
368
|
```
|
|
311
369
|
Output: `content`
|
|
@@ -341,6 +399,27 @@ p.process(template);
|
|
|
341
399
|
// <strong>bold text</strong>
|
|
342
400
|
```
|
|
343
401
|
|
|
402
|
+
### Array/List Processor
|
|
403
|
+
```javascript
|
|
404
|
+
const p = new Papagaio();
|
|
405
|
+
const template = `
|
|
406
|
+
$pattern {$${[}{]}items} {
|
|
407
|
+
$eval{
|
|
408
|
+
const arr = '$items'.split(' ');
|
|
409
|
+
return arr.map((x, i) => \`\${i + 1}. \${x}\`).join('\\n');
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
[apple][banana][cherry]
|
|
414
|
+
`;
|
|
415
|
+
|
|
416
|
+
p.process(template);
|
|
417
|
+
// Output:
|
|
418
|
+
// 1. apple
|
|
419
|
+
// 2. banana
|
|
420
|
+
// 3. cherry
|
|
421
|
+
```
|
|
422
|
+
|
|
344
423
|
### Template System with State
|
|
345
424
|
```javascript
|
|
346
425
|
const p = new Papagaio();
|
|
@@ -371,7 +450,7 @@ p.process(template);
|
|
|
371
450
|
```javascript
|
|
372
451
|
const p = new Papagaio();
|
|
373
452
|
const template = `
|
|
374
|
-
$pattern {if $
|
|
453
|
+
$pattern {if ${(}{)}cond then ${[}{]}yes else ${<}{>}no} {
|
|
375
454
|
$eval{
|
|
376
455
|
const condition = ($cond).trim();
|
|
377
456
|
return condition === 'true' ? '$yes' : '$no';
|
|
@@ -407,6 +486,24 @@ p.process(template);
|
|
|
407
486
|
// 18
|
|
408
487
|
```
|
|
409
488
|
|
|
489
|
+
### Sequential Block Processing
|
|
490
|
+
```javascript
|
|
491
|
+
const p = new Papagaio();
|
|
492
|
+
const template = `
|
|
493
|
+
$pattern {sum $${[}{]}nums} {
|
|
494
|
+
$eval{
|
|
495
|
+
const numbers = '$nums'.split(' ').map(x => parseInt(x));
|
|
496
|
+
return numbers.reduce((a, b) => a + b, 0);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
sum [10][20][30][40]
|
|
501
|
+
`;
|
|
502
|
+
|
|
503
|
+
p.process(template);
|
|
504
|
+
// Output: 100
|
|
505
|
+
```
|
|
506
|
+
|
|
410
507
|
---
|
|
411
508
|
|
|
412
509
|
## Troubleshooting
|
|
@@ -421,6 +518,7 @@ p.process(template);
|
|
|
421
518
|
| Nested blocks fail | Ensure delimiters are properly balanced |
|
|
422
519
|
| Multi-char delimiters broken | Check delimiters don't conflict; use escaping if needed |
|
|
423
520
|
| Regex not matching | Test regex pattern separately; ensure it matches at the exact position |
|
|
521
|
+
| Empty delimiter behavior | `${}{}x` uses defaults; explicitly set if you need different behavior |
|
|
424
522
|
|
|
425
523
|
---
|
|
426
524
|
|
|
@@ -430,7 +528,9 @@ p.process(template);
|
|
|
430
528
|
$pattern {$x $y} {$y, $x} # pattern with variables
|
|
431
529
|
$pattern {$x? $y} {$y, $x} # optional variable
|
|
432
530
|
$pattern {$regex n {[0-9]+}} {$n} # regex capture
|
|
433
|
-
$pattern {$
|
|
531
|
+
$pattern {${o}{c}n} {$n} # recursive block (nested)
|
|
532
|
+
$pattern {$${o}{c}n} {$n} # sequential block (adjacent)
|
|
533
|
+
$pattern {${}{}n} {$n} # block with default delimiters
|
|
434
534
|
$eval{code} # JavaScript evaluation
|
|
435
535
|
```
|
|
436
536
|
|
|
@@ -440,7 +540,7 @@ $eval{code} # JavaScript evaluation
|
|
|
440
540
|
|
|
441
541
|
### Constructor
|
|
442
542
|
```javascript
|
|
443
|
-
new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw)
|
|
543
|
+
new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw, blockseqKw)
|
|
444
544
|
```
|
|
445
545
|
|
|
446
546
|
**Parameters:**
|
|
@@ -449,7 +549,6 @@ new Papagaio(sigil, open, close, pattern, evalKw, blockKw, regexKw)
|
|
|
449
549
|
- `close` (default: `'}'`) - Closing delimiter
|
|
450
550
|
- `pattern` (default: `'pattern'`) - Pattern keyword
|
|
451
551
|
- `evalKw` (default: `'eval'`) - Eval keyword
|
|
452
|
-
- `blockKw` (default: `'block'`) - Block keyword
|
|
453
552
|
- `regexKw` (default: `'regex'`) - Regex keyword
|
|
454
553
|
|
|
455
554
|
### Properties
|
|
@@ -475,6 +574,7 @@ p.process('$pattern {x} {y}\nx');
|
|
|
475
574
|
## Performance Notes
|
|
476
575
|
|
|
477
576
|
* Multi-character delimiter matching is optimized with substring operations
|
|
577
|
+
* Sequential blocks scan for adjacent matches without recursion overhead
|
|
478
578
|
* Nested patterns inherit parent patterns through recursive application
|
|
479
579
|
* Nested blocks and patterns have no theoretical depth limit
|
|
480
580
|
* Large recursion limits can impact performance on complex inputs
|
package/examples/simple.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</head>
|
|
8
8
|
<script src="../src/papagaio-bootstrap.mjs" type="module"></script>
|
|
9
9
|
<script type="papagaio">
|
|
10
|
-
$pattern {abc} {$eval{console.log(
|
|
10
|
+
$pattern {abc} {$eval{console.log(this)} aaaaaaaaaaaaaaaaaaaaaaaaaaaa}
|
|
11
11
|
abc
|
|
12
12
|
</script>
|
|
13
13
|
<body>
|
package/examples/wasm.papagaio
CHANGED
|
@@ -13,7 +13,7 @@ $eval{
|
|
|
13
13
|
return ""
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
$pattern {export function $name $
|
|
16
|
+
$pattern {export function $name ${(}{)}params:$rets ${}{}content} {
|
|
17
17
|
$pattern {parametrize}
|
|
18
18
|
{
|
|
19
19
|
$eval {
|
|
@@ -36,7 +36,7 @@ $pattern {export function $name $block params{(}{)}:$rets $block content{}{}} {
|
|
|
36
36
|
)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
$pattern {function $name $
|
|
39
|
+
$pattern {function $name ${}{}params{(}{)}:$rets ${}{}content} {
|
|
40
40
|
$pattern {parametrize}
|
|
41
41
|
{
|
|
42
42
|
$eval {
|
package/mobile.html
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
6
|
+
<title>🦜 papagaio</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
-webkit-tap-highlight-color: transparent;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
17
|
+
background: #fff;
|
|
18
|
+
color: #000;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.app {
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
height: 100vh;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.header {
|
|
30
|
+
background: #fff;
|
|
31
|
+
padding: 24px 20px;
|
|
32
|
+
text-align: center;
|
|
33
|
+
border-bottom: 1px solid #ddd;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.header h1 {
|
|
37
|
+
font-size: 2rem;
|
|
38
|
+
color: #000;
|
|
39
|
+
margin-bottom: 4px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.header p {
|
|
43
|
+
font-size: 0.9rem;
|
|
44
|
+
color: #666;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tabs {
|
|
48
|
+
display: flex;
|
|
49
|
+
background: #fff;
|
|
50
|
+
border-bottom: 1px solid #ddd;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.tab {
|
|
54
|
+
flex: 1;
|
|
55
|
+
padding: 18px;
|
|
56
|
+
text-align: center;
|
|
57
|
+
font-size: 1rem;
|
|
58
|
+
font-weight: 500;
|
|
59
|
+
background: #f5f5f5;
|
|
60
|
+
border: none;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
transition: all 0.2s;
|
|
63
|
+
color: #666;
|
|
64
|
+
border-right: 1px solid #ddd;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.tab:last-child {
|
|
68
|
+
border-right: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.tab.active {
|
|
72
|
+
background: #fff;
|
|
73
|
+
color: #000;
|
|
74
|
+
border-bottom: 2px solid #000;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.content {
|
|
78
|
+
flex: 1;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.editor-container {
|
|
85
|
+
display: none;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
flex: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.editor-container.active {
|
|
91
|
+
display: flex;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
textarea {
|
|
95
|
+
flex: 1;
|
|
96
|
+
padding: 20px;
|
|
97
|
+
border: none;
|
|
98
|
+
font-family:
|
|
99
|
+
ui-monospace,
|
|
100
|
+
SFMono-Regular,
|
|
101
|
+
Menlo,
|
|
102
|
+
Consolas,
|
|
103
|
+
"Liberation Mono",
|
|
104
|
+
monospace;
|
|
105
|
+
font-size: 15px;
|
|
106
|
+
line-height: 1.55;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
letter-spacing: 0.2px;
|
|
109
|
+
resize: none;
|
|
110
|
+
background: #fff;
|
|
111
|
+
color: #000;
|
|
112
|
+
white-space: pre;
|
|
113
|
+
overflow-x: auto;
|
|
114
|
+
overflow-y: auto;
|
|
115
|
+
word-break: normal;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
textarea:focus {
|
|
119
|
+
outline: none;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#output {
|
|
123
|
+
background: #fafafa;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@media (min-width: 768px) {
|
|
127
|
+
.app {
|
|
128
|
+
max-width: 600px;
|
|
129
|
+
margin: 0 auto;
|
|
130
|
+
border-left: 1px solid #ddd;
|
|
131
|
+
border-right: 1px solid #ddd;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div class="app">
|
|
138
|
+
<div class="header">
|
|
139
|
+
<h1>🦜 papagaio</h1>
|
|
140
|
+
<p>easy yet powerful text preprocessor</p>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="tabs">
|
|
144
|
+
<button class="tab active" onclick="switchTab('input')">INPUT</button>
|
|
145
|
+
<button class="tab" onclick="switchTab('output')">OUTPUT</button>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="content">
|
|
149
|
+
<div class="editor-container active" id="inputContainer">
|
|
150
|
+
<textarea id="input"></textarea>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="editor-container" id="outputContainer">
|
|
153
|
+
<textarea id="output" readonly></textarea>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<script type="module">
|
|
159
|
+
import { Papagaio } from './src/papagaio.js';
|
|
160
|
+
|
|
161
|
+
const processor = new Papagaio();
|
|
162
|
+
|
|
163
|
+
function loadSaved() {
|
|
164
|
+
try {
|
|
165
|
+
const saved = localStorage.getItem('papagaio_input');
|
|
166
|
+
if (saved) {
|
|
167
|
+
document.getElementById('input').value = saved;
|
|
168
|
+
}
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function saveInput() {
|
|
173
|
+
try {
|
|
174
|
+
const input = document.getElementById('input').value;
|
|
175
|
+
localStorage.setItem('papagaio_input', input);
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function switchTab(tab) {
|
|
180
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
181
|
+
document.querySelectorAll('.editor-container').forEach(c => c.classList.remove('active'));
|
|
182
|
+
|
|
183
|
+
if (tab === 'input') {
|
|
184
|
+
document.querySelectorAll('.tab')[0].classList.add('active');
|
|
185
|
+
document.getElementById('inputContainer').classList.add('active');
|
|
186
|
+
} else {
|
|
187
|
+
document.querySelectorAll('.tab')[1].classList.add('active');
|
|
188
|
+
document.getElementById('outputContainer').classList.add('active');
|
|
189
|
+
processCode();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function processCode() {
|
|
194
|
+
try {
|
|
195
|
+
const input = document.getElementById('input').value;
|
|
196
|
+
const output = processor.process(input);
|
|
197
|
+
document.getElementById('output').value = output;
|
|
198
|
+
saveInput();
|
|
199
|
+
} catch (err) {
|
|
200
|
+
document.getElementById('output').value = `Error: ${err.message}`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
window.addEventListener('load', loadSaved);
|
|
205
|
+
setInterval(saveInput, 2000);
|
|
206
|
+
window.switchTab = switchTab;
|
|
207
|
+
</script>
|
|
208
|
+
</body>
|
|
209
|
+
</html>
|
package/package.json
CHANGED
package/src/papagaio.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// papagaio - https://github.com/jardimdanificado/papagaio
|
|
2
2
|
function parsePattern(p, pat) {
|
|
3
|
-
const t = [], S = p.symbols.sigil, O = p.symbols.open;
|
|
3
|
+
const t = [], S = p.symbols.sigil, O = p.symbols.open, C = p.symbols.close;
|
|
4
4
|
let i = 0;
|
|
5
5
|
while (i < pat.length) {
|
|
6
6
|
if (pat.startsWith(S + p.symbols.regex, i)) {
|
|
@@ -17,36 +17,30 @@ function parsePattern(p, pat) {
|
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
-
if (pat
|
|
21
|
-
let j = i + S.length
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const [c, e] = extractBlock(p, pat, j);
|
|
35
|
-
cd = unescapeDelim(c.trim()) || cd;
|
|
36
|
-
j = e;
|
|
20
|
+
if (pat[i] === S) {
|
|
21
|
+
let j = i + S.length;
|
|
22
|
+
const isDouble = pat[j] === S;
|
|
23
|
+
if (isDouble) j++;
|
|
24
|
+
if (pat[j] === O) {
|
|
25
|
+
const [od, e1] = extractBlock(p, pat, j);
|
|
26
|
+
if (pat[e1] === O) {
|
|
27
|
+
const [cd, e2] = extractBlock(p, pat, e1);
|
|
28
|
+
let v = '', k = e2;
|
|
29
|
+
while (k < pat.length && /[A-Za-z0-9_]/.test(pat[k])) v += pat[k++];
|
|
30
|
+
if (v) {
|
|
31
|
+
t.push({ type: isDouble ? 'blockseq' : 'block', varName: v, open: unescapeDelim(od.trim()) || O, close: unescapeDelim(cd.trim()) || C });
|
|
32
|
+
i = k; continue;
|
|
33
|
+
}
|
|
37
34
|
}
|
|
38
|
-
t.push({ type: 'block', varName: v, open: od, close: cd });
|
|
39
|
-
i = j; continue;
|
|
40
35
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let j = i + S.length, v = '';
|
|
36
|
+
j = i + S.length;
|
|
37
|
+
let v = '';
|
|
44
38
|
while (j < pat.length && /[A-Za-z0-9_]/.test(pat[j])) v += pat[j++];
|
|
45
|
-
if (v) {
|
|
39
|
+
if (v) {
|
|
46
40
|
const optional = pat[j] === '?';
|
|
47
41
|
if (optional) j++;
|
|
48
|
-
t.push({ type: 'var', varName: v, optional });
|
|
49
|
-
i = j; continue;
|
|
42
|
+
t.push({ type: 'var', varName: v, optional });
|
|
43
|
+
i = j; continue;
|
|
50
44
|
}
|
|
51
45
|
t.push({ type: 'lit', value: S }); i += S.length; continue;
|
|
52
46
|
}
|
|
@@ -80,11 +74,9 @@ function matchPattern(p, src, tok, pos = 0) {
|
|
|
80
74
|
while (pos < src.length && /\s/.test(src[pos])) pos++;
|
|
81
75
|
const nx = findNext(tok, ti);
|
|
82
76
|
let v = '';
|
|
83
|
-
if (nx && nx.type === 'block') {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
} else if (nx && nx.type === 'lit') {
|
|
87
|
-
while (pos < src.length && !src.startsWith(nx.value, pos) && src[pos] !== '\n') v += src[pos++];
|
|
77
|
+
if (nx && (nx.type === 'block' || nx.type === 'lit')) {
|
|
78
|
+
const stop = nx.type === 'block' ? nx.open : nx.value;
|
|
79
|
+
while (pos < src.length && !src.startsWith(stop, pos) && src[pos] !== '\n') v += src[pos++];
|
|
88
80
|
v = v.trimEnd();
|
|
89
81
|
} else {
|
|
90
82
|
while (pos < src.length && !/\s/.test(src[pos])) v += src[pos++];
|
|
@@ -93,6 +85,18 @@ function matchPattern(p, src, tok, pos = 0) {
|
|
|
93
85
|
cap[p.symbols.sigil + t.varName] = v;
|
|
94
86
|
continue;
|
|
95
87
|
}
|
|
88
|
+
if (t.type === 'blockseq') {
|
|
89
|
+
let blocks = [];
|
|
90
|
+
while (pos < src.length && src.startsWith(t.open, pos)) {
|
|
91
|
+
const [c, e] = extractBlock(p, src, pos, t.open, t.close);
|
|
92
|
+
blocks.push(c);
|
|
93
|
+
pos = e;
|
|
94
|
+
while (pos < src.length && /\s/.test(src[pos])) pos++;
|
|
95
|
+
}
|
|
96
|
+
if (!blocks.length) return null;
|
|
97
|
+
cap[p.symbols.sigil + t.varName] = blocks.join(' ');
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
96
100
|
if (t.type === 'block') {
|
|
97
101
|
if (!src.startsWith(t.open, pos)) return null;
|
|
98
102
|
const [c, e] = extractBlock(p, src, pos, t.open, t.close);
|
|
@@ -151,18 +155,15 @@ function extractEvals(p, txt) {
|
|
|
151
155
|
const ev = [], S = p.symbols.sigil, O = p.symbols.open;
|
|
152
156
|
let i = 0, out = txt, off = 0;
|
|
153
157
|
while (i < txt.length) {
|
|
154
|
-
if (txt.substring(i, i + S.length) === S) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
out = out.substring(0, sp - off) + ph + out.substring(ep - off);
|
|
164
|
-
off += (ep - sp) - ph.length; i = ep; continue;
|
|
165
|
-
}
|
|
158
|
+
if (txt.substring(i, i + S.length) === S && txt.substring(i + S.length).startsWith(p.symbols.eval)) {
|
|
159
|
+
let j = i + S.length + p.symbols.eval.length;
|
|
160
|
+
while (j < txt.length && /\s/.test(txt[j])) j++;
|
|
161
|
+
if (j < txt.length && txt.substring(j, j + O.length) === O) {
|
|
162
|
+
const sp = i, bp = j, [c, ep] = extractBlock(p, txt, bp);
|
|
163
|
+
ev.push({ code: c, sp: sp - off, ep: ep - off });
|
|
164
|
+
const ph = `__E${ev.length - 1}__`;
|
|
165
|
+
out = out.substring(0, sp - off) + ph + out.substring(ep - off);
|
|
166
|
+
off += (ep - sp) - ph.length; i = ep; continue;
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
i++;
|
|
@@ -174,10 +175,12 @@ function applyEvals(p, txt, ev) {
|
|
|
174
175
|
let r = txt;
|
|
175
176
|
for (let i = ev.length - 1; i >= 0; i--) {
|
|
176
177
|
const ph = `__E${i}__`;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
try {
|
|
179
|
+
r = r.replace(ph, String(
|
|
180
|
+
Function("ctx", `"use strict";${ev[i].code}`).call(p, {})
|
|
181
|
+
));
|
|
182
|
+
}
|
|
183
|
+
catch (e) { r = r.replace(ph, "error: " + e.message); }
|
|
181
184
|
}
|
|
182
185
|
return r;
|
|
183
186
|
}
|
|
@@ -219,8 +222,8 @@ function unescapeDelim(s) {
|
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
export class Papagaio {
|
|
222
|
-
constructor(sigil = '$', open = '{', close = '}', pattern = 'pattern', evalKw = 'eval', blockKw = '
|
|
223
|
-
this.symbols = { pattern, open, close, sigil, eval: evalKw, block: blockKw, regex: regexKw };
|
|
225
|
+
constructor(sigil = '$', open = '{', close = '}', pattern = 'pattern', evalKw = 'eval', blockKw = 'recursive', regexKw = 'regex', blockseqKw = 'sequential') {
|
|
226
|
+
this.symbols = { pattern, open, close, sigil, eval: evalKw, block: blockKw, regex: regexKw, blockseq: blockseqKw };
|
|
224
227
|
this.content = "";
|
|
225
228
|
this.match = "";
|
|
226
229
|
}
|
package/tests/tests.json
CHANGED
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
{
|
|
16
16
|
"id": 3,
|
|
17
17
|
"name": "Block with custom delimiters",
|
|
18
|
-
"code": "$pattern {$
|
|
18
|
+
"code": "$pattern {${(}{)}content} {[$content]}\ndata (hello world)",
|
|
19
19
|
"expected": "data [hello world]"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"id": 4,
|
|
23
23
|
"name": "Block with multi-char delimiters",
|
|
24
|
-
"code": "$pattern {$
|
|
24
|
+
"code": "$pattern {${<<}{>>}code} {CODE[$code]}\n<<console.log()>>",
|
|
25
25
|
"expected": "CODE[console.log()]"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"id": 5,
|
|
29
29
|
"name": "Block with nested delimiters",
|
|
30
|
-
"code": "$pattern {$
|
|
30
|
+
"code": "$pattern {${<}{>}txt} {[$txt]}\nouter <middle <inner> content>",
|
|
31
31
|
"expected": "outer [middle <inner> content]"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
{
|
|
64
64
|
"id": 11,
|
|
65
65
|
"name": "Multiple blocks in one pattern",
|
|
66
|
-
"code": "$pattern {$
|
|
66
|
+
"code": "$pattern {${(}{)}a and ${[}{]}b} {$a|$b}\n(first) and [second]",
|
|
67
67
|
"expected": "first|second"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
{
|
|
88
88
|
"id": 15,
|
|
89
89
|
"name": "Block with angle brackets",
|
|
90
|
-
"code": "$pattern {$
|
|
90
|
+
"code": "$pattern {${<}{>}inner} {WRAPPED[$inner]}\n<content>",
|
|
91
91
|
"expected": "WRAPPED[content]"
|
|
92
92
|
},
|
|
93
93
|
{
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
{
|
|
106
106
|
"id": 18,
|
|
107
107
|
"name": "Block capturing with square brackets",
|
|
108
|
-
"code": "$pattern {$
|
|
108
|
+
"code": "$pattern {${[}{]}arr} {ARRAY[$arr]}\n[1, 2, 3]",
|
|
109
109
|
"expected": "ARRAY[1, 2, 3]"
|
|
110
110
|
},
|
|
111
111
|
{
|
|
@@ -129,31 +129,31 @@
|
|
|
129
129
|
{
|
|
130
130
|
"id": 22,
|
|
131
131
|
"name": "Block delimiter balancing - parentheses",
|
|
132
|
-
"code": "$pattern {$
|
|
132
|
+
"code": "$pattern {${(}{)}code} {RESULT[$code]}\n(a (b (c) d) e)",
|
|
133
133
|
"expected": "RESULT[a (b (c) d) e]"
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
"id": 23,
|
|
137
137
|
"name": "Block delimiter balancing - angle brackets",
|
|
138
|
-
"code": "$pattern {$
|
|
138
|
+
"code": "$pattern {${<}{>}inner} {X[$inner]}\n<outer <middle <deep>> more>",
|
|
139
139
|
"expected": "X[outer <middle <deep>> more]"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
"id": 24,
|
|
143
143
|
"name": "Block delimiter balancing - square brackets",
|
|
144
|
-
"code": "$pattern {$
|
|
144
|
+
"code": "$pattern {${[}{]}data} {DATA[$data]}\n[outer [inner [deep]] more]",
|
|
145
145
|
"expected": "DATA[outer [inner [deep]] more]"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"id": 25,
|
|
149
149
|
"name": "Block delimiter balancing - curly braces",
|
|
150
|
-
"code": "$pattern {$
|
|
150
|
+
"code": "$pattern {${}{}obj} {OBJ[$obj]}\n{key: {nested: {value}}}",
|
|
151
151
|
"expected": "OBJ[key: {nested: {value}}]"
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
"id": 26,
|
|
155
155
|
"name": "Multiple nested delimiters in same pattern",
|
|
156
|
-
"code": "$pattern {$
|
|
156
|
+
"code": "$pattern {${(}{)}a ${[}{]}b} {[$a|$b]}\n(test1) [test2]",
|
|
157
157
|
"expected": "[test1|test2]"
|
|
158
158
|
},
|
|
159
159
|
{
|
|
@@ -165,25 +165,25 @@
|
|
|
165
165
|
{
|
|
166
166
|
"id": 28,
|
|
167
167
|
"name": "Block with quotes as delimiters",
|
|
168
|
-
"code": "$pattern {$
|
|
168
|
+
"code": "$pattern {${\"}{\"}str} {STRING[$str]}\n\"hello world\"",
|
|
169
169
|
"expected": "STRING[hello world]"
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
"id": 29,
|
|
173
173
|
"name": "Block with pipe delimiters",
|
|
174
|
-
"code": "$pattern {$
|
|
174
|
+
"code": "$pattern {${|}{|}val} {PIPE[$val]}\n|content here|",
|
|
175
175
|
"expected": "PIPE[content here]"
|
|
176
176
|
},
|
|
177
177
|
{
|
|
178
178
|
"id": 30,
|
|
179
179
|
"name": "Deeply nested block delimiters",
|
|
180
|
-
"code": "$pattern {$
|
|
180
|
+
"code": "$pattern {${<}{>}deep} {[$deep]}\n<a <b <c <d> c> b> a>",
|
|
181
181
|
"expected": "[a <b <c <d> c> b> a]"
|
|
182
182
|
},
|
|
183
183
|
{
|
|
184
184
|
"id": 31,
|
|
185
185
|
"name": "Two sequential patterns",
|
|
186
|
-
"code": "$pattern {$
|
|
186
|
+
"code": "$pattern {${(}{)}content} {BLOCK [$content]}\n$pattern {BLOCK $x} {RESULT: $x}\n(inner data)",
|
|
187
187
|
"expected": "RESULT: [inner data]"
|
|
188
188
|
},
|
|
189
189
|
{
|
|
@@ -195,13 +195,13 @@
|
|
|
195
195
|
{
|
|
196
196
|
"id": 33,
|
|
197
197
|
"name": "Block delimiter edge case - empty content",
|
|
198
|
-
"code": "$pattern {$
|
|
198
|
+
"code": "$pattern {${(}{)}empty} {EMPTY[$empty]}\n()",
|
|
199
199
|
"expected": "EMPTY[]"
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
"id": 34,
|
|
203
203
|
"name": "Multi-char delimiters with nesting",
|
|
204
|
-
"code": "$pattern {$
|
|
204
|
+
"code": "$pattern {${<<}{>>}code} {CODE[$code]}\n<<outer <<inner>> more>>",
|
|
205
205
|
"expected": "CODE[outer <<inner>> more]"
|
|
206
206
|
},
|
|
207
207
|
{
|