functionalscript 0.40.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/cas/evo/module.f.d.ts +27 -7
- package/fjs/cas/evo/module.f.js +42 -9
- package/fjs/cas/evo/proof.f.d.ts +4 -0
- package/fjs/cas/evo/proof.f.js +81 -0
- package/fjs/ci/config/module.f.d.ts +5 -5
- package/fjs/ci/config/module.f.js +5 -5
- 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/node/proof.f.d.ts +3 -0
- package/fjs/ci/node/proof.f.js +17 -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/effects/module.f.d.ts +20 -0
- package/fjs/effects/module.f.js +25 -1
- package/fjs/effects/proof.f.d.ts +5 -0
- package/fjs/effects/proof.f.js +22 -0
- 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/module.f.d.ts +7 -2
- package/fjs/mcp/evo/module.f.js +10 -5
- package/fjs/mcp/evo/proof.f.d.ts +1 -0
- package/fjs/mcp/evo/proof.f.js +22 -1
- package/fjs/mcp/module.f.d.ts +1 -1
- package/fjs/mcp/module.f.js +1 -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
package/fjs/media/proof.f.js
CHANGED
|
@@ -1,14 +1,37 @@
|
|
|
1
1
|
import { assertEq } from '../asserts/module.f.js';
|
|
2
2
|
import { msb, u8ListToVec, repeat, vec8 } from '../types/bit_vec/module.f.js';
|
|
3
|
-
import { detect } from './module.f.js';
|
|
4
|
-
import { dialect } from './revision/module.f.js';
|
|
3
|
+
import { detect, dialectEntry } from './module.f.js';
|
|
4
|
+
import { dialect, revisionDialect } from './revision/module.f.js';
|
|
5
|
+
import { number, string } from '../types/rtti/module.f.js';
|
|
5
6
|
// All test strings here are ASCII, so char code === UTF-8 byte value.
|
|
6
7
|
const utf8Bytes = (s) => u8ListToVec(msb)([...s].map(c => c.charCodeAt(0)));
|
|
7
8
|
const revisionJson = `{"dialect":"${dialect}","subject":"8","parents":[],"snapshot":"8","generation":0}`;
|
|
9
|
+
const dialects = [revisionDialect];
|
|
10
|
+
const detectRevision = detect(dialects);
|
|
11
|
+
/**
|
|
12
|
+
* A second dialect following the same `vnd.fjs.<name>` convention, registered
|
|
13
|
+
* with no refinement: structure alone decides the match.
|
|
14
|
+
*/
|
|
15
|
+
const noteSchema = {
|
|
16
|
+
dialect: 'vnd.fjs.note',
|
|
17
|
+
text: string,
|
|
18
|
+
};
|
|
19
|
+
const noteDialect = dialectEntry(noteSchema);
|
|
20
|
+
/** A dialect name outside `vnd.fjs.*` — registerable, and detected as itself. */
|
|
21
|
+
const gadgetSchema = {
|
|
22
|
+
dialect: 'application.gadget',
|
|
23
|
+
size: number,
|
|
24
|
+
};
|
|
25
|
+
const gadgetDialect = dialectEntry(gadgetSchema);
|
|
26
|
+
/**
|
|
27
|
+
* `DialectEntry` is deliberately not opaque, so overlapping entries — which a
|
|
28
|
+
* self-discriminating schema can't produce on its own — can be stated directly.
|
|
29
|
+
*/
|
|
30
|
+
const anyEntry = (name) => ({ dialect: name, match: () => true });
|
|
8
31
|
export const proof = {
|
|
9
32
|
// A valid revision blob is recognized and reported under its derived media type.
|
|
10
33
|
validRevision: () => {
|
|
11
|
-
const m =
|
|
34
|
+
const m = detectRevision(utf8Bytes(revisionJson));
|
|
12
35
|
assertEq(m.type, 'text');
|
|
13
36
|
assertEq(m.mime_type, 'application/vnd.fjs.revision+json');
|
|
14
37
|
},
|
|
@@ -16,7 +39,7 @@ export const proof = {
|
|
|
16
39
|
// not first must still be detected.
|
|
17
40
|
keyOrderIndependent: () => {
|
|
18
41
|
const text = `{"parents":[],"subject":"8","dialect":"${dialect}","snapshot":"8","generation":0}`;
|
|
19
|
-
const m =
|
|
42
|
+
const m = detectRevision(utf8Bytes(text));
|
|
20
43
|
assertEq(m.mime_type, 'application/vnd.fjs.revision+json');
|
|
21
44
|
},
|
|
22
45
|
// A revision missing a required field (here `snapshot` and `generation`)
|
|
@@ -24,25 +47,63 @@ export const proof = {
|
|
|
24
47
|
// classification.
|
|
25
48
|
invalidRevisionFallsThrough: () => {
|
|
26
49
|
const text = `{"dialect":"${dialect}","subject":"8","parents":["8","r"]}`;
|
|
27
|
-
const m =
|
|
50
|
+
const m = detectRevision(utf8Bytes(text));
|
|
51
|
+
assertEq(m.type, 'text');
|
|
52
|
+
assertEq(m.mime_type, 'text/plain');
|
|
53
|
+
},
|
|
54
|
+
// The `extraValidate` path: structurally a revision, but `snapshot` is not
|
|
55
|
+
// a cbase32 hash, so `revisionDialect`'s refinement rejects it and the
|
|
56
|
+
// verdict stays `text/plain` — exactly what `decodeText` would say.
|
|
57
|
+
nonHashSnapshotFallsThrough: () => {
|
|
58
|
+
const text = `{"dialect":"${dialect}","subject":"8","parents":[],"snapshot":"not a hash","generation":0}`;
|
|
59
|
+
const m = detectRevision(utf8Bytes(text));
|
|
60
|
+
assertEq(m.type, 'text');
|
|
61
|
+
assertEq(m.mime_type, 'text/plain');
|
|
62
|
+
},
|
|
63
|
+
// A second dialect, registered by the caller, is recognized alongside the
|
|
64
|
+
// first — and an entry with no refinement matches on structure alone.
|
|
65
|
+
secondDialect: () => {
|
|
66
|
+
const d = detect([revisionDialect, noteDialect]);
|
|
67
|
+
assertEq(d(utf8Bytes('{"dialect":"vnd.fjs.note","text":"hi"}')).mime_type, 'application/vnd.fjs.note+json');
|
|
68
|
+
assertEq(d(utf8Bytes(revisionJson)).mime_type, 'application/vnd.fjs.revision+json');
|
|
69
|
+
// Same tag, wrong shape: no entry matches.
|
|
70
|
+
assertEq(d(utf8Bytes('{"dialect":"vnd.fjs.note","text":42}')).mime_type, 'text/plain');
|
|
71
|
+
},
|
|
72
|
+
// The name is neither grammar-checked nor allowlisted: a dialect outside
|
|
73
|
+
// `vnd.fjs.*` yields its own derived media type.
|
|
74
|
+
nonVndDialectName: () => {
|
|
75
|
+
const m = detect([gadgetDialect])(utf8Bytes('{"dialect":"application.gadget","size":3}'));
|
|
76
|
+
assertEq(m.mime_type, 'application/application.gadget+json');
|
|
77
|
+
},
|
|
78
|
+
// Entries are tried in order and the first match wins.
|
|
79
|
+
firstMatchWins: () => {
|
|
80
|
+
const bytes = utf8Bytes('{}');
|
|
81
|
+
assertEq(detect([anyEntry('vnd.fjs.a'), anyEntry('vnd.fjs.b')])(bytes).mime_type, 'application/vnd.fjs.a+json');
|
|
82
|
+
assertEq(detect([anyEntry('vnd.fjs.b'), anyEntry('vnd.fjs.a')])(bytes).mime_type, 'application/vnd.fjs.b+json');
|
|
83
|
+
},
|
|
84
|
+
// An empty registry recognizes nothing; every blob keeps the
|
|
85
|
+
// `fjs/media/type` verdict.
|
|
86
|
+
noDialects: () => {
|
|
87
|
+
const m = detect([])(utf8Bytes(revisionJson));
|
|
28
88
|
assertEq(m.type, 'text');
|
|
29
89
|
assertEq(m.mime_type, 'text/plain');
|
|
30
90
|
},
|
|
31
91
|
// Ordinary JSON with no `dialect` field at all falls through unchanged.
|
|
32
92
|
ordinaryJsonFallsThrough: () => {
|
|
33
|
-
const m =
|
|
93
|
+
const m = detectRevision(utf8Bytes('{"hello":"world"}'));
|
|
34
94
|
assertEq(m.type, 'text');
|
|
35
95
|
assertEq(m.mime_type, 'text/plain');
|
|
36
96
|
},
|
|
37
|
-
// Plain (non-JSON) text is unaffected
|
|
97
|
+
// Plain (non-JSON) text is unaffected: the parse fails and the
|
|
98
|
+
// `fjs/media/type` verdict stands.
|
|
38
99
|
plainTextFallsThrough: () => {
|
|
39
|
-
const m =
|
|
100
|
+
const m = detectRevision(utf8Bytes('hello'));
|
|
40
101
|
assertEq(m.type, 'text');
|
|
41
102
|
assertEq(m.mime_type, 'text/plain');
|
|
42
103
|
},
|
|
43
104
|
// Binary content (magic-byte hit) is unaffected by dialect detection.
|
|
44
105
|
binaryFallsThrough: () => {
|
|
45
|
-
const m =
|
|
106
|
+
const m = detectRevision(u8ListToVec(msb)([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
|
|
46
107
|
assertEq(m.type, 'base64');
|
|
47
108
|
assertEq(m.mime_type, 'image/png');
|
|
48
109
|
},
|
|
@@ -51,8 +112,16 @@ export const proof = {
|
|
|
51
112
|
// non-JSON content within that bound.
|
|
52
113
|
largeNonJsonWithinBound: () => {
|
|
53
114
|
const a = repeat(1000n)(vec8(0x61n)); // 1,000 bytes of 'a'
|
|
54
|
-
const m =
|
|
115
|
+
const m = detectRevision(a);
|
|
55
116
|
assertEq(m.type, 'text');
|
|
56
117
|
assertEq(m.mime_type, 'text/plain');
|
|
57
118
|
},
|
|
119
|
+
throw: {
|
|
120
|
+
// The `dialect` member must be a direct string const. A thunk-form one
|
|
121
|
+
// is a valid rtti schema but is not registerable, and `dialectEntry`
|
|
122
|
+
// says so at registration rather than per blob.
|
|
123
|
+
thunkDialectMember: () => dialectEntry({ dialect: string }),
|
|
124
|
+
// A schema with no `dialect` member at all is likewise refused.
|
|
125
|
+
noDialectMember: () => dialectEntry({ text: string }),
|
|
126
|
+
},
|
|
58
127
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ValidationError } from '../../types/rtti/validate/module.f.ts';
|
|
2
2
|
import type { Ts } from '../../types/rtti/ts/module.f.ts';
|
|
3
|
-
import type
|
|
3
|
+
import { type Unknown } from '../json/module.f.ts';
|
|
4
4
|
import { type Result } from '../../types/result/module.f.ts';
|
|
5
|
+
import { type DialectEntry } from '../module.f.ts';
|
|
5
6
|
/**
|
|
6
7
|
* Format tag: names the dialect of this BLOB. The media type it is served
|
|
7
8
|
* with is derived mechanically: `application/` + `dialect` + `+json`.
|
|
@@ -76,3 +77,10 @@ export declare const validate: (value: Unknown) => Result<Revision, RevisionErro
|
|
|
76
77
|
* satisfies the schema is a revision, regardless of key order or whitespace.
|
|
77
78
|
*/
|
|
78
79
|
export declare const decodeText: (text: string) => Result<Revision, RevisionError>;
|
|
80
|
+
/**
|
|
81
|
+
* This dialect as a registry entry for `fjs/media`'s `detect`. It carries the
|
|
82
|
+
* semantic checks too, so a blob is detected as `vnd.fjs.revision` exactly when
|
|
83
|
+
* {@link decodeText} would accept it — a structurally valid revision whose
|
|
84
|
+
* `snapshot` is not a cbase32 hash is not one.
|
|
85
|
+
*/
|
|
86
|
+
export declare const revisionDialect: DialectEntry;
|
|
@@ -16,11 +16,10 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { array, number, option, string } from '../../types/rtti/module.f.js';
|
|
18
18
|
import { validate as rttiValidate } from '../../types/rtti/validate/module.f.js';
|
|
19
|
-
import { parse as
|
|
20
|
-
import { tokenize as jsonTokenize } from '../json/tokenizer/module.f.js';
|
|
21
|
-
import { stringToList } from '../../text/utf16/module.f.js';
|
|
19
|
+
import { parse as parseJson } from '../json/module.f.js';
|
|
22
20
|
import { cBase32ToVec } from '../../basen/cbase32/module.f.js';
|
|
23
21
|
import { error, ok } from '../../types/result/module.f.js';
|
|
22
|
+
import { dialectEntry } from '../module.f.js';
|
|
24
23
|
/**
|
|
25
24
|
* Format tag: names the dialect of this BLOB. The media type it is served
|
|
26
25
|
* with is derived mechanically: `application/` + `dialect` + `+json`.
|
|
@@ -106,8 +105,6 @@ export const validate = (value) => {
|
|
|
106
105
|
}
|
|
107
106
|
return checkReferences(v);
|
|
108
107
|
};
|
|
109
|
-
/** Parses `text` as JSON without relying on `JSON.parse` (the shared `fjs/media/json` parser/tokenizer). */
|
|
110
|
-
const parseJson = (text) => jsonParse(jsonTokenize(stringToList(text)));
|
|
111
108
|
/**
|
|
112
109
|
* Decodes `text` as a `revision` BLOB: JSON-parses it, then validates it per
|
|
113
110
|
* {@link validate}. Detection is semantic, not syntactic — any JSON that
|
|
@@ -120,3 +117,15 @@ export const decodeText = (text) => {
|
|
|
120
117
|
}
|
|
121
118
|
return validate(v);
|
|
122
119
|
};
|
|
120
|
+
/** {@link checkReferences} as the `boolean` refinement a {@link DialectEntry} takes. */
|
|
121
|
+
const isValidRevision = (r) => {
|
|
122
|
+
const [tag] = checkReferences(r);
|
|
123
|
+
return tag === 'ok';
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* This dialect as a registry entry for `fjs/media`'s `detect`. It carries the
|
|
127
|
+
* semantic checks too, so a blob is detected as `vnd.fjs.revision` exactly when
|
|
128
|
+
* {@link decodeText} would accept it — a structurally valid revision whose
|
|
129
|
+
* `snapshot` is not a cbase32 hash is not one.
|
|
130
|
+
*/
|
|
131
|
+
export const revisionDialect = dialectEntry(revisionSchema, isValidRevision);
|
|
@@ -32,10 +32,7 @@
|
|
|
32
32
|
import { pure, step } from '../../../effects/module.f.js';
|
|
33
33
|
import { readLine, write } from '../../../effects/node/module.f.js';
|
|
34
34
|
import { tryUtf8 } from '../../../text/module.f.js';
|
|
35
|
-
import {
|
|
36
|
-
import { stringify } from '../../../media/json/module.f.js';
|
|
37
|
-
import { tokenize } from '../../../media/json/tokenizer/module.f.js';
|
|
38
|
-
import { parse } from '../../../media/json/parser/module.f.js';
|
|
35
|
+
import { parse, stringify } from '../../../media/json/module.f.js';
|
|
39
36
|
import { sort } from '../../../types/object/module.f.js';
|
|
40
37
|
import { internalError, jsonrpc, parseError } from '../../json_rpc/module.f.js';
|
|
41
38
|
import { error, ok } from '../../../types/result/module.f.js';
|
|
@@ -61,7 +58,7 @@ export const stdioTransport = (handler) => step(readLine('stdin'), line => line
|
|
|
61
58
|
? pure(undefined)
|
|
62
59
|
: handleLine(handler)(line));
|
|
63
60
|
const handleLine = (handler) => (line) => {
|
|
64
|
-
const [t, value] = parse(
|
|
61
|
+
const [t, value] = parse(line);
|
|
65
62
|
return step(t === 'error'
|
|
66
63
|
? writeResponse(parseErrorResponse)
|
|
67
64
|
: step(handler(value), resp => resp === null
|
|
@@ -135,3 +135,27 @@ export declare const listToString: (input: List<U16>) => string;
|
|
|
135
135
|
* ```
|
|
136
136
|
*/
|
|
137
137
|
export declare const codePointListToString: (input: List<CodePoint>) => string;
|
|
138
|
+
/**
|
|
139
|
+
* Converts a single Unicode code point to a string — the scalar counterpart of
|
|
140
|
+
* {@link codePointListToString}, for callers that hold one code point rather
|
|
141
|
+
* than a list of them and would otherwise wrap it in a single-element list.
|
|
142
|
+
*
|
|
143
|
+
* Input outside the assignable range — a surrogate, a value above
|
|
144
|
+
* `0x10FFFF`, or a negative one — is truncated to its low 16 bits and encoded
|
|
145
|
+
* as that code unit, rather than rejected. This matches
|
|
146
|
+
* {@link codePointListToString}, which reports malformed input through the
|
|
147
|
+
* decoder's `errorMask` on the way *in* rather than through a failure on the
|
|
148
|
+
* way out.
|
|
149
|
+
*
|
|
150
|
+
* @param codePoint - A Unicode code point (`CodePoint`).
|
|
151
|
+
* @returns The one- or two-code-unit string encoding it.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
*
|
|
155
|
+
* ```ts
|
|
156
|
+
* codePointToString(0x48) // 'H'
|
|
157
|
+
* codePointToString(0x1F600) // the 😀 surrogate pair
|
|
158
|
+
* codePointToString(0x110000) // '\u0000' — out of range, low 16 bits kept
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare const codePointToString: (codePoint: CodePoint) => string;
|
|
@@ -285,3 +285,27 @@ export const listToString = fn(map(String.fromCharCode))
|
|
|
285
285
|
* ```
|
|
286
286
|
*/
|
|
287
287
|
export const codePointListToString = (input) => listToString(fromCodePointList(input));
|
|
288
|
+
/**
|
|
289
|
+
* Converts a single Unicode code point to a string — the scalar counterpart of
|
|
290
|
+
* {@link codePointListToString}, for callers that hold one code point rather
|
|
291
|
+
* than a list of them and would otherwise wrap it in a single-element list.
|
|
292
|
+
*
|
|
293
|
+
* Input outside the assignable range — a surrogate, a value above
|
|
294
|
+
* `0x10FFFF`, or a negative one — is truncated to its low 16 bits and encoded
|
|
295
|
+
* as that code unit, rather than rejected. This matches
|
|
296
|
+
* {@link codePointListToString}, which reports malformed input through the
|
|
297
|
+
* decoder's `errorMask` on the way *in* rather than through a failure on the
|
|
298
|
+
* way out.
|
|
299
|
+
*
|
|
300
|
+
* @param codePoint - A Unicode code point (`CodePoint`).
|
|
301
|
+
* @returns The one- or two-code-unit string encoding it.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
*
|
|
305
|
+
* ```ts
|
|
306
|
+
* codePointToString(0x48) // 'H'
|
|
307
|
+
* codePointToString(0x1F600) // the 😀 surrogate pair
|
|
308
|
+
* codePointToString(0x110000) // '\u0000' — out of range, low 16 bits kept
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export const codePointToString = (codePoint) => String.fromCharCode(...codePointToUtf16(codePoint));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toCodePointList, fromCodePointList, stringToList, listToString, stringToCodePointList, codePointListToString } from './module.f.js';
|
|
1
|
+
import { toCodePointList, fromCodePointList, stringToList, listToString, stringToCodePointList, codePointListToString, codePointToString } from './module.f.js';
|
|
2
2
|
import { stringify as jsonStringify } from '../../media/json/module.f.js';
|
|
3
3
|
import { sort } from '../../types/object/module.f.js';
|
|
4
4
|
import { toArray } from '../../types/list/module.f.js';
|
|
@@ -131,5 +131,14 @@ export const proof = {
|
|
|
131
131
|
const codePoints = [0x48, 0x65, 0x6C, 0x6C, 0x6F];
|
|
132
132
|
const outputString = codePointListToString(codePoints);
|
|
133
133
|
}
|
|
134
|
+
],
|
|
135
|
+
codePointToString: [
|
|
136
|
+
() => { assertEq(codePointToString(0x48), 'H'); },
|
|
137
|
+
// supplementary plane: one code point, two code units
|
|
138
|
+
() => { assertEq(codePointToString(0x1f600), '😀'); },
|
|
139
|
+
// a surrogate is not a code point; it round-trips as its own code unit
|
|
140
|
+
() => { assertEq(codePointToString(0xd800), '\ud800'); },
|
|
141
|
+
// above the maximum code point, the low 16 bits are kept
|
|
142
|
+
() => { assertEq(codePointToString(0x110000), '\u0000'); },
|
|
134
143
|
]
|
|
135
144
|
};
|
|
@@ -288,4 +288,62 @@ export const proof = {
|
|
|
288
288
|
assertError(v({ a: 42 }));
|
|
289
289
|
},
|
|
290
290
|
},
|
|
291
|
+
funcParam: () => {
|
|
292
|
+
const paramSet0 = ['hello', bigint];
|
|
293
|
+
const paramSet1 = ['goodbye', string, 43];
|
|
294
|
+
const paramSet01 = or(paramSet0, paramSet1);
|
|
295
|
+
const v0 = validate(paramSet0);
|
|
296
|
+
const v1 = validate(paramSet1);
|
|
297
|
+
const func = (f0, f1) => (...args) => {
|
|
298
|
+
{
|
|
299
|
+
const [t, r] = v0(args);
|
|
300
|
+
if (t === 'ok') {
|
|
301
|
+
return f0(...r);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
{
|
|
305
|
+
const [t, r] = v1(args);
|
|
306
|
+
if (t === 'ok') {
|
|
307
|
+
return f1(...r);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
throw 'unreachable: args did not match any parameter set';
|
|
311
|
+
};
|
|
312
|
+
const f0 = (a, b) => 42;
|
|
313
|
+
const f1 = (a, b, c) => 13;
|
|
314
|
+
const x = func(f0, f1);
|
|
315
|
+
return () => {
|
|
316
|
+
assertEq(x('hello', 42n), 42);
|
|
317
|
+
assertEq(x('goodbye', 'world', 43), 13);
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
funcObj: () => {
|
|
321
|
+
const param0 = { a: 'hello', b: bigint };
|
|
322
|
+
const param1 = { a: 'goodbye', b: string, c: 43 };
|
|
323
|
+
const param01 = or(param0, param1);
|
|
324
|
+
const v0 = validate(param0);
|
|
325
|
+
const v1 = validate(param1);
|
|
326
|
+
const func = (f0, f1) => (args) => {
|
|
327
|
+
{
|
|
328
|
+
const [t, r] = v0(args);
|
|
329
|
+
if (t === 'ok') {
|
|
330
|
+
return f0(r);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
{
|
|
334
|
+
const [t, r] = v1(args);
|
|
335
|
+
if (t === 'ok') {
|
|
336
|
+
return f1(r);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
throw 'unreachable: args did not match any parameter set';
|
|
340
|
+
};
|
|
341
|
+
const f0 = (args) => Number(args.b) + args.a.length;
|
|
342
|
+
const f1 = (args) => args.c;
|
|
343
|
+
const x = func(f0, f1);
|
|
344
|
+
return () => {
|
|
345
|
+
assertEq(x({ a: 'hello', b: 42n }), 47);
|
|
346
|
+
assertEq(x({ a: 'goodbye', b: 'world', c: 43 }), 43);
|
|
347
|
+
};
|
|
348
|
+
}
|
|
291
349
|
};
|
|
@@ -88,6 +88,15 @@ export declare const proof: {
|
|
|
88
88
|
};
|
|
89
89
|
function: () => void;
|
|
90
90
|
};
|
|
91
|
+
mul: () => {
|
|
92
|
+
nullish: () => void;
|
|
93
|
+
boolean: () => void;
|
|
94
|
+
number: () => void;
|
|
95
|
+
bigint: () => void;
|
|
96
|
+
string: () => void;
|
|
97
|
+
array: () => void;
|
|
98
|
+
object: () => void;
|
|
99
|
+
};
|
|
91
100
|
stringCoercion: {
|
|
92
101
|
number: () => void;
|
|
93
102
|
bool: () => void;
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { assert } from '../../fjs/asserts/module.f.js';
|
|
1
2
|
const { is } = Object;
|
|
2
3
|
const ois = (a) => (b) => {
|
|
3
|
-
|
|
4
|
-
else {
|
|
5
|
-
throw [a, 'is', b];
|
|
6
|
-
}
|
|
4
|
+
assert(is(a, b), [a, 'is', b]);
|
|
7
5
|
};
|
|
8
6
|
const { isNaN } = Number;
|
|
9
7
|
const nanRes = (op) => (n) => {
|
|
10
8
|
const result = op(n);
|
|
11
|
-
|
|
12
|
-
throw result;
|
|
13
|
-
}
|
|
9
|
+
assert(isNaN(result), result);
|
|
14
10
|
};
|
|
15
11
|
const stringCoercion = String;
|
|
12
|
+
const multiply = (a) => (b) => a * b;
|
|
13
|
+
const multiplyEq = (a) => (b) => (expected) => {
|
|
14
|
+
ois(multiply(a)(b))(expected);
|
|
15
|
+
ois(multiply(b)(a))(expected);
|
|
16
|
+
};
|
|
16
17
|
export const proof = {
|
|
17
18
|
eq: () => {
|
|
18
19
|
const e = (a) => (b) => {
|
|
@@ -174,6 +175,56 @@ export const proof = {
|
|
|
174
175
|
function: () => nan(op(() => { }))
|
|
175
176
|
};
|
|
176
177
|
},
|
|
178
|
+
mul: () => {
|
|
179
|
+
return {
|
|
180
|
+
nullish: () => {
|
|
181
|
+
multiplyEq(null)(null)(0);
|
|
182
|
+
multiplyEq(null)(0)(0);
|
|
183
|
+
multiplyEq(undefined)(0)(NaN);
|
|
184
|
+
},
|
|
185
|
+
boolean: () => {
|
|
186
|
+
multiplyEq(true)(0)(0);
|
|
187
|
+
multiplyEq(true)(1)(1);
|
|
188
|
+
multiplyEq(true)(10)(10);
|
|
189
|
+
multiplyEq(false)(0)(0);
|
|
190
|
+
multiplyEq(false)(1)(0);
|
|
191
|
+
multiplyEq(false)(10)(0);
|
|
192
|
+
},
|
|
193
|
+
number: () => {
|
|
194
|
+
multiplyEq(0)(0)(0);
|
|
195
|
+
multiplyEq(0)(1)(0);
|
|
196
|
+
multiplyEq(1)(1)(1);
|
|
197
|
+
multiplyEq(1)(-1)(-1);
|
|
198
|
+
multiplyEq(1)(10)(10);
|
|
199
|
+
multiplyEq(-1)(10)(-10);
|
|
200
|
+
multiplyEq(10)(10)(100);
|
|
201
|
+
multiplyEq(-10)(10)(-100);
|
|
202
|
+
},
|
|
203
|
+
bigint: () => {
|
|
204
|
+
multiplyEq(0n)(0n)(0n);
|
|
205
|
+
multiplyEq(0n)(1n)(0n);
|
|
206
|
+
multiplyEq(1n)(1n)(1n);
|
|
207
|
+
multiplyEq(1n)(-1n)(-1n);
|
|
208
|
+
multiplyEq(1n)(10n)(10n);
|
|
209
|
+
multiplyEq(-1n)(10n)(-10n);
|
|
210
|
+
multiplyEq(10n)(10n)(100n);
|
|
211
|
+
multiplyEq(-10n)(10n)(-100n);
|
|
212
|
+
},
|
|
213
|
+
string: () => {
|
|
214
|
+
multiplyEq('')(1)(0);
|
|
215
|
+
multiplyEq('10')(1)(10);
|
|
216
|
+
multiplyEq('a')(1)(NaN);
|
|
217
|
+
multiplyEq('1n')(1)(NaN);
|
|
218
|
+
},
|
|
219
|
+
array: () => {
|
|
220
|
+
multiplyEq([])(1)(0);
|
|
221
|
+
multiplyEq([10])(1)(10);
|
|
222
|
+
multiplyEq(['10'])(1)(10);
|
|
223
|
+
multiplyEq([0, 0])(1)(NaN);
|
|
224
|
+
},
|
|
225
|
+
object: () => multiplyEq({})(1)(NaN)
|
|
226
|
+
};
|
|
227
|
+
},
|
|
177
228
|
stringCoercion: {
|
|
178
229
|
number: () => {
|
|
179
230
|
if (stringCoercion(123) !== '123') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "functionalscript",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"**/*.js",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"prepack": "tsc --NoEmit false",
|
|
12
12
|
"test": "tsc && node ./fjs/module.ts t",
|
|
13
|
-
"cov": "node --test --experimental-test-coverage --test-coverage-include=**/module.f.ts",
|
|
13
|
+
"cov": "node --test --experimental-test-coverage --test-coverage-include=**/module.f.ts --test-coverage-include=**/module.f.mjs",
|
|
14
14
|
"start": "node ./fjs/module.ts",
|
|
15
15
|
"ci-update": "node ./fjs/module.ts ci",
|
|
16
16
|
"dev-update": "node ./fjs/module.ts r ./fjs/dev/update/module.f.ts",
|