functionalscript 0.33.0 → 0.35.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.
Files changed (89) hide show
  1. package/README.md +27 -14
  2. package/fs/asserts/module.f.d.ts +1 -1
  3. package/fs/asserts/module.f.js +4 -1
  4. package/fs/base64/proof.f.d.ts +1 -0
  5. package/fs/base64/proof.f.js +25 -56
  6. package/fs/base_n/module.f.js +11 -5
  7. package/fs/base_n/proof.f.d.ts +1 -0
  8. package/fs/base_n/proof.f.js +12 -1
  9. package/fs/bnf/data/module.f.d.ts +0 -103
  10. package/fs/bnf/data/module.f.js +10 -246
  11. package/fs/bnf/data/proof.f.d.ts +0 -10
  12. package/fs/bnf/data/proof.f.js +2 -642
  13. package/fs/bnf/descent/module.f.d.ts +56 -0
  14. package/fs/bnf/descent/module.f.js +119 -0
  15. package/fs/bnf/descent/proof.f.d.ts +5 -0
  16. package/fs/bnf/descent/proof.f.js +296 -0
  17. package/fs/bnf/ll1/module.f.d.ts +72 -0
  18. package/fs/bnf/ll1/module.f.js +153 -0
  19. package/fs/bnf/ll1/proof.f.d.ts +9 -0
  20. package/fs/bnf/ll1/proof.f.js +358 -0
  21. package/fs/cas/cli/module.f.d.ts +6 -0
  22. package/fs/cas/cli/module.f.js +56 -0
  23. package/fs/cas/cli/proof.f.d.ts +14 -0
  24. package/fs/cas/cli/proof.f.js +149 -0
  25. package/fs/cas/mcp/module.f.d.ts +9 -17
  26. package/fs/cas/mcp/module.f.js +123 -96
  27. package/fs/cas/mcp/proof.f.d.ts +12 -1
  28. package/fs/cas/mcp/proof.f.js +199 -32
  29. package/fs/cas/module.f.d.ts +35 -15
  30. package/fs/cas/module.f.js +168 -131
  31. package/fs/cas/proof.f.d.ts +16 -13
  32. package/fs/cas/proof.f.js +255 -121
  33. package/fs/ci/config/module.f.d.ts +14 -14
  34. package/fs/ci/config/module.f.js +14 -14
  35. package/fs/djs/tokenizer-new/module.f.js +1 -1
  36. package/fs/djs/tokenizer-new/proof.f.d.ts +2 -0
  37. package/fs/djs/tokenizer-new/proof.f.js +357 -229
  38. package/fs/effects/list/module.f.d.ts +31 -0
  39. package/fs/effects/list/module.f.js +16 -0
  40. package/fs/effects/module.f.d.ts +11 -5
  41. package/fs/effects/module.f.js +16 -6
  42. package/fs/effects/node/module.f.d.ts +29 -3
  43. package/fs/effects/node/module.f.js +35 -1
  44. package/fs/effects/node/module.js +32 -18
  45. package/fs/effects/node/virtual/module.f.js +57 -0
  46. package/fs/effects/proof.f.d.ts +4 -0
  47. package/fs/effects/proof.f.js +18 -1
  48. package/fs/fjs/module.f.js +2 -8
  49. package/fs/html/module.f.js +6 -8
  50. package/fs/mcp/module.f.d.ts +1 -1
  51. package/fs/mcp/stdio/module.f.d.ts +8 -0
  52. package/fs/mcp/stdio/module.f.js +33 -6
  53. package/fs/mcp/stdio/proof.f.d.ts +3 -0
  54. package/fs/mcp/stdio/proof.f.js +52 -1
  55. package/fs/mime/module.f.d.ts +101 -5
  56. package/fs/mime/module.f.js +159 -6
  57. package/fs/mime/proof.f.d.ts +25 -0
  58. package/fs/mime/proof.f.js +193 -3
  59. package/fs/text/code_point/module.f.d.ts +39 -2
  60. package/fs/text/code_point/module.f.js +74 -2
  61. package/fs/text/code_point/proof.f.d.ts +8 -0
  62. package/fs/text/code_point/proof.f.js +72 -0
  63. package/fs/text/module.f.d.ts +11 -1
  64. package/fs/text/module.f.js +13 -3
  65. package/fs/text/proof.f.d.ts +6 -0
  66. package/fs/text/proof.f.js +25 -2
  67. package/fs/text/utf16/module.f.js +4 -26
  68. package/fs/text/utf8/module.f.d.ts +27 -0
  69. package/fs/text/utf8/module.f.js +8 -14
  70. package/fs/types/bit_vec/module.f.d.ts +20 -2
  71. package/fs/types/bit_vec/module.f.js +77 -27
  72. package/fs/types/bit_vec/proof.f.d.ts +8 -0
  73. package/fs/types/bit_vec/proof.f.js +24 -6
  74. package/fs/types/function/operator/module.f.d.ts +26 -0
  75. package/fs/types/list/module.f.d.ts +3 -0
  76. package/fs/types/nullable/module.f.d.ts +9 -5
  77. package/fs/types/nullable/module.f.js +19 -0
  78. package/fs/types/result/module.d.ts +2 -4
  79. package/fs/types/result/module.js +9 -3
  80. package/package.json +3 -3
  81. package/issues/demo/data/data.f.js +0 -12
  82. package/issues/demo/data/shared.f.js +0 -3
  83. package/issues/demo/fs/app.js +0 -4
  84. package/issues/demo/fs/math.f.js +0 -4
  85. package/issues/demo/sample/proof.f.js +0 -13
  86. /package/{issues → todo}/031-json.f.d.ts +0 -0
  87. /package/{issues → todo}/031-json.f.js +0 -0
  88. /package/{issues → todo}/proof.f.d.ts +0 -0
  89. /package/{issues → todo}/proof.f.js +0 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@ FunctionalScript is a safe, purely functional programming language and a strict
