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 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: "block", // block keyword
26
- regex: "regex" // regex keyword
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 $block content {(}{)}} {$name: $content}
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
- Capture content between delimiters with full nesting support.
130
+ Papagaio supports two types of block capture: **nested** and **adjacent**.
130
131
 
131
- ### Syntax
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
- $block varName {open}{close}
138
+ ${opening_delimiter}{closing_delimiter}varName
134
139
  ```
135
140
 
136
- ### Basic Example
141
+ #### Examples
142
+
143
+ **Basic Recursive Block:**
137
144
  ```
138
- $pattern {$name $block content {(}{)}} {[$content]}
145
+ $pattern {$name ${(}{)}content} {[$content]}
139
146
  data (hello world)
140
147
  ```
141
148
  Output: `[hello world]`
142
149
 
143
- ### Custom Delimiters
150
+ **Custom Delimiters:**
144
151
  ```
145
- $pattern {$block data {<<}{>>}} {DATA: $data}
152
+ $pattern {${<<}{>>}data} {DATA: $data}
146
153
  <<json stuff>>
147
154
  ```
148
155
  Output: `DATA: json stuff`
149
156
 
150
- ### Multi-Character Delimiters
157
+ **Multi-Character Delimiters:**
151
158
  ```
152
- $pattern {$block code {```}{```}} {<pre>$code</pre>}
159
+ $pattern {${```}{```}code} {<pre>$code</pre>}
153
160
  ```markdown
154
161
  # Title
155
162
  ```
156
163
  Output: `<pre># Title</pre>`
157
164
 
158
- ### Multiple Blocks
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
- $pattern {$block a {(}{)}, $block b {[}{]}} {$a|$b}
161
- (first), [second]
186
+ $${opening_delimiter}{closing_delimiter}varName
162
187
  ```
163
- Output: `first|second`
164
188
 
165
- ### Nested Blocks
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 {$block outer {(}{)}} {[$outer]}
168
- (outer (inner))
207
+ $pattern {$${}{}data} {Result: $data}
208
+ {a}{b}{c}
169
209
  ```
170
- Output: `[outer (inner)]`
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<<<$block data {<<}{>>}>>> <<<$data>>>
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 $block cond {(}{)} then $block yes {[}{]} else $block no {<}{>}} {
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 {$block n {o}{c}} {$n} # block capture with custom delimiters
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
@@ -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(papagaio)} aaaaaaaaaaaaaaaaaaaaaaaaaaaa}
10
+ $pattern {abc} {$eval{console.log(this)} aaaaaaaaaaaaaaaaaaaaaaaaaaaa}
11
11
  abc
12
12
  </script>
13
13
  <body>
@@ -13,7 +13,7 @@ $eval{
13
13
  return ""
14
14
  }
15
15
 
16
- $pattern {export function $name $block params{(}{)}:$rets $block content{}{}} {
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 $block params{(}{)}:$rets $block content{}{}} {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "papagaio",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "easy yet powerful preprocessor",
5
5
  "main": "src/papagaio.js",
6
6
  "type": "module",
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.startsWith(S + p.symbols.block, i)) {
21
- let j = i + S.length + p.symbols.block.length;
22
- while (j < pat.length && /\s/.test(pat[j])) j++;
23
- let v = '';
24
- while (j < pat.length && /[A-Za-z0-9_]/.test(pat[j])) v += pat[j++];
25
- if (v) {
26
- while (j < pat.length && /\s/.test(pat[j])) j++;
27
- let od = O, cd = p.symbols.close;
28
- if (pat[j] === O) {
29
- const [c, e] = extractBlock(p, pat, j);
30
- od = unescapeDelim(c.trim()) || O;
31
- j = e; while (j < pat.length && /\s/.test(pat[j])) j++;
32
- }
33
- if (pat[j] === O) {
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
- if (pat[i] === S) {
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
- while (pos < src.length && !src.startsWith(nx.open, pos) && src[pos] !== '\n') v += src[pos++];
85
- v = v.trimEnd();
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
- const rest = txt.substring(i + S.length);
156
- if (rest.startsWith(p.symbols.eval)) {
157
- let j = i + S.length + p.symbols.eval.length;
158
- while (j < txt.length && /\s/.test(txt[j])) j++;
159
- if (j < txt.length && txt.substring(j, j + O.length) === O) {
160
- const sp = i, bp = j, [c, ep] = extractBlock(p, txt, bp);
161
- ev.push({ code: c, sp: sp - off, ep: ep - off });
162
- const ph = `__E${ev.length - 1}__`;
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
- let res;
178
- try { res = String(Function("papagaio", "ctx", `"use strict";return(function(){${ev[i].code}})();`)(p, {})); }
179
- catch (e) { res = "error: " + e.message; }
180
- r = r.replace(ph, res);
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 = 'block', regexKw = 'regex') {
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 {$block content {(}{)}} {[$content]}\ndata (hello world)",
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 {$block code {<<}{>>}} {CODE[$code]}\n<<console.log()>>",
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 {$block txt {<}{>}} {[$txt]}\nouter <middle <inner> content>",
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 {$block a {(}{)} and $block b {[}{]}} {$a|$b}\n(first) and [second]",
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 {$block inner {<}{>}} {WRAPPED[$inner]}\n<content>",
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 {$block arr {[}{]}} {ARRAY[$arr]}\n[1, 2, 3]",
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 {$block code {(}{)}} {RESULT[$code]}\n(a (b (c) d) e)",
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 {$block inner {<}{>}} {X[$inner]}\n<outer <middle <deep>> more>",
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 {$block data {[}{]}} {DATA[$data]}\n[outer [inner [deep]] more]",
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 {$block obj {}{}} {OBJ[$obj]}\n{key: {nested: {value}}}",
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 {$block a {(}{)} $block b {[}{]}} {[$a|$b]}\n(test1) [test2]",
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 {$block str {\"}{\"}} {STRING[$str]}\n\"hello world\"",
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 {$block val {|}{|}} {PIPE[$val]}\n|content here|",
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 {$block deep {<}{>}} {[$deep]}\n<a <b <c <d> c> b> a>",
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 {$block content {(}{)}} {BLOCK [$content]}\n$pattern {BLOCK $x} {RESULT: $x}\n(inner data)",
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 {$block empty {(}{)}} {EMPTY[$empty]}\n()",
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 {$block code {<<}{>>}} {CODE[$code]}\n<<outer <<inner>> more>>",
204
+ "code": "$pattern {${<<}{>>}code} {CODE[$code]}\n<<outer <<inner>> more>>",
205
205
  "expected": "CODE[outer <<inner>> more]"
206
206
  },
207
207
  {