functionalscript 0.30.0 → 0.31.1
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/fs/asn.1/module.f.js +18 -22
- package/fs/asn.1/proof.f.d.ts +5 -0
- package/fs/asn.1/proof.f.js +38 -2
- package/fs/base64/module.f.d.ts +9 -0
- package/fs/base64/module.f.js +60 -0
- package/fs/base64/proof.f.d.ts +11 -0
- package/fs/base64/proof.f.js +117 -0
- package/fs/base_n/module.f.d.ts +44 -0
- package/fs/base_n/module.f.js +54 -0
- package/fs/base_n/proof.f.d.ts +10 -0
- package/fs/base_n/proof.f.js +86 -0
- package/fs/bnf/data/module.f.d.ts +4 -9
- package/fs/bnf/data/module.f.js +4 -3
- package/fs/bnf/module.f.d.ts +3 -4
- package/fs/bnf/module.f.js +3 -2
- package/fs/bnf/proof.f.d.ts +9 -0
- package/fs/bnf/proof.f.js +98 -1
- package/fs/cas/mcp/module.f.d.ts +40 -0
- package/fs/cas/mcp/module.f.js +223 -0
- package/fs/cas/mcp/proof.f.d.ts +30 -0
- package/fs/cas/mcp/proof.f.js +310 -0
- package/fs/cas/module.f.d.ts +9 -3
- package/fs/cas/module.f.js +104 -69
- package/fs/cas/proof.f.d.ts +2 -0
- package/fs/cas/proof.f.js +30 -1
- package/fs/cbase32/module.f.d.ts +1 -1
- package/fs/cbase32/module.f.js +19 -40
- package/fs/cbase32/proof.f.d.ts +1 -0
- package/fs/cbase32/proof.f.js +15 -0
- package/fs/ci/config/module.f.d.ts +6 -6
- package/fs/ci/config/module.f.js +6 -6
- package/fs/ci/deno/module.f.js +2 -2
- package/fs/ci/module.f.d.ts +5 -0
- package/fs/ci/module.f.js +2 -3
- package/fs/ci/proof.f.d.ts +1 -0
- package/fs/ci/proof.f.js +9 -3
- package/fs/cli/module.f.d.ts +3 -1
- package/fs/cli/module.f.js +22 -12
- package/fs/cli/proof.f.d.ts +5 -0
- package/fs/cli/proof.f.js +76 -0
- package/fs/crypto/hmac/proof.f.d.ts +1 -0
- package/fs/crypto/hmac/proof.f.js +8 -0
- package/fs/crypto/secp/proof.f.d.ts +1 -0
- package/fs/crypto/secp/proof.f.js +10 -0
- package/fs/crypto/sign/module.f.js +1 -4
- package/fs/dev/module.f.d.ts +5 -3
- package/fs/dev/module.f.js +13 -2
- package/fs/dev/package_json/proof.f.d.ts +1 -0
- package/fs/dev/package_json/proof.f.js +5 -0
- package/fs/dev/proof.f.d.ts +1 -0
- package/fs/dev/proof.f.js +12 -1
- package/fs/djs/ast/module.f.d.ts +1 -1
- package/fs/djs/ast/proof.f.d.ts +5 -0
- package/fs/djs/ast/proof.f.js +17 -0
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +2 -3
- package/fs/djs/serializer/module.f.js +24 -11
- package/fs/djs/transpiler/module.f.js +3 -4
- package/fs/effects/memory/module.f.d.ts +37 -0
- package/fs/effects/memory/module.f.js +24 -0
- package/fs/effects/memory/proof.f.d.ts +6 -0
- package/fs/effects/memory/proof.f.js +64 -0
- package/fs/effects/mock/module.f.d.ts +7 -2
- package/fs/effects/mock/module.f.js +12 -8
- package/fs/effects/module.d.ts +1 -1
- package/fs/effects/module.f.d.ts +41 -0
- package/fs/effects/module.f.js +25 -0
- package/fs/effects/module.js +6 -7
- package/fs/effects/node/memory/module.d.ts +20 -0
- package/fs/effects/node/memory/module.js +44 -0
- package/fs/effects/node/memory/proof.d.ts +10 -0
- package/fs/effects/node/memory/proof.js +35 -0
- package/fs/effects/node/module.d.ts +1 -1
- package/fs/effects/node/module.f.d.ts +54 -8
- package/fs/effects/node/module.f.js +55 -4
- package/fs/effects/node/module.js +167 -137
- package/fs/effects/node/proof.f.d.ts +10 -0
- package/fs/effects/node/proof.f.js +88 -21
- package/fs/effects/node/virtual/module.f.d.ts +6 -0
- package/fs/effects/node/virtual/module.f.js +48 -16
- package/fs/effects/node/virtual/proof.f.d.ts +17 -0
- package/fs/effects/node/virtual/proof.f.js +79 -0
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +54 -34
- package/fs/emergent_testing/module.f.d.ts +6 -1
- package/fs/emergent_testing/module.f.js +8 -3
- package/fs/emergent_testing/proof.f.d.ts +11 -0
- package/fs/emergent_testing/proof.f.js +52 -16
- package/fs/fjs/module.f.js +2 -2
- package/fs/fjs/proof.f.d.ts +8 -0
- package/fs/fjs/proof.f.js +39 -0
- package/fs/fsc/module.f.d.ts +5 -0
- package/fs/fsc/module.f.js +17 -27
- package/fs/fsc/proof.f.d.ts +8 -0
- package/fs/fsc/proof.f.js +48 -2
- package/fs/fsm/module.f.d.ts +2 -3
- package/fs/fsm/module.f.js +1 -1
- package/fs/html/module.f.d.ts +2 -3
- package/fs/html/module.f.js +7 -6
- package/fs/html/proof.f.d.ts +1 -0
- package/fs/html/proof.f.js +7 -0
- package/fs/js/tokenizer/module.f.js +1 -1
- package/fs/js/tokenizer/proof.f.js +7 -0
- package/fs/json/module.f.d.ts +9 -2
- package/fs/json/module.f.js +9 -7
- package/fs/json/parser/module.f.js +4 -6
- package/fs/json/parser/proof.f.js +19 -10
- package/fs/json/proof.f.d.ts +2 -1
- package/fs/json/proof.f.js +33 -6
- package/fs/json/rpc/module.f.d.ts +5 -0
- package/fs/json/rpc/module.f.js +16 -11
- package/fs/json/schema/module.f.d.ts +15 -14
- package/fs/json/schema/proof.f.d.ts +3 -0
- package/fs/json/schema/proof.f.js +16 -1
- package/fs/mcp/module.f.d.ts +103 -5
- package/fs/mcp/module.f.js +139 -2
- package/fs/mcp/proof.f.d.ts +34 -0
- package/fs/mcp/proof.f.js +207 -0
- package/fs/mcp/stdio/module.f.d.ts +39 -0
- package/fs/mcp/stdio/module.f.js +54 -0
- package/fs/mcp/stdio/proof.f.d.ts +10 -0
- package/fs/mcp/stdio/proof.f.js +92 -0
- package/fs/mime/module.f.d.ts +39 -0
- package/fs/mime/module.f.js +78 -0
- package/fs/mime/proof.f.d.ts +15 -0
- package/fs/mime/proof.f.js +30 -0
- package/fs/path/module.f.d.ts +8 -0
- package/fs/path/module.f.js +10 -3
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/text/sgr/proof.f.js +30 -2
- package/fs/text/utf8/module.f.d.ts +7 -0
- package/fs/text/utf8/module.f.js +85 -31
- package/fs/text/utf8/proof.f.d.ts +1 -0
- package/fs/text/utf8/proof.f.js +60 -1
- package/fs/types/bigfloat/module.f.d.ts +3 -0
- package/fs/types/bigfloat/module.f.js +30 -31
- package/fs/types/bigint/module.f.d.ts +24 -2
- package/fs/types/bigint/module.f.js +30 -2
- package/fs/types/bigint/proof.f.d.ts +7 -0
- package/fs/types/bigint/proof.f.js +47 -16
- package/fs/types/bit_vec/module.f.d.ts +18 -0
- package/fs/types/bit_vec/module.f.js +18 -0
- package/fs/types/bit_vec/proof.f.d.ts +1 -0
- package/fs/types/bit_vec/proof.f.js +13 -1
- package/fs/types/function/operator/proof.f.d.ts +2 -0
- package/fs/types/function/operator/proof.f.js +20 -2
- package/fs/types/list/module.f.js +1 -4
- package/fs/types/list/proof.f.d.ts +1 -1
- package/fs/types/list/proof.f.js +14 -6
- package/fs/types/map/proof.f.d.ts +1 -0
- package/fs/types/map/proof.f.js +10 -1
- package/fs/types/nibble_set/module.f.d.ts +13 -0
- package/fs/types/nibble_set/module.f.js +16 -0
- package/fs/types/object/module.f.d.ts +12 -8
- package/fs/types/object/module.f.js +9 -2
- package/fs/types/object/proof.f.d.ts +14 -0
- package/fs/types/object/proof.f.js +4 -0
- package/fs/types/ordered_map/proof.f.d.ts +1 -0
- package/fs/types/ordered_map/proof.f.js +39 -79
- package/fs/types/phantom/module.f.d.ts +22 -0
- package/fs/types/phantom/module.f.js +11 -0
- package/fs/types/prime_field/proof.f.d.ts +7 -0
- package/fs/types/prime_field/proof.f.js +63 -122
- package/fs/types/rtti/common/module.f.d.ts +4 -4
- package/fs/types/rtti/common/module.f.js +1 -1
- package/fs/types/rtti/module.f.d.ts +2 -3
- package/fs/types/rtti/parse/module.f.js +0 -1
- package/fs/types/rtti/ts/module.f.d.ts +26 -32
- package/fs/types/rtti/ts/module.f.js +2 -2
- package/fs/types/rtti/ts/proof.f.js +3 -3
- package/fs/types/rtti/validate/module.f.js +0 -1
- package/fs/types/sorted_list/module.f.d.ts +6 -0
- package/fs/types/sorted_list/module.f.js +11 -0
- package/fs/types/sorted_set/module.f.js +2 -8
- package/fs/types/ts/module.f.d.ts +1 -1
- package/fs/types/ts/module.f.js +3 -1
- package/fs/types/ts/proof.f.js +3 -2
- package/fs/website/proof.f.d.ts +1 -0
- package/fs/website/proof.f.js +8 -1
- package/package.json +3 -3
package/fs/djs/module.f.js
CHANGED
|
@@ -2,8 +2,7 @@ import { transpile } from "./transpiler/module.f.js";
|
|
|
2
2
|
import { stringify, stringifyAsTree } from "./serializer/module.f.js";
|
|
3
3
|
import { sort } from "../types/object/module.f.js";
|
|
4
4
|
import { pure } from "../effects/module.f.js";
|
|
5
|
-
import {
|
|
6
|
-
import { utf8 } from "../text/module.f.js";
|
|
5
|
+
import { writeUtf8File, error, } from "../effects/node/module.f.js";
|
|
7
6
|
export const compile = args => {
|
|
8
7
|
if (args.length < 2) {
|
|
9
8
|
return error('Error: Requires 2 or more arguments')
|
|
@@ -20,7 +19,7 @@ export const compile = args => {
|
|
|
20
19
|
const content = outputFileName.endsWith('.json')
|
|
21
20
|
? stringifyAsTree(sort)(result[1])
|
|
22
21
|
: stringify(sort)(result[1]);
|
|
23
|
-
return
|
|
22
|
+
return writeUtf8File(outputFileName, content)
|
|
24
23
|
.step(() => pure(0));
|
|
25
24
|
});
|
|
26
25
|
};
|
|
@@ -7,10 +7,20 @@ import { serialize as bigintSerialize } from "../../types/bigint/module.f.js";
|
|
|
7
7
|
import { objectWrap, arrayWrap, stringSerialize, numberSerialize, nullSerialize, boolSerialize } from "../../json/serializer/module.f.js";
|
|
8
8
|
const colon = [':'];
|
|
9
9
|
export const undefinedSerialize = ['undefined'];
|
|
10
|
+
/**
|
|
11
|
+
* Returns the value's `RefCounter` only if it is *shared* (referenced more
|
|
12
|
+
* than once) — otherwise `undefined`. Names the single predicate that drives
|
|
13
|
+
* const hoisting in both `getConstants` (decide which values become consts)
|
|
14
|
+
* and `serializeWithConst` (emit a `c<N>` reference to one).
|
|
15
|
+
*/
|
|
16
|
+
const sharedRef = (refs) => (v) => {
|
|
17
|
+
const rc = refs.get(v);
|
|
18
|
+
return rc !== undefined && rc[1] > 1 ? rc : undefined;
|
|
19
|
+
};
|
|
10
20
|
const getConstants = refs => {
|
|
21
|
+
const shared = sharedRef(refs);
|
|
11
22
|
const checkSelf = djs => state => {
|
|
12
|
-
|
|
13
|
-
if (refCounter !== undefined && refCounter[1] > 1 && !state.added.has(djs)) {
|
|
23
|
+
if (shared(djs) !== undefined && !state.added.has(djs)) {
|
|
14
24
|
return {
|
|
15
25
|
added: new Set([...state.added, djs]),
|
|
16
26
|
consts: { head: state.consts, tail: [djs] }
|
|
@@ -95,16 +105,19 @@ const buildSerialize = refLookup => sort => {
|
|
|
95
105
|
return f;
|
|
96
106
|
};
|
|
97
107
|
export const serializeWithoutConst = buildSerialize(noRef);
|
|
98
|
-
const serializeWithConst = sort => refs =>
|
|
99
|
-
|
|
108
|
+
const serializeWithConst = sort => refs => {
|
|
109
|
+
const shared = sharedRef(refs);
|
|
110
|
+
return root => buildSerialize(value => {
|
|
111
|
+
if (value === root) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const rc = shared(value);
|
|
115
|
+
if (rc !== undefined) {
|
|
116
|
+
return [`c${rc[0]}`];
|
|
117
|
+
}
|
|
100
118
|
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (refCounter !== undefined && refCounter[1] > 1) {
|
|
104
|
-
return [`c${refCounter[0]}`];
|
|
105
|
-
}
|
|
106
|
-
return null;
|
|
107
|
-
})(sort);
|
|
119
|
+
})(sort);
|
|
120
|
+
};
|
|
108
121
|
const countRefsOp = djs => refs => {
|
|
109
122
|
switch (typeof djs) {
|
|
110
123
|
case 'boolean':
|
|
@@ -13,8 +13,7 @@ import { concat as pathConcat } from "../../path/module.f.js";
|
|
|
13
13
|
import { parseFromTokens } from "../parser/module.f.js";
|
|
14
14
|
import { run } from "../ast/module.f.js";
|
|
15
15
|
import { foldStep, pure } from "../../effects/module.f.js";
|
|
16
|
-
import {
|
|
17
|
-
import { utf8ToString } from "../../text/module.f.js";
|
|
16
|
+
import { readUtf8File } from "../../effects/node/module.f.js";
|
|
18
17
|
const mapDjs = context => path => {
|
|
19
18
|
const res = at(path)(context.complete);
|
|
20
19
|
if (res === null) {
|
|
@@ -22,11 +21,11 @@ const mapDjs = context => path => {
|
|
|
22
21
|
}
|
|
23
22
|
return res.djs;
|
|
24
23
|
};
|
|
25
|
-
const parseModule = path =>
|
|
24
|
+
const parseModule = path => readUtf8File(path).step(result => {
|
|
26
25
|
if (result[0] === 'error') {
|
|
27
26
|
return pure(error({ message: 'file not found', metadata: null }));
|
|
28
27
|
}
|
|
29
|
-
const tokens = tokenize(stringToList(
|
|
28
|
+
const tokens = tokenize(stringToList(result[1]))(path);
|
|
30
29
|
return pure(parseFromTokens(tokens));
|
|
31
30
|
});
|
|
32
31
|
const transpileWithImports = path => parseModuleResult => context => {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed key-value memory effects for state that persists across effect steps.
|
|
3
|
+
*
|
|
4
|
+
* A memory key is opaque at runtime and carries the value type at compile time,
|
|
5
|
+
* so a key returned by {@link create} can only be read and written with values
|
|
6
|
+
* of the same type. The concrete interpreter owns the actual storage and key
|
|
7
|
+
* generation strategy.
|
|
8
|
+
*
|
|
9
|
+
* Memory effects compose with other effects by unioning operation types, for
|
|
10
|
+
* example `Effect<IoOp | MemOp, T>` for a program that performs both I/O and
|
|
11
|
+
* memory operations.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { Phantom } from '../../types/phantom/module.f.ts';
|
|
16
|
+
import { type Nominal } from '../../types/nominal/module.f.ts';
|
|
17
|
+
import { type Effect } from '../module.f.ts';
|
|
18
|
+
/** Nominal brand version for memory keys. */
|
|
19
|
+
type MemKeyHash = '3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286';
|
|
20
|
+
/** Opaque handle for a value stored by the memory interpreter. */
|
|
21
|
+
export type Key<T> = Phantom<Nominal<'MemKey', MemKeyHash, string>, T>;
|
|
22
|
+
export declare const asBase: (n: Nominal<"MemKey", "3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286", string>) => string;
|
|
23
|
+
export declare const asNominal: (b: string) => Nominal<"MemKey", "3f114fa6036a8da026b827f0c3e6d901f5e81ad9a320e431ccce31451892d286", string>;
|
|
24
|
+
/** Allocates a fresh memory slot and initializes it with `value`. */
|
|
25
|
+
export type MemCreate = readonly ['memCreate', <T>(value: T) => Key<T>];
|
|
26
|
+
/** Reads the current value stored at `key`. */
|
|
27
|
+
export type MemRead = readonly ['memRead', <T>(key: Key<T>) => T];
|
|
28
|
+
/** Replaces the current value stored at `key`. */
|
|
29
|
+
export type MemWrite = readonly ['memWrite', <T>(key: Key<T>, value: T) => void];
|
|
30
|
+
export type MemOp = MemCreate | MemRead | MemWrite;
|
|
31
|
+
/** Creates a new typed memory slot with `value` as its initial contents. */
|
|
32
|
+
export declare const create: <T>(value: T) => Effect<MemCreate, Key<T>>;
|
|
33
|
+
/** Reads the current contents of a typed memory slot. */
|
|
34
|
+
export declare const read: <T>(ket: Key<T>) => Effect<MemRead, T>;
|
|
35
|
+
/** Replaces the current contents of a typed memory slot. */
|
|
36
|
+
export declare const write: <T>(key: Key<T>, value: T) => Effect<MemWrite, void>;
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed key-value memory effects for state that persists across effect steps.
|
|
3
|
+
*
|
|
4
|
+
* A memory key is opaque at runtime and carries the value type at compile time,
|
|
5
|
+
* so a key returned by {@link create} can only be read and written with values
|
|
6
|
+
* of the same type. The concrete interpreter owns the actual storage and key
|
|
7
|
+
* generation strategy.
|
|
8
|
+
*
|
|
9
|
+
* Memory effects compose with other effects by unioning operation types, for
|
|
10
|
+
* example `Effect<IoOp | MemOp, T>` for a program that performs both I/O and
|
|
11
|
+
* memory operations.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import { asBase as nominalAsBase, asNominal as nominalAsNominal } from "../../types/nominal/module.f.js";
|
|
16
|
+
import { do_ } from "../module.f.js";
|
|
17
|
+
export const asBase = (nominalAsBase);
|
|
18
|
+
export const asNominal = (nominalAsNominal);
|
|
19
|
+
/** Creates a new typed memory slot with `value` as its initial contents. */
|
|
20
|
+
export const create = do_('memCreate');
|
|
21
|
+
/** Reads the current contents of a typed memory slot. */
|
|
22
|
+
export const read = do_('memRead');
|
|
23
|
+
/** Replaces the current contents of a typed memory slot. */
|
|
24
|
+
export const write = do_('memWrite');
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { run } from "../mock/module.f.js";
|
|
2
|
+
import { pure } from "../module.f.js";
|
|
3
|
+
import { asBase, asNominal, create, read, write, } from "./module.f.js";
|
|
4
|
+
const initial = { next: 0, values: {} };
|
|
5
|
+
const mock = {
|
|
6
|
+
memCreate: value => state => {
|
|
7
|
+
const id = `k${state.next}`;
|
|
8
|
+
const key = asNominal(id);
|
|
9
|
+
return [{
|
|
10
|
+
next: state.next + 1,
|
|
11
|
+
values: { ...state.values, [id]: value },
|
|
12
|
+
}, key];
|
|
13
|
+
},
|
|
14
|
+
memRead: key => state => [state, state.values[asBase(key)]],
|
|
15
|
+
memWrite: (key, value) => state => {
|
|
16
|
+
const id = asBase(key);
|
|
17
|
+
if (!(id in state.values)) {
|
|
18
|
+
throw id;
|
|
19
|
+
}
|
|
20
|
+
return [{
|
|
21
|
+
...state,
|
|
22
|
+
values: { ...state.values, [id]: value },
|
|
23
|
+
}, undefined];
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const program = create(1).step(key => read(key).step(value => write(key, value + 41).step(() => read(key))));
|
|
27
|
+
export const proof = {
|
|
28
|
+
roundTrip: () => {
|
|
29
|
+
const [state, result] = run(mock)(initial)(program);
|
|
30
|
+
if (result !== 42) {
|
|
31
|
+
throw result;
|
|
32
|
+
}
|
|
33
|
+
if (state.values.k0 !== 42) {
|
|
34
|
+
throw state;
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
allocatesFreshKeys: () => {
|
|
38
|
+
const effect = create('a').step(a => create('b').step(b => pure([
|
|
39
|
+
asBase(a),
|
|
40
|
+
asBase(b),
|
|
41
|
+
])));
|
|
42
|
+
const [state, [a, b]] = run(mock)(initial)(effect);
|
|
43
|
+
if (a !== 'k0') {
|
|
44
|
+
throw a;
|
|
45
|
+
}
|
|
46
|
+
if (b !== 'k1') {
|
|
47
|
+
throw b;
|
|
48
|
+
}
|
|
49
|
+
if (state.values.k0 !== 'a') {
|
|
50
|
+
throw state;
|
|
51
|
+
}
|
|
52
|
+
if (state.values.k1 !== 'b') {
|
|
53
|
+
throw state;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
typeTest: () => {
|
|
57
|
+
// const e = create(1).step(k => write(k, 'bad').step(() => read(k)))
|
|
58
|
+
create(1).step(k => write(k, 5).step(() => read(k)));
|
|
59
|
+
},
|
|
60
|
+
throw: () => {
|
|
61
|
+
const key = asNominal('missing');
|
|
62
|
+
run(mock)(initial)(write(key, 1));
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
|
-
import type
|
|
6
|
+
import { type Effect, type Operation, type Pr } from "../module.f.ts";
|
|
7
|
+
/**
|
|
8
|
+
* A synchronous, state-threading operation map. An entry takes the command's
|
|
9
|
+
* payload (fixed when the command is issued) and returns a state transition —
|
|
10
|
+
* the curried `state` parameter is data the runner supplies on each step.
|
|
11
|
+
*/
|
|
7
12
|
export type MemOperationMap<O extends Operation, S> = {
|
|
8
|
-
readonly [K in O[0]]: (
|
|
13
|
+
readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => (state: S) => readonly [S, Pr<O, K>[1]];
|
|
9
14
|
};
|
|
10
15
|
export type RunInstance<O extends Operation, S> = (state: S) => <O1 extends O, T>(effect: Effect<O1, T>) => readonly [S, T];
|
|
11
16
|
export declare const run: <O extends Operation, S>(o: MemOperationMap<O, S>) => RunInstance<O, S>;
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock effect runtimes for testing effectful programs.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { match } from "../module.f.js";
|
|
1
7
|
export const run = (o) => state => effect => {
|
|
8
|
+
const next = match(o);
|
|
2
9
|
let s = state;
|
|
3
10
|
let e = effect;
|
|
4
11
|
while (true) {
|
|
5
|
-
const
|
|
6
|
-
if (
|
|
7
|
-
|
|
8
|
-
return [s, v];
|
|
12
|
+
const r = next(e);
|
|
13
|
+
if (r[0] === 'done') {
|
|
14
|
+
return [s, r[1]];
|
|
9
15
|
}
|
|
10
|
-
const [
|
|
11
|
-
const operation = o[cmd];
|
|
12
|
-
const [ns, m] = operation(s, ...payload);
|
|
16
|
+
const [ns, m] = r[1](s);
|
|
13
17
|
s = ns;
|
|
14
|
-
e =
|
|
18
|
+
e = r[2](m);
|
|
15
19
|
}
|
|
16
20
|
};
|
package/fs/effects/module.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Effect, type Operation, type ToAsyncOperationMap } from "./module.f.ts";
|
|
2
2
|
export declare const asyncRun: <O extends Operation>(map: ToAsyncOperationMap<O>) => <T, E extends Effect<O, T>>(effect: Effect<O, T>) => Promise<T>;
|
package/fs/effects/module.f.d.ts
CHANGED
|
@@ -36,6 +36,47 @@ export declare const foldStep: <O extends Operation, T, S>(f: (item: T) => (stat
|
|
|
36
36
|
* results. The `void` accumulator sibling of `foldStep`.
|
|
37
37
|
*/
|
|
38
38
|
export declare const forEachStep: <O extends Operation, T>(f: (item: T) => Effect<O, void>) => (items: List<T>) => Effect<O, void>;
|
|
39
|
+
/**
|
|
40
|
+
* The decoded form of an effect's next step: either a final `result`, or a
|
|
41
|
+
* `command` to perform with its `payload` and the `continuation` to resume
|
|
42
|
+
* with the command's output.
|
|
43
|
+
*/
|
|
44
|
+
export type Decoded<O extends Operation, T> = {
|
|
45
|
+
readonly done: true;
|
|
46
|
+
readonly result: T;
|
|
47
|
+
} | {
|
|
48
|
+
readonly done: false;
|
|
49
|
+
readonly command: Do<O, T>[0];
|
|
50
|
+
readonly payload: Do<O, T>[1];
|
|
51
|
+
readonly continuation: Do<O, T>[2];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Decodes an effect's next step: a pure result, or a command to perform.
|
|
55
|
+
*
|
|
56
|
+
* This is the only function that knows how `Value` is laid out (a length-1
|
|
57
|
+
* tuple for `Pure`, a `[command, payload, continuation]` tuple for `Do`).
|
|
58
|
+
* Interpreters and proofs must go through `decode` (or `match`) instead of
|
|
59
|
+
* inspecting the tuple, so the representation can change without touching
|
|
60
|
+
* them.
|
|
61
|
+
*/
|
|
62
|
+
export declare const decode: <O extends Operation, T>({ value }: Effect<O, T>) => Decoded<O, T>;
|
|
63
|
+
/**
|
|
64
|
+
* An operation map whose entries take a command's payload and return some
|
|
65
|
+
* output `R`. Generalizes `ToAsyncOperationMap` (`R = Promise<…>`) and the
|
|
66
|
+
* curried `MemOperationMap` (`R = (state) => [state, …]`).
|
|
67
|
+
*/
|
|
68
|
+
export type OperationMap<O extends Operation, R> = {
|
|
69
|
+
readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => R;
|
|
70
|
+
};
|
|
71
|
+
export type MatchResult<O extends Operation, T, R> = readonly ['done', T] | readonly ['cont', R, Do<O, T>[2]];
|
|
72
|
+
/**
|
|
73
|
+
* Decodes an effect's next step and dispatches its command to `map`,
|
|
74
|
+
* returning either the final result or the operation's output `R` paired
|
|
75
|
+
* with the continuation. The one world-specific step — `await` for async
|
|
76
|
+
* runners, state threading for sync ones — is left to the caller, so every
|
|
77
|
+
* interpreter loop is this skeleton plus a single eliminator line.
|
|
78
|
+
*/
|
|
79
|
+
export declare const match: <O extends Operation, R>(map: OperationMap<O, R>) => <O1 extends O, T>(effect: Effect<O1, T>) => MatchResult<O1, T, R>;
|
|
39
80
|
export type ToAsyncOperationMap<O extends Operation> = {
|
|
40
81
|
readonly [K in O[0]]: (...payload: Pr<O, K>[0]) => Promise<Pr<O, K>[1]>;
|
|
41
82
|
};
|
package/fs/effects/module.f.js
CHANGED
|
@@ -29,3 +29,28 @@ export const foldStep = (f) => (init) => (items) => fold(item => acc => acc.step
|
|
|
29
29
|
* results. The `void` accumulator sibling of `foldStep`.
|
|
30
30
|
*/
|
|
31
31
|
export const forEachStep = (f) => (items) => foldStep((item) => () => f(item))(undefined)(items);
|
|
32
|
+
/**
|
|
33
|
+
* Decodes an effect's next step: a pure result, or a command to perform.
|
|
34
|
+
*
|
|
35
|
+
* This is the only function that knows how `Value` is laid out (a length-1
|
|
36
|
+
* tuple for `Pure`, a `[command, payload, continuation]` tuple for `Do`).
|
|
37
|
+
* Interpreters and proofs must go through `decode` (or `match`) instead of
|
|
38
|
+
* inspecting the tuple, so the representation can change without touching
|
|
39
|
+
* them.
|
|
40
|
+
*/
|
|
41
|
+
export const decode = ({ value }) => value.length === 1
|
|
42
|
+
? { done: true, result: value[0] }
|
|
43
|
+
: { done: false, command: value[0], payload: value[1], continuation: value[2] };
|
|
44
|
+
/**
|
|
45
|
+
* Decodes an effect's next step and dispatches its command to `map`,
|
|
46
|
+
* returning either the final result or the operation's output `R` paired
|
|
47
|
+
* with the continuation. The one world-specific step — `await` for async
|
|
48
|
+
* runners, state threading for sync ones — is left to the caller, so every
|
|
49
|
+
* interpreter loop is this skeleton plus a single eliminator line.
|
|
50
|
+
*/
|
|
51
|
+
export const match = (map) => (effect) => {
|
|
52
|
+
const d = decode(effect);
|
|
53
|
+
return d.done
|
|
54
|
+
? ['done', d.result]
|
|
55
|
+
: ['cont', map[d.command](...d.payload), d.continuation];
|
|
56
|
+
};
|
package/fs/effects/module.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
import { match } from "./module.f.js";
|
|
1
2
|
export const asyncRun = (map) => async (effect) => {
|
|
3
|
+
const next = match(map);
|
|
2
4
|
while (true) {
|
|
3
|
-
const
|
|
4
|
-
if (
|
|
5
|
-
return
|
|
5
|
+
const r = next(effect);
|
|
6
|
+
if (r[0] === 'done') {
|
|
7
|
+
return r[1];
|
|
6
8
|
}
|
|
7
|
-
|
|
8
|
-
const operation = map[command];
|
|
9
|
-
const result = await operation(...payload);
|
|
10
|
-
effect = continuation(result);
|
|
9
|
+
effect = r[2](await r[1]);
|
|
11
10
|
}
|
|
12
11
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js interpreter helpers for memory effects.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { Effect, ToAsyncOperationMap } from '../../module.f.ts';
|
|
7
|
+
import { type MemOp } from '../../memory/module.f.ts';
|
|
8
|
+
export type MemoryOperationMap = ToAsyncOperationMap<MemOp>;
|
|
9
|
+
export type Uuid = () => string;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a stateful operation map backed by an immutable `Map` snapshot.
|
|
12
|
+
*
|
|
13
|
+
* Each returned operation map owns its own store. Reusing the same map across
|
|
14
|
+
* multiple `asyncRun` calls preserves memory across those calls; creating a new
|
|
15
|
+
* map starts with an empty store. Keys are generated with `crypto.randomUUID()`
|
|
16
|
+
* by default.
|
|
17
|
+
*/
|
|
18
|
+
export declare const memoryOperationMap: (uuid?: Uuid) => MemoryOperationMap;
|
|
19
|
+
/** Runs a memory-only effect using a fresh memory store. */
|
|
20
|
+
export declare const run: <T>(effect: Effect<MemOp, T>) => Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js interpreter helpers for memory effects.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { asyncRun } from "../../module.js";
|
|
8
|
+
import { asBase, asNominal } from "../../memory/module.f.js";
|
|
9
|
+
const missingKey = (id) => new Error(`memory key not found: ${id}`);
|
|
10
|
+
/**
|
|
11
|
+
* Creates a stateful operation map backed by an immutable `Map` snapshot.
|
|
12
|
+
*
|
|
13
|
+
* Each returned operation map owns its own store. Reusing the same map across
|
|
14
|
+
* multiple `asyncRun` calls preserves memory across those calls; creating a new
|
|
15
|
+
* map starts with an empty store. Keys are generated with `crypto.randomUUID()`
|
|
16
|
+
* by default.
|
|
17
|
+
*/
|
|
18
|
+
export const memoryOperationMap = (uuid = randomUUID) => {
|
|
19
|
+
const store = new Map();
|
|
20
|
+
return {
|
|
21
|
+
memCreate: async (value) => {
|
|
22
|
+
const id = uuid();
|
|
23
|
+
const key = asNominal(id);
|
|
24
|
+
store.set(id, value);
|
|
25
|
+
return key;
|
|
26
|
+
},
|
|
27
|
+
memRead: async (key) => {
|
|
28
|
+
const id = asBase(key);
|
|
29
|
+
if (!store.has(id)) {
|
|
30
|
+
throw missingKey(id);
|
|
31
|
+
}
|
|
32
|
+
return store.get(id);
|
|
33
|
+
},
|
|
34
|
+
memWrite: async (key, value) => {
|
|
35
|
+
const id = asBase(key);
|
|
36
|
+
if (!store.has(id)) {
|
|
37
|
+
throw missingKey(id);
|
|
38
|
+
}
|
|
39
|
+
store.set(id, value);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** Runs a memory-only effect using a fresh memory store. */
|
|
44
|
+
export const run = (effect) => asyncRun(memoryOperationMap())(effect);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js interpreter proofs for memory effects.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { asyncRun } from "../../module.js";
|
|
7
|
+
import { asNominal, create, read, write, } from "../../memory/module.f.js";
|
|
8
|
+
import { memoryOperationMap, run } from "./module.js";
|
|
9
|
+
export const proof = {
|
|
10
|
+
nodeInterpreter: async () => {
|
|
11
|
+
const result = await run(create(1).step(key => write(key, 2).step(() => read(key))));
|
|
12
|
+
if (result !== 2) {
|
|
13
|
+
throw result;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
reusedOperationMapPersists: async () => {
|
|
17
|
+
const runner = asyncRun(memoryOperationMap());
|
|
18
|
+
const key = await runner(create(1));
|
|
19
|
+
await runner(write(key, 2));
|
|
20
|
+
const result = await runner(read(key));
|
|
21
|
+
if (result !== 2) {
|
|
22
|
+
throw result;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
missingKeyThrows: async () => {
|
|
26
|
+
const key = asNominal('missing');
|
|
27
|
+
const result = await run(read(key)).then(() => undefined, error => error);
|
|
28
|
+
if (!(result instanceof Error)) {
|
|
29
|
+
throw result;
|
|
30
|
+
}
|
|
31
|
+
if (result.message !== 'memory key not found: missing') {
|
|
32
|
+
throw result;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type NodeProgram } from './module.f.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Runs a `NodeProgram` against the real Node
|
|
3
|
+
* Runs a `NodeProgram` against the real Node globals and process arguments,
|
|
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
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import type { Vec } from '../../types/bit_vec/module.f.ts';
|
|
2
|
+
import type { MemOp } from '../memory/module.f.ts';
|
|
2
3
|
import type { Nominal } from '../../types/nominal/module.f.ts';
|
|
3
|
-
import type
|
|
4
|
+
import { type Result } from '../../types/result/module.f.ts';
|
|
5
|
+
import type { StringMap } from '../../types/object/module.f.ts';
|
|
4
6
|
import { type Effect, type Func, type Operation, type ToAsyncOperationMap } from '../module.f.ts';
|
|
5
7
|
export type IoResult<T> = Result<T, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* True if `e` is a "file or directory does not exist" (`ENOENT`) error.
|
|
10
|
+
*
|
|
11
|
+
* Node's filesystem rejections are `Error`s carrying `code: 'ENOENT'`; the
|
|
12
|
+
* virtual interpreter mirrors that shape for absent paths. Lets callers swallow
|
|
13
|
+
* only the missing-path case (e.g. a fresh store) while propagating genuine
|
|
14
|
+
* failures (permissions, corruption) rather than masking them.
|
|
15
|
+
*/
|
|
16
|
+
export declare const isNotFound: (e: unknown) => boolean;
|
|
6
17
|
export type All = ['all', <T>(...effects: Effect<never, T>[]) => readonly T[]];
|
|
7
18
|
/**
|
|
8
19
|
* To run the operation `O` should be known by the runner/engine.
|
|
@@ -22,6 +33,14 @@ export type Mkdir = readonly ['mkdir', (path: string, options?: MakeDirectoryOpt
|
|
|
22
33
|
export declare const mkdir: Func<Mkdir>;
|
|
23
34
|
export type ReadFile = readonly ['readFile', (path: string) => IoResult<Vec>];
|
|
24
35
|
export declare const readFile: Func<ReadFile>;
|
|
36
|
+
/**
|
|
37
|
+
* Reads a file as UTF-8 text.
|
|
38
|
+
*
|
|
39
|
+
* Preserves the `IoResult` instead of unwrapping so callers can pattern-match
|
|
40
|
+
* on errors (e.g. convert them into domain-specific errors) or `unwrap` at the
|
|
41
|
+
* call site.
|
|
42
|
+
*/
|
|
43
|
+
export declare const readUtf8File: (path: string) => Effect<ReadFile, IoResult<string>>;
|
|
25
44
|
/**
|
|
26
45
|
* Represents a directory entry (file or directory) in the filesystem
|
|
27
46
|
* @see https://nodejs.org/api/fs.html#class-fsdirent
|
|
@@ -38,6 +57,8 @@ export type Readdir = readonly ['readdir', (path: string, options: ReaddirOption
|
|
|
38
57
|
export declare const readdir: Func<Readdir>;
|
|
39
58
|
export type WriteFile = readonly ['writeFile', (path: string, data: Vec) => IoResult<void>];
|
|
40
59
|
export declare const writeFile: Func<WriteFile>;
|
|
60
|
+
/** Writes a string to `path` as UTF-8 bytes. */
|
|
61
|
+
export declare const writeUtf8File: (path: string, content: string) => Effect<WriteFile, IoResult<void>>;
|
|
41
62
|
export type Rm = readonly ['rm', (path: string) => IoResult<void>];
|
|
42
63
|
export declare const rm: Func<Rm>;
|
|
43
64
|
export type ExecResult = {
|
|
@@ -50,9 +71,7 @@ export type Access = readonly ['access', (path: string) => IoResult<void>];
|
|
|
50
71
|
export declare const access: Func<Access>;
|
|
51
72
|
export type Fs = Mkdir | ReadFile | Readdir | WriteFile | Rm | Exec | Access;
|
|
52
73
|
export type Server = Nominal<'server', `160855c4f69310fece3273c1853ac32de43dee1eb41bf59d821917f8eebe9272`, unknown>;
|
|
53
|
-
export type Headers =
|
|
54
|
-
readonly [k in string]: string;
|
|
55
|
-
};
|
|
74
|
+
export type Headers = StringMap<string, string>;
|
|
56
75
|
export type IncomingMessage = {
|
|
57
76
|
readonly method: string;
|
|
58
77
|
readonly url: string;
|
|
@@ -72,9 +91,7 @@ export declare const listen: Func<Listen>;
|
|
|
72
91
|
export type Http = CreateServer | Listen;
|
|
73
92
|
export type Forever = ['forever', () => never];
|
|
74
93
|
export declare const forever: Func<Forever>;
|
|
75
|
-
export type Module =
|
|
76
|
-
readonly [k in string]: unknown;
|
|
77
|
-
};
|
|
94
|
+
export type Module = StringMap<string, unknown>;
|
|
78
95
|
export type Import = ['import', (path: string) => IoResult<Module>];
|
|
79
96
|
export declare const import_: Func<Import>;
|
|
80
97
|
/** Named output streams accepted by the `Write` effect. */
|
|
@@ -92,6 +109,34 @@ export type Console = (s: string) => Effect<Write, void>;
|
|
|
92
109
|
export declare const log: Console;
|
|
93
110
|
/** Writes a line to `stderr`. Replaces the retired `Error` effect. */
|
|
94
111
|
export declare const error: Console;
|
|
112
|
+
/** Named input streams accepted by the `Read` effect. */
|
|
113
|
+
export type ReadConsoles = 'stdin';
|
|
114
|
+
/**
|
|
115
|
+
* Reads a single byte from a named input stream — the byte-granular dual of
|
|
116
|
+
* {@link write}. Resolves to the byte value (`0`–`255`) or `null` at end of
|
|
117
|
+
* input (EOF). One byte at a time: the effect carries no buffering or line
|
|
118
|
+
* policy, so higher-level framing (see {@link readLine}) lives in pure code
|
|
119
|
+
* rather than the interpreter. Back-pressure is naturally sequential — the next
|
|
120
|
+
* `read` is only issued once the previous byte is consumed.
|
|
121
|
+
*/
|
|
122
|
+
export type Read = readonly ['read', (stream: ReadConsoles) => number | null];
|
|
123
|
+
/** Emits a `Read` effect, yielding the next input byte or `null` at EOF. */
|
|
124
|
+
export declare const read: Func<Read>;
|
|
125
|
+
/**
|
|
126
|
+
* Reads one line from `stream` as a pure combinator over the byte-level
|
|
127
|
+
* {@link read}: accumulates bytes until a `\n` terminator or EOF, then
|
|
128
|
+
* UTF-8-decodes them. The terminator is consumed but excluded from the result.
|
|
129
|
+
*
|
|
130
|
+
* Reading a single byte per step means a line never over-reads past its
|
|
131
|
+
* terminator, so no leftover-byte buffer has to survive between calls — each
|
|
132
|
+
* `readLine` is self-contained. Yields `null` only at EOF with nothing
|
|
133
|
+
* buffered; a final line lacking a trailing newline is returned in full.
|
|
134
|
+
*
|
|
135
|
+
* Bytes accumulate into a cons-list by prepending (O(1) per byte) and are
|
|
136
|
+
* reversed and decoded once at the terminator, so a large line costs O(n)
|
|
137
|
+
* rather than the O(n²) of copying a growing array on every byte.
|
|
138
|
+
*/
|
|
139
|
+
export declare const readLine: (stream: ReadConsoles) => Effect<Read, string | null>;
|
|
95
140
|
export type Now = readonly ['now', () => number];
|
|
96
141
|
export declare const now: Func<Now>;
|
|
97
142
|
/**
|
|
@@ -159,7 +204,7 @@ export type TestContext = {
|
|
|
159
204
|
/** Effect operation that registers a named test with the active `TestContext`. */
|
|
160
205
|
export type Test = readonly ['test', (ctx: TestContext, name: string, expectFailure: boolean, test: (t: TestContext) => Effect<Test | All | Await, void>) => void];
|
|
161
206
|
export declare const test: Func<Test>;
|
|
162
|
-
export type NodeOp = All | Await | Fetch | Fs | Http | Forever | Import | Now | Sandbox | Write | Test;
|
|
207
|
+
export type NodeOp = All | Await | Fetch | Fs | Http | Forever | Import | MemOp | Now | Read | Sandbox | Write | Test;
|
|
163
208
|
export type NodeEffect<T> = Effect<NodeOp, T>;
|
|
164
209
|
/**
|
|
165
210
|
* Writes an error line to `stderr` and yields exit code `1`. The canonical
|
|
@@ -192,6 +237,7 @@ export type Engine = 'node' | 'bun' | 'playwright';
|
|
|
192
237
|
export type NodeProgramOptions = {
|
|
193
238
|
readonly args: readonly string[];
|
|
194
239
|
readonly env: Env;
|
|
240
|
+
readonly home: string;
|
|
195
241
|
readonly std: {
|
|
196
242
|
readonly [k in WriteConsoles]: {
|
|
197
243
|
readonly isTTY: boolean;
|