@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-08-27T11:20:59Z
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.07 KB
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 | 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/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) |
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.rules.expr)(ctx)
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, oneOf, seq, zeroOrMore,
477
- collect, discard, xform,
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 results from AST
484
+ // discard() removes matched result from AST
483
485
  const wsc = discard(zeroOrMore(oneOf(" ,")));
484
486
 
485
- // svg path parser rules
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(seq([oneOf("Cc"), WS0, point, wsc, point, wsc, point, WS0]));
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(oneOf("Zz"), ($) => ($.result = [$.result], $));
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
- // [["M", [0, 1]], ["L", [2, 3]], ["c", [4, 5], [-6, 7], [8, 9]], ["z"]]
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, oneOf, xform, zeroOrMore
516
- defContext
528
+ alt, altS, defContext, xform, zeroOrMore
517
529
  } from "@thi.ng/parse";
518
530
 
519
- // data stack for execution
520
- const stack = [];
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
- // operator implementations
523
- const ops = {
524
- "+": (a, b) => a + b,
525
- "-": (a, b) => a - b,
526
- "*": (a, b) => a * b,
527
- "/": (a, b) => a / b,
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
- // signed integer parser (using INT preset) with transform fn.
531
- // the user fn here is only used for pushing values on data stack
532
- // also, if a transform returns null, the parse scope will
533
- // be removed from the result AST
534
- const value = xform(INT, (scope) => {
535
- stack.push(scope.result);
536
- return null;
537
- });
538
-
539
- // parser for any of the registered operators (again w/ transform)
540
- // the transform here applies the op in RPN fashion to the data stack
541
- // stack underflow handling omitted for brevity
542
- const op = xform(oneOf(Object.keys(ops)), (scope) => {
543
- const b = stack.pop();
544
- const a = stack.pop();
545
- stack.push(ops[scope.result](a, b));
546
- return null;
547
- });
548
-
549
- // parser for complete RPN program, combines above two parsers
550
- // and the whitespace preset as alternatives
551
- const program = zeroOrMore(alt([value, op, WS0]))
552
-
553
- // prepare parser context (incl. reader) and execute
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
@@ -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
@@ -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.2.41",
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": "^2.1.44",
39
+ "@thi.ng/defmulti": "^3.0.0",
40
40
  "@thi.ng/errors": "^2.3.5",
41
- "@thi.ng/strings": "^3.4.13"
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": "5929dd20497668496af13415cdf784a4d6f69aa3\n"
244
+ "gitHead": "b2ef5a1b8932d067af4ec2fc7da03d59d6868dc7\n"
245
245
  }