papagaio 0.1.7 → 0.1.9

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.
Files changed (4) hide show
  1. package/README.md +639 -158
  2. package/cli.js +4 -1
  3. package/package.json +1 -2
  4. package/papagaio.js +32 -161
package/README.md CHANGED
@@ -1,336 +1,817 @@
1
- # Papagaio Preprocessor
1
+ # Papagaio
2
2
 
3
- Papagaio is a lightweight, class‑based text preprocessor designed to support pattern rewriting, macro expansion, scoped transforms, and embedded JavaScript evaluation. It is engineered to be predictable, recursion‑safe, and easily embeddable in any JavaScript runtime.
3
+ Papagaio is a flexible text preprocessor. It allows defining patterns, context blocks, dynamic variable capture, and runtime transformations directly within text. It is ideal for templates, macros, DSLs, or any structured text manipulation.
4
+
5
+ The JavaScript API is minimal: just instantiate and call `process(input)`. Everything else happens in the text itself.
6
+
7
+ ---
8
+
9
+ # Core Concepts
10
+
11
+ Papagaio operates with four central mechanisms:
12
+
13
+ 1. **pattern{match}{replace}**
14
+ Defines transformation rules. Can capture arbitrary content from the input.
15
+
16
+ 2. **context{...}**
17
+ Processes its content recursively before reinserting it into the output.
18
+
19
+ 3. **Sigils (default `$`)**
20
+ Introduce variables, special operations, optional spaces, and greedy captures.
21
+
22
+ 4. **Configurable delimiters**
23
+ Define which pairs of characters are recognized as block boundaries (`{}`, `[]`, `()`, etc.).
24
+
25
+ The engine processes text in layers: it detects patterns and contexts, applies transformations, and repeats until the text stabilizes.
4
26
 
5
27
  ---
6
28
 
7
- ## Overview
29
+ # 1. Patterns
8
30
 
9
- Papagaio processes an input string through a deterministic multi‑stage pipeline:
31
+ ```
10
32
 
11
- 1. **Scope blocks** (recursive processing)
12
- 2. **Eval blocks** (JS execution)
13
- 3. **Macro collection**
14
- 4. **Pattern collection**
15
- 5. **Pattern application**
16
- 6. **Macro expansion**
33
+ pattern{MATCH}{REPLACE}
17
34
 
18
- The engine runs until it reaches a fixed point or hits the recursion limit.
35
+ ```
19
36
 
20
- Papagaio supports **nested delimiters**, **custom sigils**, and **configurable keywords**.
37
+ - **MATCH**: the string or structure to detect.
38
+ - **REPLACE**: the replacement string, which may include captured variables.
21
39
 
22
40
  ---
23
41
 
24
- ## Installation
42
+ ## 1.1 Simple variables
43
+
44
+ ```
45
+
46
+ pattern{Hello $name}{Hi $name}
47
+ Hello John
48
+
49
+ ```
50
+
51
+ Output:
52
+
53
+ ```
54
+
55
+ Hi John
56
+
57
+ ```
58
+
59
+ `$name` captures a single token (non-whitespace).
60
+
61
+ ---
62
+
63
+ ## 1.2 Greedy variables: `$var...TOKEN`
64
+
65
+ Captures everything until it encounters `TOKEN`.
66
+
67
+ ```
68
+
69
+ pattern{start $x...end}{X=$x}
70
+ start abc def end
71
+
72
+ ```
73
+
74
+ Output:
75
+
76
+ ```
77
+
78
+ X=abc def
79
+
80
+ ```
81
+
82
+ `TOKEN` can include spaces, and you can use `$$` for zero or more optional spaces.
83
+
84
+ ```
85
+
86
+ pattern{A $y...B$$C}{Y=$y}
87
+ A hello world B C
88
+
89
+ ```
90
+
91
+ Also matches:
92
+
93
+ ```
25
94
 
26
- Papagaio ships as a standalone ES module.
95
+ A hello world BC
27
96
 
28
- ```js
29
- import { Papagaio } from "./papagaio.js";
30
97
  ```
31
98
 
32
99
  ---
33
100
 
34
- ## Basic Usage
101
+ ## 1.3 Balanced variables: `<$var>`
102
+
103
+ Captures content inside delimiters, respecting nested structures.
104
+
105
+ ```
106
+
107
+ pattern{<$c>}{C=$c}
108
+ <one {two} three>
109
+
110
+ ```
111
+
112
+ Output:
113
+
114
+ ```
115
+
116
+ C=one {two} three
35
117
 
36
- ```js
37
- const p = new Papagaio();
38
- const output = p.process("pattern {a} {b} a a a");
39
- console.log(output); // "b b b"
40
118
  ```
41
119
 
42
120
  ---
43
121
 
