@thi.ng/parse 2.2.41 → 2.3.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/CHANGELOG.md +7 -1
- package/README.md +130 -62
- package/combinators/alt.d.ts +12 -0
- package/combinators/alt.js +13 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-
|
|
3
|
+
- **Last updated**: 2023-09-15T12:33:37Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -9,6 +9,12 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
9
9
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
10
10
|
and/or version bumps of transitive dependencies.
|
|
11
11
|
|
|
12
|
+
## [2.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/parse@2.3.0) (2023-09-06)
|
|
13
|
+
|
|
14
|
+
#### 🚀 Features
|
|
15
|
+
|
|
16
|
+
- add altS() combinator ([52c76ca](https://github.com/thi-ng/umbrella/commit/52c76ca))
|
|
17
|
+
|
|
12
18
|
## [2.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/parse@2.2.0) (2022-06-15)
|
|
13
19
|
|
|
14
20
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ For Node.js REPL:
|
|
|
90
90
|
const parse = await import("@thi.ng/parse");
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 5.
|
|
93
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 5.09 KB
|
|
94
94
|
|
|
95
95
|
## Dependencies
|
|
96
96
|
|
|
@@ -108,10 +108,11 @@ directory are using this package.
|
|
|
108
108
|
|
|
109
109
|
A selection:
|
|
110
110
|
|
|
111
|
-
| Screenshot | Description
|
|
112
|
-
|
|
113
|
-
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/markdown-parser.jpg" width="240"/> | Markdown to Hiccup to HTML parser / transformer
|
|
114
|
-
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/
|
|
111
|
+
| Screenshot | Description | Live demo | Source |
|
|
112
|
+
|:------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------|:-------------------------------------------------------|:------------------------------------------------------------------------------------|
|
|
113
|
+
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/markdown-parser.jpg" width="240"/> | Markdown to Hiccup to HTML parser / transformer | [Demo](https://demo.thi.ng/umbrella/markdown/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/markdown) |
|
|
114
|
+
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/mastodon-feed.jpg" width="240"/> | Mastodon API feed reader with support for different media types, fullscreen media modal, HTML rewriting | [Demo](https://demo.thi.ng/umbrella/mastodon-feed/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/mastodon-feed) |
|
|
115
|
+
| <img src="https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/examples/parse-playground.png" width="240"/> | Parser grammar livecoding editor/playground & codegen | [Demo](https://demo.thi.ng/umbrella/parse-playground/) | [Source](https://github.com/thi-ng/umbrella/tree/develop/examples/parse-playground) |
|
|
115
116
|
|
|
116
117
|
**Note:** Please also see the [dedicated wiki
|
|
117
118
|
page](https://github.com/thi-ng/umbrella/wiki/Parser-grammars) collecting
|
|
@@ -189,7 +190,7 @@ in order to decide about successful matching.
|
|
|
189
190
|
Source:
|
|
190
191
|
[/combinators](https://github.com/thi-ng/umbrella/tree/develop/packages/parse/src/combinators)
|
|
191
192
|
|
|
192
|
-
- `alt` / `altD`
|
|
193
|
+
- `alt` / `altS` / `altD`
|
|
193
194
|
- `check`
|
|
194
195
|
- `expect`
|
|
195
196
|
- `lookahead`
|
|
@@ -418,7 +419,9 @@ rule references in the grammar definition as well:
|
|
|
418
419
|
[@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
|
|
419
420
|
as a useful tool for processing/interpreting/compiling the result AST)
|
|
420
421
|
|
|
421
|
-
```ts
|
|
422
|
+
```ts tangle:export/readme-sexpr.ts
|
|
423
|
+
import { defContext, defGrammar, print } from "@thi.ng/parse";
|
|
424
|
+
|
|
422
425
|
// define language via grammar DSL
|
|
423
426
|
// the upper-cased rule names are built-ins
|
|
424
427
|
const lang = defGrammar(`
|
|
@@ -435,7 +438,7 @@ const ctx = defContext(`
|
|
|
435
438
|
`);
|
|
436
439
|
|
|
437
440
|
// parse & print AST
|
|
438
|
-
print(lang
|
|
441
|
+
print(lang!.rules.expr)(ctx);
|
|
439
442
|
// expr: null
|
|
440
443
|
// list: null
|
|
441
444
|
// expr: null
|
|
@@ -461,7 +464,7 @@ print(lang.rules.expr)(ctx)
|
|
|
461
464
|
// true
|
|
462
465
|
|
|
463
466
|
// the two top-level s-expressions...
|
|
464
|
-
ctx.children
|
|
467
|
+
console.log(ctx.children);
|
|
465
468
|
// [
|
|
466
469
|
// { id: 'list', state: null, children: [ [Object] ], result: null },
|
|
467
470
|
// { id: 'list', state: null, children: [ [Object] ], result: null }
|
|
@@ -470,28 +473,32 @@ ctx.children
|
|
|
470
473
|
|
|
471
474
|
### SVG path parser example
|
|
472
475
|
|
|
473
|
-
```ts
|
|
476
|
+
```ts tangle:export/readme-svg.ts
|
|
474
477
|
import {
|
|
475
478
|
INT, WS0,
|
|
476
|
-
alt,
|
|
477
|
-
|
|
478
|
-
defContext
|
|
479
|
+
alt, collect, defContext, discard,
|
|
480
|
+
oneOf, seq, xform, zeroOrMore
|
|
479
481
|
} from "@thi.ng/parse";
|
|
480
482
|
|
|
481
483
|
// whitespace parser
|
|
482
|
-
// discard() removes
|
|
484
|
+
// discard() removes matched result from AST
|
|
483
485
|
const wsc = discard(zeroOrMore(oneOf(" ,")));
|
|
484
486
|
|
|
485
|
-
//
|
|
487
|
+
// SVG path parser rules
|
|
486
488
|
// collect() collects child results in array, then removes children
|
|
487
|
-
// INT & WS0 are preset parsers (see section above)
|
|
489
|
+
// INT & WS0 are preset parsers (see readme section above)
|
|
488
490
|
const point = collect(seq([INT, wsc, INT]));
|
|
489
491
|
const move = collect(seq([oneOf("Mm"), WS0, point, WS0]));
|
|
490
492
|
const line = collect(seq([oneOf("Ll"), WS0, point, WS0]));
|
|
491
|
-
const curve = collect(
|
|
493
|
+
const curve = collect(
|
|
494
|
+
seq([oneOf("Cc"), WS0, point, wsc, point, wsc, point, WS0])
|
|
495
|
+
);
|
|
492
496
|
// xform used here to wrap result in array
|
|
493
497
|
// (to produce same result format as parsers above)
|
|
494
|
-
const close = xform(
|
|
498
|
+
const close = xform(
|
|
499
|
+
oneOf("Zz"),
|
|
500
|
+
(scope) => ((scope!.result = [scope!.result]), scope)
|
|
501
|
+
);
|
|
495
502
|
|
|
496
503
|
// main path parser
|
|
497
504
|
const path = collect(zeroOrMore(alt([move, line, curve, close])));
|
|
@@ -499,64 +506,125 @@ const path = collect(zeroOrMore(alt([move, line, curve, close])));
|
|
|
499
506
|
// prepare parse context & reader
|
|
500
507
|
const ctx = defContext("M0,1L2 3c4,5-6,7 8 9z");
|
|
501
508
|
// parse input into AST
|
|
502
|
-
path(ctx);
|
|
509
|
+
console.log(path(ctx));
|
|
503
510
|
// true
|
|
504
511
|
|
|
505
512
|
// transformed result of AST root node
|
|
506
|
-
ctx.result
|
|
507
|
-
// [
|
|
513
|
+
console.log(ctx.result);
|
|
514
|
+
// [
|
|
515
|
+
// [ 'M', [ 0, 1 ] ],
|
|
516
|
+
// [ 'L', [ 2, 3 ] ],
|
|
517
|
+
// [ 'c', [ 4, 5 ], [ -6, 7 ], [ 8, 9 ] ],
|
|
518
|
+
// [ 'z' ]
|
|
519
|
+
// ]
|
|
508
520
|
```
|
|
509
521
|
|
|
510
522
|
### RPN parser & interpreter example
|
|
511
523
|
|
|
512
|
-
```ts
|
|
524
|
+
```ts tangle:export/readme-rpn.ts
|
|
525
|
+
import type { Fn, FnN2 } from "@thi.ng/api";
|
|
513
526
|
import {
|
|
514
527
|
INT, WS0,
|
|
515
|
-
alt,
|
|
516
|
-
defContext
|
|
528
|
+
alt, altS, defContext, xform, zeroOrMore
|
|
517
529
|
} from "@thi.ng/parse";
|
|
518
530
|
|
|
519
|
-
|
|
520
|
-
|
|
531
|
+
type StackFn = Fn<number[], void>;
|
|
532
|
+
type Op = { arity: number; fn: StackFn };
|
|
533
|
+
|
|
534
|
+
// wrapper for pure 2-arity stack functions/ops
|
|
535
|
+
const defOp2 =
|
|
536
|
+
(fn: FnN2): StackFn =>
|
|
537
|
+
(stack) => {
|
|
538
|
+
const b = stack.pop()!;
|
|
539
|
+
const a = stack.pop()!;
|
|
540
|
+
stack.push(fn(a, b));
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// operator/word implementations
|
|
544
|
+
const ops: Record<string, Op> = {
|
|
545
|
+
"+": { arity: 2, fn: defOp2((a, b) => a + b) },
|
|
546
|
+
"-": { arity: 2, fn: defOp2((a, b) => a - b) },
|
|
547
|
+
"*": { arity: 2, fn: defOp2((a, b) => a * b) },
|
|
548
|
+
"/": { arity: 2, fn: defOp2((a, b) => a / b) },
|
|
549
|
+
// prints top stack item to console
|
|
550
|
+
print: { arity: 1, fn: (stack) => console.log(stack.pop()) },
|
|
551
|
+
// duplicates top stack item
|
|
552
|
+
dup: { arity: 1, fn: (stack) => stack.push(stack[stack.length - 1]) },
|
|
553
|
+
// swaps two topmost stack items
|
|
554
|
+
swap: {
|
|
555
|
+
arity: 2,
|
|
556
|
+
fn: (stack) => {
|
|
557
|
+
const a = stack.pop()!;
|
|
558
|
+
const b = stack.pop()!;
|
|
559
|
+
stack.push(a, b);
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
};
|
|
521
563
|
|
|
522
|
-
//
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
564
|
+
// simple RPN parser & interpreter runtime
|
|
565
|
+
const interpret = (src: string, debug = true) => {
|
|
566
|
+
// data stack for execution
|
|
567
|
+
const stack: number[] = [];
|
|
568
|
+
|
|
569
|
+
// signed integer parser (using INT preset) with transform fn.
|
|
570
|
+
// the user fn here is only used for pushing values on data stack
|
|
571
|
+
// also, if a transform returns null, the parse scope will
|
|
572
|
+
// be removed from the result AST
|
|
573
|
+
const value = xform(INT, (scope) => {
|
|
574
|
+
stack.push(scope!.result);
|
|
575
|
+
return null;
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// parser (with transform) for any of the registered operators
|
|
579
|
+
// the transform here applies the op in RPN fashion to the data stack
|
|
580
|
+
// stack underflow handling omitted for brevity
|
|
581
|
+
const op = xform(altS(Object.keys(ops)), (scope) => {
|
|
582
|
+
const id = scope!.result;
|
|
583
|
+
const { arity, fn } = ops[id];
|
|
584
|
+
if (debug) console.log(id, stack);
|
|
585
|
+
if (stack.length < arity) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
"stack underflow " +
|
|
588
|
+
`("${id}" needs ${arity} args, got ${JSON.stringify(stack)})`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
fn(stack);
|
|
592
|
+
return null;
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// parser for complete RPN program, combines above two parsers
|
|
596
|
+
// and the whitespace preset as alternatives
|
|
597
|
+
const program = zeroOrMore(alt([value, op, WS0]));
|
|
598
|
+
// apply parser to source code
|
|
599
|
+
program(defContext(src));
|
|
600
|
+
// return result stack
|
|
601
|
+
return stack;
|
|
528
602
|
};
|
|
529
603
|
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
//
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
//
|
|
554
|
-
program(defContext("10 5 3 * + -2 * 10 /"));
|
|
555
|
-
|
|
556
|
-
// print result data stack, i.e. result of:
|
|
557
|
-
// (3 * 5 + 10) * -2 / 10 (in infix notation)
|
|
558
|
-
console.log(stack);
|
|
559
|
-
// [-5]
|
|
604
|
+
// checking operator arity
|
|
605
|
+
try { interpret("1 +"); } catch (e) { console.warn(e); }
|
|
606
|
+
// Error: stack underflow ("+" needs 2 args, got [1])
|
|
607
|
+
|
|
608
|
+
// execute: (3 * 5 + 10) * -2 / 10 (in infix notation)
|
|
609
|
+
interpret("10 5 3 * + -2 * 10 / print");
|
|
610
|
+
// * [ 10, 5, 3 ]
|
|
611
|
+
// + [ 10, 15 ]
|
|
612
|
+
// * [ 25, -2 ]
|
|
613
|
+
// / [ -50, 10 ]
|
|
614
|
+
// print [ -5 ]
|
|
615
|
+
// -5
|
|
616
|
+
|
|
617
|
+
// execute: ((5 + 3)**2) / 2) - (5 + 3)**2
|
|
618
|
+
interpret("5 3 + dup * dup 2 / swap - print");
|
|
619
|
+
// + [ 5, 3 ]
|
|
620
|
+
// dup [ 8 ]
|
|
621
|
+
// * [ 8, 8 ]
|
|
622
|
+
// dup [ 64 ]
|
|
623
|
+
// / [ 64, 64, 2 ]
|
|
624
|
+
// swap [ 64, 32 ]
|
|
625
|
+
// - [ 32, 64 ]
|
|
626
|
+
// print [ -32 ]
|
|
627
|
+
// -32
|
|
560
628
|
```
|
|
561
629
|
|
|
562
630
|
## Authors
|
package/combinators/alt.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { Parser } from "../api.js";
|
|
2
2
|
export declare const alt: <T>(parsers: Parser<T>[]) => Parser<T>;
|
|
3
|
+
/**
|
|
4
|
+
* Syntax sugar for {@link alt}. Takes an array of strings to match and creates
|
|
5
|
+
* parsers for each before passing them to `alt()`.
|
|
6
|
+
*
|
|
7
|
+
* @param strings
|
|
8
|
+
*/
|
|
9
|
+
export declare const altS: (strings: string[]) => Parser<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Wrapped version of {@link alt} which discards results if successful match.
|
|
12
|
+
*
|
|
13
|
+
* @param parsers
|
|
14
|
+
*/
|
|
3
15
|
export declare const altD: <T>(parsers: Parser<T>[]) => Parser<any>;
|
|
4
16
|
//# sourceMappingURL=alt.d.ts.map
|
package/combinators/alt.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { string } from "../prims/string.js";
|
|
1
2
|
import { discard } from "../xform/discard.js";
|
|
2
3
|
export const alt = (parsers) => (ctx) => {
|
|
3
4
|
if (ctx.done)
|
|
@@ -9,4 +10,16 @@ export const alt = (parsers) => (ctx) => {
|
|
|
9
10
|
}
|
|
10
11
|
return false;
|
|
11
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Syntax sugar for {@link alt}. Takes an array of strings to match and creates
|
|
15
|
+
* parsers for each before passing them to `alt()`.
|
|
16
|
+
*
|
|
17
|
+
* @param strings
|
|
18
|
+
*/
|
|
19
|
+
export const altS = (strings) => alt(strings.map((x) => string(x)));
|
|
20
|
+
/**
|
|
21
|
+
* Wrapped version of {@link alt} which discards results if successful match.
|
|
22
|
+
*
|
|
23
|
+
* @param parsers
|
|
24
|
+
*/
|
|
12
25
|
export const altD = (parsers) => discard(alt(parsers));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/parse",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Purely functional parser combinators & AST generation for generic inputs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@thi.ng/api": "^8.9.5",
|
|
38
38
|
"@thi.ng/checks": "^3.4.5",
|
|
39
|
-
"@thi.ng/defmulti": "^
|
|
39
|
+
"@thi.ng/defmulti": "^3.0.0",
|
|
40
40
|
"@thi.ng/errors": "^2.3.5",
|
|
41
|
-
"@thi.ng/strings": "^3.
|
|
41
|
+
"@thi.ng/strings": "^3.5.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@microsoft/api-extractor": "^7.36.4",
|
|
@@ -241,5 +241,5 @@
|
|
|
241
241
|
],
|
|
242
242
|
"year": 2020
|
|
243
243
|
},
|
|
244
|
-
"gitHead": "
|
|
244
|
+
"gitHead": "b2ef5a1b8932d067af4ec2fc7da03d59d6868dc7\n"
|
|
245
245
|
}
|