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
|
@@ -1,25 +1,46 @@
|
|
|
1
1
|
import { empty, isVec, uint, vec, vec8 } from "../../types/bit_vec/module.f.js";
|
|
2
2
|
import { utf8, utf8ToString } from "../../text/module.f.js";
|
|
3
|
-
import {
|
|
4
|
-
import { both, fetch, mkdir, now, readdir, readFile, readUtf8File, rm, sandbox, writeFile, writeUtf8File, rename, readBytes, randomInt, writeFromStream } from "./module.f.js";
|
|
3
|
+
import { match, pure, step } from "../module.f.js";
|
|
4
|
+
import { both, fetch, mkdir, now, readdir, readFile, readUtf8File, rm, sandbox, writeFile, writeUtf8File, rename, readBytes, randomInt, writeFromStream, usesInlineTestContext, versionLessThan } from "./module.f.js";
|
|
5
5
|
import { create as memCreate, read as memRead, write as memWrite } from "../memory/module.f.js";
|
|
6
6
|
import { empty as listEmpty, nonEmpty as listNonEmpty } from "../list/module.f.js";
|
|
7
7
|
import { emptyState, virtual } from "./virtual/module.f.js";
|
|
8
8
|
import { assert, assertEq, assertNotNullish } from '../../asserts/module.f.js';
|
|
9
|
+
import { ok } from '../../types/result/module.f.js';
|
|
10
|
+
// Answers the one command the `map` proof below drives. Routing the loop
|
|
11
|
+
// through `match` keeps the `Pure`/`Do` layout out of this module: the map key
|
|
12
|
+
// is the command assertion, and `MatchResult` types the continuation.
|
|
13
|
+
const readHello = match({
|
|
14
|
+
readFile: path => {
|
|
15
|
+
assertEq(path, 'hello');
|
|
16
|
+
return ok(vec8(0x15n));
|
|
17
|
+
},
|
|
18
|
+
});
|
|
9
19
|
export const proof = {
|
|
20
|
+
externalTestContext: () => {
|
|
21
|
+
assert(usesInlineTestContext('node', 'v22.20.0'));
|
|
22
|
+
assert(usesInlineTestContext('node', '25.99.99'));
|
|
23
|
+
assert(!usesInlineTestContext('node', '26.0.0'));
|
|
24
|
+
assert(!usesInlineTestContext('node', '26.1.0'));
|
|
25
|
+
assert(!usesInlineTestContext('node'));
|
|
26
|
+
assert(usesInlineTestContext('bun'));
|
|
27
|
+
assert(!usesInlineTestContext('deno', '22.0.0'));
|
|
28
|
+
assert(versionLessThan('25.99.99', '26.0.0'));
|
|
29
|
+
assert(versionLessThan('26.0.0', '26.1.0'));
|
|
30
|
+
assert(versionLessThan('26.1.0', '26.1.1'));
|
|
31
|
+
assert(!versionLessThan('26.1.1', '26.1.1'));
|
|
32
|
+
},
|
|
10
33
|
map: () => {
|
|
11
|
-
const e = readFile('hello')
|
|
34
|
+
const e = step(readFile('hello'), ([k, v]) => {
|
|
12
35
|
assert(k !== 'error', v);
|
|
13
36
|
return pure(uint(v) * 2n);
|
|
14
37
|
});
|
|
15
38
|
//
|
|
16
|
-
let
|
|
17
|
-
while (
|
|
18
|
-
|
|
19
|
-
assert(d.payload[0] === 'hello', d.payload);
|
|
20
|
-
d = decode(d.continuation(['ok', vec8(0x15n)]));
|
|
39
|
+
let r = readHello(e);
|
|
40
|
+
while (r[0] === 'cont') {
|
|
41
|
+
r = readHello(r[2](r[1]));
|
|
21
42
|
}
|
|
22
|
-
assertEq(
|
|
43
|
+
assertEq(r[1], 0x2an);
|
|
23
44
|
},
|
|
24
45
|
fetch: () => {
|
|
25
46
|
const [_, [t, result]] = virtual({
|
|
@@ -282,12 +303,12 @@ export const proof = {
|
|
|
282
303
|
},
|
|
283
304
|
memory: {
|
|
284
305
|
createAndRead: () => {
|
|
285
|
-
const effect = memCreate(42)
|
|
306
|
+
const effect = step(memCreate(42), key => memRead(key));
|
|
286
307
|
const [_, value] = virtual(emptyState)(effect);
|
|
287
308
|
assertEq(value, 42);
|
|
288
309
|
},
|
|
289
310
|
createAndWrite: () => {
|
|
290
|
-
const effect = memCreate(1)
|
|
311
|
+
const effect = step(memCreate(1), key => step(memWrite(key, 99), () => memRead(key)));
|
|
291
312
|
const [_, value] = virtual(emptyState)(effect);
|
|
292
313
|
assertEq(value, 99);
|
|
293
314
|
},
|
package/fjs/effects/proof.f.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { type Effect, type Operation } from './module.f.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Asserts that `e` yields `expected` without performing a command. Exported so
|
|
4
|
+
* `eff/proof.f.ts` shares this definition instead of repeating it.
|
|
5
|
+
*
|
|
6
|
+
* Not `assertEq(runPure(e), [expected])`: `assertEq` compares with `===`, so a
|
|
7
|
+
* freshly allocated `[expected]` is never equal to the returned option. Assert
|
|
8
|
+
* the option's shape first, then compare the value inside it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const assertPure: <O extends Operation, T>(e: Effect<O, T>, expected: T) => void;
|
|
1
11
|
export declare const proof: {
|
|
2
|
-
lazy: {
|
|
3
|
-
value: () => void;
|
|
4
|
-
deferred: () => void;
|
|
5
|
-
step: () => void;
|
|
6
|
-
};
|
|
7
12
|
foldStep: {
|
|
8
13
|
empty: () => void;
|
|
9
14
|
threadsState: () => void;
|
|
@@ -17,9 +22,30 @@ export declare const proof: {
|
|
|
17
22
|
ok: () => void;
|
|
18
23
|
error: () => void;
|
|
19
24
|
};
|
|
20
|
-
|
|
25
|
+
runPure: {
|
|
26
|
+
pure: () => void;
|
|
27
|
+
pureNull: () => void;
|
|
28
|
+
do_: () => void;
|
|
29
|
+
};
|
|
30
|
+
doNode: () => void;
|
|
21
31
|
match: {
|
|
22
32
|
done: () => void;
|
|
23
33
|
cont: () => void;
|
|
24
34
|
};
|
|
35
|
+
step: {
|
|
36
|
+
pure: () => void;
|
|
37
|
+
chain: () => void;
|
|
38
|
+
overDo: () => void;
|
|
39
|
+
};
|
|
40
|
+
mapStep: {
|
|
41
|
+
pure: () => void;
|
|
42
|
+
constant: () => void;
|
|
43
|
+
overDo: () => void;
|
|
44
|
+
};
|
|
45
|
+
historyStep: {
|
|
46
|
+
pure: () => void;
|
|
47
|
+
overDo: () => void;
|
|
48
|
+
chain: () => void;
|
|
49
|
+
fReceivesWholeHistory: () => void;
|
|
50
|
+
};
|
|
25
51
|
};
|
package/fjs/effects/proof.f.js
CHANGED
|
@@ -1,72 +1,93 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { step, do_, foldStep, forEachStep, mapStep, match, okStep, history, pure, runPure, historyStep } from './module.f.js';
|
|
2
2
|
import { error, ok } from '../types/result/module.f.js';
|
|
3
3
|
import { assert, assertEq } from '../asserts/module.f.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Asserts that `e` yields `expected` without performing a command. Exported so
|
|
6
|
+
* `eff/proof.f.ts` shares this definition instead of repeating it.
|
|
7
|
+
*
|
|
8
|
+
* Not `assertEq(runPure(e), [expected])`: `assertEq` compares with `===`, so a
|
|
9
|
+
* freshly allocated `[expected]` is never equal to the returned option. Assert
|
|
10
|
+
* the option's shape first, then compare the value inside it.
|
|
11
|
+
*/
|
|
12
|
+
export const assertPure = (e, expected) => {
|
|
13
|
+
const o = runPure(e);
|
|
14
|
+
assert(o.length === 1, e);
|
|
15
|
+
assertEq(o[0], expected);
|
|
8
16
|
};
|
|
9
17
|
const next = match({ add: (a, b) => a + b });
|
|
10
18
|
export const proof = {
|
|
11
|
-
lazy: {
|
|
12
|
-
value: () => {
|
|
13
|
-
assertPure(lazy(() => 42), 42);
|
|
14
|
-
},
|
|
15
|
-
deferred: () => {
|
|
16
|
-
// The thunk runs only when the effect is decoded, not when `lazy` is called.
|
|
17
|
-
let evaluated = false;
|
|
18
|
-
const e = lazy(() => { evaluated = true; return 7; });
|
|
19
|
-
assert(!(evaluated), 'lazy must not evaluate eagerly');
|
|
20
|
-
assertPure(e, 7);
|
|
21
|
-
assert(evaluated, 'decode must force the thunk');
|
|
22
|
-
},
|
|
23
|
-
step: () => {
|
|
24
|
-
const e = lazy(() => 5).step(v => pure(v * 2));
|
|
25
|
-
assertPure(e, 10);
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
19
|
foldStep: {
|
|
29
20
|
empty: () => {
|
|
30
|
-
const e = foldStep((
|
|
21
|
+
const e = foldStep(pure([]), 10, x => s => pure(s + x));
|
|
31
22
|
assertPure(e, 10);
|
|
32
23
|
},
|
|
33
24
|
threadsState: () => {
|
|
34
|
-
const e = foldStep((
|
|
25
|
+
const e = foldStep(pure([1, 2, 3, 4]), 0, x => s => pure(s + x));
|
|
35
26
|
assertPure(e, 10);
|
|
36
27
|
},
|
|
37
28
|
order: () => {
|
|
38
|
-
const e = foldStep((
|
|
29
|
+
const e = foldStep(pure(['a', 'b', 'c']), '', x => s => pure(s + x));
|
|
39
30
|
assertPure(e, 'abc');
|
|
40
31
|
},
|
|
41
32
|
},
|
|
42
33
|
forEachStep: {
|
|
43
34
|
empty: () => {
|
|
44
|
-
const e = forEachStep(() => pure(undefined))
|
|
35
|
+
const e = forEachStep(pure([]), () => pure(undefined));
|
|
45
36
|
assertPure(e, undefined);
|
|
46
37
|
},
|
|
47
38
|
runs: () => {
|
|
48
|
-
const e = forEachStep(
|
|
39
|
+
const e = forEachStep(pure([1, 2, 3]), () => pure(undefined));
|
|
49
40
|
assertPure(e, undefined);
|
|
50
41
|
},
|
|
51
42
|
},
|
|
52
43
|
okStep: {
|
|
53
44
|
ok: () => {
|
|
54
|
-
const e = pure(ok(5))
|
|
55
|
-
const
|
|
56
|
-
assert(
|
|
45
|
+
const e = step(pure(ok(5)), okStep((v) => pure(ok(v * 2))));
|
|
46
|
+
const o = runPure(e);
|
|
47
|
+
assert(o.length === 1, e);
|
|
48
|
+
const [r] = o;
|
|
49
|
+
assert(r[0] === 'ok', r);
|
|
50
|
+
assertEq(r[1], 10);
|
|
57
51
|
},
|
|
58
52
|
error: () => {
|
|
59
|
-
const e = pure(error('oops'))
|
|
60
|
-
const
|
|
61
|
-
assert(
|
|
53
|
+
const e = step(pure(error('oops')), okStep(v => pure(ok(v * 2))));
|
|
54
|
+
const o = runPure(e);
|
|
55
|
+
assert(o.length === 1, e);
|
|
56
|
+
const [r] = o;
|
|
57
|
+
assert(r[0] === 'error', r);
|
|
58
|
+
assertEq(r[1], 'oops');
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
runPure: {
|
|
62
|
+
pure: () => {
|
|
63
|
+
const o = runPure(pure(5));
|
|
64
|
+
assert(o.length === 1, o);
|
|
65
|
+
assertEq(o[0], 5);
|
|
66
|
+
},
|
|
67
|
+
// A pure `null` is `[null]`, never `[]`. Collapsing the two is what a
|
|
68
|
+
// `T | null` result would do, and why the option is tagged.
|
|
69
|
+
pureNull: () => {
|
|
70
|
+
const o = runPure(pure(null));
|
|
71
|
+
assertEq(o.length, 1, o);
|
|
72
|
+
assertEq(o[0], null);
|
|
73
|
+
},
|
|
74
|
+
do_: () => {
|
|
75
|
+
assertEq(runPure(do_('add')(2, 3)).length, 0);
|
|
62
76
|
},
|
|
63
77
|
},
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
// The one place a `Do` node is opened on purpose: `do_` builds the command,
|
|
79
|
+
// the payload it was called with, and a continuation that resumes with the
|
|
80
|
+
// command's output. Everything else goes through `step`, `match`, or
|
|
81
|
+
// `runPure`.
|
|
82
|
+
doNode: () => {
|
|
83
|
+
const e = do_('add')(2, 3);
|
|
84
|
+
assert(typeof e !== 'function', e);
|
|
85
|
+
const { command, payload, continuation } = e;
|
|
86
|
+
assertEq(command, 'add');
|
|
87
|
+
const [a, b] = payload;
|
|
88
|
+
assertEq(a, 2, payload);
|
|
89
|
+
assertEq(b, 3, payload);
|
|
90
|
+
assertPure(continuation(5), 5);
|
|
70
91
|
},
|
|
71
92
|
match: {
|
|
72
93
|
done: () => {
|
|
@@ -83,4 +104,75 @@ export const proof = {
|
|
|
83
104
|
assertEq(r2[1], 5);
|
|
84
105
|
},
|
|
85
106
|
},
|
|
107
|
+
step: {
|
|
108
|
+
pure: () => {
|
|
109
|
+
assertPure(step(pure(3), v => pure(v + 1)), 4);
|
|
110
|
+
},
|
|
111
|
+
chain: () => {
|
|
112
|
+
// Chains as step(step(e, f), g), raw effect in and out.
|
|
113
|
+
assertPure(step(step(pure(3), v => pure(v + 1)), v => pure(v * 2)), 8);
|
|
114
|
+
},
|
|
115
|
+
overDo: () => {
|
|
116
|
+
// Stepping a Do node preserves the command and threads the result
|
|
117
|
+
// through the rebuilt continuation.
|
|
118
|
+
const e = step(do_('add')(2, 3), r => pure(r * 10));
|
|
119
|
+
const r = next(e);
|
|
120
|
+
assert(r[0] === 'cont', r);
|
|
121
|
+
assertEq(r[1], 5);
|
|
122
|
+
assertPure(r[2](r[1]), 50);
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
mapStep: {
|
|
126
|
+
pure: () => {
|
|
127
|
+
assertPure(mapStep(pure(3), v => v + 1), 4);
|
|
128
|
+
},
|
|
129
|
+
constant: () => {
|
|
130
|
+
// The `() => v` shape a `constStep` would have covered.
|
|
131
|
+
assertPure(mapStep(pure('ignored'), () => 1), 1);
|
|
132
|
+
},
|
|
133
|
+
overDo: () => {
|
|
134
|
+
// A projection over a `Do` node keeps the command intact and is
|
|
135
|
+
// applied to the command's output when the continuation resumes.
|
|
136
|
+
const r = next(mapStep(do_('add')(2, 3), v => v * 10));
|
|
137
|
+
assert(r[0] === 'cont', r);
|
|
138
|
+
assertEq(r[1], 5);
|
|
139
|
+
assertPure(r[2](r[1]), 50);
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
historyStep: {
|
|
143
|
+
pure: () => {
|
|
144
|
+
const o = runPure(historyStep(history(pure(3)), v => pure(v * 2)));
|
|
145
|
+
assert(o.length === 1, o);
|
|
146
|
+
const [[result, param]] = o;
|
|
147
|
+
assertEq(param, 3);
|
|
148
|
+
assertEq(result, 6);
|
|
149
|
+
},
|
|
150
|
+
overDo: () => {
|
|
151
|
+
// The captured value survives a command boundary: the history is
|
|
152
|
+
// rebuilt inside the continuation rather than lost when `e` is a Do.
|
|
153
|
+
const c = next(historyStep(history(do_('add')(2, 3)), r => pure(r * 10)));
|
|
154
|
+
assert(c[0] === 'cont', c);
|
|
155
|
+
assertEq(c[1], 5);
|
|
156
|
+
const o = runPure(c[2](c[1]));
|
|
157
|
+
assert(o.length === 1, o);
|
|
158
|
+
const [[result, param]] = o;
|
|
159
|
+
assertEq(param, 5);
|
|
160
|
+
assertEq(result, 50);
|
|
161
|
+
},
|
|
162
|
+
chain: () => {
|
|
163
|
+
// `historyStep` takes a history and returns one, so link two is
|
|
164
|
+
// spelled exactly like link one. The tuple is newest first, so a
|
|
165
|
+
// destructuring reads reverse-chronologically.
|
|
166
|
+
const a = historyStep(history(pure(1)), x => pure(x + 1));
|
|
167
|
+
const b = historyStep(a, (result, param) => pure(result + param));
|
|
168
|
+
assertPure(step(b, ([z, y, x]) => pure(`${x}${y}${z}`)), '123');
|
|
169
|
+
},
|
|
170
|
+
fReceivesWholeHistory: () => {
|
|
171
|
+
// `f` is handed the whole history spread as arguments, not just the
|
|
172
|
+
// most recent value - which is what lets a later link reach back.
|
|
173
|
+
const a = historyStep(history(pure(1)), x => pure(x + 1));
|
|
174
|
+
const b = historyStep(a, (...p) => pure(p[1] * 100 + p[0]));
|
|
175
|
+
assertPure(step(b, ([result]) => pure(result)), 102);
|
|
176
|
+
},
|
|
177
|
+
},
|
|
86
178
|
};
|
|
@@ -62,8 +62,8 @@ export type Reporter<O extends Operation> = {
|
|
|
62
62
|
* Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
|
|
63
63
|
* lets the external framework own scheduling: each registered test callback
|
|
64
64
|
* calls `fn`, then recursively registers any sub-trees returned by the function.
|
|
65
|
-
* This is the correct model for Node `--test`, Bun, and
|
|
66
|
-
*
|
|
65
|
+
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
66
|
+
* be declared upfront and the framework drives execution.
|
|
67
67
|
*/
|
|
68
68
|
export declare const registerModule: (ctx: TestContext, k: string, v: unknown, star: string) => Effect<Test | All | Await, void>;
|
|
69
69
|
/**
|
|
@@ -130,7 +130,7 @@ export declare const defaultReporter: (options: NodeProgramOptions) => Reporter<
|
|
|
130
130
|
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
131
131
|
export declare const main: NodeProgram;
|
|
132
132
|
/**
|
|
133
|
-
* Entry point for external test frameworks (Node `--test`, Bun,
|
|
133
|
+
* Entry point for external test frameworks (Node `--test`, Bun, Deno).
|
|
134
134
|
*
|
|
135
135
|
* Discovers test modules via `loadModuleMap`, then registers each with the
|
|
136
136
|
* framework-appropriate `TestContext` selected from `NodeProgramOptions`
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
* - `runModule` / `Reporter<O>` — self-hosted Effects runner used by `fjs t`;
|
|
6
6
|
* sandboxes each leaf call individually and accumulates `TestState`.
|
|
7
7
|
* - `registerModule` / `TestContext` — registers tests with an external
|
|
8
|
-
* framework (Node `--test`, Bun,
|
|
9
|
-
*
|
|
8
|
+
* framework (Node `--test`, Bun, Deno) at import time; the framework owns
|
|
9
|
+
* scheduling and pass/fail counting.
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import { reset, fgGreen, fgRed, bold, csiWrite } from '../text/sgr/module.f.js';
|
|
14
14
|
import { all, awaitIfPromise, sandbox, test } from '../effects/node/module.f.js';
|
|
15
|
-
import { pure } from '../effects/module.f.js';
|
|
15
|
+
import { history, historyStep, pure, step } from '../effects/module.f.js';
|
|
16
|
+
import { eff } from '../effects/eff/module.f.js';
|
|
16
17
|
import { loadModuleMap, shouldLoad } from '../dev/module.f.js';
|
|
17
18
|
import { invert } from '../types/result/module.f.js';
|
|
18
19
|
import { definedEntries } from '../types/object/module.f.js';
|
|
@@ -78,18 +79,18 @@ export const collectTests = (path, throws, v) => {
|
|
|
78
79
|
* Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
|
|
79
80
|
* lets the external framework own scheduling: each registered test callback
|
|
80
81
|
* calls `fn`, then recursively registers any sub-trees returned by the function.
|
|
81
|
-
* This is the correct model for Node `--test`, Bun, and
|
|
82
|
-
*
|
|
82
|
+
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
83
|
+
* be declared upfront and the framework drives execution.
|
|
83
84
|
*/
|
|
84
85
|
export const registerModule = (ctx, k, v, star) => {
|
|
85
86
|
const registerOne = (ctx, [path, { fn, throws }]) => {
|
|
86
|
-
// ' *' (non-empty only for Bun
|
|
87
|
+
// ' *' (non-empty only for Bun) signals that all sub-tests run
|
|
87
88
|
// inline inside this single registration. Not appended to throw-tests since
|
|
88
89
|
// those never produce sub-tests. The path already contains '.throw' when a
|
|
89
90
|
// test is expected to throw, so no extra suffix is needed.
|
|
90
91
|
const base = fmtImport(k, path);
|
|
91
92
|
const name = throws ? base : `${base}${star}`;
|
|
92
|
-
return test(ctx, name, throws, (t) => awaitIfPromise(fn())
|
|
93
|
+
return test(ctx, name, throws, (t) => eff(awaitIfPromise(fn()))
|
|
93
94
|
.step(resolved => {
|
|
94
95
|
if (throws) {
|
|
95
96
|
return pure(undefined);
|
|
@@ -98,42 +99,49 @@ export const registerModule = (ctx, k, v, star) => {
|
|
|
98
99
|
if (sub.length === 0) {
|
|
99
100
|
return pure(undefined);
|
|
100
101
|
}
|
|
101
|
-
return all(...sub.map(e => registerOne(t, e)))
|
|
102
|
-
|
|
102
|
+
return eff(all(...sub.map(e => registerOne(t, e))))
|
|
103
|
+
.step(() => pure(undefined))
|
|
104
|
+
.value;
|
|
105
|
+
})
|
|
106
|
+
.value);
|
|
103
107
|
};
|
|
104
108
|
const tests = collectTests([], false, v);
|
|
105
109
|
if (tests.length === 0) {
|
|
106
110
|
return pure(undefined);
|
|
107
111
|
}
|
|
108
|
-
return all(...tests.map(e => registerOne(ctx, e)))
|
|
112
|
+
return eff(all(...tests.map(e => registerOne(ctx, e))))
|
|
113
|
+
.step(() => pure(undefined))
|
|
114
|
+
.value;
|
|
109
115
|
};
|
|
110
116
|
const mergeState = (a, b) => ({ time: a.time + b.time, pass: a.pass + b.pass, fail: a.fail + b.fail });
|
|
111
117
|
const zero = { time: 0, pass: 0, fail: 0 };
|
|
112
118
|
const runModule = ({ result, test }) => (k, v) => (ts) => {
|
|
113
|
-
const one = ([testPath, set]) =>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
// Walk return-value sub-tree; null marks the call boundary so
|
|
123
|
-
// paths render as e.g. `outer().inner`. throws resets to false.
|
|
124
|
-
return walk([...testPath, null], false, r)
|
|
125
|
-
.step(sub => pure(mergeState(addPass(duration)(zero), sub)));
|
|
119
|
+
const one = ([testPath, set]) => {
|
|
120
|
+
// The sandbox result is still needed after it has been reported, so the
|
|
121
|
+
// reporting call is captured rather than nested inside its own step.
|
|
122
|
+
const reported = historyStep(history(test(k, testPath, set)), sr => result(k, testPath, sr, set.throws));
|
|
123
|
+
return step(reported, ([, sr]) => {
|
|
124
|
+
const { result: [s, r], duration } = sr;
|
|
125
|
+
if (s !== 'ok') {
|
|
126
|
+
return pure(addFail(duration)(zero));
|
|
126
127
|
}
|
|
127
|
-
|
|
128
|
+
if (set.throws) {
|
|
129
|
+
return pure(addPass(duration)(zero));
|
|
130
|
+
}
|
|
131
|
+
// Walk return-value sub-tree; null marks the call boundary so
|
|
132
|
+
// paths render as e.g. `outer().inner`. throws resets to false.
|
|
133
|
+
return step(walk([...testPath, null], false, r), sub => pure(mergeState(addPass(duration)(zero), sub)));
|
|
128
134
|
});
|
|
129
|
-
}
|
|
135
|
+
};
|
|
130
136
|
const walk = (path, throws, v) => {
|
|
131
137
|
const effects = collectTests(path, throws, v).map(one);
|
|
132
|
-
return all(...effects)
|
|
133
|
-
.step(states => pure(states.reduce(mergeState, zero)))
|
|
138
|
+
return eff(all(...effects))
|
|
139
|
+
.step(states => pure(states.reduce(mergeState, zero)))
|
|
140
|
+
.value;
|
|
134
141
|
};
|
|
135
|
-
return walk([], false, v)
|
|
136
|
-
.step(delta => pure(mergeState(ts, delta)))
|
|
142
|
+
return eff(walk([], false, v))
|
|
143
|
+
.step(delta => pure(mergeState(ts, delta)))
|
|
144
|
+
.value;
|
|
137
145
|
};
|
|
138
146
|
const proofEntries = (moduleMap) => definedEntries(moduleMap)
|
|
139
147
|
.flatMap(([k, v]) => v.proof !== undefined ? [[k, v.proof]] : []);
|
|
@@ -145,17 +153,20 @@ const proofEntries = (moduleMap) => definedEntries(moduleMap)
|
|
|
145
153
|
export const runModuleMap = (reporter) => (moduleMap) => {
|
|
146
154
|
const { summary } = reporter;
|
|
147
155
|
const modules = proofEntries(moduleMap);
|
|
148
|
-
return all(...modules.map(([k, v]) => runModule(reporter)(k, v)(zero)))
|
|
156
|
+
return eff(all(...modules.map(([k, v]) => runModule(reporter)(k, v)(zero))))
|
|
149
157
|
.step(m => pure(m.reduce(mergeState, zero)))
|
|
150
|
-
.step(ts => summary(ts.pass, ts.fail, ts.time)
|
|
151
|
-
.step(() => pure(ts.fail !== 0 ? 1 : 0))
|
|
158
|
+
.step(ts => summary(ts.pass, ts.fail, ts.time))
|
|
159
|
+
.step((_, ts) => pure(ts.fail !== 0 ? 1 : 0))
|
|
160
|
+
.value;
|
|
152
161
|
};
|
|
153
162
|
/**
|
|
154
163
|
* Discovers all test modules via `loadModuleMap`, then runs them through
|
|
155
164
|
* `runModuleMap`. The composed effect is a `NodeProgram` entry point for the
|
|
156
165
|
* `fjs t` test runner.
|
|
157
166
|
*/
|
|
158
|
-
export const testAll = (reporter) => options => loadModuleMap(options.env)
|
|
167
|
+
export const testAll = (reporter) => options => eff(loadModuleMap(options.env))
|
|
168
|
+
.step(runModuleMap(reporter))
|
|
169
|
+
.value;
|
|
159
170
|
/**
|
|
160
171
|
* Registers all modules in `moduleMap` that export a `proof` property with
|
|
161
172
|
* `ctx`. Delegates to `registerModule` for each matching entry.
|
|
@@ -165,7 +176,9 @@ const registerModuleMap = (ctx, star) => (moduleMap) => {
|
|
|
165
176
|
if (modules.length === 0) {
|
|
166
177
|
return pure(undefined);
|
|
167
178
|
}
|
|
168
|
-
return all(...modules.map(([k, v]) => registerModule(ctx, k, v, star)))
|
|
179
|
+
return eff(all(...modules.map(([k, v]) => registerModule(ctx, k, v, star))))
|
|
180
|
+
.step(() => pure(undefined))
|
|
181
|
+
.value;
|
|
169
182
|
};
|
|
170
183
|
const isAlpha = (c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_' || c === '$';
|
|
171
184
|
const isDigit = (c) => c >= '0' && c <= '9';
|
|
@@ -219,8 +232,9 @@ export const ghEscape = (s) => s.replaceAll('%', '%25')
|
|
|
219
232
|
* Default `Reporter.test` implementation: sandboxes `fn` once and inverts the
|
|
220
233
|
* result when `throws` is `true` (caught error → pass, clean return → fail).
|
|
221
234
|
*/
|
|
222
|
-
export const defaultTest = (file, path, { fn, throws }) => sandbox(fn)
|
|
223
|
-
.step(r => pure(throws ? { ...r, result: invert(r.result) } : r))
|
|
235
|
+
export const defaultTest = (file, path, { fn, throws }) => eff(sandbox(fn))
|
|
236
|
+
.step(r => pure(throws ? { ...r, result: invert(r.result) } : r))
|
|
237
|
+
.value;
|
|
224
238
|
const fmtResultLine = (file, path, color, label, duration) => `${fmtImport(file, path)}: ${color}${label}${reset}, ${timeFormat(duration)}`;
|
|
225
239
|
/**
|
|
226
240
|
* The terminal/GitHub reporter used by `fjs t`. Output goes through
|
|
@@ -244,12 +258,14 @@ export const defaultReporter = (options) => {
|
|
|
244
258
|
? csiLog(fmtResultLine(file, path, fgGreen, 'ok', duration) + (throws ? ' # EXPECTED TO THROW' : ''))
|
|
245
259
|
: isGitHub
|
|
246
260
|
? csiError(`::error file=${file},line=1,title=${ghEscape(fmtImport(file, path))}::${ghEscape(String(v))}`)
|
|
247
|
-
: csiError(fmtResultLine(file, path, fgRed, 'error', duration))
|
|
248
|
-
.step(() => csiError(`${fgRed}${v}${reset}`))
|
|
261
|
+
: eff(csiError(fmtResultLine(file, path, fgRed, 'error', duration)))
|
|
262
|
+
.step(() => csiError(`${fgRed}${v}${reset}`))
|
|
263
|
+
.value,
|
|
249
264
|
summary: (pass, fail, time) => {
|
|
250
265
|
const fgFail = fail === 0 ? fgGreen : fgRed;
|
|
251
|
-
return csiLog(`${bold}Number of tests: pass: ${fgGreen}${pass}${reset}${bold}, fail: ${fgFail}${fail}${reset}${bold}, total: ${pass + fail}${reset}`)
|
|
252
|
-
.step(() => csiLog(`${bold}Time: ${timeFormat(time)}${reset}`))
|
|
266
|
+
return eff(csiLog(`${bold}Number of tests: pass: ${fgGreen}${pass}${reset}${bold}, fail: ${fgFail}${fail}${reset}${bold}, total: ${pass + fail}${reset}`))
|
|
267
|
+
.step(() => csiLog(`${bold}Time: ${timeFormat(time)}${reset}`))
|
|
268
|
+
.value;
|
|
253
269
|
},
|
|
254
270
|
test: defaultTest,
|
|
255
271
|
};
|
|
@@ -257,19 +273,17 @@ export const defaultReporter = (options) => {
|
|
|
257
273
|
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
258
274
|
export const main = options => testAll(defaultReporter(options))(options);
|
|
259
275
|
/**
|
|
260
|
-
* Entry point for external test frameworks (Node `--test`, Bun,
|
|
276
|
+
* Entry point for external test frameworks (Node `--test`, Bun, Deno).
|
|
261
277
|
*
|
|
262
278
|
* Discovers test modules via `loadModuleMap`, then registers each with the
|
|
263
279
|
* framework-appropriate `TestContext` selected from `NodeProgramOptions`
|
|
264
280
|
* based on the detected `engine`.
|
|
265
281
|
*/
|
|
266
282
|
export const register = o => {
|
|
267
|
-
const star = o.
|
|
268
|
-
const ctx = o.engine === 'bun' ? o.bunTestContext :
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
.step(r)
|
|
274
|
-
.step(() => pure(0));
|
|
283
|
+
const star = o.inlineTestContext ? ' ...' : '';
|
|
284
|
+
const ctx = o.engine === 'bun' ? o.bunTestContext : o.testContext;
|
|
285
|
+
return eff(loadModuleMap(o.env))
|
|
286
|
+
.step(registerModuleMap(ctx, star))
|
|
287
|
+
.step(() => pure(0))
|
|
288
|
+
.value;
|
|
275
289
|
};
|
|
@@ -14,6 +14,8 @@ export declare const defaultReporterOutputLargeDuration: () => void;
|
|
|
14
14
|
export declare const defaultReporterFailOutput: () => void;
|
|
15
15
|
export declare const githubReporterOutput: () => void;
|
|
16
16
|
export declare const registerSuffixes: () => void;
|
|
17
|
+
export declare const registerThrowsWithoutThrowing: () => void;
|
|
18
|
+
export declare const registerEmptyProof: () => void;
|
|
17
19
|
export declare const helpers: {
|
|
18
20
|
isInteger: () => void;
|
|
19
21
|
isIdentifier: () => void;
|
|
@@ -45,6 +47,8 @@ export declare const proof: {
|
|
|
45
47
|
defaultReporterFailOutput: typeof defaultReporterFailOutput;
|
|
46
48
|
githubReporterOutput: typeof githubReporterOutput;
|
|
47
49
|
registerSuffixes: typeof registerSuffixes;
|
|
50
|
+
registerThrowsWithoutThrowing: typeof registerThrowsWithoutThrowing;
|
|
51
|
+
registerEmptyProof: typeof registerEmptyProof;
|
|
48
52
|
defaultReporterExpectedToThrow: typeof defaultReporterExpectedToThrow;
|
|
49
53
|
helpers: {
|
|
50
54
|
isInteger: () => void;
|