44
- ## Core Features
122
+ ## 1.4 Multiple captures
123
+
124
+ ```
125
+
126
+ pattern{$a + $b}{sum($a,$b)}
127
+ 2 + 3
45
128
 
46
- ### 1. Pattern Blocks
129
+ ```
47
130
 
48
- Patterns rewrite text using a match → replacement structure:
131
+ Output:
49
132
 
50
133
  ```
51
- pattern { MATCH } { REPLACEMENT }
134
+
135
+ sum(2,3)
136
+
52
137
  ```
53
138
 
54
- Patterns are collected once per iteration and applied globally.
139
+ ---
140
+
141
+ ## 1.5 Special substitutions in REPLACE
142
+
143
+ - `$pre` — text before the match
144
+ - `$post` — text after the match
145
+ - `$match` — the raw matched content
146
+ - `$unique` — generates unique identifiers
147
+ - `$eval{JS}` — executes JavaScript and substitutes the return value
55
148
 
56
149
  Example:
57
150
 
58
151
  ```
59
- pattern {hello} {hi}
60
- hello world
152
+
153
+ pattern{calc $num}{value=$eval{return $num*2;} }
154
+ calc 21
155
+
61
156
  ```
62
157
 
63
158
  Output:
64
159
 
65
160
  ```
66
- hi world
161
+
162
+ value=42
163
+
67
164
  ```
68
165
 
69
- #### Variables
166
+ ---
167
+
168
+ ## 1.6 Clearing the content with `$clear`
70
169
 
71
- Variables use the configured **sigil** (default `$`).
170
+ If `$clear` appears in REPLACE, the entire current text is replaced with the latest result.
72
171
 
73
172
  ```
74
- pattern {say $x} {[$x]}
75
- say hello
173
+
174
+ pattern{reset}{RESULT$clear}
175
+ x y z reset aaa
176
+
76
177
  ```
77
178
 
78
- → `[hello]`
179
+ Output:
79
180
 
80
- #### Balanced Block Variables
181
+ ```
81
182
 
82
- Papagaio supports deep matching of balanced blocks:
183
+ RESULT
83
184
 
84
185
  ```
85
- pattern {($x)} {[BLOCK:$x]}
86
- (do something)
186
+
187
+ ---
188
+
189
+ ## 1.7 Recursive patterns
190
+
191
+ Papagaio applies patterns until text stabilizes:
192
+
87
193
  ```
88
194
 
89
- → `[BLOCK:do something]`
195
+ pattern{a}{b}
196
+ pattern{b}{c}
197
+ pattern{c}{stop}
198
+ a
90
199
 
91
- #### Spread Variables
200
+ ```
92
201
 
93
- Spread variables capture until a terminating token:
202
+ Output:
94
203
 
95
204
  ```
96
- pattern {from $x...to} {$x}
97
- from A B C to
205
+
206
+ stop
207
+
98
208
  ```
99
209
 
100
- → `A B C`
210
+ ---
101
211
 
102
- #### Metavariables
212
+ # 2. Context
103
213
 
104
- Papagaio provides special `$keywords` inside replacements:
214
+ Blocks `context{}` process their content recursively.
105
215
 
106
- | Variable | Meaning |
107
- | --------- | ------------------------------------------ |
108
- | `$match` | Full matched text |
109
- | `$pre` | Text before match |
110
- | `$post` | Text after match |
111
- | `$unique` | Auto‑increment unique token |
112
- | `$$` | Whitespace wildcard in patterns |
113
- | `$clear` | Triggers clear‑rewrite of last replacement |
216
+ ```
114
217
 
115
- Example:
218
+ context{
219
+ pattern{Hi $x}{Hello $x}
220
+ Hi Alice
221
+ }
222
+
223
+ ```
224
+
225
+ Output:
116
226
 
117
227
  ```
118
- pattern {x} {$pre[${unique}]$post}
119
- abcxdef
228
+
229
+ Hello Alice
230
+
120
231
  ```
121
232
 
122
233
  ---
123
234
 
124
- ### 2. Macro Blocks
235
+ ## 2.1 Empty context
236
+
237
+ ```
238
+
239
+ before context{} after
240
+
241
+ ```
125
242
 
126
- Macros behave like simple template functions.
243
+ Output:
127
244
 
128
245
  ```
129
- macro name { BODY }
130
- name(arg1, arg2)
246
+
247
+ before after
248
+
131
249
  ```
132
250
 
133
- Example:
251
+ ---
252
+
253
+ ## 2.2 Nested contexts
134
254
 
135
255
  ```
136
- macro wrap {[$1][$2]}
137
- wrap(a, b)
256
+
257
+ context{
258
+ pattern{X}{$unique}
259
+ context{
260
+ X
261
+ X
262
+ }
263
+ }
264
+
138
265
  ```
139
266
 
140
- `[a][b]`
267
+ Generates two different unique IDs.
141
268
 
142
- #### Argument Mapping
269
+ ---
143
270
 
