functionalscript 0.38.0 → 0.40.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 +1 -1
- package/fjs/bnf/descent/module.f.d.ts +43 -2
- package/fjs/bnf/descent/module.f.js +37 -12
- package/fjs/bnf/descent/proof.f.d.ts +1 -0
- package/fjs/bnf/descent/proof.f.js +73 -32
- package/fjs/bnf/ll1/module.f.js +2 -2
- package/fjs/bnf/token_symbol/module.f.d.ts +38 -0
- package/fjs/bnf/token_symbol/module.f.js +48 -0
- package/fjs/bnf/token_symbol/proof.f.d.ts +10 -0
- package/fjs/bnf/token_symbol/proof.f.js +37 -0
- package/fjs/cas/cli/module.f.js +6 -9
- package/fjs/cas/evo/module.f.d.ts +77 -13
- package/fjs/cas/evo/module.f.js +130 -18
- package/fjs/cas/evo/proof.f.d.ts +7 -0
- package/fjs/cas/evo/proof.f.js +120 -1
- package/fjs/cas/module.f.js +64 -37
- package/fjs/cas/proof.f.d.ts +3 -9
- package/fjs/cas/proof.f.js +94 -74
- package/fjs/ci/bun/module.f.js +3 -3
- package/fjs/ci/common/module.f.d.ts +1 -2
- package/fjs/ci/common/module.f.js +1 -5
- package/fjs/ci/config/module.f.d.ts +11 -8
- package/fjs/ci/config/module.f.js +24 -9
- package/fjs/ci/deno/module.f.js +3 -3
- package/fjs/ci/module.f.js +13 -7
- package/fjs/ci/nix/module.f.d.ts +61 -0
- package/fjs/ci/nix/module.f.js +92 -0
- package/fjs/ci/nix/proof.f.d.ts +23 -0
- package/fjs/ci/nix/proof.f.js +109 -0
- package/fjs/ci/node/module.f.d.ts +23 -1
- package/fjs/ci/node/module.f.js +58 -12
- package/fjs/ci/proof.f.d.ts +2 -0
- package/fjs/ci/proof.f.js +49 -9
- package/fjs/cli/module.f.js +2 -2
- package/fjs/common/monoid/module.f.d.ts +46 -2
- package/fjs/common/monoid/module.f.js +44 -0
- package/fjs/common/monoid/proof.f.d.ts +5 -0
- package/fjs/common/monoid/proof.f.js +27 -1
- package/fjs/dev/module.f.d.ts +1 -0
- package/fjs/dev/module.f.js +38 -27
- package/fjs/dev/update/module.f.d.ts +11 -0
- package/fjs/dev/update/module.f.js +20 -0
- package/fjs/dev/update/proof.f.d.ts +7 -0
- package/fjs/dev/update/proof.f.js +35 -0
- package/fjs/djs/ast/module.f.d.ts +47 -0
- package/fjs/djs/ast/module.f.js +9 -0
- package/fjs/djs/module.f.js +5 -8
- package/fjs/djs/parser/module.f.d.ts +3 -0
- package/fjs/djs/parser/module.f.js +17 -0
- package/fjs/djs/tokenizer/module.f.js +2 -2
- package/fjs/djs/tokenizer/proof.f.d.ts +1 -0
- package/fjs/djs/tokenizer/proof.f.js +70 -16
- package/fjs/djs/transpiler/module.f.js +6 -6
- package/fjs/effects/eff/module.f.d.ts +72 -0
- package/fjs/effects/eff/module.f.js +46 -0
- package/fjs/effects/eff/proof.f.d.ts +11 -0
- package/fjs/effects/eff/proof.f.js +58 -0
- package/fjs/effects/list/module.f.d.ts +11 -5
- package/fjs/effects/list/module.f.js +2 -1
- package/fjs/effects/memory/proof.f.js +7 -8
- package/fjs/effects/module.f.d.ts +334 -52
- package/fjs/effects/module.f.js +253 -45
- package/fjs/effects/node/memory/proof.js +3 -2
- package/fjs/effects/node/module.d.ts +3 -3
- package/fjs/effects/node/module.f.d.ts +18 -8
- package/fjs/effects/node/module.f.js +31 -11
- package/fjs/effects/node/module.js +12 -12
- package/fjs/effects/node/proof.f.d.ts +1 -0
- package/fjs/effects/node/proof.f.js +32 -11
- package/fjs/effects/node/virtual/module.f.js +1 -1
- package/fjs/effects/proof.f.d.ts +32 -6
- package/fjs/effects/proof.f.js +131 -39
- package/fjs/emergent_testing/all.test.js +2 -1
- package/fjs/emergent_testing/module.f.d.ts +3 -3
- package/fjs/emergent_testing/module.f.js +63 -49
- package/fjs/emergent_testing/proof.f.d.ts +4 -0
- package/fjs/emergent_testing/proof.f.js +54 -21
- package/fjs/emergent_testing/scenarios/thenable.pass.js +1 -1
- package/fjs/fsc/module.f.js +4 -4
- package/fjs/fsm/module.f.js +1 -1
- package/fjs/js/tokenizer/module.f.d.ts +1 -0
- package/fjs/js/tokenizer/module.f.js +13 -6
- package/fjs/{cas/mcp → mcp/cas}/module.f.d.ts +3 -25
- package/fjs/{cas/mcp → mcp/cas}/module.f.js +26 -64
- package/fjs/mcp/evo/module.f.d.ts +27 -0
- package/fjs/mcp/evo/module.f.js +94 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +2 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +40 -9
- package/fjs/mcp/module.f.d.ts +54 -237
- package/fjs/mcp/module.f.js +55 -258
- package/fjs/mcp/proof.f.d.ts +43 -32
- package/fjs/mcp/proof.f.js +509 -200
- package/fjs/media/nix/module.f.d.ts +30 -0
- package/fjs/media/nix/module.f.js +166 -0
- package/fjs/media/nix/proof.f.d.ts +32 -0
- package/fjs/media/nix/proof.f.js +127 -0
- package/fjs/media/type/module.f.js +2 -2
- package/fjs/media/type/proof.f.js +7 -10
- package/fjs/module.f.js +5 -9
- package/fjs/protocol/json_rpc/module.f.d.ts +114 -0
- package/fjs/{media/json/rpc → protocol/json_rpc}/module.f.js +3 -3
- package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.js +3 -3
- package/fjs/protocol/mcp/module.f.d.ts +239 -0
- package/fjs/protocol/mcp/module.f.js +272 -0
- package/fjs/protocol/mcp/proof.f.d.ts +34 -0
- package/fjs/protocol/mcp/proof.f.js +208 -0
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.d.ts +7 -7
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +22 -22
- package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
- package/fjs/types/bigint/module.f.js +4 -3
- package/fjs/types/bit_vec/module.f.js +5 -15
- package/fjs/types/btree/remove/module.f.d.ts +5 -0
- package/fjs/types/btree/remove/module.f.js +12 -0
- package/fjs/types/list/module.f.d.ts +21 -0
- package/fjs/types/list/module.f.js +17 -0
- package/fjs/types/list/proof.f.d.ts +1 -0
- package/fjs/types/list/proof.f.js +24 -1
- package/fjs/types/number/module.f.js +2 -1
- package/fjs/types/range_map/module.f.d.ts +14 -13
- package/fjs/types/range_map/module.f.js +18 -13
- package/fjs/types/range_map/proof.f.js +26 -39
- package/fjs/types/range_set/module.f.d.ts +5 -0
- package/fjs/types/range_set/module.f.js +16 -0
- package/fjs/types/range_set/proof.f.d.ts +1 -0
- package/fjs/types/range_set/proof.f.js +19 -0
- package/fjs/types/string/module.f.js +10 -2
- package/fjs/website/module.f.js +2 -3
- package/package.json +5 -5
- package/fjs/cas/evo/mcp/module.f.d.ts +0 -19
- package/fjs/cas/evo/mcp/module.f.js +0 -56
- package/fjs/cas/mcp/proof.f.d.ts +0 -45
- package/fjs/cas/mcp/proof.f.js +0 -545
- package/fjs/ci/playwright/module.f.d.ts +0 -2
- package/fjs/ci/playwright/module.f.js +0 -25
- package/fjs/media/json/rpc/module.f.d.ts +0 -114
- /package/fjs/{media/json/rpc → protocol/json_rpc}/proof.f.d.ts +0 -0
- /package/fjs/{mcp → protocol/mcp}/stdio/proof.f.d.ts +0 -0
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ The CAS is also exposed as an [MCP](https://modelcontextprotocol.io/) server so
|
|
|
60
60
|
claude mcp add cas -- npx functionalscript m
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
See [`fjs/
|
|
63
|
+
See [`fjs/mcp/README.md`](fjs/mcp/README.md) for details on the `cas_add`, `cas_get`, and `cas_list` tools.
|
|
64
64
|
|
|
65
65
|
## Vision
|
|
66
66
|
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
* AST ({@link AstRuleMeta}). Nullability (which rule can match empty input) is
|
|
8
8
|
* computed once by {@link emptyTagMap} in `fjs/bnf/data`.
|
|
9
9
|
*
|
|
10
|
+
* A failed result also carries a {@link DescentFailure}: the furthest position a
|
|
11
|
+
* terminal was rejected at, which — unlike the result's own index — never
|
|
12
|
+
* rewinds and is what diagnostics should be built from.
|
|
13
|
+
*
|
|
10
14
|
* @module
|
|
11
15
|
*/
|
|
12
16
|
import { type CodePoint } from '../../text/utf16/module.f.ts';
|
|
17
|
+
import { type TerminalRange } from '../module.f.ts';
|
|
13
18
|
import { type Rule as FRule } from '../module.f.ts';
|
|
14
19
|
export type AstTag = string | true | undefined;
|
|
15
20
|
/**
|
|
@@ -17,9 +22,45 @@ export type AstTag = string | true | undefined;
|
|
|
17
22
|
*/
|
|
18
23
|
export type DescentMatchRule<T> = (name: string, tag: AstTag, s: readonly CodePointMeta<T>[], idx: number) => DescentMatchResult<T>;
|
|
19
24
|
/**
|
|
20
|
-
*
|
|
25
|
+
* Where a match ran out of road, for diagnostics.
|
|
26
|
+
*
|
|
27
|
+
* `idx` is the furthest position any terminal was tried at and rejected, and
|
|
28
|
+
* `expected` holds the terminals that would have allowed progress there, in the
|
|
29
|
+
* order the grammar tried them and without repeats.
|
|
30
|
+
*
|
|
31
|
+
* Unlike a failed result's own index, this never rewinds: a failing sequence
|
|
32
|
+
* item rewinds the result to the sequence's start, while the furthest failure is
|
|
33
|
+
* a high-water mark over the whole match — including branches the grammar
|
|
34
|
+
* backtracked out of. That is what makes "expected X or Y at N" possible.
|
|
35
|
+
*
|
|
36
|
+
* `idx` is `0` with an empty `expected` when the match failed without ever
|
|
37
|
+
* rejecting a terminal, as an empty variant does.
|
|
21
38
|
*/
|
|
22
|
-
export type
|
|
39
|
+
export type DescentFailure = {
|
|
40
|
+
readonly idx: number;
|
|
41
|
+
readonly expected: readonly TerminalRange[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Result of a descent match operation.
|
|
45
|
+
*
|
|
46
|
+
* `failure` is present exactly when `success` is `false`: a successful match has
|
|
47
|
+
* nothing to diagnose, and its `idx` already says where matching stopped. Note
|
|
48
|
+
* the consequence for a match that succeeds *without consuming all input* —
|
|
49
|
+
* `idx` still locates the position it stopped at, but the terminals that would
|
|
50
|
+
* have let it continue are not reported.
|
|
51
|
+
*
|
|
52
|
+
* On failure `idx` has rewound to the start of the enclosing sequence and
|
|
53
|
+
* locates nothing; read `failure.idx` instead.
|
|
54
|
+
*
|
|
55
|
+
* The same type describes a match in progress, where `failure` is likewise
|
|
56
|
+
* absent until the match ends.
|
|
57
|
+
*/
|
|
58
|
+
export type DescentMatchResult<T> = {
|
|
59
|
+
readonly ast: AstRuleMeta<T>;
|
|
60
|
+
readonly success: boolean;
|
|
61
|
+
readonly idx: number;
|
|
62
|
+
readonly failure?: DescentFailure;
|
|
63
|
+
};
|
|
23
64
|
/**
|
|
24
65
|
* Entry-point recursive descent matcher.
|
|
25
66
|
*/
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* AST ({@link AstRuleMeta}). Nullability (which rule can match empty input) is
|
|
8
8
|
* computed once by {@link emptyTagMap} in `fjs/bnf/data`.
|
|
9
9
|
*
|
|
10
|
+
* A failed result also carries a {@link DescentFailure}: the furthest position a
|
|
11
|
+
* terminal was rejected at, which — unlike the result's own index — never
|
|
12
|
+
* rewinds and is what diagnostics should be built from.
|
|
13
|
+
*
|
|
10
14
|
* @module
|
|
11
15
|
*/
|
|
12
16
|
import {} from '../../text/utf16/module.f.js';
|
|
@@ -15,6 +19,20 @@ import { contains as rangeContains } from '../../types/range/module.f.js';
|
|
|
15
19
|
import { definedEntries } from '../../types/object/module.f.js';
|
|
16
20
|
import { emptyTagMap, toData } from '../data/module.f.js';
|
|
17
21
|
import {} from '../module.f.js';
|
|
22
|
+
/**
|
|
23
|
+
* Folds one rejected terminal into the furthest-failure record: further along
|
|
24
|
+
* replaces, the same position accumulates (ignoring repeats), earlier is
|
|
25
|
+
* discarded.
|
|
26
|
+
*/
|
|
27
|
+
const recordFailure = (failure, idx, terminal) => {
|
|
28
|
+
if (idx > failure.idx) {
|
|
29
|
+
return { idx, expected: [terminal] };
|
|
30
|
+
}
|
|
31
|
+
if (idx < failure.idx || failure.expected.includes(terminal)) {
|
|
32
|
+
return failure;
|
|
33
|
+
}
|
|
34
|
+
return { idx, expected: [...failure.expected, terminal] };
|
|
35
|
+
};
|
|
18
36
|
/**
|
|
19
37
|
* Creates a recursive descent parser that preserves metadata for each consumed
|
|
20
38
|
* code point.
|
|
@@ -29,11 +47,14 @@ export const descentParser = (fr) => {
|
|
|
29
47
|
// grammar recursion depth — right-recursive rules (e.g. repeat0Plus chains) no longer
|
|
30
48
|
// overflow on long input (see the longInput proof group).
|
|
31
49
|
const f = (name, tag, cp, idx) => {
|
|
32
|
-
const mrSuccess = (tag, sequence, idx) =>
|
|
33
|
-
const mrFail = (tag, sequence, idx) =>
|
|
50
|
+
const mrSuccess = (tag, sequence, idx) => ({ ast: { tag, sequence }, success: true, idx });
|
|
51
|
+
const mrFail = (tag, sequence, idx) => ({ ast: { tag, sequence }, success: false, idx });
|
|
34
52
|
let stack = null;
|
|
35
53
|
let task = { name, tag, idx };
|
|
36
54
|
let result = mrFail(undefined, [], idx);
|
|
55
|
+
// High-water mark across the whole match, so it survives the rewinds a
|
|
56
|
+
// failing sequence item does to `result`.
|
|
57
|
+
let furthest = { idx: 0, expected: [] };
|
|
37
58
|
while (true) {
|
|
38
59
|
if (task !== null) {
|
|
39
60
|
const { name, tag, idx } = task;
|
|
@@ -43,14 +64,17 @@ export const descentParser = (fr) => {
|
|
|
43
64
|
// later `task` assignments that `name`'s narrowing depends on.
|
|
44
65
|
const rule = data[0][name];
|
|
45
66
|
if (typeof rule === 'number') {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
67
|
+
// No nullable case: `emptyTagOf` in `bnf/data` returns `undefined`
|
|
68
|
+
// for every terminal, so `emptyTags[name]` here is always
|
|
69
|
+
// `undefined` and a terminal either consumes one symbol or fails.
|
|
70
|
+
if (idx < cp.length && rangeContains(...rangeDecode(rule))(cp[idx][0])) {
|
|
71
|
+
result = mrSuccess(tag, [cp[idx]], idx + 1);
|
|
49
72
|
}
|
|
50
73
|
else {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
74
|
+
// The only place a terminal is rejected, so the only place
|
|
75
|
+
// the furthest failure can advance.
|
|
76
|
+
furthest = recordFailure(furthest, idx, rule);
|
|
77
|
+
result = mrFail(undefined, [], idx);
|
|
54
78
|
}
|
|
55
79
|
}
|
|
56
80
|
else if (rule instanceof Array) {
|
|
@@ -78,12 +102,13 @@ export const descentParser = (fr) => {
|
|
|
78
102
|
continue;
|
|
79
103
|
}
|
|
80
104
|
if (stack === null) {
|
|
81
|
-
|
|
105
|
+
// A success has nothing to diagnose; only a failure carries it.
|
|
106
|
+
return result.success ? result : { ...result, failure: furthest };
|
|
82
107
|
}
|
|
83
108
|
const frame = stack.top;
|
|
84
109
|
stack = stack.rest;
|
|
85
110
|
if (frame.kind === 'seq') {
|
|
86
|
-
const
|
|
111
|
+
const { ast: astRule, success, idx: nidx } = result;
|
|
87
112
|
if (success === false) {
|
|
88
113
|
result = mrFail(frame.tag, [], frame.startIdx);
|
|
89
114
|
}
|
|
@@ -102,8 +127,8 @@ export const descentParser = (fr) => {
|
|
|
102
127
|
else {
|
|
103
128
|
// success that consumed input wins immediately: the frame stays popped and
|
|
104
129
|
// `result` propagates to the frame below, matching the recursive `return m`.
|
|
105
|
-
if (!(result
|
|
106
|
-
const emptyResult = result
|
|
130
|
+
if (!(result.success && frame.idx !== result.idx)) {
|
|
131
|
+
const emptyResult = result.success ? result : frame.emptyResult;
|
|
107
132
|
const entryIndex = frame.entryIndex + 1;
|
|
108
133
|
if (entryIndex < frame.entries.length) {
|
|
109
134
|
stack = { top: { ...frame, entryIndex, emptyResult }, rest: stack };
|
|
@@ -4,8 +4,12 @@ import { commaJoin0Plus, option, range, repeat0Plus, set } from '../module.f.js'
|
|
|
4
4
|
import { deterministic } from '../testlib.f.js';
|
|
5
5
|
import { emptyTagMap, toData } from '../data/module.f.js';
|
|
6
6
|
import { descentParser } from './module.f.js';
|
|
7
|
-
import { assertEq } from '../../asserts/module.f.js';
|
|
7
|
+
import { assertEq, assertNotNullish } from '../../asserts/module.f.js';
|
|
8
8
|
const mapCodePoint = (cp) => [cp, undefined];
|
|
9
|
+
// The code point of a one-character string, for expectations that would
|
|
10
|
+
// otherwise spell it as a bare number. Goes through the module's own
|
|
11
|
+
// conversion, the same one that builds the parser's input.
|
|
12
|
+
const cp1 = (s) => toArray(stringToCodePointList(s))[0];
|
|
9
13
|
const descentParserCpOnly = (m, name, cp) => {
|
|
10
14
|
const cpm = toArray(map(mapCodePoint)(cp));
|
|
11
15
|
return m(name, cpm);
|
|
@@ -83,52 +87,59 @@ export const proof = {
|
|
|
83
87
|
const m = descentParser(emptyRule);
|
|
84
88
|
const mr = m("", []);
|
|
85
89
|
const result = JSON.stringify(mr);
|
|
86
|
-
if (result !== '
|
|
90
|
+
if (result !== '{"ast":{"sequence":[]},"success":true,"idx":0}') {
|
|
87
91
|
throw result;
|
|
88
92
|
}
|
|
89
93
|
},
|
|
90
94
|
() => {
|
|
91
95
|
const emptyRule = '';
|
|
92
96
|
const m = descentParser(emptyRule);
|
|
93
|
-
const mr = descentParserCpOnly(m, "",
|
|
97
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('AF')));
|
|
94
98
|
const result = JSON.stringify(mr);
|
|
95
|
-
if (result !== '
|
|
99
|
+
if (result !== '{"ast":{"sequence":[]},"success":true,"idx":0}') {
|
|
96
100
|
throw result;
|
|
97
101
|
}
|
|
98
102
|
},
|
|
99
103
|
() => {
|
|
104
|
+
// Literal code point on purpose. Elsewhere both sides of the
|
|
105
|
+
// assertion come from `stringToCodePointList` — the input is built
|
|
106
|
+
// with it and the expectation interpolates `cp1`, which calls it
|
|
107
|
+
// too — so a change to the conversion would move both sides
|
|
108
|
+
// together and the test would still pass. Pinning `A` to 65 here
|
|
109
|
+
// covers the conversion itself.
|
|
100
110
|
const terminalRangeRule = range('AF');
|
|
101
111
|
const m = descentParser(terminalRangeRule);
|
|
102
|
-
const mr = descentParserCpOnly(m, "",
|
|
112
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('A')));
|
|
103
113
|
const result = JSON.stringify(mr);
|
|
104
|
-
if (result !== '
|
|
114
|
+
if (result !== '{"ast":{"sequence":[[65,null]]},"success":true,"idx":1}') {
|
|
105
115
|
throw result;
|
|
106
116
|
}
|
|
107
117
|
},
|
|
108
118
|
() => {
|
|
109
119
|
const terminalRangeRule = range('AF');
|
|
110
120
|
const m = descentParser(terminalRangeRule);
|
|
111
|
-
const mr = descentParserCpOnly(m, "",
|
|
121
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('@')));
|
|
112
122
|
const result = JSON.stringify(mr);
|
|
113
|
-
if (result !==
|
|
123
|
+
if (result !== `{"ast":{"sequence":[]},"success":false,"idx":0,"failure":{"idx":0,"expected":[${range('AF')}]}}`) {
|
|
114
124
|
throw result;
|
|
115
125
|
}
|
|
116
126
|
},
|
|
117
127
|
() => {
|
|
118
128
|
const variantRule = { 'a': range('AA'), 'b': range('BB') };
|
|
119
129
|
const m = descentParser(variantRule);
|
|
120
|
-
const mr = descentParserCpOnly(m, "",
|
|
130
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('A')));
|
|
121
131
|
const result = JSON.stringify(mr);
|
|
122
|
-
if (result !==
|
|
132
|
+
if (result !== `{"ast":{"tag":"a","sequence":[[${cp1('A')},null]]},"success":true,"idx":1}`) {
|
|
123
133
|
throw result;
|
|
124
134
|
}
|
|
125
135
|
},
|
|
126
136
|
() => {
|
|
127
137
|
const variantRule = { 'a': range('AA'), 'b': range('BB') };
|
|
128
138
|
const m = descentParser(variantRule);
|
|
129
|
-
const mr = descentParserCpOnly(m, "",
|
|
139
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('@')));
|
|
130
140
|
const result = JSON.stringify(mr);
|
|
131
|
-
|
|
141
|
+
// Both branches were rejected at 0, so both terminals are expected there.
|
|
142
|
+
if (result !== `{"ast":{"sequence":[]},"success":false,"idx":0,"failure":{"idx":0,"expected":[${range('AA')},${range('BB')}]}}`) {
|
|
132
143
|
throw result;
|
|
133
144
|
}
|
|
134
145
|
},
|
|
@@ -138,7 +149,7 @@ export const proof = {
|
|
|
138
149
|
const m = descentParser(variantRule);
|
|
139
150
|
const mr = m("", []);
|
|
140
151
|
const result = JSON.stringify(mr);
|
|
141
|
-
if (result !== '
|
|
152
|
+
if (result !== '{"ast":{"tag":"e","sequence":[]},"success":true,"idx":0}') {
|
|
142
153
|
throw result;
|
|
143
154
|
}
|
|
144
155
|
},
|
|
@@ -146,9 +157,9 @@ export const proof = {
|
|
|
146
157
|
const emptyRule = '';
|
|
147
158
|
const variantRule = { 'e': emptyRule, 'a': range('AA') };
|
|
148
159
|
const m = descentParser(variantRule);
|
|
149
|
-
const mr = descentParserCpOnly(m, "",
|
|
160
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('@')));
|
|
150
161
|
const result = JSON.stringify(mr);
|
|
151
|
-
if (result !== '
|
|
162
|
+
if (result !== '{"ast":{"tag":"e","sequence":[]},"success":true,"idx":0}') {
|
|
152
163
|
throw result;
|
|
153
164
|
}
|
|
154
165
|
},
|
|
@@ -157,25 +168,29 @@ export const proof = {
|
|
|
157
168
|
const m = descentParser(emptyVariantRule);
|
|
158
169
|
const mr = m("", []);
|
|
159
170
|
const result = JSON.stringify(mr);
|
|
160
|
-
|
|
171
|
+
// A variant with no branches fails without ever trying a terminal,
|
|
172
|
+
// so there is nothing to expect.
|
|
173
|
+
if (result !== '{"ast":{"sequence":[]},"success":false,"idx":0,"failure":{"idx":0,"expected":[]}}') {
|
|
161
174
|
throw result;
|
|
162
175
|
}
|
|
163
176
|
},
|
|
164
177
|
() => {
|
|
165
178
|
const stringRule = 'AB';
|
|
166
179
|
const m = descentParser(stringRule);
|
|
167
|
-
const mr = descentParserCpOnly(m, "",
|
|
180
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('AB')));
|
|
168
181
|
const result = JSON.stringify(mr);
|
|
169
|
-
if (result !==
|
|
182
|
+
if (result !== `{"ast":{"sequence":[{"sequence":[[${cp1('A')},null]]},{"sequence":[[${cp1('B')},null]]}]},"success":true,"idx":2}`) {
|
|
170
183
|
throw result;
|
|
171
184
|
}
|
|
172
185
|
},
|
|
173
186
|
() => {
|
|
174
187
|
const stringRule = 'AB';
|
|
175
188
|
const m = descentParser(stringRule);
|
|
176
|
-
const mr = descentParserCpOnly(m, "",
|
|
189
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('AC')));
|
|
177
190
|
const result = JSON.stringify(mr);
|
|
178
|
-
|
|
191
|
+
// The result index rewound to the sequence's start, but the furthest
|
|
192
|
+
// failure kept the position where 'B' was actually rejected.
|
|
193
|
+
if (result !== `{"ast":{"sequence":[]},"success":false,"idx":0,"failure":{"idx":1,"expected":[${range('BB')}]}}`) {
|
|
179
194
|
throw result;
|
|
180
195
|
}
|
|
181
196
|
},
|
|
@@ -186,9 +201,9 @@ export const proof = {
|
|
|
186
201
|
const digitRule = range('09');
|
|
187
202
|
const numberRule = [optionalMinusRule, digitRule];
|
|
188
203
|
const m = descentParser(numberRule);
|
|
189
|
-
const mr = descentParserCpOnly(m, "",
|
|
204
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('2')));
|
|
190
205
|
const result = JSON.stringify(mr);
|
|
191
|
-
if (result !==
|
|
206
|
+
if (result !== `{"ast":{"sequence":[{"tag":"none","sequence":[]},{"sequence":[[${cp1('2')},null]]}]},"success":true,"idx":1}`) {
|
|
192
207
|
throw result;
|
|
193
208
|
}
|
|
194
209
|
},
|
|
@@ -199,9 +214,9 @@ export const proof = {
|
|
|
199
214
|
const digitRule = range('09');
|
|
200
215
|
const numberRule = [optionalMinusRule, digitRule];
|
|
201
216
|
const m = descentParser(numberRule);
|
|
202
|
-
const mr = descentParserCpOnly(m, "",
|
|
217
|
+
const mr = descentParserCpOnly(m, "", toArray(stringToCodePointList('-2')));
|
|
203
218
|
const result = JSON.stringify(mr);
|
|
204
|
-
if (result !==
|
|
219
|
+
if (result !== `{"ast":{"sequence":[{"tag":"minus","sequence":[[${cp1('-')},null]]},{"sequence":[[${cp1('2')},null]]}]},"success":true,"idx":2}`) {
|
|
205
220
|
throw result;
|
|
206
221
|
}
|
|
207
222
|
},
|
|
@@ -214,7 +229,8 @@ export const proof = {
|
|
|
214
229
|
const m = descentParser(numberRule);
|
|
215
230
|
const mr = m("", []);
|
|
216
231
|
const result = JSON.stringify(mr);
|
|
217
|
-
|
|
232
|
+
// Past the end: '-' and then the digit range were both rejected at 0.
|
|
233
|
+
if (result !== `{"ast":{"sequence":[]},"success":false,"idx":0,"failure":{"idx":0,"expected":[${range('--')},${range('09')}]}}`) {
|
|
218
234
|
throw result;
|
|
219
235
|
}
|
|
220
236
|
},
|
|
@@ -223,7 +239,7 @@ export const proof = {
|
|
|
223
239
|
const expect = (s, expected) => {
|
|
224
240
|
const cp = toArray(stringToCodePointList(s));
|
|
225
241
|
const mr = descentParserCpOnly(m, '', cp);
|
|
226
|
-
const success = mr
|
|
242
|
+
const success = mr.success && mr.idx === cp.length;
|
|
227
243
|
assertEq(success, expected, mr);
|
|
228
244
|
};
|
|
229
245
|
expect('a', true);
|
|
@@ -243,7 +259,7 @@ export const proof = {
|
|
|
243
259
|
const expect = (s, expected) => {
|
|
244
260
|
const cp = toArray(stringToCodePointList(s));
|
|
245
261
|
const mr = descentParserCpOnly(m, 'value', cp);
|
|
246
|
-
const success = mr
|
|
262
|
+
const success = mr.success && mr.idx === cp.length;
|
|
247
263
|
assertEq(success, expected, mr);
|
|
248
264
|
};
|
|
249
265
|
expect('', false);
|
|
@@ -257,7 +273,7 @@ export const proof = {
|
|
|
257
273
|
const expect = (s, expected) => {
|
|
258
274
|
const cp = toArray(stringToCodePointList(s));
|
|
259
275
|
const mr = descentParserCpOnly(m, '', cp);
|
|
260
|
-
const success = mr
|
|
276
|
+
const success = mr.success && mr.idx === cp.length;
|
|
261
277
|
assertEq(success, expected, mr);
|
|
262
278
|
};
|
|
263
279
|
expect(' true ', true);
|
|
@@ -293,7 +309,7 @@ export const proof = {
|
|
|
293
309
|
const name = toData(rule)[1];
|
|
294
310
|
const m = descentParser(rule);
|
|
295
311
|
const cp = toArray(stringToCodePointList(' '.repeat(10000)));
|
|
296
|
-
const
|
|
312
|
+
const { success: ok, idx } = descentParserCpOnly(m, name, cp);
|
|
297
313
|
assertEq(ok, true);
|
|
298
314
|
assertEq(idx, 10000);
|
|
299
315
|
},
|
|
@@ -304,7 +320,7 @@ export const proof = {
|
|
|
304
320
|
const m = descentParser(deterministic());
|
|
305
321
|
const n = 5000;
|
|
306
322
|
const cp = toArray(stringToCodePointList('['.repeat(n) + ']'.repeat(n)));
|
|
307
|
-
const
|
|
323
|
+
const { success: ok, idx } = descentParserCpOnly(m, '', cp);
|
|
308
324
|
assertEq(ok, true);
|
|
309
325
|
assertEq(idx, n * 2);
|
|
310
326
|
},
|
|
@@ -317,11 +333,36 @@ export const proof = {
|
|
|
317
333
|
const digitRule = range('09');
|
|
318
334
|
const numberRule = [optionalMinusRule, digitRule];
|
|
319
335
|
const m = descentParser(numberRule);
|
|
320
|
-
const mr = m("", [[
|
|
336
|
+
const mr = m("", [[cp1('-'), 'minus'], [cp1('2'), 'two']]);
|
|
321
337
|
const result = JSON.stringify(mr);
|
|
322
|
-
if (result !==
|
|
338
|
+
if (result !== `{"ast":{"sequence":[{"tag":"minus","sequence":[[${cp1('-')},"minus"]]},{"sequence":[[${cp1('2')},"two"]]}]},"success":true,"idx":2}`) {
|
|
323
339
|
throw result;
|
|
324
340
|
}
|
|
325
341
|
},
|
|
326
342
|
],
|
|
343
|
+
furthestFailure: [
|
|
344
|
+
() => {
|
|
345
|
+
// A branch rejected *before* the high-water mark must not pull it back:
|
|
346
|
+
// `x` gets to index 1 before failing, then `y` fails at 0.
|
|
347
|
+
const m = descentParser({ x: ['A', 'B'], y: 'B' });
|
|
348
|
+
const { success: ok, idx, failure } = descentParserCpOnly(m, '', toArray(stringToCodePointList('AC')));
|
|
349
|
+
assertEq(ok, false);
|
|
350
|
+
assertEq(idx, 0);
|
|
351
|
+
// A failed match always carries a failure; that is the contract.
|
|
352
|
+
const f = assertNotNullish(failure);
|
|
353
|
+
assertEq(f.idx, 1);
|
|
354
|
+
assertEq(f.expected.length, 1);
|
|
355
|
+
assertEq(f.expected[0], range('BB'));
|
|
356
|
+
},
|
|
357
|
+
() => {
|
|
358
|
+
// The same terminal rejected at the same index by two branches is
|
|
359
|
+
// expected once, not twice.
|
|
360
|
+
const m = descentParser({ x: ['A', 'B'], y: ['A', 'B', 'C'] });
|
|
361
|
+
const { failure } = descentParserCpOnly(m, '', toArray(stringToCodePointList('AC')));
|
|
362
|
+
const f = assertNotNullish(failure);
|
|
363
|
+
assertEq(f.idx, 1);
|
|
364
|
+
assertEq(f.expected.length, 1);
|
|
365
|
+
assertEq(f.expected[0], range('BB'));
|
|
366
|
+
},
|
|
367
|
+
],
|
|
327
368
|
};
|
package/fjs/bnf/ll1/module.f.js
CHANGED
|
@@ -55,7 +55,7 @@ export const dispatchMap = (ruleSet) => {
|
|
|
55
55
|
const rule = ruleSet[name];
|
|
56
56
|
if (typeof rule === 'number') {
|
|
57
57
|
const range = rangeDecode(rule);
|
|
58
|
-
const dispatch = dispatchOp.fromRange(
|
|
58
|
+
const dispatch = dispatchOp.fromRange({ tag: undefined, rules: [] })(range);
|
|
59
59
|
const dr = { emptyTag: undefined, rangeMap: dispatch };
|
|
60
60
|
return { ...dm, [name]: dr };
|
|
61
61
|
}
|
|
@@ -126,7 +126,7 @@ export const parserRuleSet = (ruleSet) => {
|
|
|
126
126
|
return mrSuccess(emptyTag, [], emptyTag === undefined ? null : cp);
|
|
127
127
|
}
|
|
128
128
|
const [cp0] = cp;
|
|
129
|
-
const dr = dispatchOp.get(
|
|
129
|
+
const dr = dispatchOp.get(rangeMap)(cp0);
|
|
130
130
|
if (dr === null) {
|
|
131
131
|
return emptyTag === undefined
|
|
132
132
|
? mrFail(emptyTag, [], cp)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Nullable } from '../../types/nullable/module.f.ts';
|
|
2
|
+
/**
|
|
3
|
+
* How many names one encoding holds: every symbol from {@link start} up to but
|
|
4
|
+
* not including `eof` (`0xFFFFFF`, the top of the 24-bit symbol space).
|
|
5
|
+
*/
|
|
6
|
+
export declare const capacity: number;
|
|
7
|
+
/**
|
|
8
|
+
* A bidirectional map between a fixed alphabet of token names and the symbol
|
|
9
|
+
* range reserved for them.
|
|
10
|
+
*/
|
|
11
|
+
export type Encoding<T extends string> = {
|
|
12
|
+
/**
|
|
13
|
+
* The input symbol standing for `name`.
|
|
14
|
+
*
|
|
15
|
+
* The result is a bare symbol, the form a tokenizer emits. Wrap it in
|
|
16
|
+
* `oneEncode` to use it as a terminal of a grammar rule — a symbol and a
|
|
17
|
+
* `TerminalRange` are both plain numbers, so passing one where the other
|
|
18
|
+
* belongs is not a type error.
|
|
19
|
+
*/
|
|
20
|
+
readonly encode: (name: T) => number;
|
|
21
|
+
/**
|
|
22
|
+
* The name a symbol stands for, or `null` when the symbol belongs to no
|
|
23
|
+
* registered name — a code point, `eof`, or a symbol past the end of the
|
|
24
|
+
* alphabet.
|
|
25
|
+
*/
|
|
26
|
+
readonly decode: (symbol: number) => Nullable<T>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Builds an encoding over the complete list of token names.
|
|
30
|
+
*
|
|
31
|
+
* A name takes the symbol at its index in `names`, so the list is append-only:
|
|
32
|
+
* inserting or reordering names changes the symbols of everything after the
|
|
33
|
+
* edit.
|
|
34
|
+
*
|
|
35
|
+
* @throws When `names` holds more than {@link capacity} entries, or when a name
|
|
36
|
+
* repeats — a repeated name has no single symbol to decode back to.
|
|
37
|
+
*/
|
|
38
|
+
export declare const encoding: <T extends string>(names: readonly T[]) => Encoding<T>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding of multi-character token names as single BNF input symbols.
|
|
3
|
+
*
|
|
4
|
+
* A parser built on top of a tokenizer consumes one input symbol per token, so
|
|
5
|
+
* multi-character operators (`>>>=`) and keywords (`instanceof`) each need a
|
|
6
|
+
* symbol of their own. Names are registered as one fixed alphabet and get a
|
|
7
|
+
* symbol from their position in it, above the Unicode range and below `eof`.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import { assert } from '../../asserts/module.f.js';
|
|
12
|
+
import { fromUndefined } from '../../types/nullable/module.f.js';
|
|
13
|
+
import { eof, rangeDecode, unicodeRange } from '../module.f.js';
|
|
14
|
+
const [, unicodeLast] = rangeDecode(unicodeRange);
|
|
15
|
+
const [eofSymbol] = rangeDecode(eof);
|
|
16
|
+
/**
|
|
17
|
+
* The symbol of the first registered name: `0x110000`, one past the last
|
|
18
|
+
* Unicode scalar value, so a token symbol can never be mistaken for a code
|
|
19
|
+
* point of the layer below.
|
|
20
|
+
*/
|
|
21
|
+
const start = unicodeLast + 1;
|
|
22
|
+
/**
|
|
23
|
+
* How many names one encoding holds: every symbol from {@link start} up to but
|
|
24
|
+
* not including `eof` (`0xFFFFFF`, the top of the 24-bit symbol space).
|
|
25
|
+
*/
|
|
26
|
+
export const capacity = eofSymbol - start;
|
|
27
|
+
/**
|
|
28
|
+
* Builds an encoding over the complete list of token names.
|
|
29
|
+
*
|
|
30
|
+
* A name takes the symbol at its index in `names`, so the list is append-only:
|
|
31
|
+
* inserting or reordering names changes the symbols of everything after the
|
|
32
|
+
* edit.
|
|
33
|
+
*
|
|
34
|
+
* @throws When `names` holds more than {@link capacity} entries, or when a name
|
|
35
|
+
* repeats — a repeated name has no single symbol to decode back to.
|
|
36
|
+
*/
|
|
37
|
+
export const encoding = (names) => {
|
|
38
|
+
assert(names.length <= capacity, ['too many token names', names.length]);
|
|
39
|
+
assert(new Set(names).size === names.length, ['duplicate token name', names]);
|
|
40
|
+
return {
|
|
41
|
+
encode: name => {
|
|
42
|
+
const index = names.indexOf(name);
|
|
43
|
+
assert(index !== -1, ['unregistered token name', name]);
|
|
44
|
+
return start + index;
|
|
45
|
+
},
|
|
46
|
+
decode: symbol => fromUndefined(names[symbol - start]),
|
|
47
|
+
};
|
|
48
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { assertEq } from '../../asserts/module.f.js';
|
|
2
|
+
import { capacity, encoding } from './module.f.js';
|
|
3
|
+
const names = ['>>', '>>>=', 'instanceof'];
|
|
4
|
+
export const proof = {
|
|
5
|
+
encode: () => {
|
|
6
|
+
const { encode } = encoding(names);
|
|
7
|
+
assertEq(encode('>>'), 0x110000);
|
|
8
|
+
assertEq(encode('instanceof'), 0x110002);
|
|
9
|
+
},
|
|
10
|
+
decode: [
|
|
11
|
+
() => {
|
|
12
|
+
const { decode } = encoding(names);
|
|
13
|
+
assertEq(decode(0x110000), '>>');
|
|
14
|
+
assertEq(decode(0x110002), 'instanceof');
|
|
15
|
+
},
|
|
16
|
+
() => {
|
|
17
|
+
const { decode } = encoding(names);
|
|
18
|
+
// past the end of the alphabet, a code point, and `eof`
|
|
19
|
+
assertEq(decode(0x110003), null);
|
|
20
|
+
assertEq(decode(0x10FFFF), null);
|
|
21
|
+
assertEq(decode(0xFFFFFF), null);
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
roundTrip: () => {
|
|
25
|
+
const { encode, decode } = encoding(names);
|
|
26
|
+
for (const name of names) {
|
|
27
|
+
assertEq(decode(encode(name)), name);
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
throw: {
|
|
31
|
+
duplicateName: () => { encoding(['a', 'b', 'a']); },
|
|
32
|
+
unregisteredName: () => { encoding(['a']).encode('b'); },
|
|
33
|
+
// `capacity` names fit, so one more is the smallest list that doesn't.
|
|
34
|
+
// The array is sparse, so this costs a length, not the strings.
|
|
35
|
+
tooManyNames: () => { encoding(new Array(capacity + 1)); },
|
|
36
|
+
},
|
|
37
|
+
};
|
package/fjs/cas/cli/module.f.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { sha256 } from '../../crypto/sha2/module.f.js';
|
|
7
7
|
import { cBase32ToVec, vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
8
|
-
import { forEachStep, pure } from '../../effects/module.f.js';
|
|
8
|
+
import { forEachStep, pure, step } from '../../effects/module.f.js';
|
|
9
9
|
import { errorExit, log, writeFromStream } from '../../effects/node/module.f.js';
|
|
10
10
|
import { dispatch } from '../../cli/module.f.js';
|
|
11
11
|
import {} from '../../effects/memory/module.f.js';
|
|
@@ -19,10 +19,9 @@ export const commands = [
|
|
|
19
19
|
return errorExit("'cas add' expects one parameter");
|
|
20
20
|
}
|
|
21
21
|
const c = fileCas(sha256)(home);
|
|
22
|
-
return casAddFile(c)(path)
|
|
23
|
-
.step(hashResult => hashResult[0] === 'error'
|
|
22
|
+
return step(casAddFile(c)(path), hashResult => hashResult[0] === 'error'
|
|
24
23
|
? pure(1)
|
|
25
|
-
: log(vecToCBase32(hashResult[1]))
|
|
24
|
+
: step(log(vecToCBase32(hashResult[1])), () => pure(0)));
|
|
26
25
|
},
|
|
27
26
|
},
|
|
28
27
|
{
|
|
@@ -38,8 +37,7 @@ export const commands = [
|
|
|
38
37
|
}
|
|
39
38
|
const c = fileCas(sha256)(home);
|
|
40
39
|
const x = c.read(hash);
|
|
41
|
-
return writeFromStream(path, x)
|
|
42
|
-
.step(([r, v]) => r === 'error' ? errorExit(`e: ` + String(v)) : pure(0));
|
|
40
|
+
return step(writeFromStream(path, x), ([r, v]) => r === 'error' ? errorExit(`e: ` + String(v)) : pure(0));
|
|
43
41
|
},
|
|
44
42
|
},
|
|
45
43
|
{
|
|
@@ -47,9 +45,8 @@ export const commands = [
|
|
|
47
45
|
description: 'List all stored content hashes',
|
|
48
46
|
handler: ({ home }) => {
|
|
49
47
|
const c = fileCas(sha256)(home);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.step(() => pure(0));
|
|
48
|
+
const x0 = forEachStep(c.list(), j => log(vecToCBase32(j)));
|
|
49
|
+
return step(x0, () => pure(0));
|
|
53
50
|
},
|
|
54
51
|
},
|
|
55
52
|
];
|