@thi.ng/parse 2.2.40 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/README.md +125 -58
- package/combinators/alt.d.ts +12 -0
- package/combinators/alt.js +13 -0
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2023-
|
|
3
|
+
- **Last updated**: 2023-09-06T13:36:28Z
|
|
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
|
|
|
@@ -189,7 +189,7 @@ in order to decide about successful matching.
|
|
|
189
189
|
Source:
|
|
190
190
|
[/combinators](https://github.com/thi-ng/umbrella/tree/develop/packages/parse/src/combinators)
|
|
191
191
|
|
|
192
|
-
- `alt` / `altD`
|
|
192
|
+
- `alt` / `altS` / `altD`
|
|
193
193
|
- `check`
|
|
194
194
|
- `expect`
|
|
195
195
|
- `lookahead`
|
|
@@ -418,7 +418,9 @@ rule references in the grammar definition as well:
|
|
|
418
418
|
[@thi.ng/defmulti](https://github.com/thi-ng/umbrella/tree/develop/packages/defmulti)
|
|
419
419
|
as a useful tool for processing/interpreting/compiling the result AST)
|
|
420
420
|
|
|
421
|
-
```ts
|
|
421
|
+
```ts tangle:export/readme-sexpr.ts
|
|
422
|
+
import { defContext, defGrammar, print } from "@thi.ng/parse";
|
|
423
|
+
|
|
422
424
|
// define language via grammar DSL
|
|
423
425
|
// the upper-cased rule names are built-ins
|
|
424
426
|
const lang = defGrammar(`
|
|
@@ -435,7 +437,7 @@ const ctx = defContext(`
|
|
|
435
437
|
`);
|
|
436
438
|
|
|
437
439
|
// parse & print AST
|
|
438
|
-
print(lang
|
|
440
|
+
print(lang!.rules.expr)(ctx);
|
|
439
441
|
// expr: null
|
|
440
442
|
// list: null
|
|
441
443
|
// expr: null
|
|
@@ -461,7 +463,7 @@ print(lang.rules.expr)(ctx)
|
|
|
461
463
|
// true
|
|
462
464
|
|
|
463
465
|
// the two top-level s-expressions...
|
|
464
|
-
ctx.children
|
|
466
|
+
console.log(ctx.children);
|
|
465
467
|
// [
|
|
466
468
|
// { id: 'list', state: null, children: [ [Object] ], result: null },
|
|
467
469
|
// { id: 'list', state: null, children: [ [Object] ], result: null }
|
|
@@ -470,28 +472,32 @@ ctx.children
|
|
|
470
472
|
|
|
471
473
|
### SVG path parser example
|
|
472
474
|
|
|
473
|
-
```ts
|
|
475
|
+
```ts tangle:export/readme-svg.ts
|
|
474
476
|
import {
|
|
475
477
|
INT, WS0,
|
|
476
|
-
alt,
|
|
477
|
-
|
|
478
|
-
defContext
|
|
478
|
+
alt, collect, defContext, discard,
|
|
479
|
+
oneOf, seq, xform, zeroOrMore
|
|
479
480
|
} from "@thi.ng/parse";
|
|
480
481
|
|
|
481
482
|
// whitespace parser
|
|
482
|
-
// discard() removes
|
|
483
|
+
// discard() removes matched result from AST
|
|
483
484
|
const wsc = discard(zeroOrMore(oneOf(" ,")));
|
|
484
485
|
|
|
485
|
-
//
|
|
486
|
+
// SVG path parser rules
|
|
486
487
|
// collect() collects child results in array, then removes children
|
|
487
|
-
// INT & WS0 are preset parsers (see section above)
|
|
488
|
+
// INT & WS0 are preset parsers (see readme section above)
|
|
488
489
|
const point = collect(seq([INT, wsc, INT]));
|
|
489
490
|
const move = collect(seq([oneOf("Mm"), WS0, point, WS0]));
|
|
490
491
|
const line = collect(seq([oneOf("Ll"), WS0, point, WS0]));
|
|
491
|
-
const curve = collect(
|
|
492
|
+
const curve = collect(
|
|
493
|
+
seq([oneOf("Cc"), WS0, point, wsc, point, wsc, point, WS0])
|
|
494
|
+
);
|
|
492
495
|
// xform used here to wrap result in array
|
|
493
496
|
// (to produce same result format as parsers above)
|
|
494
|
-
const close = xform(
|
|
497
|
+
const close = xform(
|
|
498
|
+
oneOf("Zz"),
|
|
499
|
+
(scope) => ((scope!.result = [scope!.result]), scope)
|
|
500
|
+
);
|
|
495
501
|
|
|
496
502
|
// main path parser
|
|
497
503
|
const path = collect(zeroOrMore(alt([move, line, curve, close])));
|
|
@@ -499,64 +505,125 @@ const path = collect(zeroOrMore(alt([move, line, curve, close])));
|
|
|
499
505
|
// prepare parse context & reader
|
|
500
506
|
const ctx = defContext("M0,1L2 3c4,5-6,7 8 9z");
|
|
501
507
|
// parse input into AST
|
|
502
|
-
path(ctx);
|
|
508
|
+
console.log(path(ctx));
|
|
503
509
|
// true
|
|
504
510
|
|
|
505
511
|
// transformed result of AST root node
|
|
506
|
-
ctx.result
|
|
507
|
-
// [
|
|
512
|
+
console.log(ctx.result);
|
|
513
|
+
// [
|
|
514
|
+
// [ 'M', [ 0, 1 ] ],
|
|
515
|
+
// [ 'L', [ 2, 3 ] ],
|
|
516
|
+
// [ 'c', [ 4, 5 ], [ -6, 7 ], [ 8, 9 ] ],
|
|
517
|
+
// [ 'z' ]
|
|
518
|
+
// ]
|
|
508
519
|
```
|
|
509
520
|
|
|
510
521
|
### RPN parser & interpreter example
|
|
511
522
|
|
|
512
|
-
```ts
|
|
523
|
+
```ts tangle:export/readme-rpn.ts
|
|
524
|
+
import type { Fn, FnN2 } from "@thi.ng/api";
|
|
513
525
|
import {
|
|
514
526
|
INT, WS0,
|
|
515
|
-
alt,
|
|
516
|
-
defContext
|
|
527
|
+
alt, altS, defContext, xform, zeroOrMore
|
|
517
528
|
} from "@thi.ng/parse";
|
|
518
529
|
|
|
519
|
-
|
|
520
|
-
|
|
530
|
+
type StackFn = Fn<number[], void>;
|
|
531
|
+
type Op = { arity: number; fn: StackFn };
|
|
532
|
+
|
|
533
|
+
// wrapper for pure 2-arity stack functions/ops
|
|
534
|
+
const defOp2 =
|
|
535
|
+
(fn: FnN2): StackFn =>
|
|
536
|
+
(stack) => {
|
|
537
|
+
const b = stack.pop()!;
|
|
538
|
+
const a = stack.pop()!;
|
|
539
|
+
stack.push(fn(a, b));
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// operator/word implementations
|
|
543
|
+
const ops: Record<string, Op> = {
|
|
544
|
+
"+": { arity: 2, fn: defOp2((a, b) => a + b) },
|
|
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
|
+
// prints top stack item to console
|
|
549
|
+
print: { arity: 1, fn: (stack) => console.log(stack.pop()) },
|
|
550
|
+
// duplicates top stack item
|
|
551
|
+
dup: { arity: 1, fn: (stack) => stack.push(stack[stack.length - 1]) },
|
|
552
|
+
// swaps two topmost stack items
|
|
553
|
+
swap: {
|
|
554
|
+
arity: 2,
|
|
555
|
+
fn: (stack) => {
|
|
556
|
+
const a = stack.pop()!;
|
|
557
|
+
const b = stack.pop()!;
|
|
558
|
+
stack.push(a, b);
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
};
|
|
521
562
|
|
|
522
|
-
//
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
563
|
+
// simple RPN parser & interpreter runtime
|
|
564
|
+
const interpret = (src: string, debug = true) => {
|
|
565
|
+
// data stack for execution
|
|
566
|
+
const stack: number[] = [];
|
|
567
|
+
|
|
568
|
+
// signed integer parser (using INT preset) with transform fn.
|
|
569
|
+
// the user fn here is only used for pushing values on data stack
|
|
570
|
+
// also, if a transform returns null, the parse scope will
|
|
571
|
+
// be removed from the result AST
|
|
572
|
+
const value = xform(INT, (scope) => {
|
|
573
|
+
stack.push(scope!.result);
|
|
574
|
+
return null;
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// parser (with transform) for any of the registered operators
|
|
578
|
+
// the transform here applies the op in RPN fashion to the data stack
|
|
579
|
+
// stack underflow handling omitted for brevity
|
|
580
|
+
const op = xform(altS(Object.keys(ops)), (scope) => {
|
|
581
|
+
const id = scope!.result;
|
|
582
|
+
const { arity, fn } = ops[id];
|
|
583
|
+
if (debug) console.log(id, stack);
|
|
584
|
+
if (stack.length < arity) {
|
|
585
|
+
throw new Error(
|
|
586
|
+
"stack underflow " +
|
|
587
|
+
`("${id}" needs ${arity} args, got ${JSON.stringify(stack)})`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
fn(stack);
|
|
591
|
+
return null;
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// parser for complete RPN program, combines above two parsers
|
|
595
|
+
// and the whitespace preset as alternatives
|
|
596
|
+
const program = zeroOrMore(alt([value, op, WS0]));
|
|
597
|
+
// apply parser to source code
|
|
598
|
+
program(defContext(src));
|
|
599
|
+
// return result stack
|
|
600
|
+
return stack;
|
|
528
601
|
};
|
|
529
602
|
|
|
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]
|
|
603
|
+
// checking operator arity
|
|
604
|
+
try { interpret("1 +"); } catch (e) { console.warn(e); }
|
|
605
|
+
// Error: stack underflow ("+" needs 2 args, got [1])
|
|
606
|
+
|
|
607
|
+
// execute: (3 * 5 + 10) * -2 / 10 (in infix notation)
|
|
608
|
+
interpret("10 5 3 * + -2 * 10 / print");
|
|
609
|
+
// * [ 10, 5, 3 ]
|
|
610
|
+
// + [ 10, 15 ]
|
|
611
|
+
// * [ 25, -2 ]
|
|
612
|
+
// / [ -50, 10 ]
|
|
613
|
+
// print [ -5 ]
|
|
614
|
+
// -5
|
|
615
|
+
|
|
616
|
+
// execute: ((5 + 3)**2) / 2) - (5 + 3)**2
|
|
617
|
+
interpret("5 3 + dup * dup 2 / swap - print");
|
|
618
|
+
// + [ 5, 3 ]
|
|
619
|
+
// dup [ 8 ]
|
|
620
|
+
// * [ 8, 8 ]
|
|
621
|
+
// dup [ 64 ]
|
|
622
|
+
// / [ 64, 64, 2 ]
|
|
623
|
+
// swap [ 64, 32 ]
|
|
624
|
+
// - [ 32, 64 ]
|
|
625
|
+
// print [ -32 ]
|
|
626
|
+
// -32
|
|
560
627
|
```
|
|
561
628
|
|
|
562
629
|
## 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.0",
|
|
4
4
|
"description": "Purely functional parser combinators & AST generation for generic inputs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -34,19 +34,19 @@
|
|
|
34
34
|
"test": "testament test"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@thi.ng/api": "^8.9.
|
|
38
|
-
"@thi.ng/checks": "^3.4.
|
|
39
|
-
"@thi.ng/defmulti": "^2.1.
|
|
40
|
-
"@thi.ng/errors": "^2.3.
|
|
41
|
-
"@thi.ng/strings": "^3.4.
|
|
37
|
+
"@thi.ng/api": "^8.9.5",
|
|
38
|
+
"@thi.ng/checks": "^3.4.5",
|
|
39
|
+
"@thi.ng/defmulti": "^2.1.45",
|
|
40
|
+
"@thi.ng/errors": "^2.3.5",
|
|
41
|
+
"@thi.ng/strings": "^3.4.13"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@microsoft/api-extractor": "^7.36.4",
|
|
45
|
-
"@thi.ng/testament": "^0.3.
|
|
45
|
+
"@thi.ng/testament": "^0.3.23",
|
|
46
46
|
"rimraf": "^5.0.1",
|
|
47
47
|
"tools": "^0.0.1",
|
|
48
|
-
"typedoc": "^0.
|
|
49
|
-
"typescript": "^5.
|
|
48
|
+
"typedoc": "^0.25.0",
|
|
49
|
+
"typescript": "^5.2.2"
|
|
50
50
|
},
|
|
51
51
|
"keywords": [
|
|
52
52
|
"ast",
|
|
@@ -241,5 +241,5 @@
|
|
|
241
241
|
],
|
|
242
242
|
"year": 2020
|
|
243
243
|
},
|
|
244
|
-
"gitHead": "
|
|
244
|
+
"gitHead": "1bbef970bab7f7def0700e587cf0471e1e1ef9f3\n"
|
|
245
245
|
}
|