144
- Arguments map to `$1`, `$2`, … automatically. `$0` expands to the macro name.
271
+ ## 2.3 Sandbox behavior
145
272
 
146
- Example:
273
+ Patterns defined inside a context do not affect outer text.
147
274
 
148
275
  ```
149
- macro tag {<$0 $1>}
150
- tag(title)
276
+
277
+ context{
278
+ pattern{A}{1}
279
+ A
280
+ }
281
+ A
282
+
283
+ ```
284
+
285
+ Output:
286
+
151
287
  ```
152
288
 
153
- → `<tag title>`
289
+ 1
290
+ A
291
+
292
+ ```
154
293
 
155
294
  ---
156
295
 
157
- ### 3. Eval Blocks
296
+ # 3. Custom delimiters
158
297
 
159
- Executes embedded JavaScript and returns the result as a string.
298
+ `delimiters` define block boundaries. Defaults: `{}`, `[]`, `()`.
299
+
300
+ You can change them at runtime:
160
301
 
161
302
  ```
162
- eval { return 3 + 7 }
303
+
304
+ pap.delimiters = [["<", ">"], ["{", "}"]];
305
+
163
306
  ```
164
307
 
165
- `10`
308
+ ### Example: XML-style parsing
166
309
 
167
- Eval executes inside a strict IIFE.
310
+ ```
168
311
 
169
- You may access:
312
+ pap.delimiters = [["<", ">"]];
170
313
 
171
- * `papagaio` the processor instance
172
- * `ctx` → an empty object for temporary state
314
+ pattern{<tag $x>}{TAG=$x} <tag content>
173
315
 
174
- Example:
316
+ ```
317
+
318
+ Output:
319
+
320
+ ```
321
+
322
+ TAG=content
323
+
324
+ ```
325
+
326
+ ---
327
+
328
+ # 4. Custom sigil
329
+
330
+ Default: `$`. Can be changed:
331
+
332
+ ```
333
+
334
+ pap.sigil = "@";
335
+ pattern{hello @x}{H=@x}
336
+ hello world
337
+
338
+ ```
339
+
340
+ Output:
341
+
342
+ ```
343
+
344
+ H=world
345
+
346
+ ```
347
+
348
+ ---
349
+
350
+ # 5. Custom keywords
351
+
352
+ `pattern` and `context` words can be redefined:
353
+
354
+ ```
355
+
356
+ pap.keywords.pattern = "macro";
357
+ pap.keywords.context = "scope";
358
+
359
+ ```
360
+ ```
361
+
362
+ macro{Hello $x}{Hi $x}
363
+ Hello World
364
+
365
+ ```
366
+
367
+ Output:
368
+
369
+ ```
370
+
371
+ Hi World
372
+
373
+ ```
374
+
375
+ ---
376
+
377
+ # 6. Advanced hacks
378
+
379
+ ## 6.1 Dynamic delimiter changes mid-process
380
+
381
+ ```
382
+
383
+ pattern{setdelim}{$eval{ pap.delimiters=[["<",">"]]; return "";} }
384
+ setdelim <hello>
385
+
386
+ ```
387
+
388
+ ---
389
+
390
+ ## 6.2 Custom syntax macros
391
+
392
+ ```
393
+
394
+ pap.keywords.pattern = "::";
395
+ pap.sigil = "%";
396
+
397
+ ::{hello %x}{HELLO %x}
398
+ hello world
399
+
400
+ ```
401
+
402
+ Output:
403
+
404
+ ```
405
+
406
+ HELLO world
407
+
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 6.3 Mini DSL creation
413
+
414
+ ```
415
+
416
+ pattern{IF $cond THEN $body}{if ($cond) { $body }}
417
+ pattern{PRINT $x}{console.log($x)}
418
+
419
+ IF x > 10 THEN PRINT x
420
+
421
+ ```
422
+
423
+ ---
424
+
425
+ ## 6.4 Template engine
426
+
427
+ ```
428
+
429
+ pattern{{{$var}}}{$var}
430
+ Hello {{{$user}}}
431
+
432
+ ```
433
+
434
+ ---
435
+
436
+ ## 6.5 Multiline captures
437
+
438
+ ```
439
+
440
+ pattern{BEGIN $x...END}{[$x]}
441
+ BEGIN
442
+ multi
443
+ line
444
+ text
445
+ END
446
+
447
+ ```
448
+
449
+ ---
450
+
451
+ ## 6.6 $pre and $post example
452
+
453
+ ```
454
+
455
+ pattern{<$x>}{($pre|$x|$post)}
456
+ A <mid> B
175
457
 
