functionalscript 0.39.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/cas/evo/module.f.d.ts +1 -1
- package/fjs/cas/evo/module.f.js +1 -1
- package/fjs/cas/evo/proof.f.js +1 -1
- package/fjs/ci/config/module.f.d.ts +7 -4
- package/fjs/ci/config/module.f.js +20 -5
- package/fjs/ci/module.f.js +12 -5
- 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 +47 -3
- package/fjs/ci/proof.f.d.ts +2 -0
- package/fjs/ci/proof.f.js +47 -9
- package/fjs/dev/module.f.d.ts +1 -0
- package/fjs/dev/module.f.js +13 -2
- 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/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/effects/node/module.d.ts +3 -3
- package/fjs/effects/node/module.f.d.ts +17 -7
- package/fjs/effects/node/module.f.js +22 -0
- 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 +14 -1
- package/fjs/effects/node/virtual/module.f.js +1 -1
- 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 +8 -10
- package/fjs/emergent_testing/proof.f.js +2 -2
- 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 +18 -58
- package/fjs/mcp/evo/module.f.d.ts +27 -0
- package/fjs/{cas/evo/mcp → mcp/evo}/module.f.js +9 -9
- package/fjs/{cas/evo/mcp → mcp/evo}/proof.f.js +7 -7
- 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 +508 -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/module.f.js +1 -1
- 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 +5 -5
- package/fjs/{mcp → protocol/mcp}/stdio/module.f.js +11 -11
- package/fjs/{mcp → protocol/mcp}/stdio/proof.f.js +9 -9
- 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/package.json +5 -5
- package/fjs/cas/evo/mcp/module.f.d.ts +0 -27
- 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/{cas/evo/mcp → mcp/evo}/proof.f.d.ts +0 -0
- /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/fjs/djs/ast/module.f.js
CHANGED
|
@@ -42,6 +42,15 @@ const toDjs = state => ast => {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Evaluates a module body against its imported modules and returns the value
|
|
47
|
+
* the module yields — the last entry of the body.
|
|
48
|
+
*
|
|
49
|
+
* Entries are evaluated left to right, so a `cref` resolves to an already
|
|
50
|
+
* evaluated entry. A reference is shared, not copied: two properties holding
|
|
51
|
+
* the same `['cref', i]` deserialize to the same object, which is what lets a
|
|
52
|
+
* DJS module denote a graph rather than a tree.
|
|
53
|
+
*/
|
|
45
54
|
export const run = body => args => {
|
|
46
55
|
const state = fold(foldOp)({ body, args, consts: null })(body);
|
|
47
56
|
return last(null)(state.consts);
|
|
@@ -364,7 +364,7 @@ export const tokenizeString = s => {
|
|
|
364
364
|
}
|
|
365
365
|
const m = descentParser(jsGrammar());
|
|
366
366
|
const cpm = codePointsWithMetadata('')(cp);
|
|
367
|
-
const
|
|
367
|
+
const { ast, success: ok, idx: len } = m('', cpm);
|
|
368
368
|
if (!ok || len !== cp.length)
|
|
369
369
|
return 'error';
|
|
370
370
|
const flatTokens = toArray(getTokensFromAstRule(ast));
|
|
@@ -394,7 +394,7 @@ export const tokenizeJs = input => path => {
|
|
|
394
394
|
return [{ token: { kind: 'eof' }, metadata: initial }];
|
|
395
395
|
const m = descentParser(jsGrammar());
|
|
396
396
|
const cpm = codePointsWithMetadata(path)(cp);
|
|
397
|
-
const
|
|
397
|
+
const { ast, success: ok, idx: len } = m('', cpm);
|
|
398
398
|
const finalMetadata = fold(advanceMetadata)(initial)(cp);
|
|
399
399
|
if (!ok || len !== cp.length) {
|
|
400
400
|
const errorMetadata = len < cpm.length ? cpm[len][1] : finalMetadata;
|
|
@@ -8,13 +8,21 @@ import { sort } from '../../types/object/module.f.js';
|
|
|
8
8
|
// DjsTokenWithMetadata carries bigint fields (bf, bigint value) that JSON.stringify can't
|
|
9
9
|
// serialize — reuse the same djs stringifyAsTree serializer tokenizeString uses internally.
|
|
10
10
|
const stringify = stringifyAsTree(sort);
|
|
11
|
+
// 'line:column' of the error token `s` tokenizes to, or 'no error'. Collapsing the
|
|
12
|
+
// position to one string keeps an expectation readable as the caret a reader would
|
|
13
|
+
// draw, instead of two separate assertions per case.
|
|
14
|
+
const errorAt = (s) => {
|
|
15
|
+
const found = toArray(tokenizeJs(stringToList(s))('a.js'))
|
|
16
|
+
.find(t => t.token.kind === 'error');
|
|
17
|
+
return found === undefined ? 'no error' : `${found.metadata.line}:${found.metadata.column}`;
|
|
18
|
+
};
|
|
11
19
|
export const proof = {
|
|
12
20
|
isValid: [() => {
|
|
13
21
|
const m = descentParser(jsGrammar());
|
|
14
22
|
const expect = (s, expected) => {
|
|
15
23
|
const cp = toArray(stringToCodePointList(s));
|
|
16
24
|
const mr = descentParserCpOnly(m, '', cp);
|
|
17
|
-
const success = mr
|
|
25
|
+
const success = mr.success && mr.idx === cp.length;
|
|
18
26
|
assertEq(success, expected, JSON.stringify([s, mr]));
|
|
19
27
|
};
|
|
20
28
|
expect('"', false);
|
|
@@ -51,7 +59,7 @@ export const proof = {
|
|
|
51
59
|
const m = descentParser(jsGrammar());
|
|
52
60
|
const cp = toArray(stringToCodePointList('tr'));
|
|
53
61
|
const mr = descentParserCpOnly(m, '', cp);
|
|
54
|
-
const seq = mr
|
|
62
|
+
const seq = mr.ast.sequence[0];
|
|
55
63
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
56
64
|
assertEq(seq.tag, 'id', JSON.stringify(mr));
|
|
57
65
|
},
|
|
@@ -59,7 +67,7 @@ export const proof = {
|
|
|
59
67
|
const m = descentParser(jsGrammar());
|
|
60
68
|
const cp = toArray(stringToCodePointList('"tr"'));
|
|
61
69
|
const mr = descentParserCpOnly(m, '', cp);
|
|
62
|
-
const seq = mr
|
|
70
|
+
const seq = mr.ast.sequence[0];
|
|
63
71
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
64
72
|
assertEq(seq.tag, 'string', JSON.stringify(mr));
|
|
65
73
|
},
|
|
@@ -67,7 +75,7 @@ export const proof = {
|
|
|
67
75
|
const m = descentParser(jsGrammar());
|
|
68
76
|
const cp = toArray(stringToCodePointList('56.7e+5'));
|
|
69
77
|
const mr = descentParserCpOnly(m, '', cp);
|
|
70
|
-
const seq = mr
|
|
78
|
+
const seq = mr.ast.sequence[0];
|
|
71
79
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
72
80
|
assertEq(seq.tag, 'number', JSON.stringify(mr));
|
|
73
81
|
},
|
|
@@ -75,7 +83,7 @@ export const proof = {
|
|
|
75
83
|
const m = descentParser(jsGrammar());
|
|
76
84
|
const cp = toArray(stringToCodePointList('56n'));
|
|
77
85
|
const mr = descentParserCpOnly(m, '', cp);
|
|
78
|
-
const seq = mr
|
|
86
|
+
const seq = mr.ast.sequence[0];
|
|
79
87
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
80
88
|
assertEq(seq.tag, 'number', JSON.stringify(mr));
|
|
81
89
|
},
|
|
@@ -83,7 +91,7 @@ export const proof = {
|
|
|
83
91
|
const m = descentParser(jsGrammar());
|
|
84
92
|
const cp = toArray(stringToCodePointList('*'));
|
|
85
93
|
const mr = descentParserCpOnly(m, '', cp);
|
|
86
|
-
const seq = mr
|
|
94
|
+
const seq = mr.ast.sequence[0];
|
|
87
95
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
88
96
|
assertEq(seq.tag, '*', JSON.stringify(mr));
|
|
89
97
|
},
|
|
@@ -91,7 +99,7 @@ export const proof = {
|
|
|
91
99
|
const m = descentParser(jsGrammar());
|
|
92
100
|
const cp = toArray(stringToCodePointList('**'));
|
|
93
101
|
const mr = descentParserCpOnly(m, '', cp);
|
|
94
|
-
const seq = mr
|
|
102
|
+
const seq = mr.ast.sequence[0];
|
|
95
103
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
96
104
|
assertEq(seq.tag, '**', JSON.stringify(mr));
|
|
97
105
|
},
|
|
@@ -99,7 +107,7 @@ export const proof = {
|
|
|
99
107
|
const m = descentParser(jsGrammar());
|
|
100
108
|
const cp = toArray(stringToCodePointList('=>'));
|
|
101
109
|
const mr = descentParserCpOnly(m, '', cp);
|
|
102
|
-
const seq = mr
|
|
110
|
+
const seq = mr.ast.sequence[0];
|
|
103
111
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
104
112
|
assertEq(seq.tag, '=>', JSON.stringify(mr));
|
|
105
113
|
},
|
|
@@ -107,7 +115,7 @@ export const proof = {
|
|
|
107
115
|
const m = descentParser(jsGrammar());
|
|
108
116
|
const cp = toArray(stringToCodePointList('=='));
|
|
109
117
|
const mr = descentParserCpOnly(m, '', cp);
|
|
110
|
-
const seq = mr
|
|
118
|
+
const seq = mr.ast.sequence[0];
|
|
111
119
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
112
120
|
assert(!(seq.tag !== '=='), JSON.stringify(mr));
|
|
113
121
|
},
|
|
@@ -115,7 +123,7 @@ export const proof = {
|
|
|
115
123
|
const m = descentParser(jsGrammar());
|
|
116
124
|
const cp = toArray(stringToCodePointList('==='));
|
|
117
125
|
const mr = descentParserCpOnly(m, '', cp);
|
|
118
|
-
const seq = mr
|
|
126
|
+
const seq = mr.ast.sequence[0];
|
|
119
127
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
120
128
|
assert(!(seq.tag !== '==='), JSON.stringify(mr));
|
|
121
129
|
},
|
|
@@ -123,7 +131,7 @@ export const proof = {
|
|
|
123
131
|
const m = descentParser(jsGrammar());
|
|
124
132
|
const cp = toArray(stringToCodePointList('='));
|
|
125
133
|
const mr = descentParserCpOnly(m, '', cp);
|
|
126
|
-
const seq = mr
|
|
134
|
+
const seq = mr.ast.sequence[0];
|
|
127
135
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
128
136
|
assertEq(seq.tag, '=', JSON.stringify(mr));
|
|
129
137
|
},
|
|
@@ -131,7 +139,7 @@ export const proof = {
|
|
|
131
139
|
const m = descentParser(jsGrammar());
|
|
132
140
|
const cp = toArray(stringToCodePointList(' '));
|
|
133
141
|
const mr = descentParserCpOnly(m, '', cp);
|
|
134
|
-
const seq = mr
|
|
142
|
+
const seq = mr.ast.sequence[0];
|
|
135
143
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
136
144
|
assertEq(seq.tag, ' ', JSON.stringify(mr));
|
|
137
145
|
},
|
|
@@ -139,7 +147,7 @@ export const proof = {
|
|
|
139
147
|
const m = descentParser(jsGrammar());
|
|
140
148
|
const cp = toArray(stringToCodePointList('\n'));
|
|
141
149
|
const mr = descentParserCpOnly(m, '', cp);
|
|
142
|
-
const seq = mr
|
|
150
|
+
const seq = mr.ast.sequence[0];
|
|
143
151
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
144
152
|
assertEq(seq.tag, '\n', JSON.stringify(mr));
|
|
145
153
|
},
|
|
@@ -147,7 +155,7 @@ export const proof = {
|
|
|
147
155
|
const m = descentParser(jsGrammar());
|
|
148
156
|
const cp = toArray(stringToCodePointList('/\n'));
|
|
149
157
|
const mr = descentParserCpOnly(m, '', cp);
|
|
150
|
-
const seq = mr
|
|
158
|
+
const seq = mr.ast.sequence[0];
|
|
151
159
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
152
160
|
assertEq(seq.tag, '/', JSON.stringify(mr));
|
|
153
161
|
},
|
|
@@ -155,7 +163,7 @@ export const proof = {
|
|
|
155
163
|
const m = descentParser(jsGrammar());
|
|
156
164
|
const cp = toArray(stringToCodePointList('//\n'));
|
|
157
165
|
const mr = descentParserCpOnly(m, '', cp);
|
|
158
|
-
const seq = mr
|
|
166
|
+
const seq = mr.ast.sequence[0];
|
|
159
167
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
160
168
|
assertEq(seq.tag, 'comment', JSON.stringify(mr));
|
|
161
169
|
},
|
|
@@ -163,7 +171,7 @@ export const proof = {
|
|
|
163
171
|
const m = descentParser(jsGrammar());
|
|
164
172
|
const cp = toArray(stringToCodePointList('/*1*/'));
|
|
165
173
|
const mr = descentParserCpOnly(m, '', cp);
|
|
166
|
-
const seq = mr
|
|
174
|
+
const seq = mr.ast.sequence[0];
|
|
167
175
|
assert(!(seq instanceof Array), JSON.stringify(mr));
|
|
168
176
|
assertEq(seq.tag, 'comment', JSON.stringify(mr));
|
|
169
177
|
}
|
|
@@ -1056,6 +1064,52 @@ export const proof = {
|
|
|
1056
1064
|
assertEq(JSON.stringify(result), '[{"token":{"kind":"error","message":"invalid token"},"metadata":{"path":"a.js","line":1,"column":2}}]');
|
|
1057
1065
|
},
|
|
1058
1066
|
],
|
|
1067
|
+
// Where an error token is reported. The `tokenizer` group above checks that
|
|
1068
|
+
// these inputs are rejected at all, through the metadata-free
|
|
1069
|
+
// `tokenizeString`; here the same shapes go through `tokenizeJs` so the
|
|
1070
|
+
// position is pinned too.
|
|
1071
|
+
errorPosition: [
|
|
1072
|
+
() => {
|
|
1073
|
+
// input that tokenizes cleanly produces no error token at all —
|
|
1074
|
+
// without this the helper's own no-error branch never runs
|
|
1075
|
+
assertEq(errorAt('x'), 'no error');
|
|
1076
|
+
assertEq(errorAt('{ "a": 1 }'), 'no error');
|
|
1077
|
+
},
|
|
1078
|
+
() => {
|
|
1079
|
+
// a character no rule accepts: reported where it stands
|
|
1080
|
+
assertEq(errorAt('ᄑ'), '1:1');
|
|
1081
|
+
},
|
|
1082
|
+
() => {
|
|
1083
|
+
// after a good prefix, the position advances past it
|
|
1084
|
+
assertEq(errorAt('x @'), '1:3');
|
|
1085
|
+
},
|
|
1086
|
+
() => {
|
|
1087
|
+
// line as well as column, once newlines have been consumed
|
|
1088
|
+
assertEq(errorAt('a\nb\n@'), '3:1');
|
|
1089
|
+
assertEq(errorAt('x\n\n @y'), '3:3');
|
|
1090
|
+
},
|
|
1091
|
+
() => {
|
|
1092
|
+
// a number poisoned by a trailing character points at that character
|
|
1093
|
+
assertEq(errorAt('00'), '1:2');
|
|
1094
|
+
},
|
|
1095
|
+
() => {
|
|
1096
|
+
// a number cut short points just past the input
|
|
1097
|
+
assertEq(errorAt('1.'), '1:3');
|
|
1098
|
+
},
|
|
1099
|
+
() => {
|
|
1100
|
+
// Unterminated tokens anchor at the token's *start*, not where the
|
|
1101
|
+
// input ran out: the grammar matches them and tags them
|
|
1102
|
+
// 'unterminated', so this is the structural-error path, not a failed
|
|
1103
|
+
// match. See ./todo/error-position-range.md.
|
|
1104
|
+
assertEq(errorAt('"value'), '1:1');
|
|
1105
|
+
assertEq(errorAt('"a\nb"'), '1:1');
|
|
1106
|
+
},
|
|
1107
|
+
() => {
|
|
1108
|
+
// an unterminated block comment is the exception: its content is
|
|
1109
|
+
// consumed, so the report lands at the end of input
|
|
1110
|
+
assertEq(errorAt('/* c'), '1:5');
|
|
1111
|
+
},
|
|
1112
|
+
],
|
|
1059
1113
|
// DJS-level: keyword remapping and '-'-folding on top of tokenizeJs. Doesn't re-test
|
|
1060
1114
|
// JS-level token shapes already covered above/elsewhere in this file.
|
|
1061
1115
|
djsTokenize: [
|
|
@@ -4,9 +4,9 @@ import { type NodeProgram } from './module.f.ts';
|
|
|
4
4
|
* resolving to its exit code **without** terminating the process.
|
|
5
5
|
*
|
|
6
6
|
* Use this when the caller must stay alive afterwards — e.g. when proofs are
|
|
7
|
-
* registered under an external test runner (Node `--test`, Bun,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* registered under an external test runner (Node `--test`, Bun, Deno) that owns
|
|
8
|
+
* the process lifecycle. For a standalone CLI entry point that should exit with
|
|
9
|
+
* the program's code, use {@link run} instead.
|
|
10
10
|
*/
|
|
11
11
|
export declare const runEffect: (p: NodeProgram) => Promise<number>;
|
|
12
12
|
/**
|
|
@@ -224,8 +224,7 @@ export type Await = readonly ['await', (p: unknown) => readonly [unknown]];
|
|
|
224
224
|
export declare const awaitIfPromise: (p: unknown) => Effect<Await, unknown>;
|
|
225
225
|
/**
|
|
226
226
|
* Signature of a framework test-registration function (e.g. `nodeTest.test`,
|
|
227
|
-
* `bunTest.test
|
|
228
|
-
* be awaited.
|
|
227
|
+
* `bunTest.test`). Returns `Promise<void>` so async sub-tests can be awaited.
|
|
229
228
|
*/
|
|
230
229
|
export type TestFn = (name: string, options: {
|
|
231
230
|
readonly expectFailure: boolean;
|
|
@@ -233,8 +232,8 @@ export type TestFn = (name: string, options: {
|
|
|
233
232
|
/**
|
|
234
233
|
* A thin wrapper around a framework's `test` function. Passed through
|
|
235
234
|
* `registerModule` so nested test registration uses the appropriate context
|
|
236
|
-
* (e.g. `inlineContext` on Bun
|
|
237
|
-
*
|
|
235
|
+
* (e.g. `inlineContext` on Bun, which does not support nested `test()` calls
|
|
236
|
+
* inside a callback).
|
|
238
237
|
*/
|
|
239
238
|
export type TestContext = {
|
|
240
239
|
readonly test: TestFn;
|
|
@@ -258,7 +257,16 @@ export type Env = {
|
|
|
258
257
|
readonly [k: string]: string | undefined;
|
|
259
258
|
};
|
|
260
259
|
/** Identifies the JavaScript runtime detected at startup. */
|
|
261
|
-
export type Engine = 'node' | 'bun' | '
|
|
260
|
+
export type Engine = 'node' | 'bun' | 'deno';
|
|
261
|
+
/** Compares semantic versions numerically by major, minor, then patch. */
|
|
262
|
+
export declare const versionLessThan: (version: string, minimum: string) => boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Reports whether an external runner needs FunctionalScript's flattened test
|
|
265
|
+
* registration strategy. Node uses the native `expectFailure` option only
|
|
266
|
+
* from the Node 26 baseline; Deno is deliberately exempt from this Node-only
|
|
267
|
+
* version check.
|
|
268
|
+
*/
|
|
269
|
+
export declare const usesInlineTestContext: (engine: Engine, nodeVersion?: string) => boolean;
|
|
262
270
|
/**
|
|
263
271
|
* Runtime options passed to every `NodeProgram`.
|
|
264
272
|
*
|
|
@@ -269,8 +277,9 @@ export type Engine = 'node' | 'bun' | 'playwright';
|
|
|
269
277
|
* - `testContext`: Node `--test` context; used by `register` on Node.
|
|
270
278
|
* - `bunTestContext`: Bun-compatible context that flattens nested tests inline,
|
|
271
279
|
* working around Bun's lack of nested `test()` support.
|
|
272
|
-
* - `playwrightTestContext`: Playwright context using the same inline strategy.
|
|
273
280
|
* - `engine`: runtime detected at startup; controls which context `register` selects.
|
|
281
|
+
* - `nodeVersion`: detected Node version; absent for other and virtual runtimes.
|
|
282
|
+
* - `inlineTestContext`: whether the selected context flattens nested tests.
|
|
274
283
|
*/
|
|
275
284
|
export type NodeProgramOptions = {
|
|
276
285
|
readonly args: readonly string[];
|
|
@@ -283,8 +292,9 @@ export type NodeProgramOptions = {
|
|
|
283
292
|
};
|
|
284
293
|
readonly testContext: TestContext;
|
|
285
294
|
readonly bunTestContext: TestContext;
|
|
286
|
-
readonly playwrightTestContext: TestContext;
|
|
287
295
|
readonly engine: Engine;
|
|
296
|
+
readonly nodeVersion?: string;
|
|
297
|
+
readonly inlineTestContext: boolean;
|
|
288
298
|
};
|
|
289
299
|
export type Program<O extends Operation> = (options: NodeProgramOptions) => Effect<O, number>;
|
|
290
300
|
export type NodeProgram = Program<NodeOp>;
|
|
@@ -147,3 +147,25 @@ export const test = do_('test');
|
|
|
147
147
|
* compose `mapStep(error(s), () => n)` directly.
|
|
148
148
|
*/
|
|
149
149
|
export const errorExit = (s) => mapStep(error(s), () => 1);
|
|
150
|
+
const versionParts = (version) => version.replace(/^v/, '').split('.').map(Number);
|
|
151
|
+
/** Compares semantic versions numerically by major, minor, then patch. */
|
|
152
|
+
export const versionLessThan = (version, minimum) => {
|
|
153
|
+
const [major = 0, minor = 0, patch = 0] = versionParts(version);
|
|
154
|
+
const [minMajor = 0, minMinor = 0, minPatch = 0] = versionParts(minimum);
|
|
155
|
+
return major < minMajor || major === minMajor && (minor < minMinor || minor === minMinor && patch < minPatch);
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Reports whether an external runner needs FunctionalScript's flattened test
|
|
159
|
+
* registration strategy. Node uses the native `expectFailure` option only
|
|
160
|
+
* from the Node 26 baseline; Deno is deliberately exempt from this Node-only
|
|
161
|
+
* version check.
|
|
162
|
+
*/
|
|
163
|
+
export const usesInlineTestContext = (engine, nodeVersion) => {
|
|
164
|
+
if (engine === 'bun') {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (engine !== 'node' || nodeVersion === undefined) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return versionLessThan(nodeVersion, '26.0.0');
|
|
171
|
+
};
|
|
@@ -32,7 +32,7 @@ import { concat, normalize, toPosix } from '../../path/module.f.js';
|
|
|
32
32
|
import {} from '../module.f.js';
|
|
33
33
|
import { asyncRun } from '../module.js';
|
|
34
34
|
import { memoryOperationMap } from './memory/module.js';
|
|
35
|
-
import {} from './module.f.js';
|
|
35
|
+
import { usesInlineTestContext, } from './module.f.js';
|
|
36
36
|
import { asBase, asNominal } from '../../types/nominal/module.f.js';
|
|
37
37
|
import { error, ok } from '../../types/result/module.f.js';
|
|
38
38
|
import { asyncTryCatch } from '../../types/result/module.js';
|
|
@@ -250,10 +250,6 @@ const runNodeEffect = asyncRun({
|
|
|
250
250
|
read: readStdinByte,
|
|
251
251
|
test: async (ctx, name, expectFailure, test) => ctx.test(name, { expectFailure }, async (t) => runNodeEffect(test(t))),
|
|
252
252
|
});
|
|
253
|
-
const isPlaywright = 'PLAYWRIGHT_TEST' in (process?.env ?? {});
|
|
254
|
-
const pwTest = isPlaywright
|
|
255
|
-
? (await import('@playwright/test')).test
|
|
256
|
-
: undefined;
|
|
257
253
|
const inlineTest = async (name, { expectFailure }, fn) => {
|
|
258
254
|
if (expectFailure) {
|
|
259
255
|
try {
|
|
@@ -273,25 +269,29 @@ const wrapInlineTest = (register) => ({
|
|
|
273
269
|
test: (name, opts, fn) => register(name, () => inlineTest(name, opts, fn))
|
|
274
270
|
});
|
|
275
271
|
const bunTestContext = wrapInlineTest(testContext.test);
|
|
276
|
-
const
|
|
272
|
+
const engine = 'Bun' in globalThis ? 'bun' :
|
|
273
|
+
'Deno' in globalThis ? 'deno' : 'node';
|
|
274
|
+
const nodeVersion = engine === 'node' ? process.version : undefined;
|
|
275
|
+
const inlineTestContext = usesInlineTestContext(engine, nodeVersion);
|
|
277
276
|
const options = {
|
|
278
277
|
args: process.argv.slice(2),
|
|
279
278
|
env: process.env,
|
|
280
279
|
home: toPosix(os.homedir()),
|
|
281
280
|
std: { stdout: process.stdout, stderr: process.stderr },
|
|
282
|
-
testContext,
|
|
281
|
+
testContext: inlineTestContext ? wrapInlineTest(testContext.test) : testContext,
|
|
283
282
|
bunTestContext,
|
|
284
|
-
|
|
285
|
-
|
|
283
|
+
engine,
|
|
284
|
+
...(nodeVersion === undefined ? {} : { nodeVersion }),
|
|
285
|
+
inlineTestContext,
|
|
286
286
|
};
|
|
287
287
|
/**
|
|
288
288
|
* Runs a `NodeProgram` against the real Node globals and process arguments,
|
|
289
289
|
* resolving to its exit code **without** terminating the process.
|
|
290
290
|
*
|
|
291
291
|
* Use this when the caller must stay alive afterwards — e.g. when proofs are
|
|
292
|
-
* registered under an external test runner (Node `--test`, Bun,
|
|
293
|
-
*
|
|
294
|
-
*
|
|
292
|
+
* registered under an external test runner (Node `--test`, Bun, Deno) that owns
|
|
293
|
+
* the process lifecycle. For a standalone CLI entry point that should exit with
|
|
294
|
+
* the program's code, use {@link run} instead.
|
|
295
295
|
*/
|
|
296
296
|
export const runEffect = program => runNodeEffect(program(options));
|
|
297
297
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
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
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 } 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";
|
|
@@ -17,6 +17,19 @@ const readHello = match({
|
|
|
17
17
|
},
|
|
18
18
|
});
|
|
19
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
|
+
},
|
|
20
33
|
map: () => {
|
|
21
34
|
const e = step(readFile('hello'), ([k, v]) => {
|
|
22
35
|
assert(k !== 'error', v);
|
|
@@ -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,8 +5,8 @@
|
|
|
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
|
*/
|
|
@@ -79,12 +79,12 @@ export const collectTests = (path, throws, v) => {
|
|
|
79
79
|
* Unlike `runModule`, which sandboxes only the leaf function, `registerModule`
|
|
80
80
|
* lets the external framework own scheduling: each registered test callback
|
|
81
81
|
* calls `fn`, then recursively registers any sub-trees returned by the function.
|
|
82
|
-
* This is the correct model for Node `--test`, Bun, and
|
|
83
|
-
*
|
|
82
|
+
* This is the correct model for Node `--test`, Bun, and Deno, where tests must
|
|
83
|
+
* be declared upfront and the framework drives execution.
|
|
84
84
|
*/
|
|
85
85
|
export const registerModule = (ctx, k, v, star) => {
|
|
86
86
|
const registerOne = (ctx, [path, { fn, throws }]) => {
|
|
87
|
-
// ' *' (non-empty only for Bun
|
|
87
|
+
// ' *' (non-empty only for Bun) signals that all sub-tests run
|
|
88
88
|
// inline inside this single registration. Not appended to throw-tests since
|
|
89
89
|
// those never produce sub-tests. The path already contains '.throw' when a
|
|
90
90
|
// test is expected to throw, so no extra suffix is needed.
|
|
@@ -273,17 +273,15 @@ export const defaultReporter = (options) => {
|
|
|
273
273
|
/** The `fjs t` entry point: runs all tests using `defaultReporter`. */
|
|
274
274
|
export const main = options => testAll(defaultReporter(options))(options);
|
|
275
275
|
/**
|
|
276
|
-
* Entry point for external test frameworks (Node `--test`, Bun,
|
|
276
|
+
* Entry point for external test frameworks (Node `--test`, Bun, Deno).
|
|
277
277
|
*
|
|
278
278
|
* Discovers test modules via `loadModuleMap`, then registers each with the
|
|
279
279
|
* framework-appropriate `TestContext` selected from `NodeProgramOptions`
|
|
280
280
|
* based on the detected `engine`.
|
|
281
281
|
*/
|
|
282
282
|
export const register = o => {
|
|
283
|
-
const star = o.
|
|
284
|
-
const ctx = o.engine === 'bun' ? o.bunTestContext :
|
|
285
|
-
o.engine === 'playwright' ? o.playwrightTestContext :
|
|
286
|
-
o.testContext;
|
|
283
|
+
const star = o.inlineTestContext ? ' ...' : '';
|
|
284
|
+
const ctx = o.engine === 'bun' ? o.bunTestContext : o.testContext;
|
|
287
285
|
return eff(loadModuleMap(o.env))
|
|
288
286
|
.step(registerModuleMap(ctx, star))
|
|
289
287
|
.step(() => pure(0))
|
|
@@ -236,7 +236,7 @@ const makeRegisterRunner = (testOp) => {
|
|
|
236
236
|
});
|
|
237
237
|
return runner;
|
|
238
238
|
};
|
|
239
|
-
// registerModule appends ' ...' for inline runners (Bun
|
|
239
|
+
// registerModule appends ' ...' for inline runners (Bun).
|
|
240
240
|
// This mock never invokes the registered callback; it only records names.
|
|
241
241
|
export const registerSuffixes = () => {
|
|
242
242
|
const runner = makeRegisterRunner((_runner, _ctx, name, _xf, _fn) => (s) => [[...s, name], undefined]);
|
|
@@ -249,7 +249,7 @@ export const registerSuffixes = () => {
|
|
|
249
249
|
assertEq(nodeNames.length, 2);
|
|
250
250
|
assert(nodeNames[0] === 'import("./a.f.ts").proof.ok()');
|
|
251
251
|
assertEq(nodeNames[1], 'import("./a.f.ts").proof.throw.a()');
|
|
252
|
-
// Bun
|
|
252
|
+
// Bun (star = ' ...'): ... on normal tests, path shows throw for throw-tests
|
|
253
253
|
const [inlineNames] = runner([])(registerModule(registerNoopCtx, './a.f.ts', proof, ' ...'));
|
|
254
254
|
assertEq(inlineNames.length, 2);
|
|
255
255
|
assert(inlineNames[0] === 'import("./a.f.ts").proof.ok() ...');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A test that returns a thenable (Promise-like object, not a real Promise).
|
|
2
2
|
// Per FunctionalScript convention, thenables are treated as plain values —
|
|
3
|
-
// not awaited. Both sandbox (fjs) and registerModule (node/bun/deno
|
|
3
|
+
// not awaited. Both sandbox (fjs) and registerModule (node/bun/deno)
|
|
4
4
|
// must exit 0: the thenable object is walked as a sub-tree whose only key
|
|
5
5
|
// `then` is a function with parameters, so no leaf tests are found and the
|
|
6
6
|
// test trivially passes.
|
package/fjs/fsc/module.f.js
CHANGED
|
@@ -7,7 +7,7 @@ import { strictEqual } from '../types/function/operator/module.f.js';
|
|
|
7
7
|
import { merge as rangeMapMerge, fromRange, get, } from '../types/range_map/module.f.js';
|
|
8
8
|
import { reduce as listReduce, toArray, map } from '../types/list/module.f.js';
|
|
9
9
|
import { range as asciiRange } from '../text/ascii/module.f.js';
|
|
10
|
-
import { fn } from '../types/function/module.f.js';
|
|
10
|
+
import { flip, fn } from '../types/function/module.f.js';
|
|
11
11
|
import { one } from '../types/range/module.f.js';
|
|
12
12
|
import { assertEq } from '../asserts/module.f.js';
|
|
13
13
|
const fromCharCode = String.fromCharCode;
|
|
@@ -31,17 +31,17 @@ const reduce = (a) => {
|
|
|
31
31
|
});
|
|
32
32
|
return toArray(listReduce(merge)(empty)(a));
|
|
33
33
|
};
|
|
34
|
-
const codePointRange = fromRange(def);
|
|
34
|
+
const codePointRange = flip(fromRange(def));
|
|
35
35
|
const range = fn(asciiRange).map(codePointRange).result;
|
|
36
36
|
const rangeSet = (l) => (f) => {
|
|
37
|
-
const codePointRange = fromRange(def);
|
|
37
|
+
const codePointRange = flip(fromRange(def));
|
|
38
38
|
const g = r => codePointRange(asciiRange(r))(f);
|
|
39
39
|
return reduce(map(g)(l));
|
|
40
40
|
};
|
|
41
41
|
const create = (a) => {
|
|
42
42
|
const i = reduce(a);
|
|
43
43
|
const x = get(def);
|
|
44
|
-
return v => c => x(
|
|
44
|
+
return v => c => x(i)(c)(v)(c);
|
|
45
45
|
};
|
|
46
46
|
export const terminal = -1;
|
|
47
47
|
const toInit = () => () => [[], init];
|
package/fjs/fsm/module.f.js
CHANGED
|
@@ -51,5 +51,5 @@ const initialState = [''];
|
|
|
51
51
|
const initialStateStringify = stringifyIdentity(initialState);
|
|
52
52
|
export const dfa = grammar => addEntry(grammar)(initialState)({});
|
|
53
53
|
const get = rangeMapGet(emptyStateStringify);
|
|
54
|
-
const runOp = dfa => input => s => get(
|
|
54
|
+
const runOp = dfa => input => s => get(dfa[s] ?? [])(input);
|
|
55
55
|
export const run = (dfa) => (input) => foldScan(runOp(dfa))(initialStateStringify)(input);
|
|
@@ -96,6 +96,7 @@ type ErrorMessage = '" are missing' | 'unescaped character' | 'invalid hex value
|
|
|
96
96
|
export declare const isKeywordToken: (token: JsToken) => boolean;
|
|
97
97
|
export declare const tokenize: (input: List<number>) => (path: string) => List<JsTokenWithMetadata>;
|
|
98
98
|
export declare const proof: {
|
|
99
|
+
getOperatorTokenInvalid: () => void;
|
|
99
100
|
throw: {
|
|
100
101
|
unionConflict: () => void;
|
|
101
102
|
};
|