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.
- package/README.md +27 -14
- package/fs/asserts/module.f.d.ts +1 -1
- package/fs/asserts/module.f.js +4 -1
- package/fs/base64/proof.f.d.ts +1 -0
- package/fs/base64/proof.f.js +25 -56
- package/fs/base_n/module.f.js +11 -5
- package/fs/base_n/proof.f.d.ts +1 -0
- package/fs/base_n/proof.f.js +12 -1
- package/fs/bnf/data/module.f.d.ts +0 -103
- package/fs/bnf/data/module.f.js +10 -246
- package/fs/bnf/data/proof.f.d.ts +0 -10
- package/fs/bnf/data/proof.f.js +2 -642
- package/fs/bnf/descent/module.f.d.ts +56 -0
- package/fs/bnf/descent/module.f.js +119 -0
- package/fs/bnf/descent/proof.f.d.ts +5 -0
- package/fs/bnf/descent/proof.f.js +296 -0
- package/fs/bnf/ll1/module.f.d.ts +72 -0
- package/fs/bnf/ll1/module.f.js +153 -0
- package/fs/bnf/ll1/proof.f.d.ts +9 -0
- package/fs/bnf/ll1/proof.f.js +358 -0
- package/fs/cas/cli/module.f.d.ts +6 -0
- package/fs/cas/cli/module.f.js +56 -0
- package/fs/cas/cli/proof.f.d.ts +14 -0
- package/fs/cas/cli/proof.f.js +149 -0
- package/fs/cas/mcp/module.f.d.ts +9 -17
- package/fs/cas/mcp/module.f.js +123 -96
- package/fs/cas/mcp/proof.f.d.ts +12 -1
- package/fs/cas/mcp/proof.f.js +199 -32
- package/fs/cas/module.f.d.ts +35 -15
- package/fs/cas/module.f.js +168 -131
- package/fs/cas/proof.f.d.ts +16 -13
- package/fs/cas/proof.f.js +255 -121
- package/fs/ci/config/module.f.d.ts +14 -14
- package/fs/ci/config/module.f.js +14 -14
- package/fs/djs/tokenizer-new/module.f.js +1 -1
- package/fs/djs/tokenizer-new/proof.f.d.ts +2 -0
- package/fs/djs/tokenizer-new/proof.f.js +357 -229
- package/fs/effects/list/module.f.d.ts +31 -0
- package/fs/effects/list/module.f.js +16 -0
- package/fs/effects/module.f.d.ts +11 -5
- package/fs/effects/module.f.js +16 -6
- package/fs/effects/node/module.f.d.ts +29 -3
- package/fs/effects/node/module.f.js +35 -1
- package/fs/effects/node/module.js +32 -18
- package/fs/effects/node/virtual/module.f.js +57 -0
- package/fs/effects/proof.f.d.ts +4 -0
- package/fs/effects/proof.f.js +18 -1
- package/fs/fjs/module.f.js +2 -8
- package/fs/html/module.f.js +6 -8
- package/fs/mcp/module.f.d.ts +1 -1
- package/fs/mcp/stdio/module.f.d.ts +8 -0
- package/fs/mcp/stdio/module.f.js +33 -6
- package/fs/mcp/stdio/proof.f.d.ts +3 -0
- package/fs/mcp/stdio/proof.f.js +52 -1
- package/fs/mime/module.f.d.ts +101 -5
- package/fs/mime/module.f.js +159 -6
- package/fs/mime/proof.f.d.ts +25 -0
- package/fs/mime/proof.f.js +193 -3
- package/fs/text/code_point/module.f.d.ts +39 -2
- package/fs/text/code_point/module.f.js +74 -2
- package/fs/text/code_point/proof.f.d.ts +8 -0
- package/fs/text/code_point/proof.f.js +72 -0
- package/fs/text/module.f.d.ts +11 -1
- package/fs/text/module.f.js +13 -3
- package/fs/text/proof.f.d.ts +6 -0
- package/fs/text/proof.f.js +25 -2
- package/fs/text/utf16/module.f.js +4 -26
- package/fs/text/utf8/module.f.d.ts +27 -0
- package/fs/text/utf8/module.f.js +8 -14
- package/fs/types/bit_vec/module.f.d.ts +20 -2
- package/fs/types/bit_vec/module.f.js +77 -27
- package/fs/types/bit_vec/proof.f.d.ts +8 -0
- package/fs/types/bit_vec/proof.f.js +24 -6
- package/fs/types/function/operator/module.f.d.ts +26 -0
- package/fs/types/list/module.f.d.ts +3 -0
- package/fs/types/nullable/module.f.d.ts +9 -5
- package/fs/types/nullable/module.f.js +19 -0
- package/fs/types/result/module.d.ts +2 -4
- package/fs/types/result/module.js +9 -3
- package/package.json +3 -3
- package/issues/demo/data/data.f.js +0 -12
- package/issues/demo/data/shared.f.js +0 -3
- package/issues/demo/fs/app.js +0 -4
- package/issues/demo/fs/math.f.js +0 -4
- package/issues/demo/sample/proof.f.js +0 -13
- /package/{issues → todo}/031-json.f.d.ts +0 -0
- /package/{issues → todo}/031-json.f.js +0 -0
- /package/{issues → todo}/proof.f.d.ts +0 -0
- /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](./
|
|
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
|
|
31
|
+
The `fjs` CLI provides several commands:
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
42
|
+
### Content-Addressable Storage (CAS)
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
FunctionalScript ships a built-in CAS for storing and retrieving blobs by their cryptographic hash:
|
|
39
45
|
|
|
40
46
|
```bash
|
|
41
|
-
fjs
|
|
42
|
-
#
|
|
43
|
-
fjs
|
|
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
|
-
|
|
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),
|
package/fs/asserts/module.f.d.ts
CHANGED
|
@@ -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>(
|
|
3
|
+
export declare const assertEq: <T>(...x: readonly [T, T, unknown?]) => void;
|
|
4
4
|
export type Assert<T extends true> = T;
|
package/fs/asserts/module.f.js
CHANGED
package/fs/base64/proof.f.d.ts
CHANGED
package/fs/base64/proof.f.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/fs/base_n/module.f.js
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* @module
|
|
13
13
|
*/
|
|
14
|
-
import { msb, length, vec
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
+
chunks = { first: vecN(BigInt(index)), tail: chunks };
|
|
50
56
|
}
|
|
51
|
-
return
|
|
57
|
+
return reversedListToVec(chunks);
|
|
52
58
|
},
|
|
53
59
|
};
|
|
54
60
|
};
|
package/fs/base_n/proof.f.d.ts
CHANGED
package/fs/base_n/proof.f.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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 {};
|
package/fs/bnf/data/module.f.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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 {
|
|
11
|
-
import {
|
|
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
|
-
};
|
package/fs/bnf/data/proof.f.d.ts
CHANGED
|
@@ -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
|
};
|