176
458
  ```
177
- pattern {sum $a $b} {eval { return Number($a) + Number($b) }}
178
- sum 2 8
459
+
460
+ Output:
461
+
462
+ ```
463
+
464
+ A (A | mid | B) B
465
+
179
466
  ```
180
467
 
181
- → `10`
468
+ ---
469
+
470
+ ## 6.7 Full rewrite via $clear
471
+
472
+ ```
473
+
474
+ pattern{main}{program initialized$clear}
475
+ xxx main yyy
476
+
477
+ ```
182
478
 
183
479
  ---
184
480
 
185
- ### 4. Scope Blocks
481
+ # 7. Depth-isolated patterns
186
482
 
187
- Scope blocks create isolated processing regions using the same pipeline.
483
+ Inner patterns are ignored until outer patterns resolve:
188
484
 
189
485
  ```
190
- scope {
191
- pattern {x} {y}
192
- x x x
486
+
487
+ pattern{A}{1}
488
+ pattern{
489
+ B
490
+ }{
491
+ context{
492
+ pattern{C}{2}
493
+ C
494
+ }
193
495
  }
496
+ B
497
+
498
+ ```
499
+
500
+ ---
501
+
502
+ # 8. Complete Examples
503
+
504
+ ### Example 1: Components DSL
505
+
194
506
  ```
195
507
 
196
- `y y y`
508
+ pattern{component $name { $body...} }
509
+ {
510
+ function $name(){return `$body`;}
511
+ }
197
512
 
198
- Scopes do not leak macros or patterns to the outside.
513
+ component Button { <button>click</button>
514
+ }
515
+
516
+ Button
517
+
518
+ ```
199
519
 
200
520
  ---
201
521
 
202
- ## Delimiters
522
+ ### Example 2: Sequential expansion with $unique
523
+
524
+ ```
525
+
526
+ pattern{ID}{id_$unique}
527
+ X: ID, Y: ID, Z: ID
203
528
 
204
- Papagaio supports configurable opening/closing pairs.
529
+ ```
530
+
531
+ ---
532
+
533
+ ### Example 3: Markdown preprocessor
205
534
 
206
- ```js
207
- p.delimiters = [["{", "}"], ["(", ")"]];
208
535
  ```
209
536
 
210
- Balanced variable matching works across all registered delimiter pairs.
537
+ pattern{# $t}{<h1>$t</h1>}
538
+ pattern{## $t}{<h2>$t</h2>}
539
+
540
+ # Title
541
+
542
+ ## Subtitle
543
+
544
+ ```
211
545
 
212
546
  ---
213
547
 
214
- ## Recursion Model
548
+ ### Example 4: Embedded interpreter
549
+
550
+ ```
215
551
 
216
- Papagaio runs a fixed‑point loop:
552
+ pattern{calc $x + $y}{ $eval{return Number($x)+Number($y);} }
553
+ calc 2 + 5
217
554
 
218
- 1. Process scopes
219
- 2. Process eval blocks
220
- 3. Collect macros
221
- 4. Collect top‑level patterns
222
- 5. Apply patterns
223
- 6. Expand macros
555
+ ```
224
556
 
225
- If any Papagaio keyword remains in the output, it repeats.
557
+ ---
226
558
 
227
- You can configure the iteration cap:
559
+ ### Example 5: Selective removal with context
560
+
561
+ ```
562
+
563
+ context{
564
+ pattern{debug $x}{}
565
+ debug remove this
566
+ }
567
+ debug keep
568
+
569
+ ```
570
+
571
+ Output:
572
+
573
+ ```
574
+
575
+ debug keep
228
576
 
229
- ```js
230
- p.maxRecursion = 256;
231
577
  ```
232
578
 
233
579
  ---
234
580
 
235
- ## Error Handling
581
+ # 9. Public properties
236
582
 
237
- Eval blocks are wrapped in a try/catch. Exceptions produce empty output.
583
+ - `maxRecursion`
584
+ - `delimiters`
585
+ - `sigil`
586
+ - `keywords.pattern`
587
+ - `keywords.context`
588
+ - `content`
238
589
 
239
- Pattern/macro recursion halts upon reaching `maxRecursion`.
590
+ All mutable at runtime.
240
591
 
241
592
  ---
242
593
 
243
- ## Advanced Examples
594
+ # 10. Summary
595
+
596
+ Papagaio is a powerful text transformation engine. By combining recursive patterns, isolated contexts, configurable sigils, mutable delimiters, and `$eval`, you can build full DSLs, templating engines, mini-transpilers, or entirely new macro systems directly within text.
597
+
598
+ Perfect. Let’s expand the README with **advanced topics** that push Papagaio into full meta-programming territory. I’ll add new sections on **self-modifying patterns, hybrid pipelines, self-referential patterns, Lisp-style macros, and advanced transpiler flows**. Everything stays text-focused; JavaScript API calls remain just `process(text)`.
599
+
600
+ ---
601
+
602
+ # 11. Self-Modifying Patterns
603
+
604
+ Papagaio patterns can modify themselves or other patterns at runtime using `$eval` and `$unique`. This allows dynamic generation of rules inside the same preprocessing pass.
244
605
 
245
- ### Nested Macros
606
+ ### Example: generating numbered variables
246
607
 
247
608
  ```
248
- macro A {($1)}
249
- macro B {A($1)}
250
- B(hello)
609
+
610
+ pattern{define $x}{pattern{$x}{$x_$unique}}
611
+ define foo
612
+ foo
613
+ foo
614
+
251
615
  ```
252
616
 
253
- → `(hello)`
617
+ Output:
618
+
619
+ ```
254
620
 
255
- ### Dynamic Rewriting with `$unique`
621
+ foo_u0
622
+ foo_u1
256
623
 
257
624
  ```
258
- pattern {node} {id_$unique}
259
- node node node
625
+
626
+ The first line defines a new pattern dynamically; subsequent uses expand into unique identifiers.
627
+
628
+ ---
629
+
630
+ # 12. Hybrid Parsing Pipelines
631
+
632
+ You can combine multiple processing passes with different delimiters, sigils, and keywords.
633
+
634
+ ### Example: XML and Markdown pipeline
635
+
260
636
  ```
261
637
 
262
- `id_0 id_1 id_2`
638
+ pap.delimiters = [["<", ">"], ["{", "}"]];
639
+ pap.sigil = "$";
263
640
 
264
- ### Working with Pre/Post Context
641
+ pattern{<bold $x>}{**$x**}
642
+ pattern{# $t}{<h1>$t</h1>}
265
643
 
266
644
  ```
267
- pattern {x} {$pre|X|$post}
268
- a x b
645
+
646
+ Text:
647
+
269
648
  ```
270
649
 
271
- `a |X| b`
650
+ # Title
651
+
652
+ <bold text>
653
+ ```
272
654
 
273
- ### Spread Matching
655
+ Output:
274
656
 
275
657
  ```
276
- pattern {let $name = $v...;} {[decl $name $v]}
277
- let x = 1 + 2 + 3;
658
+ <h1>Title</h1>
659
+ **text**
278
660
  ```
279
661
 
280
- `[decl x 1 + 2 + 3]`
662
+ This allows building multi-layer pre-processing pipelines where each pass targets different syntax conventions.
281
663
 
282
664
  ---
283
665
 
284
- ## API Reference
666
+ # 13. Self-Referential Patterns
285
667
 
286
- ### Class: `Papagaio`
668
+ Patterns can reference themselves or other patterns recursively.
287
669
 
288
- #### `process(input: string): string`
670
+ ### Example: expanding repeated lists
289
671
 
290
- Runs the full pipeline and returns the transformed text.
672
+ ```
673
+ pattern{LIST $x}{ITEM $x LIST $x}
674
+ pattern{LIST $x}{ITEM $x}
675
+ LIST A
676
+ ```
291
677
 
292
- #### `delimiters: Array<[string, string]>`
678
+ Output:
293
679
 
294
- List of opening/closing delimiter pairs.
680
+ ```
681
+ ITEM A
682
+ ```
295
683
 
296
- #### `sigil: string`
684
+ Patterns resolve recursively until the text stabilizes. Use `maxRecursion` to prevent infinite loops.
297
685
 
298
- Variable prefix. Default `$`.
686
+ ---
299
687
 
300
- #### `keywords`
688
+ # 14. Lisp-Style Macro Systems
301
689
 
302
- Keyword configuration:
690
+ Papagaio can emulate a Lisp-like macro expansion engine using contexts and dynamic patterns.
303
691
 
304
- * `pattern`
305
- * `macro`
306
- * `eval`
307
- * `scope`
692
+ ### Example: defining macros inside contexts
308
693
 
309
- #### Internal State
694
+ ```
695
+ context{
696
+ pattern{defmacro $name { $body... }}{pattern{$name}{$body}}
697
+ defmacro greet {Hello $1}
698
+ greet John
699
+ }
700
+ ```
701
+
702
+ Output:
310
703
 
311
- For debugging only:
704
+ ```
705
+ Hello John
706
+ ```
312
707
 
313
- * `content` latest processed text
314
- * `#matchContent`
315
- * `#scopeContent`
316
- * `#evalContent`
708
+ Inside a `context`, macro definitions are local and can create arbitrary code expansions dynamically.
317
709
 
318
710
  ---
319
711
 
320
- ## Embedding
712
+ # 15. Advanced Transpiler Flows
321
713
 
322
- Papagaio is pure JS and safe to embed in build pipelines, CLIs, game engines, or runtime scripting layers.
714
+ Papagaio can be used to build mini-transpilers by chaining pattern expansions and contexts.
323
715
 
324
- Example minimal CLI wrapper:
716
+ ### Example: pseudo-language to JavaScript
325
717
 
326
- ```js
327
- #!/usr/bin/env node
328
- const fs = require("fs");
329
- const { Papagaio } = require("./papagaio");
718
+ ```
719
+ context{
720
+ pattern{PRINT $x}{console.log($x);}
721
+ pattern{IF $cond THEN $body}{if ($cond) { $body }}
722
+ PRINT 42
723
+ IF x > 10 THEN PRINT x
724
+ }
725
+ ```
726
+
727
+ Output:
330
728
 
331
- const input = fs.readFileSync(0, "utf8");
332
- const out = new Papagaio().process(input);
333
- process.stdout.write(out);
334
729
  ```
730
+ console.log(42);
731
+ if (x > 10) { console.log(x); }
732
+ ```
733
+
734
+ You can extend this by defining multiple layers of contexts, dynamically switching delimiters, or even generating patterns on the fly.
735
+
736
+ ---
737
+
738
+ # 16. Dynamic Runtime Hacks
739
+
740
+ ### 16.1 Change delimiters mid-process
741
+
742
+ ```
743
+ pattern{switch}{ $eval{ pap.delimiters=[["<",">"]]; return ""; } }
744
+ switch
745
+ <hello>
746
+ ```
747
+
748
+ Output:
749
+
750
+ ```
751
+ hello
752
+ ```
753
+
754
+ ### 16.2 Dynamic sigil change
755
+
756
+ ```
757
+ pattern{sigil}{ $eval{ pap.sigil="@"; return ""; } }
758
+ sigil
759
+ @var
760
+ ```
761
+
762
+ Output:
763
+
764
+ ```
765
+ var
766
+ ```
767
+
768
+ ### 16.3 Keywords swapping
769
+
770
+ ```
771
+ pattern{switch_keywords}{ $eval{ pap.keywords.pattern="macro"; pap.keywords.context="scope"; return ""; } }
772
+ switch_keywords
773
+ macro{X}{Y}
774
+ ```
775
+
776
+ Output:
777
+
778
+ ```
779
+ Y
780
+ ```
781
+
782
+ ---
783
+
784
+ # 17. Meta-Programming Examples
785
+
786
+ * **Dynamic template engines**: generate arbitrary nested templates inside contexts.
787
+ * **Self-expanding DSLs**: macros that define other macros.
788
+ * **Text-based code generation**: precompile repetitive boilerplate using `$unique` and `$eval`.
789
+ * **Hybrid syntaxes**: combine HTML, Markdown, custom DSL, and other syntaxes in a single pipeline.
790
+ * **Sandboxed rule execution**: define rules inside a context that never leak to the global scope.
791
+
792
+ ---
793
+
794
+ # 18. Practical Guidelines
795
+
796
+ * Use `$eval` carefully; errors will produce empty output.
797
+ * `$unique` is essential for safe auto-generated identifiers.
798
+ * `maxRecursion` prevents infinite loops with recursive or self-referential patterns.
799
+ * Contexts act like local scopes for patterns; define macros or temporary rules inside them.
800
+ * Delimiters and sigils can be swapped mid-processing for DSL adaptation.
801
+ * Always trim input to avoid unintended whitespace captures with `...` patterns.
802
+
803
+ ---
804
+
805
+ # 19. Summary
806
+
807
+ With self-modifying patterns, hybrid pipelines, recursive expansions, Lisp-style macros, and dynamic runtime hacks, Papagaio is a **full meta-programming framework** entirely based on text. You can:
808
+
809
+ * Build DSLs
810
+ * Create macro engines
811
+ * Implement templating pipelines
812
+ * Write mini-transpilers
813
+ * Automate complex text generation
814
+
815
+ Everything is declarative in the text, and runtime manipulation of delimiters, sigils, and keywords allows infinite flexibility.
335
816
 
336
817
  ---
package/cli.js CHANGED
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { Papagaio } from "./papagaio.js";
3
3
  import fs from "fs";
4
- import pkg from "./package.json" assert { type: "json" };
4
+ import { createRequire } from "module";
5
+ const require = createRequire(import.meta.url);
6
+ const pkg = require("./package.json");
7
+
5
8
 
6
9
  // Help & Version
7
10
  const args = process.argv.slice(2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "papagaio",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "easy yet powerful preprocessor",
5
5
  "main": "papagaio.js",
6
6
  "type": "module",
@@ -13,7 +13,6 @@
13
13
  "macro",
14
14
  "pattern",
15
15
  "eval",
16
- "isolate",
17
16
  "parser"
18
17
  ],
19
18
  "author": "jardimdanificado",
package/papagaio.js CHANGED
@@ -9,20 +9,19 @@ export class Papagaio {
9
9
  #counterState = { value: 0, unique: 0 };
10
10
 
11
11
  // Public configuration
12
- delimiters = [["{", "}"]];
12
+ delimiters = [
13
+ ["{", "}"],
14
+ ["[", "]"],
15
+ ["(", ")"],
16
+ ];
13
17
  sigil = "$";
14
18
  keywords = {
15
19
  pattern: "pattern",
16
- macro: "macro",
17
- eval: "eval",
18
- scope: "scope"
20
+ context: "context"
19
21
  };
20
22
 
21
23
  // Public state - processing state
22
24
  content = "";
23
- #matchContent = "";
24
- #scopeContent = "";
25
- #evalContent = "";
26
25
 
27
26
  constructor() {
28
27
  this.#resetCounterState();
@@ -44,14 +43,10 @@ export class Papagaio {
44
43
 
45
44
  // regex para detectar blocos papagaio remanescentes
46
45
  const pending = () => {
47
- const rEval = new RegExp(`\\b${this.keywords.eval}\\s*\\${open}`, "g");
48
- const rScope = new RegExp(`\\b${this.keywords.scope}\\s*\\${open}`, "g");
46
+ const rContext = new RegExp(`\\b${this.keywords.context}\\s*\\${open}`, "g");
49
47
  const rPattern = new RegExp(`\\b${this.keywords.pattern}\\s*\\${open}`, "g");
50
- const rMacro = new RegExp(`\\b${this.keywords.macro}\\s+[A-Za-z_][A-Za-z0-9_]*\\s*\\${open}`, "g");
51
- return rEval.test(src)
52
- || rScope.test(src)
48
+ return rContext.test(src)
53
49
  || rPattern.test(src)
54
- || rMacro.test(src);
55
50
  };
56
51
 
57
52
  // fixpoint loop
@@ -60,17 +55,12 @@ export class Papagaio {
60
55
  last = src;
61
56
 
62
57
  // --- pipeline padrão ---
63
- src = this.#processScopeBlocks(src);
64
- src = this.#processEvalBlocks(src);
65
-
66
- const [macros, s1] = this.#collectMacros(src);
67
- src = s1;
58
+ src = this.#processContextBlocks(src);
68
59
 
69
60
  const [patterns, s2] = this.#collectPatterns(src);
70
61
  src = s2;
71
62
 
72
63
  src = this.#applyPatterns(src, patterns);
73
- src = this.#expandMacros(src, macros);
74
64
 
75
65
  // --- se sobrou bloco papagaio → roda de novo ---
76
66
  if (!pending()) break;
@@ -323,35 +313,6 @@ export class Papagaio {
323
313
  return vars;
324
314
  }
325
315
 
326
- #collectMacros(src) {
327
- const macros = {};
328
- const open = this.#getDefaultOpen();
329
- const macroRegex = new RegExp(`\\b${this.keywords.macro}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\${open}`, "g");
330
-
331
- let match;
332
- const matches = [];
333
-
334
- while ((match = macroRegex.exec(src)) !== null) {
335
- matches.push({
336
- name: match[1],
337
- matchStart: match.index,
338
- openPos: match.index + match[0].length - 1
339
- });
340
- }
341
-
342
- for (let j = matches.length - 1; j >= 0; j--) {
343
- const m = matches[j];
344
- const [body, posAfter] = this.#extractBlock(src, m.openPos);
345
- macros[m.name] = body;
346
-
347
- let left = src.substring(0, m.matchStart);
348
- let right = src.substring(posAfter);
349
- src = this.#collapseLocalNewlines(left, right);
350
- }
351
-
352
- return [macros, src];
353
- }
354
-
355
316
  #patternDepthAt(src, pos) {
356
317
  const open = this.keywords.pattern;
357
318
  let depth = 0;
@@ -465,8 +426,6 @@ export class Papagaio {
465
426
  varMap[varNames[i]] = captures[i] || '';
466
427
  }
467
428
 
468
- this.#matchContent = fullMatch;
469
-
470
429
  const _pre = src.slice(0, matchStart);
471
430
  const _post = src.slice(matchEnd);
472
431
 
@@ -481,6 +440,20 @@ export class Papagaio {
481
440
  () => this.#genUnique()
482
441
  );
483
442
 
443
+ result = result.replace(/\$eval\{([^}]*)\}/g, (_, code) => {
444
+ try {
445
+ // O conteúdo é o corpo de uma função autoinvocada
446
+ const wrappedCode = `"use strict"; return (function() { ${code} })();`;
447
+
448
+ let out = String(
449
+ Function("papagaio", "ctx", wrappedCode)(this, {})
450
+ );
451
+ return out;
452
+ } catch {
453
+ return "";
454
+ }
455
+ });
456
+
484
457
  const S2 = S + S;
485
458
  result = result.replace(new RegExp(this.#escapeRegex(S2), 'g'), '');
486
459
 
@@ -510,83 +483,18 @@ export class Papagaio {
510
483
  return src;
511
484
  }
512
485
 
513
- #expandMacros(src, macros) {
514
- const S = this.sigil;
515
-
516
- for (const name of Object.keys(macros)) {
517
- const body = macros[name];
518
- let changed = true;
519
- let iterations = 0;
520
-
521
- while (changed && iterations < this.maxRecursion) {
522
- changed = false;
523
- iterations++;
524
-
525
- const callRegex = new RegExp(`\\b${this.#escapeRegex(name)}\\s*\\(`, 'g');
526
-
527
- let match;
528
- const matches = [];
529
-
530
- while ((match = callRegex.exec(src)) !== null) {
531
- matches.push({
532
- matchStart: match.index,
533
- openPos: match.index + match[0].length - 1
534
- });
535
- }
536
-
537
- for (let j = matches.length - 1; j >= 0; j--) {
538
- const m = matches[j];
539
- const [argsStr, posAfter] = this.#extractBlock(src, m.openPos, '(', ')');
540
- const vals = argsStr.split(',').map(v => v.trim());
541
-
542
- let exp = body;
543
-
544
- // Substituição flexível de $0, $1, $2 etc, mesmo dentro de palavras
545
- for (let k = vals.length; k >= 0; k--) {
546
- const sigil = k === 0 ? S + '0' : S + k;
547
- const pattern = new RegExp(this.#escapeRegex(sigil) + '(?![0-9])', 'g');
548
- const replacement = k === 0 ? name : (vals[k - 1] !== undefined ? vals[k - 1] : '');
549
- exp = exp.replace(pattern, replacement);
550
- }
551
-
552
- let left = src.substring(0, m.matchStart);
553
- let right = src.substring(posAfter);
554
- src = left + exp + right;
555
- changed = true;
556
- }
557
- }
558
- }
559
-
560
- return src;
561
- }
562
-
563
486
  #escapeRegex(str) {
564
487
  return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
565
488
  }