11
11
  as a subset of JavaScript.
12
12
  - [TypeScript](https://en.wikipedia.org/wiki/TypeScript), as a superset of JavaScript.
13
13
 
14
- [A working draft of the FunctionalScript specification](./issues/lang/README.md).
14
+ [A working draft of the FunctionalScript specification](./todo/lang/README.md).
15
15
 
16
16
  Learn more about
17
17
 
@@ -28,22 +28,39 @@ Install FunctionalScript via npm:
28
28
  npm install -g functionalscript
29
29
  ```
30
30
 
31
- The FunctionalScript compiler command (`fjs compile`) currently supports:
31
+ The `fjs` CLI provides several commands:
32
32
 
33
- * `import` statements
34
- * `const` declarations
33
+ | Command | Description |
34
+ |------------------|-----------------------------------------------------------|
35
+ | `fjs test` / `t` | Run the FunctionalScript test suite |
36
+ | `fjs compile` / `c` | Compile a `.f.ts` module to JavaScript |
37
+ | `fjs run` / `r` | Run a FunctionalScript module as a Node program |
38
+ | `fjs cas` / `s` | Content-addressable storage (`add`, `get`, `list`) |
39
+ | `fjs mcp` / `m` | Run an MCP server over stdio exposing the CAS as tools |
40
+ | `fjs ci` / `i` | Generate the GitHub Actions CI workflow |
35
41
 
36
- It does **not** yet support functions or complex expressions.
42
+ ### Content-Addressable Storage (CAS)
37
43
 
38
- Example usage with `fjs`:
44
+ FunctionalScript ships a built-in CAS for storing and retrieving blobs by their cryptographic hash:
39
45
 
40
46
  ```bash
41
- fjs compile example.f.js output.json
42
- # or
43
- fjs compile example.f.js output.f.js
47
+ fjs cas add myfile.txt # store a file, print its cBase32 hash
48
+ fjs cas get <hash> # restore a blob by hash
49
+ fjs cas list # list all stored hashes
44
50
  ```
45
51
 
46
- FunctionalScript code can be compiled directly into either JSON or JavaScript without imports.
52
+ Blobs are stored under `~/.cas/` and addressed by their SHA-256 hash encoded in cBase32.
53
+
54
+ ### MCP Server
55
+
56
+ The CAS is also exposed as an [MCP](https://modelcontextprotocol.io/) server so LLM agents can read and write blobs without a shell:
57
+
58
+ ```bash
59
+ # register with Claude CLI
60
+ claude mcp add cas -- npx functionalscript m
61
+ ```
62
+
63
+ See [`fs/cas/mcp/README.md`](fs/cas/mcp/README.md) for details on the `cas_add`, `cas_get`, and `cas_list` tools.
47
64
 
48
65
  ## Vision
49
66
 
@@ -67,10 +84,6 @@ In FunctionalScript:
67
84
  - A module can depend only on another FunctionalScript module.
68
85
  - It also has no standard library. Only a safe subset of standard JavaScript API can be used without referencing other modules.
69
86
 
70
- ## Our Next Step
71
-
72
- [Re-architecture of NaNVM](https://medium.com/@sergeyshandar/nanvm-re-architecture-8097f766ec1c?sk=d14ec1daf73ac5442f12ce20b2bc037a).
73
-
74
87
  ## Sponsors
75
88
 
76
89
  - [KirillOsenkov](https://github.com/KirillOsenkov),
@@ -1,4 +1,4 @@
1
1
  export declare const todo: () => never;
2
2
  export declare const assert: (v: boolean, msg?: unknown) => asserts v;
3
- export declare const assertEq: <T>(a: T, b: T) => void;
3
+ export declare const assertEq: <T>(...x: readonly [T, T, unknown?]) => void;
4
4
  export type Assert<T extends true> = T;
@@ -3,4 +3,7 @@ export const assert = (v, msg = 'assertion failed') => {
3
3
  if (!v)
4
4
  throw msg;
5
5
  };
6
- export const assertEq = (a, b) => assert(a === b, [a, b]);
6
+ export const assertEq = (...x) => {
7
+ const [a, b] = x;
8
+ assert(a === b, x);
9
+ };
@@ -8,4 +8,5 @@ export declare const proof: {
8
8
  invalidInput: () => void;
9
9
  validPadding: () => void;
10
10
  knownVectors: () => void;
11
+ decodeOverflow: () => void;
11
12
  };
@@ -1,14 +1,9 @@
1
+ import { assertEq } from "../asserts/module.f.js";
1
2
  import { empty, vec } from "../types/bit_vec/module.f.js";
2
3
  import { encode, decode } from "./module.f.js";
3
4
  const check = (s, v) => {
4
- const sr = encode(v);
5
- if (sr !== s) {
6
- throw ['encode', sr, s];
7
- }
8
- const vr = decode(s);
9
- if (vr !== v) {
10
- throw ['decode', vr, v];
11
- }
5
+ assertEq(encode(v), s);
6
+ assertEq(decode(s), v);
12
7
  };
13
8
  export const proof = {
14
9
  empty: () => {
@@ -46,62 +41,29 @@ export const proof = {
46
41
  },
47
42
  nonOctet: () => {
48
43
  // encode rejects bit vectors whose length is not a multiple of 8
49
- if (encode(vec(1n)(0n)) !== null) {
50
- throw '1-bit input should return null';
51
- }
52
- if (encode(vec(6n)(0n)) !== null) {
53
- throw '6-bit input should return null';
54
- }
55
- if (encode(vec(12n)(0n)) !== null) {
56
- throw '12-bit input should return null';
57
- }
44
+ assertEq(encode(vec(1n)(0n)), null);
45
+ assertEq(encode(vec(6n)(0n)), null);
46
+ assertEq(encode(vec(12n)(0n)), null);
58
47
  },
59
48
  invalidInput: () => {
60
- if (decode('!') !== null) {
61
- throw 'invalid char should return null';
62
- }
63
- if (decode('A!AA') !== null) {
64
- throw 'invalid char mid-string should return null';
65
- }
66
- if (decode('A===') !== null) {
67
- throw 'three equals signs should return null';
68
- }
69
- if (decode('A') !== null) {
70
- throw 'length 1 (not multiple of 4) should return null';
71
- }
72
- if (decode('AA') !== null) {
73
- throw 'length 2 (no padding) should return null';
74
- }
75
- if (decode('AAA') !== null) {
76
- throw 'length 3 (no padding) should return null';
77
- }
78
- if (decode('=AAA') !== null) {
79
- throw 'padding not at end should return null';
80
- }
49
+ assertEq(decode('!'), null);
50
+ assertEq(decode('A!AA'), null);
51
+ assertEq(decode('A==='), null);
52
+ assertEq(decode('A'), null);
53
+ assertEq(decode('AA'), null);
54
+ assertEq(decode('AAA'), null);
55
+ assertEq(decode('=AAA'), null);
81
56
  // Non-zero padding bits must be rejected (RFC 4648 §3.5)
82
- if (decode('AB==') !== null) {
83
- throw 'non-zero 4-pad-bit should return null';
84
- }
85
- if (decode('AAB=') !== null) {
86
- throw 'non-zero 2-pad-bit should return null';
87
- }
57
+ assertEq(decode('AB=='), null);
58
+ assertEq(decode('AAB='), null);
88
59
  },
89
60
  validPadding: () => {
90
61
  // "==" padding
91
- const v2 = decode('AA==');
92
- if (v2 !== vec(8n)(0n)) {
93
- throw ['AA== should decode to 8-bit zero', v2];
94
- }
62
+ assertEq(decode('AA=='), vec(8n)(0n));
95
63
  // "=" padding
96
- const v1 = decode('AAA=');
97
- if (v1 !== vec(16n)(0n)) {
98
- throw ['AAA= should decode to 16-bit zero', v1];
99
- }
64
+ assertEq(decode('AAA='), vec(16n)(0n));
100
65
  // No padding
101
- const v0 = decode('AAAA');
102
- if (v0 !== vec(24n)(0n)) {
103
- throw ['AAAA should decode to 24-bit zero', v0];
104
- }
66
+ assertEq(decode('AAAA'), vec(24n)(0n));
105
67
  },
106
68
  knownVectors: () => {
107
69
  // RFC 4648 §10 test vectors (byte-aligned, treating bytes as 8-bit MSB vectors)
@@ -114,4 +76,11 @@ export const proof = {
114
76
  // 0x66 0x6f 0x6f ('foo') → Zm9v
115
77
  check('Zm9v', vec(24n)(0x666f6fn));
116
78
  },
79
+ decodeOverflow: () => {
80
+ // 174_764 base64 chars decode to 1_048_584 bits, 8 over `maxLength`;
81
+ // `decode` should return `null` instead of throwing on (or hanging
82
+ // while building) an oversized `bigint`.
83
+ const oversized = 'A'.repeat(174_764);
84
+ assertEq(decode(oversized), null);
85
+ },
117
86
  };
@@ -11,8 +11,10 @@
11
11
  *
12
12
  * @module
13
13
  */
14
- import { msb, length, vec, empty } from "../types/bit_vec/module.f.js";
15
- const { popFront, concat } = msb;
14
+ import { msb, lsb, length, vec } from "../types/bit_vec/module.f.js";
15
+ import {} from "../types/list/module.f.js";
16
+ const { popFront } = msb;
17
+ const { tryListToVec: reversedListToVec } = lsb;
16
18
  /**
17
19
  * Builds a {@link BaseN} codec for a fixed chunk width and alphabet.
18
20
  *
@@ -40,15 +42,19 @@ export const baseN = (bits, alphabet, normalize) => {
40
42
  return result;
41
43
  },
42
44
  stringToVec: s => {
43
- let result = empty;
45
+ // Build a reversed chunk list, bailing out at the first invalid
46
+ // character so malformed input is rejected in O(prefix) time and
47
+ // `normalize` is never run past it. `listToVec` then concatenates in
48
+ // O(n log n).
49
+ let chunks = null;
44
50
  for (const c of s) {
45
51
  const index = toIndex(c);
46
52
  if (index < 0) {
47
53
  return null;
48
54
  }
49
- result = concat(result)(vecN(BigInt(index)));
55
+ chunks = { first: vecN(BigInt(index)), tail: chunks };
50
56
  }
51
- return result;
57
+ return reversedListToVec(chunks);
52
58
  },
53
59
  };
54
60
  };
@@ -7,4 +7,5 @@ export declare const proof: {
7
7
  decodeInvalid: () => void;
8
8
  normalizeHit: () => void;
9
9
  normalizeMiss: () => void;
10
+ big: () => void;
10
11
  };
@@ -1,4 +1,5 @@
1
- import { empty, vec } from "../types/bit_vec/module.f.js";
1
+ import { assertEq } from "../asserts/module.f.js";
2
+ import { empty, maxLength, vec, length } from "../types/bit_vec/module.f.js";
2
3
  import { baseN } from "./module.f.js";
3
4
  const hex = baseN(4n, '0123456789abcdef');
4
5
  const cb32 = baseN(5n, '0123456789abcdefghjkmnpqrstvwxyz', c => {
@@ -18,6 +19,9 @@ const cb32 = baseN(5n, '0123456789abcdefghjkmnpqrstvwxyz', c => {
18
19
  }
19
20
  }
20
21
  });
22
+ // Sample input for the `big` proof below: 262 144 `f` characters decode into a
23
+ // 1 Mibit (`maxLength`) vector.
24
+ const bigSampleHex = `f`.repeat(Number(maxLength >> 2n));
21
25
  export const proof = {
22
26
  encodeEmpty: () => {
23
27
  const s = hex.vecToString(empty);
@@ -83,4 +87,11 @@ export const proof = {
83
87
  throw 'unknown char should return null';
84
88
  }
85
89
  },
90
+ // Decodes a 1 Mibit hex string. With the O(n log n) `listToVec` builder this
91
+ // runs in well under a second (was ~13 s node / ~43 s bun under the old
92
+ // per-chunk `concat`).
93
+ big: () => {
94
+ const x = hex.stringToVec(bigSampleHex);
95
+ assertEq(length(x), maxLength);
96
+ }
86
97
  };
@@ -1,11 +1,3 @@
1
- /**
2
- * Provides grammar-to-data conversion and LL(1) dispatch/matching types and helpers
3
- * for FunctionalScript BNF rules.
4
- *
5
- * @module
6
- */
7
- import { type CodePoint } from '../../text/utf16/module.f.ts';
8
- import { type RangeMapArray } from '../../types/range_map/module.f.ts';
9
1
  import { type Rule as FRule } from '../module.f.ts';
10
2
  import { type StringMap } from '../../types/object/module.f.ts';
11
3
  /**
@@ -31,103 +23,8 @@ export type Variant = StringMap<string, string>;
31
23
  export type Rule = Variant | Sequence | TerminalRange;
32
24
  /** The full grammar */
33
25
  export type RuleSet = Readonly<Record<string, Rule>>;
34
- type EmptyTag = string | true | undefined;
35
- type EmptyTagEntry = string | boolean;
36
- type DispatchRule = {
37
- readonly emptyTag: EmptyTag;
38
- readonly rangeMap: Dispatch;
39
- };
40
- type Dispatch = RangeMapArray<DispatchResult>;
41
- type DispatchResult = DispatchRuleCollection | null;
42
- type DispatchRuleOrName = DispatchRule | string;
43
- type DispatchRuleCollection = {
44
- readonly tag: string | undefined;
45
- readonly rules: DispatchRuleOrName[];
46
- };
47
- type DispatchMap = StringMap<string, DispatchRule>;
48
- type EmptyTagMap = StringMap<string, EmptyTagEntry>;
49
- /**
50
- * Recursive descent matcher for a single named rule.
51
- */
52
- export type DescentMatchRule<T> = (name: string, tag: AstTag, s: readonly CodePointMeta<T>[], idx: number) => DescentMatchResult<T>;
53
- /**
54
- * Result tuple of a descent match operation: AST node, success flag, and next index.
55
- */
56
- export type DescentMatchResult<T> = readonly [AstRuleMeta<T>, boolean, number];
57
- /**
58
- * Entry-point recursive descent matcher.
59
- */
60
- export type DescentMatch<T> = (name: string, s: readonly CodePointMeta<T>[]) => DescentMatchResult<T>;
61
- /**
62
- * Code point value paired with metadata.
63
- */
64
- export type CodePointMeta<T> = readonly [CodePoint, T];
65
- /**
66
- * AST sequence for the metadata-aware parser.
67
- */
68
- export type AstSequenceMeta<T> = readonly (AstRuleMeta<T> | CodePointMeta<T>)[];
69
- /**
70
- * Metadata-aware AST node.
71
- */
72
- export type AstRuleMeta<T> = {
73
- readonly tag: AstTag;
74
- readonly sequence: AstSequenceMeta<T>;
75
- };
76
- /**
77
- * Represents a parsed Abstract Syntax Tree (AST) sequence.
78
- */
79
- export type AstSequence = readonly (AstRule | CodePoint)[];
80
- export type AstTag = string | true | undefined;
81
- /**
82
- * Represents a parsed AST rule, consisting of a rule name and its parsed sequence.
83
- */
84
- type AstRule = {
85
- readonly tag: AstTag;
86
- readonly sequence: AstSequence;
87
- };
88
- /**
89
- * Represents the remaining input after a match attempt, or `null` if no match is possible.
90
- */
91
- export type Remainder = readonly CodePoint[] | null;
92
- /**
93
- * Parsing result of {@link parser} and {@link parserRuleSet}.
94
- *
95
- * Represents the result of a match operation, including the parsed AST rule and the remainder of the input.
96
- */
97
- export type MatchResult = readonly [AstRule, boolean, Remainder];
98
- /**
99
- * LL(1) parser function for matching by rule name.
100
- */
101
- export type Match = (name: string, s: readonly CodePoint[]) => MatchResult;
102
- /**
103
- * Internal match function signature used by compiled dispatch rules.
104
- */
105
- export type MatchRule = (dr: DispatchRule, s: readonly CodePoint[]) => MatchResult;
106
26
  /**
107
27
  * Converts a functional grammar rule into serializable BNF data and returns
108
28
  * the generated rule set with the entry rule identifier.
109
29
  */
110
30
  export declare const toData: (fr: FRule) => readonly [RuleSet, string];
111
- /**
112
- * Builds a dispatch map for a {@link RuleSet} to enable predictive parsing.
113
- */
114
- export declare const dispatchMap: (ruleSet: RuleSet) => DispatchMap;
115
- /**
116
- * Creates a map that describes whether each rule can consume empty input and,
117
- * for tagged variants, which tag represents the empty match.
118
- */
119
- export declare const createEmptyTagMap: (data: readonly [RuleSet, string]) => EmptyTagMap;
120
- /**
121
- * Creates a recursive descent parser that preserves metadata for each consumed
122
- * code point.
123
- */
124
- export declare const descentParser: <T>(fr: FRule) => DescentMatch<T>;
125
- /**
126
- * Creates an LL(1) parser from a functional grammar rule.
127
- */
128
- export declare const parser: (fr: FRule) => Match;
129
- /**
130
- * Creates an LL(1) parser from an already materialized {@link RuleSet}.
131
- */
132
- export declare const parserRuleSet: (ruleSet: RuleSet) => Match;
133
- export {};
@@ -1,17 +1,19 @@
1
1
  /**
2
- * Provides grammar-to-data conversion and LL(1) dispatch/matching types and helpers
3
- * for FunctionalScript BNF rules.
2
+ * The serializable BNF intermediate representation (IR) and the
3
+ * {@link toData} conversion from the functional grammar.
4
+ *
5
+ * This module is the pure, parser-agnostic substrate: it defines the
6
+ * {@link RuleSet} data form (`Rule = Variant | Sequence | TerminalRange`) and
7
+ * converts a functional grammar into it. The automaton builders that consume a
8
+ * {@link RuleSet} live in their own sibling modules (`fs/bnf/ll1`,
9
+ * `fs/bnf/descent`, …), so the IR stays free of any one parser's machinery.
4
10
  *
5
11
  * @module
6
12
  */
7
13
  import { stringToCodePointList } from "../../text/utf16/module.f.js";
8
- import { strictEqual } from "../../types/function/operator/module.f.js";
9
14
  import { map, toArray } from "../../types/list/module.f.js";
10
- import { rangeMap } from "../../types/range_map/module.f.js";
11
- import { contains, set } from "../../types/string_set/module.f.js";
12
- import { contains as rangeContains } from "../../types/range/module.f.js";
13
- import { oneEncode, rangeDecode, } from "../module.f.js";
14
- import { definedEntries } from "../../types/object/module.f.js";
15
+ import { oneEncode, } from "../module.f.js";
16
+ import {} from "../../types/object/module.f.js";
15
17
  const { entries } = Object;
16
18
  const find = (map) => (fr) => {
17
19
  for (const [k, v] of entries(map)) {
@@ -89,241 +91,3 @@ export const toData = (fr) => {
89
91
  const [, ruleSet, id] = toDataAdd({})(fr);
90
92
  return [ruleSet, id];
91
93
  };
92
- const dispatchOp = rangeMap({
93
- union: a => b => {
94
- if (a === null) {
95
- return b;
96
- }
97
- if (b === null) {
98
- return a;
99
- }
100
- throw ['can not merge [', a, '][', b, ']'];
101
- },
102
- equal: strictEqual,
103
- def: null,
104
- });
105
- /**
106
- * Builds a dispatch map for a {@link RuleSet} to enable predictive parsing.
107
- */
108
- export const dispatchMap = (ruleSet) => {
109
- const addRuleToDispatch = (dr, name) => {
110
- if (dr === null)
111
- return null;
112
- return { tag: dr.tag, rules: [...dr.rules, name] };
113
- };
114
- const addTagToDispatch = (dr, tag) => {
115
- if (dr === null)
116
- return null;
117
- return { tag, rules: dr.rules };
118
- };
119
- const dispatchRule = (dm, name, current) => {
120
- if (name in dm) {
121
- return dm;
122
- }
123
- const newCurrent = set(name)(current);
124
- const rule = ruleSet[name];
125
- if (typeof rule === 'number') {
126
- const range = rangeDecode(rule);
127
- const dispatch = dispatchOp.fromRange(range)({ tag: undefined, rules: [] });
128
- const dr = { emptyTag: undefined, rangeMap: dispatch };
129
- return { ...dm, [name]: dr };
130
- }
131
- else if (rule instanceof Array) {
132
- let emptyTag = true;
133
- let result = [];
134
- for (const item of rule) {
135
- if (contains(item)(newCurrent)) {
136
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]]);
137
- }
138
- else {
139
- dm = dispatchRule(dm, item, newCurrent);
140
- const dr = dm[item];
141
- if (emptyTag === true) {
142
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]]);
143
- result = toArray(dispatchOp.merge(result)(dr.rangeMap));
144
- emptyTag = dr.emptyTag !== undefined ? true : undefined;
145
- }
146
- else {
147
- result = result.map(x => [addRuleToDispatch(x[0], item), x[1]]);
148
- }
149
- }
150
- }
151
- const dr = { emptyTag, rangeMap: result };
152
- return { ...dm, [name]: dr };
153
- }
154
- else {
155
- const entries = definedEntries(rule);
156
- let result = [];
157
- let emptyTag = undefined;
158
- for (const [tag, item] of entries) {
159
- dm = dispatchRule(dm, item, newCurrent);
160
- const dr = dm[item];
161
- if (dr.emptyTag !== undefined) {
162
- emptyTag = tag;
163
- }
164
- else {
165
- const d = dr.rangeMap.map(x => [addTagToDispatch(x[0], tag), x[1]]);
166
- result = toArray(dispatchOp.merge(result)(d));
167
- }
168
- }
169
- const dr = { emptyTag, rangeMap: result };
170
- return { ...dm, [name]: dr };
171
- }
172
- };
173
- let result = {};
174
- for (const k in ruleSet) {
175
- result = dispatchRule(result, k, null);
176
- }
177
- return result;
178
- };
179
- const emptyTagMapAdd = (ruleSet) => (map) => (name) => {
180
- if (name in map) {
181
- return [ruleSet, map, map[name]];
182
- }
183
- const rule = ruleSet[name];
184
- if (typeof rule === 'number') {
185
- return [ruleSet, { ...map, [name]: false }, false];
186
- }
187
- else if (rule instanceof Array) {
188
- map = { ...map, [name]: true };
189
- let emptyTag = rule.length == 0;
190
- for (const item of rule) {
191
- const [, newMap, itemEmptyTag] = emptyTagMapAdd(ruleSet)(map)(item);
192
- map = newMap;
193
- if (emptyTag === false) {
194
- emptyTag = itemEmptyTag !== false;
195
- }
196
- }
197
- return [ruleSet, { ...map, [name]: emptyTag }, emptyTag];
198
- }
199
- else {
200
- map = { ...map, [name]: true };
201
- const entries = definedEntries(rule);
202
- let emptyTag = false;
203
- for (const [tag, item] of entries) {
204
- const [, newMap, itemEmptyTag] = emptyTagMapAdd(ruleSet)(map)(item);
205
- map = newMap;
206
- if (itemEmptyTag !== false) {
207
- emptyTag = tag;
208
- }
209
- }
210
- return [ruleSet, { ...map, [name]: emptyTag }, emptyTag];
211
- }
212
- };
213
- /**
214
- * Creates a map that describes whether each rule can consume empty input and,
215
- * for tagged variants, which tag represents the empty match.
216
- */
217
- export const createEmptyTagMap = (data) => {
218
- return emptyTagMapAdd(data[0])({})(data[1])[1];
219
- };
220
- /**
221
- * Creates a recursive descent parser that preserves metadata for each consumed
222
- * code point.
223
- */
224
- export const descentParser = (fr) => {
225
- const data = toData(fr);
226
- const emptyTagMap = createEmptyTagMap(data);
227
- const getEmptyTag = (name) => {
228
- const res = emptyTagMap[name];
229
- return res === false ? undefined : res;
230
- };
231
- const f = (name, tag, cp, idx) => {
232
- const mrSuccess = (tag, sequence, idx) => [{ tag, sequence }, true, idx];
233
- const mrFail = (tag, sequence, idx) => [{ tag, sequence }, false, idx];
234
- const rule = data[0][name];
235
- if (typeof rule === 'number') {
236
- const emptyTag = getEmptyTag(name);
237
- if (idx >= cp.length) {
238
- return emptyTag === undefined ? mrFail(emptyTag, [], idx) : mrSuccess(emptyTag, [], idx);
239
- }
240
- const cpi = cp[idx];
241
- const range = rangeDecode(rule);
242
- if (rangeContains(range)(cpi[0])) {
243
- return mrSuccess(tag, [cpi], idx + 1);
244
- }
245
- return mrFail(emptyTag, [], idx);
246
- }
247
- else if (rule instanceof Array) {
248
- let seq = [];
249
- let tidx = idx;
250
- for (const item of rule) {
251
- const m = f(item, undefined, cp, tidx);
252
- const [astRule, success, nidx] = m;
253
- tidx = nidx;
254
- if (success === false) {
255
- return mrFail(tag, [], idx);
256
- }
257
- seq = [...seq, astRule];
258
- }
259
- return mrSuccess(tag, seq, tidx);
260
- }
261
- else {
262
- const entries = definedEntries(rule);
263
- const emptyTag = getEmptyTag(name);
264
- let emptyResult = mrFail(emptyTag, [], idx);
265
- for (const [tag, item] of entries) {
266
- const m = f(item, tag, cp, idx);
267
- if (m[1]) {
268
- if (idx !== m[2])
269
- return m;
270
- emptyResult = m;
271
- }
272
- }
273
- return emptyResult;
274
- }
275
- };
276
- const match = (name, cp) => {
277
- return f(name, undefined, cp, 0);
278
- };
279
- return match;
280
- };
281
- /**
282
- * Creates an LL(1) parser from a functional grammar rule.
283
- */
284
- export const parser = (fr) => {
285
- const data = toData(fr);
286
- return parserRuleSet(data[0]);
287
- };
288
- /**
289
- * Creates an LL(1) parser from an already materialized {@link RuleSet}.
290
- */
291
- export const parserRuleSet = (ruleSet) => {
292
- const map = dispatchMap(ruleSet);
293
- const f = (rule, cp) => {
294
- const mrSuccess = (tag, sequence, r) => [{ tag, sequence }, true, r];
295
- const mrFail = (tag, sequence, r) => [{ tag, sequence }, false, r];
296
- const { emptyTag, rangeMap } = rule;
297
- if (cp.length === 0) {
298
- return mrSuccess(emptyTag, [], emptyTag === undefined ? null : cp);
299
- }
300
- const cp0 = cp[0];
301
- const dr = dispatchOp.get(cp0)(rangeMap);
302
- if (dr === null) {
303
- if (emptyTag === undefined) {
304
- return mrFail(emptyTag, [], cp);
305
- }
306
- return mrSuccess(emptyTag, [], cp);
307
- }
308
- let seq = [cp0];
309
- let r = cp;
310
- const [_, ...restCp] = cp;
311
- r = restCp;
312
- const { tag, rules } = dr;
313
- for (const i of rules) {
314
- const rule = typeof i === 'string' ? map[i] : i;
315
- const res = f(rule, r);
316
- const [astRule, success, newR] = res;
317
- if (success === false) {
318
- return res;
319
- }
320
- seq = [...seq, astRule];
321
- if (newR === null) {
322
- return mrSuccess(tag, seq, null);
323
- }
324
- r = newR;
325
- }
326
- return mrSuccess(tag, seq, r);
327
- };
328
- return (name, cp) => f(map[name], cp);
329
- };
@@ -2,16 +2,6 @@ export declare const proof: {
2
2
  rangeDecode: () => void;
3
3
  rangeEncode: () => void;
4
4
  toData: (() => void)[];
5
- emptyTags: (() => void)[];
6
5
  variantTest: () => void;
7
- dispatch: (() => void)[];
8
- parser: (() => void)[];
9
- descentParser: (() => void)[];
10
- descentParserWithMeta: (() => void)[];
11
- repeat: (() => void)[];
12
- repeatParser: (() => void)[];
13
6
  example: () => void;
14
- throw: {
15
- ambiguousVariantDispatch: () => void;
16
- };
17
7
  };