functionalscript 0.41.0 → 0.42.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/fjs/ci/config/module.f.d.ts +3 -3
- package/fjs/ci/config/module.f.js +3 -3
- package/fjs/ci/deno/module.f.d.ts +8 -0
- package/fjs/ci/deno/module.f.js +9 -1
- package/fjs/ci/deno/proof.f.d.ts +5 -0
- package/fjs/ci/deno/proof.f.js +26 -0
- package/fjs/ci/proof.f.js +3 -2
- package/fjs/dev/module.f.d.ts +15 -4
- package/fjs/dev/module.f.js +38 -7
- package/fjs/dev/package_json/module.f.js +7 -4
- package/fjs/dev/proof.f.js +10 -1
- package/fjs/djs/parser/module.f.js +13 -3
- package/fjs/djs/parser/proof.f.d.ts +1 -0
- package/fjs/djs/parser/proof.f.js +26 -0
- package/fjs/djs/tokenizer/proof.f.js +11 -4
- package/fjs/emergent_testing/proof.f.js +20 -3
- package/fjs/js/tokenizer/proof.f.js +7 -0
- package/fjs/mcp/cas/module.f.js +4 -1
- package/fjs/mcp/evo/proof.f.js +6 -1
- package/fjs/mcp/proof.f.js +37 -18
- package/fjs/media/json/module.f.d.ts +15 -1
- package/fjs/media/json/module.f.js +17 -1
- package/fjs/media/json/parser/module.f.js +13 -3
- package/fjs/media/json/parser/proof.f.d.ts +1 -0
- package/fjs/media/json/parser/proof.f.js +37 -1
- package/fjs/media/json/proof.f.d.ts +4 -0
- package/fjs/media/json/proof.f.js +14 -2
- package/fjs/media/json/serializer/module.f.d.ts +4 -0
- package/fjs/media/json/serializer/module.f.js +39 -2
- package/fjs/media/json/serializer/proof.f.js +32 -8
- package/fjs/media/module.f.d.ts +92 -10
- package/fjs/media/module.f.js +73 -8
- package/fjs/media/proof.f.d.ts +10 -0
- package/fjs/media/proof.f.js +79 -10
- package/fjs/media/revision/module.f.d.ts +9 -1
- package/fjs/media/revision/module.f.js +14 -5
- package/fjs/protocol/mcp/stdio/module.f.js +2 -5
- package/fjs/text/utf16/module.f.d.ts +24 -0
- package/fjs/text/utf16/module.f.js +24 -0
- package/fjs/text/utf16/proof.f.d.ts +1 -0
- package/fjs/text/utf16/proof.f.js +10 -1
- package/fjs/types/rtti/validate/proof.f.d.ts +2 -0
- package/fjs/types/rtti/validate/proof.f.js +58 -0
- package/nanvm-lib/tests/proof.f.d.ts +9 -0
- package/nanvm-lib/tests/proof.f.js +58 -7
- package/package.json +2 -2
|
@@ -18,17 +18,17 @@ export declare const images: {
|
|
|
18
18
|
readonly arm: 'windows-11-arm';
|
|
19
19
|
};
|
|
20
20
|
};
|
|
21
|
-
export declare const functionalscript: '0.
|
|
21
|
+
export declare const functionalscript: '0.41.0';
|
|
22
22
|
export declare const bun = "1.3.14";
|
|
23
23
|
export declare const deno = "2.9.4";
|
|
24
24
|
export declare const node: {
|
|
25
|
-
readonly default: '26.
|
|
25
|
+
readonly default: '26.6.0';
|
|
26
26
|
readonly node22: '22.23.2';
|
|
27
27
|
readonly node24: '24.18.0';
|
|
28
28
|
};
|
|
29
29
|
export declare const nixpkgs: {
|
|
30
30
|
readonly ref: 'nixos-26.05';
|
|
31
|
-
readonly commit: '
|
|
31
|
+
readonly commit: '04607e1165ac22c5fde6dcc54c9e0b3c0487c555';
|
|
32
32
|
};
|
|
33
33
|
export declare const wasmtime = "47.0.3";
|
|
34
34
|
export declare const wasmer = "7.2.1";
|
|
@@ -23,7 +23,7 @@ export const images = {
|
|
|
23
23
|
// published FunctionalScript release; do not tie it to package.json's current
|
|
24
24
|
// in-repo version.
|
|
25
25
|
// https://www.npmjs.com/package/functionalscript
|
|
26
|
-
export const functionalscript = '0.
|
|
26
|
+
export const functionalscript = '0.41.0';
|
|
27
27
|
// https://bun.sh/
|
|
28
28
|
export const bun = '1.3.14';
|
|
29
29
|
// https://deno.com/
|
|
@@ -36,7 +36,7 @@ export const deno = '2.9.4';
|
|
|
36
36
|
// it offers rather than the latest release.
|
|
37
37
|
// https://nodejs.org/en/download
|
|
38
38
|
export const node = {
|
|
39
|
-
default: '26.
|
|
39
|
+
default: '26.6.0',
|
|
40
40
|
node22: '22.23.2',
|
|
41
41
|
node24: '24.18.0',
|
|
42
42
|
};
|
|
@@ -47,7 +47,7 @@ export const node = {
|
|
|
47
47
|
// https://channels.nixos.org/nixos-26.05/git-revision
|
|
48
48
|
export const nixpkgs = {
|
|
49
49
|
ref: 'nixos-26.05',
|
|
50
|
-
commit: '
|
|
50
|
+
commit: '04607e1165ac22c5fde6dcc54c9e0b3c0487c555',
|
|
51
51
|
};
|
|
52
52
|
// https://github.com/bytecodealliance/wasmtime/releases
|
|
53
53
|
export const wasmtime = '47.0.3';
|
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
import { type MetaStep } from '../common/module.f.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The regular expression selecting FunctionalScript implementation modules for
|
|
4
|
+
* Deno coverage. Both authored extensions are included so a module migrated
|
|
5
|
+
* from `.f.ts` to `.f.mjs` stays in the report. Keep it semantically equal to
|
|
6
|
+
* the `--test-coverage-include` list in `package.json` and to the `cov` task in
|
|
7
|
+
* `deno.json`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const coverageInclude: '.*module\\.f\\.(ts|mjs)';
|
|
2
10
|
export declare const denoSteps: (version: string) => readonly MetaStep[];
|
package/fjs/ci/deno/module.f.js
CHANGED
|
@@ -7,6 +7,14 @@
|
|
|
7
7
|
import { deno } from '../config/module.f.js';
|
|
8
8
|
import { install, test, uses } from '../common/module.f.js';
|
|
9
9
|
const denoTest = 'deno test --allow-read --allow-env --allow-sys';
|
|
10
|
+
/**
|
|
11
|
+
* The regular expression selecting FunctionalScript implementation modules for
|
|
12
|
+
* Deno coverage. Both authored extensions are included so a module migrated
|
|
13
|
+
* from `.f.ts` to `.f.mjs` stays in the report. Keep it semantically equal to
|
|
14
|
+
* the `--test-coverage-include` list in `package.json` and to the `cov` task in
|
|
15
|
+
* `deno.json`.
|
|
16
|
+
*/
|
|
17
|
+
export const coverageInclude = '.*module\\.f\\.(ts|mjs)';
|
|
10
18
|
export const denoSteps = (version) => [
|
|
11
19
|
install(uses('denoland/setup-deno', { 'deno-version': deno })),
|
|
12
20
|
// We need --minimum-dependency-age=0 for functionalscript because we would like to use
|
|
@@ -16,5 +24,5 @@ export const denoSteps = (version) => [
|
|
|
16
24
|
install({ run: `deno install -g -A --minimum-dependency-age=0 npm:functionalscript@${version}` }),
|
|
17
25
|
test({ run: `deno run -A --minimum-dependency-age=0 npm:functionalscript@${version} t` }),
|
|
18
26
|
test({ run: 'deno install --frozen' }),
|
|
19
|
-
test({ run: `${denoTest} --coverage && deno coverage --include='
|
|
27
|
+
test({ run: `${denoTest} --coverage && deno coverage --include='${coverageInclude}'` }),
|
|
20
28
|
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { coverageInclude, denoSteps } from './module.f.js';
|
|
2
|
+
import { toSteps } from '../common/module.f.js';
|
|
3
|
+
import { assert, assertEq } from '../../asserts/module.f.js';
|
|
4
|
+
const coverageRuns = (version) => toSteps(denoSteps(version))
|
|
5
|
+
.flatMap(s => s.run !== undefined && s.run.includes('deno coverage') ? [s.run] : []);
|
|
6
|
+
export const proof = {
|
|
7
|
+
// A regression guard: dropping either authored implementation extension
|
|
8
|
+
// from the Deno coverage filter silently removes those modules from the
|
|
9
|
+
// CI coverage report while CI still passes.
|
|
10
|
+
coverageInclude: () => {
|
|
11
|
+
assertEq(coverageInclude, '.*module\\.f\\.(ts|mjs)');
|
|
12
|
+
},
|
|
13
|
+
coverageStep: () => {
|
|
14
|
+
const runs = coverageRuns('0.0.0');
|
|
15
|
+
assertEq(runs.length, 1);
|
|
16
|
+
const [run] = runs;
|
|
17
|
+
assert(run !== undefined);
|
|
18
|
+
assert(run.includes(`deno coverage --include='${coverageInclude}'`));
|
|
19
|
+
},
|
|
20
|
+
installsPinnedVersion: () => {
|
|
21
|
+
const runs = toSteps(denoSteps('1.2.3'))
|
|
22
|
+
.flatMap(s => s.run !== undefined && s.run.includes('npm:functionalscript@') ? [s.run] : []);
|
|
23
|
+
assertEq(runs.length, 2);
|
|
24
|
+
assert(runs.every(r => r.includes('npm:functionalscript@1.2.3')));
|
|
25
|
+
},
|
|
26
|
+
};
|
package/fjs/ci/proof.f.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ci, main } from './module.f.js';
|
|
2
2
|
import { functionalscript, node } from './config/module.f.js';
|
|
3
3
|
import { nodeNixJobs } from './node/module.f.js';
|
|
4
|
+
import { coverageInclude } from './deno/module.f.js';
|
|
4
5
|
import { utf8, utf8ToString } from '../text/module.f.js';
|
|
5
6
|
import { empty as emptyVec, isVec } from '../types/bit_vec/module.f.js';
|
|
6
7
|
import { test, ubuntu, parseGitHubAction } from './common/module.f.js';
|
|
@@ -33,7 +34,7 @@ const text = (dir, name) => {
|
|
|
33
34
|
const path = (dir, names) => names.reduce(subDir, dir);
|
|
34
35
|
const workflow = (state) => {
|
|
35
36
|
const workflows = path(state.root, ['.github', 'workflows']);
|
|
36
|
-
return unwrap(parseGitHubAction(jsonParse(text(workflows, 'ci.yml'))));
|
|
37
|
+
return unwrap(parseGitHubAction(unwrap(jsonParse(text(workflows, 'ci.yml')))));
|
|
37
38
|
};
|
|
38
39
|
const flake = (state, id) => text(path(state.root, ['nix', 'generated', id]), 'flake.nix');
|
|
39
40
|
const run = (rust, nodeExtra = () => []) => {
|
|
@@ -132,7 +133,7 @@ export const proof = {
|
|
|
132
133
|
assert(hasRun(`deno install -g -A --minimum-dependency-age=0 npm:functionalscript@${functionalscript}`)(gha), 'expected configured-version deno install cache');
|
|
133
134
|
assert(hasRun('deno install --frozen')(gha), 'expected deno lock install');
|
|
134
135
|
assert(hasRun(`deno run -A --minimum-dependency-age=0 npm:functionalscript@${functionalscript} t`)(gha), 'expected configured-version deno install');
|
|
135
|
-
assert(hasRun(
|
|
136
|
+
assert(hasRun(`deno test --allow-read --allow-env --allow-sys --coverage && deno coverage --include='${coverageInclude}'`)(gha), 'expected limited-permission deno coverage');
|
|
136
137
|
assert(hasRun(`bun install -g functionalscript@${functionalscript}`)(gha), 'expected configured-version bun cache');
|
|
137
138
|
assert(hasRun('bun install --frozen-lockfile')(gha), 'expected bun lock install');
|
|
138
139
|
assert(hasRun(`bunx functionalscript@${functionalscript} t`)(gha), 'expected configured-version bun install');
|
package/fjs/dev/module.f.d.ts
CHANGED
|
@@ -14,10 +14,19 @@ export type ModuleMap = StringMap<string, Module>;
|
|
|
14
14
|
/**
|
|
15
15
|
* Returns `true` if the file should be loaded for proof discovery.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Two symmetrical rules, each covering all four TS/JS module extensions:
|
|
18
|
+
*
|
|
19
|
+
* - **FunctionalScript modules** — anything ending in `.f.ts`, `.f.mts`,
|
|
20
|
+
* `.f.js`, or `.f.mjs`. They are safe to bulk-load by construction, since
|
|
21
|
+
* they have no import side effects, so the whole module is loaded and its
|
|
22
|
+
* internal `proof` export (if any) is discovered. `.f.ts` and `.f.mjs` are
|
|
23
|
+
* the authored extensions; `.f.js` is generated from `.f.ts`.
|
|
24
|
+
* - **Impure JavaScript/TypeScript proofs** — anything ending in `proof.ts`,
|
|
25
|
+
* `proof.mts`, `proof.js`, or `proof.mjs`. Outside FunctionalScript a module
|
|
26
|
+
* may have import side effects, so the load gate stays opt-in by filename.
|
|
27
|
+
*
|
|
28
|
+
* A `proof.f.mts` / `proof.f.mjs` matches the FunctionalScript rule by its
|
|
29
|
+
* `.f.` infix, not the vanilla proof rule.
|
|
21
30
|
*
|
|
22
31
|
* Whether a loaded module actually _contains_ a proof is determined at
|
|
23
32
|
* runtime by checking for an exported `proof` property.
|
|
@@ -42,5 +51,7 @@ export type LoadModuleOperations = Access | Import | All | Readdir;
|
|
|
42
51
|
export declare const loadModuleMap: (env: Env) => Effect<LoadModuleOperations, ModuleMap>;
|
|
43
52
|
export declare const proof: {
|
|
44
53
|
isSourceFile: () => void;
|
|
54
|
+
allFilesFindsFunctionalScript: () => void;
|
|
45
55
|
allFilesSkipsNodeModules: () => void;
|
|
56
|
+
loadModuleMapDefaultsToCwdWhenInitCwdUnset: () => void;
|
|
46
57
|
};
|
package/fjs/dev/module.f.js
CHANGED
|
@@ -13,17 +13,27 @@ import { emptyState, virtual } from '../effects/node/virtual/module.f.js';
|
|
|
13
13
|
/**
|
|
14
14
|
* Returns `true` if the file should be loaded for proof discovery.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Two symmetrical rules, each covering all four TS/JS module extensions:
|
|
17
|
+
*
|
|
18
|
+
* - **FunctionalScript modules** — anything ending in `.f.ts`, `.f.mts`,
|
|
19
|
+
* `.f.js`, or `.f.mjs`. They are safe to bulk-load by construction, since
|
|
20
|
+
* they have no import side effects, so the whole module is loaded and its
|
|
21
|
+
* internal `proof` export (if any) is discovered. `.f.ts` and `.f.mjs` are
|
|
22
|
+
* the authored extensions; `.f.js` is generated from `.f.ts`.
|
|
23
|
+
* - **Impure JavaScript/TypeScript proofs** — anything ending in `proof.ts`,
|
|
24
|
+
* `proof.mts`, `proof.js`, or `proof.mjs`. Outside FunctionalScript a module
|
|
25
|
+
* may have import side effects, so the load gate stays opt-in by filename.
|
|
26
|
+
*
|
|
27
|
+
* A `proof.f.mts` / `proof.f.mjs` matches the FunctionalScript rule by its
|
|
28
|
+
* `.f.` infix, not the vanilla proof rule.
|
|
20
29
|
*
|
|
21
30
|
* Whether a loaded module actually _contains_ a proof is determined at
|
|
22
31
|
* runtime by checking for an exported `proof` property.
|
|
23
32
|
*/
|
|
24
|
-
export const shouldLoad = (s) => s.endsWith('.f.ts') || s.endsWith('.f.
|
|
25
|
-
s.endsWith('
|
|
26
|
-
s.endsWith('proof.
|
|
33
|
+
export const shouldLoad = (s) => s.endsWith('.f.ts') || s.endsWith('.f.mts') ||
|
|
34
|
+
s.endsWith('.f.js') || s.endsWith('.f.mjs') ||
|
|
35
|
+
s.endsWith('proof.ts') || s.endsWith('proof.mts') ||
|
|
36
|
+
s.endsWith('proof.js') || s.endsWith('proof.mjs');
|
|
27
37
|
const isSourceFile = (path) => path.endsWith('.js') || path.endsWith('.ts') || path.endsWith('.mts') || path.endsWith('.mjs');
|
|
28
38
|
const allFiles = (s, predicate) => {
|
|
29
39
|
const load = (p) => {
|
|
@@ -91,6 +101,19 @@ export const proof = {
|
|
|
91
101
|
assert(!isSourceFile('readme.md'));
|
|
92
102
|
assert(!isSourceFile('module.json'));
|
|
93
103
|
},
|
|
104
|
+
allFilesFindsFunctionalScript: () => {
|
|
105
|
+
// Every FunctionalScript extension is discovered, so a module migrated
|
|
106
|
+
// from `.f.ts` to `.f.mjs` keeps its proofs. An ordinary `.mjs` is
|
|
107
|
+
// still skipped.
|
|
108
|
+
const root = {
|
|
109
|
+
'a.f.ts': [],
|
|
110
|
+
'b.f.mjs': [],
|
|
111
|
+
'c.f.mts': [],
|
|
112
|
+
'd.mjs': [],
|
|
113
|
+
};
|
|
114
|
+
const [, result] = virtual({ ...emptyState, root })(allFiles('.', shouldLoad));
|
|
115
|
+
assertEq(result.join(','), './a.f.ts,./b.f.mjs,./c.f.mts');
|
|
116
|
+
},
|
|
94
117
|
allFilesSkipsNodeModules: () => {
|
|
95
118
|
// `node_modules` is skipped without descending into it, even though
|
|
96
119
|
// it contains a file that would otherwise match the predicate.
|
|
@@ -101,4 +124,12 @@ export const proof = {
|
|
|
101
124
|
const [, result] = virtual({ ...emptyState, root })(allFiles('.', shouldLoad));
|
|
102
125
|
assertEq(result.join(','), './a.f.ts');
|
|
103
126
|
},
|
|
127
|
+
loadModuleMapDefaultsToCwdWhenInitCwdUnset: () => {
|
|
128
|
+
// With no `INIT_CWD` (e.g. `fjs t` invoked outside `npm run`), `env`
|
|
129
|
+
// lookup returns `undefined` and discovery falls back to `.`, so
|
|
130
|
+
// every path keeps its own `./`-prefix instead of having one stripped.
|
|
131
|
+
const root = { 'a.f.ts': () => ({}) };
|
|
132
|
+
const [, result] = virtual({ ...emptyState, root })(loadModuleMap({}));
|
|
133
|
+
assertEq(Object.keys(result).join(','), './a.f.ts');
|
|
134
|
+
},
|
|
104
135
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for reading package.json-style metadata without losing unrelated
|
|
3
|
+
* fields before write-back.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { parse as parseJsonText } from '../../media/json/module.f.js';
|
|
4
8
|
import { option, record, string } from '../../types/rtti/module.f.js';
|
|
5
9
|
import { validate as rttiValidate } from '../../types/rtti/validate/module.f.js';
|
|
6
10
|
import { error, ok } from '../../types/result/module.f.js';
|
|
@@ -10,7 +14,6 @@ export const packageJsonSchema = {
|
|
|
10
14
|
scripts: option(record(string)),
|
|
11
15
|
};
|
|
12
16
|
export const validatePackageJson = rttiValidate(packageJsonSchema);
|
|
13
|
-
const parseJsonText = (text) => jsonParse(jsonTokenize(stringToList(text)));
|
|
14
17
|
export const validatePackageJsonText = (text) => {
|
|
15
18
|
const [t, v] = parseJsonText(text);
|
|
16
19
|
if (t === 'error') {
|
package/fjs/dev/proof.f.js
CHANGED
|
@@ -2,13 +2,22 @@ import { assert, assertEq, todo } from '../asserts/module.f.js';
|
|
|
2
2
|
import { shouldLoad } from './module.f.js';
|
|
3
3
|
export const proof = {
|
|
4
4
|
shouldLoad: () => {
|
|
5
|
+
// Every FunctionalScript extension is loaded whatever the file name.
|
|
5
6
|
assert(shouldLoad('foo.f.ts'));
|
|
7
|
+
assert(shouldLoad('foo.f.mts'));
|
|
6
8
|
assert(shouldLoad('bar.f.js'));
|
|
9
|
+
assert(shouldLoad('module.f.mjs'));
|
|
10
|
+
assert(shouldLoad('proof.f.mts'));
|
|
11
|
+
assert(shouldLoad('proof.f.mjs'));
|
|
12
|
+
// Every impure JS/TS extension is loaded only under the `proof` name.
|
|
7
13
|
assert(shouldLoad('proof.ts'));
|
|
8
|
-
assert(shouldLoad('proof.js'));
|
|
9
14
|
assert(shouldLoad('proof.mts'));
|
|
15
|
+
assert(shouldLoad('proof.js'));
|
|
10
16
|
assert(shouldLoad('proof.mjs'));
|
|
11
17
|
assert(!shouldLoad('module.ts'));
|
|
18
|
+
assert(!shouldLoad('module.mts'));
|
|
19
|
+
assert(!shouldLoad('module.js'));
|
|
20
|
+
assert(!shouldLoad('module.mjs'));
|
|
12
21
|
assert(!shouldLoad('readme.md'));
|
|
13
22
|
},
|
|
14
23
|
shouldPass: () => ({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { error, ok } from '../../types/result/module.f.js';
|
|
7
|
-
import { fold,
|
|
7
|
+
import { fold, next, toArray, length, concat } from '../../types/list/module.f.js';
|
|
8
8
|
import { setReplace, at } from '../../types/ordered_map/module.f.js';
|
|
9
9
|
import { fromMap } from '../../types/object/module.f.js';
|
|
10
10
|
import { assertEq } from '../../asserts/module.f.js';
|
|
@@ -164,9 +164,19 @@ const startArray = state => {
|
|
|
164
164
|
const newStack = state.top === null ? null : { first: state.top, tail: state.stack };
|
|
165
165
|
return { ...state, valueState: '[', top: ['array', null], stack: newStack };
|
|
166
166
|
};
|
|
167
|
+
// Pops the enclosing container off `stack`. `next` is forced here rather than
|
|
168
|
+
// left as a `drop(1)` thunk: the stack is written only by startArray/startObject,
|
|
169
|
+
// always as a literal cons, and a lazy pop leaves one unforced thunk per closed
|
|
170
|
+
// container — a chain that overflows the call stack when it is finally forced.
|
|
171
|
+
const popStack = state => {
|
|
172
|
+
const ne = next(state.stack);
|
|
173
|
+
return ne === null
|
|
174
|
+
? { ...state, valueState: '', top: null, stack: null }
|
|
175
|
+
: { ...state, valueState: '', top: ne.first, stack: ne.tail };
|
|
176
|
+
};
|
|
167
177
|
const endArray = state => {
|
|
168
178
|
const top = state.top;
|
|
169
|
-
const newState =
|
|
179
|
+
const newState = popStack(state);
|
|
170
180
|
if (top !== null && top[0] === 'array') {
|
|
171
181
|
const array = ['array', toArray(top[1])];
|
|
172
182
|
return pushValue(newState)(array);
|
|
@@ -179,7 +189,7 @@ const startObject = state => {
|
|
|
179
189
|
};
|
|
180
190
|
const endObject = state => {
|
|
181
191
|
const obj = state?.top !== null && state?.top[0] === 'object' ? fromMap(state.top[1]) : null;
|
|
182
|
-
const newState =
|
|
192
|
+
const newState = popStack(state);
|
|
183
193
|
return pushValue(newState)(obj);
|
|
184
194
|
};
|
|
185
195
|
const tokenToValue = token => {
|
|
@@ -526,5 +526,31 @@ export const proof = {
|
|
|
526
526
|
const result = stringifyDjsModule(obj[1]);
|
|
527
527
|
assertEq(result, '[[],[null]]');
|
|
528
528
|
},
|
|
529
|
+
],
|
|
530
|
+
// Regression: closing a container popped the parser stack with `drop(1)`,
|
|
531
|
+
// which is lazy, so every closed container left one unforced thunk wrapping
|
|
532
|
+
// the stack. The chain was forced only at the end, costing a call-stack
|
|
533
|
+
// frame per container and overflowing at roughly 5000 of them — whether
|
|
534
|
+
// nested or flat siblings — while primitives were unbounded. `fjs/media/json`
|
|
535
|
+
// carried the same defect and was fixed in the same way.
|
|
536
|
+
containerStackCost: [
|
|
537
|
+
() => {
|
|
538
|
+
const [tag, value] = parseFromTokens(tokenizeString(`export default [${Array(20000).fill('{}').join(',')}]`));
|
|
539
|
+
assert(tag === 'ok', tag);
|
|
540
|
+
assertEq(value[1].length, 1);
|
|
541
|
+
},
|
|
542
|
+
() => {
|
|
543
|
+
const [tag] = parseFromTokens(tokenizeString(`export default [${Array(20000).fill('[]').join(',')}]`));
|
|
544
|
+
assert(tag === 'ok', tag);
|
|
545
|
+
},
|
|
546
|
+
() => {
|
|
547
|
+
const [tag] = parseFromTokens(tokenizeString('export default ' + '['.repeat(20000) + ']'.repeat(20000)));
|
|
548
|
+
assert(tag === 'ok', tag);
|
|
549
|
+
},
|
|
550
|
+
() => {
|
|
551
|
+
// primitives never touched the stack — the baseline that always passed
|
|
552
|
+
const [tag] = parseFromTokens(tokenizeString(`export default [${Array.from({ length: 20000 }, (_, i) => i).join(',')}]`));
|
|
553
|
+
assert(tag === 'ok', tag);
|
|
554
|
+
},
|
|
529
555
|
]
|
|
530
556
|
};
|
|
@@ -1167,12 +1167,19 @@ export const proof = {
|
|
|
1167
1167
|
() => {
|
|
1168
1168
|
// many distinct short tokens, well past the ~1000-1500 token crash threshold
|
|
1169
1169
|
// the old whole-file recursive match hit for this shape
|
|
1170
|
-
const
|
|
1170
|
+
const ids = Array.from({ length: 3000 }, (_, i) => `a${i % 10}`);
|
|
1171
|
+
const src = ids.join(' ');
|
|
1171
1172
|
const result = tokenizeString(src);
|
|
1172
1173
|
assert(result !== 'error', result);
|
|
1173
|
-
|
|
1174
|
-
//
|
|
1175
|
-
|
|
1174
|
+
// `tokenizeString` runs its own pipeline (`getTokensFromAstRule` +
|
|
1175
|
+
// filter/scan + `stringify`), not `tokenizeJs`'s, so only its dump
|
|
1176
|
+
// covers it. Compared whole rather than counted: at this size the
|
|
1177
|
+
// count was the only thing the old JSON round-trip could check,
|
|
1178
|
+
// while the reconstructed literal also pins every token's content.
|
|
1179
|
+
assertEq(result, `[${ids.map(v => `{"kind":"id","value":"${v}"}`).join(',{"kind":"ws"},')},{"kind":"eof"}]`);
|
|
1180
|
+
// The token list must agree with the dump: 3000 ids + 2999
|
|
1181
|
+
// separating ws + a trailing eof token.
|
|
1182
|
+
assertEq(toArray(tokenizeJs(stringToList(src))('a.js')).length, 3000 + 2999 + 1);
|
|
1176
1183
|
},
|
|
1177
1184
|
() => {
|
|
1178
1185
|
// same many-short-tokens shape through the metadata-aware entry point
|
|
@@ -5,10 +5,27 @@ import { assert, assertEq } from '../asserts/module.f.js';
|
|
|
5
5
|
import { testAll, defaultReporter, fmtPath, fmtTerm, fmtImport, ghEscape, isInteger, isIdentifier, registerModule, parseTestSet, defaultTest, } from './module.f.js';
|
|
6
6
|
import { run as mockRun } from '../effects/mock/module.f.js';
|
|
7
7
|
import { shouldLoad } from '../dev/module.f.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { parse as parseJson } from '../media/json/module.f.js';
|
|
9
|
+
import { array, number as rttiNumber, or, string as rttiString } from '../types/rtti/module.f.js';
|
|
10
|
+
import { parse as rttiParse } from '../types/rtti/parse/module.f.js';
|
|
11
|
+
import { unwrap } from '../types/result/module.f.js';
|
|
12
|
+
/**
|
|
13
|
+
* The mock reporter's stdout lines. A schema rather than a hand-written type:
|
|
14
|
+
* the events round-trip through JSON, and this is what turns a line back into
|
|
15
|
+
* a typed value without an `as` cast — a shape change fails at the parse
|
|
16
|
+
* instead of silently at an assertion.
|
|
17
|
+
*
|
|
18
|
+
* `Reporter.result` also receives the test's `SandboxResult`, which is not
|
|
19
|
+
* written: no assertion here reads it, and its `undefined` payloads have no
|
|
20
|
+
* JSON representation to round-trip through anyway.
|
|
21
|
+
*/
|
|
22
|
+
const event = or(['result', rttiString, array(or(rttiString, null))], ['summary', rttiNumber, rttiNumber, rttiNumber]);
|
|
23
|
+
const parseEvent = rttiParse(event);
|
|
24
|
+
const writeEvent = (e) => log(JSON.stringify(e));
|
|
25
|
+
const parseEvents = (stdout) => stdout === '' ? [] : stdout.trimEnd().split('\n')
|
|
26
|
+
.map(line => unwrap(parseEvent(unwrap(parseJson(line)))));
|
|
10
27
|
const makeReporter = () => ({
|
|
11
|
-
result: (file, path,
|
|
28
|
+
result: (file, path, _r, _throws) => writeEvent(['result', file, [...path]]),
|
|
12
29
|
summary: (pass, fail, time) => writeEvent(['summary', pass, fail, time]),
|
|
13
30
|
test: defaultTest,
|
|
14
31
|
});
|
|
@@ -357,6 +357,13 @@ export const proof = {
|
|
|
357
357
|
throw result;
|
|
358
358
|
}
|
|
359
359
|
},
|
|
360
|
+
() => {
|
|
361
|
+
// a second "." after the fraction has started (covers fullStopToToken's default branch)
|
|
362
|
+
const result = stringify(tokenizeString('1..'));
|
|
363
|
+
if (result !== '[{"kind":"error","message":"invalid number"},{"kind":"eof"}]') {
|
|
364
|
+
throw result;
|
|
365
|
+
}
|
|
366
|
+
},
|
|
360
367
|
() => {
|
|
361
368
|
const result = stringify(tokenizeString('0e1'));
|
|
362
369
|
if (result !== '[{"bf":[0n,1],"kind":"number","value":"0e1"},{"kind":"eof"}]') {
|
package/fjs/mcp/cas/module.f.js
CHANGED
|
@@ -109,7 +109,8 @@ import { cBase32ToVec, vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
|
109
109
|
import { decode as base64Decode, encode as base64Encode } from '../../basen/base64/module.f.js';
|
|
110
110
|
import { tryUtf8 } from '../../text/module.f.js';
|
|
111
111
|
import { detectStream } from '../../media/type/module.f.js';
|
|
112
|
-
import { detect
|
|
112
|
+
import { detect } from '../../media/module.f.js';
|
|
113
|
+
import { revisionDialect } from '../../media/revision/module.f.js';
|
|
113
114
|
import { maxLengthBytes } from '../../types/bit_vec/module.f.js';
|
|
114
115
|
import { ok } from '../../types/result/module.f.js';
|
|
115
116
|
import { toolEntry, errorResult, okResult, } from '../../protocol/mcp/module.f.js';
|
|
@@ -134,6 +135,8 @@ export const casGetArgs = {
|
|
|
134
135
|
export const casListArgs = {};
|
|
135
136
|
// ── Tool registry ──────────────────────────────────────────────────────────────
|
|
136
137
|
const toJson = stringify(identity);
|
|
138
|
+
/** The dialect-aware classifier, bound to the dialects this server recognizes. */
|
|
139
|
+
const detectDialect = detect([revisionDialect]);
|
|
137
140
|
/**
|
|
138
141
|
* Registry of all CAS tools, bound to `home` and the Evo cache at
|
|
139
142
|
* `cacheKey`. `cas_add` is the only tool that writes, so it is the only one
|
package/fjs/mcp/evo/proof.f.js
CHANGED
|
@@ -6,7 +6,12 @@ import { vec8 } from '../../types/bit_vec/module.f.js';
|
|
|
6
6
|
import { vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
7
7
|
import { initEvo, evo } from '../../cas/evo/module.f.js';
|
|
8
8
|
import { evoToolRegistry } from './module.f.js';
|
|
9
|
+
import { parse as parseJson } from '../../media/json/module.f.js';
|
|
10
|
+
import { array, string as rttiString } from '../../types/rtti/module.f.js';
|
|
11
|
+
import { parse as rttiParse } from '../../types/rtti/parse/module.f.js';
|
|
12
|
+
import { unwrap } from '../../types/result/module.f.js';
|
|
9
13
|
const home = '/home/user';
|
|
14
|
+
const parseSubjects = rttiParse(array(rttiString));
|
|
10
15
|
const findEntry = (registry, name) => {
|
|
11
16
|
const entry = registry.find(e => e.name === name);
|
|
12
17
|
assert(entry !== undefined, ['missing tool entry', name]);
|
|
@@ -52,7 +57,7 @@ export const proof = {
|
|
|
52
57
|
const entry = findEntry(evoToolRegistry(e), 'evo_list');
|
|
53
58
|
const [, result] = virtual(state2)(entry.handle({}));
|
|
54
59
|
assert(!result.isError);
|
|
55
|
-
const subjects =
|
|
60
|
+
const subjects = unwrap(parseSubjects(unwrap(parseJson(textOf(result)))));
|
|
56
61
|
assertEq(subjects.length, 2);
|
|
57
62
|
assert(subjects.includes('line one\nline two'), ['unexpected subjects', subjects]);
|
|
58
63
|
assert(subjects.includes(''), ['unexpected subjects', subjects]);
|