566
489
 
567
- #collapseLocalNewlines(left, right) {
568
- left = left.replace(/\n+$/, '\n');
569
- right = right.replace(/^\n+/, '\n');
570
-
571
- if (left.endsWith('\n') && right.startsWith('\n')) {
572
- right = right.replace(/^\n+/, '\n');
573
- }
574
-
575
- if (left === '' && right.startsWith('\n')) {
576
- right = right.replace(/^\n+/, '');
577
- }
578
-
579
- return left + right;
580
- }
581
-
582
- #processEvalBlocks(src) {
490
+ #processContextBlocks(src) {
583
491
  const open = this.#getDefaultOpen();
584
- const evalRegex = new RegExp(`\\b${this.keywords.eval}\\s*\\${open}`, "g");
492
+ const contextRegex = new RegExp(`\\b${this.keywords.context}\\s*\\${open}`, "g");
585
493
 
586
494
  let match;
587
495
  const matches = [];
588
496
 
589
- while ((match = evalRegex.exec(src)) !== null) {
497
+ while ((match = contextRegex.exec(src)) !== null) {
590
498
  matches.push({
591
499
  matchStart: match.index,
592
500
  openPos: match.index + match[0].length - 1
@@ -595,49 +503,14 @@ export class Papagaio {
595
503
 
596
504
  for (let j = matches.length - 1; j >= 0; j--) {
597
505
  const m = matches[j];
598
-
599
506
  const [content, posAfter] = this.#extractBlock(src, m.openPos);
600
- this.#evalContent = content;
601
-
602
- let out = "";
603
- try {
604
- // O conteúdo é o corpo de uma função autoinvocada
605
- const wrappedCode = `"use strict"; return (function() { ${content} })();`;
606
-
607
- out = String(
608
- Function("papagaio", "ctx", wrappedCode)(this, {})
609
- );
610
- } catch (e) {
611
- out = "";
612
- }
613
507
 
614
- let left = src.substring(0, m.matchStart);
615
- let right = src.substring(posAfter);
616
- src = left + out + right;
617
- }
618
-
619
- return src;
620
- }
621
-
622
- #processScopeBlocks(src) {
623
- const open = this.#getDefaultOpen();
624
- const scopeRegex = new RegExp(`\\b${this.keywords.scope}\\s*\\${open}`, "g");
625
-
626
- let match;
627
- const matches = [];
628
-
629
- while ((match = scopeRegex.exec(src)) !== null) {
630
- matches.push({
631
- matchStart: match.index,
632
- openPos: match.index + match[0].length - 1
633
- });
634
- }
635
-
636
- for (let j = matches.length - 1; j >= 0; j--) {
637
- const m = matches[j];
638
- const [content, posAfter] = this.#extractBlock(src, m.openPos);
508
+ if (!content.trim()) {
509
+ // Contexto vazio → apenas remove a palavra "context" mas mantém o resto intacto
510
+ src = src.slice(0, m.matchStart) + src.slice(posAfter);
511
+ continue;
512
+ }
639
513
 
640
- this.#scopeContent = content;
641
514
  const processedContent = this.process(content);
642
515
 
643
516
  let left = src.substring(0, m.matchStart);
@@ -654,6 +527,4 @@ export class Papagaio {
654
527
 
655
528
  return src;
656
529
  }
657
-
658
-
659
- }
530
+ }