functionalscript 0.45.0 → 0.46.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/README.md +5 -3
- package/fjs/asn.1/module.f.mjs +8 -1
- package/fjs/asn.1/proof.f.d.mts +10 -0
- package/fjs/asn.1/proof.f.mjs +16 -0
- package/fjs/basen/base128/module.f.mjs +22 -5
- package/fjs/bnf/data/module.f.d.mts +32 -6
- package/fjs/bnf/data/module.f.mjs +112 -7
- package/fjs/bnf/data/proof.f.d.mts +2 -0
- package/fjs/bnf/data/proof.f.mjs +74 -3
- package/fjs/bnf/data/types.d.ts +20 -2
- package/fjs/bnf/descent/module.f.d.mts +28 -14
- package/fjs/bnf/descent/module.f.mjs +122 -77
- package/fjs/bnf/descent/proof.f.d.mts +2 -0
- package/fjs/bnf/descent/proof.f.mjs +117 -31
- package/fjs/bnf/descent/types.d.ts +12 -14
- package/fjs/bnf/ll1/module.f.d.mts +64 -22
- package/fjs/bnf/ll1/module.f.mjs +214 -154
- package/fjs/bnf/ll1/proof.f.d.mts +15 -2
- package/fjs/bnf/ll1/proof.f.mjs +323 -149
- package/fjs/bnf/ll1/types.d.ts +16 -24
- package/fjs/bnf/matcher/module.f.d.mts +66 -0
- package/fjs/bnf/matcher/module.f.mjs +81 -0
- package/fjs/bnf/matcher/proof.f.d.mts +10 -0
- package/fjs/bnf/matcher/proof.f.mjs +79 -0
- package/fjs/bnf/matcher/types.d.ts +54 -0
- package/fjs/bnf/testlib.f.d.mts +31 -0
- package/fjs/bnf/testlib.f.mjs +80 -0
- package/fjs/cas/cli/module.f.d.mts +1 -1
- package/fjs/cas/cli/module.f.mjs +14 -20
- package/fjs/cas/cli/proof.f.d.mts +1 -3
- package/fjs/cas/cli/proof.f.mjs +44 -33
- package/fjs/cas/evo/module.f.d.mts +64 -18
- package/fjs/cas/evo/module.f.mjs +148 -70
- package/fjs/cas/evo/proof.f.d.mts +10 -1
- package/fjs/cas/evo/proof.f.mjs +305 -223
- package/fjs/cas/evo/types.d.ts +45 -9
- package/fjs/cas/module.f.d.mts +18 -32
- package/fjs/cas/module.f.mjs +129 -128
- package/fjs/cas/proof.f.d.mts +11 -8
- package/fjs/cas/proof.f.mjs +259 -147
- package/fjs/cas/types.d.ts +24 -10
- package/fjs/ci/config/module.f.d.mts +2 -2
- package/fjs/ci/config/module.f.mjs +2 -2
- package/fjs/ci/module.f.d.mts +5 -5
- package/fjs/ci/module.f.mjs +8 -7
- package/fjs/ci/nix/module.f.d.mts +7 -5
- package/fjs/ci/nix/module.f.mjs +13 -12
- package/fjs/ci/nix/proof.f.mjs +2 -2
- package/fjs/ci/proof.f.mjs +9 -5
- package/fjs/cli/module.f.d.mts +4 -6
- package/fjs/cli/module.f.mjs +4 -8
- package/fjs/cli/proof.f.mjs +17 -16
- package/fjs/cli/types.d.ts +2 -3
- package/fjs/common/monoid/types.d.ts +1 -1
- package/fjs/crypto/hmac/module.f.mjs +2 -2
- package/fjs/crypto/sha2/module.f.mjs +3 -1
- package/fjs/crypto/sha2/proof.f.d.mts +1 -0
- package/fjs/crypto/sha2/proof.f.mjs +24 -0
- package/fjs/crypto/sha2/types.d.ts +11 -0
- package/fjs/crypto/sign/module.f.mjs +2 -2
- package/fjs/dev/module.f.d.mts +13 -4
- package/fjs/dev/module.f.mjs +56 -27
- package/fjs/dev/update/module.f.d.mts +9 -4
- package/fjs/dev/update/module.f.mjs +14 -10
- package/fjs/dev/update/proof.f.d.mts +1 -3
- package/fjs/dev/update/proof.f.mjs +10 -5
- package/fjs/djs/module.f.d.mts +13 -5
- package/fjs/djs/module.f.mjs +31 -16
- package/fjs/djs/parser/module.f.d.mts +13 -3
- package/fjs/djs/parser/module.f.mjs +117 -16
- package/fjs/djs/parser/proof.f.d.mts +5 -1
- package/fjs/djs/parser/proof.f.mjs +274 -12
- package/fjs/djs/parser/types.d.ts +7 -1
- package/fjs/djs/proof.f.d.mts +18 -2
- package/fjs/djs/proof.f.mjs +187 -12
- package/fjs/djs/serializer/module.f.d.mts +25 -8
- package/fjs/djs/serializer/module.f.mjs +61 -15
- package/fjs/djs/serializer/proof.f.d.mts +5 -0
- package/fjs/djs/serializer/proof.f.mjs +23 -1
- package/fjs/djs/tokenizer/module.f.d.mts +17 -5
- package/fjs/djs/tokenizer/module.f.mjs +99 -60
- package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
- package/fjs/djs/tokenizer/proof.f.mjs +74 -67
- package/fjs/djs/transpiler/module.f.d.mts +12 -7
- package/fjs/djs/transpiler/module.f.mjs +82 -56
- package/fjs/djs/transpiler/types.d.ts +7 -3
- package/fjs/djs/types.d.ts +7 -1
- package/fjs/effects/list/module.f.d.mts +14 -11
- package/fjs/effects/list/module.f.mjs +13 -11
- package/fjs/effects/list/types.d.ts +27 -7
- package/fjs/effects/memory/module.f.d.mts +2 -1
- package/fjs/effects/memory/module.f.mjs +3 -4
- package/fjs/effects/memory/proof.f.mjs +13 -7
- package/fjs/effects/memory/types.d.ts +4 -3
- package/fjs/effects/mock/module.f.d.mts +20 -4
- package/fjs/effects/mock/module.f.mjs +37 -5
- package/fjs/effects/mock/types.d.ts +10 -1
- package/fjs/effects/module.d.mts +4 -2
- package/fjs/effects/module.f.d.mts +466 -280
- package/fjs/effects/module.f.mjs +537 -299
- package/fjs/effects/module.mjs +2 -1
- package/fjs/effects/node/memory/module.d.mts +4 -2
- package/fjs/effects/node/memory/module.mjs +6 -3
- package/fjs/effects/node/memory/proof.mjs +10 -4
- package/fjs/effects/node/module.d.mts +4 -3
- package/fjs/effects/node/module.f.d.mts +168 -33
- package/fjs/effects/node/module.f.mjs +256 -52
- package/fjs/effects/node/module.mjs +57 -34
- package/fjs/effects/node/proof.f.d.mts +28 -2
- package/fjs/effects/node/proof.f.mjs +161 -42
- package/fjs/effects/node/types.d.ts +106 -18
- package/fjs/effects/node/virtual/module.f.d.mts +18 -4
- package/fjs/effects/node/virtual/module.f.mjs +110 -68
- package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
- package/fjs/effects/node/virtual/proof.f.mjs +190 -9
- package/fjs/effects/proof.f.d.mts +69 -37
- package/fjs/effects/proof.f.mjs +410 -130
- package/fjs/effects/types.d.ts +161 -33
- package/fjs/emergent_testing/module.f.d.mts +19 -12
- package/fjs/emergent_testing/module.f.mjs +93 -33
- package/fjs/emergent_testing/proof.f.d.mts +21 -7
- package/fjs/emergent_testing/proof.f.mjs +166 -32
- package/fjs/emergent_testing/types.d.ts +22 -4
- package/fjs/fsm/module.f.d.mts +14 -4
- package/fjs/fsm/module.f.mjs +54 -37
- package/fjs/fsm/proof.f.d.mts +2 -0
- package/fjs/fsm/proof.f.mjs +83 -114
- package/fjs/js/keywords/module.f.d.mts +52 -0
- package/fjs/js/keywords/module.f.mjs +72 -0
- package/fjs/js/keywords/proof.f.d.mts +3 -0
- package/fjs/js/keywords/proof.f.mjs +13 -0
- package/fjs/js/tokenizer/module.f.d.mts +26 -6
- package/fjs/js/tokenizer/module.f.mjs +145 -159
- package/fjs/js/tokenizer/proof.f.d.mts +1 -0
- package/fjs/js/tokenizer/proof.f.mjs +54 -24
- package/fjs/js/tokenizer/types.d.ts +33 -24
- package/fjs/mcp/cas/module.f.d.mts +1 -6
- package/fjs/mcp/cas/module.f.mjs +55 -51
- package/fjs/mcp/cas/proof.f.d.mts +15 -0
- package/fjs/mcp/cas/proof.f.mjs +174 -0
- package/fjs/mcp/evo/module.f.d.mts +19 -10
- package/fjs/mcp/evo/module.f.mjs +48 -27
- package/fjs/mcp/evo/proof.f.d.mts +6 -1
- package/fjs/mcp/evo/proof.f.mjs +115 -31
- package/fjs/mcp/module.f.d.mts +4 -4
- package/fjs/mcp/module.f.mjs +6 -6
- package/fjs/mcp/proof.f.d.mts +5 -3
- package/fjs/mcp/proof.f.mjs +112 -46
- package/fjs/media/html/module.f.mjs +1 -1
- package/fjs/media/json/extended/module.f.d.mts +82 -0
- package/fjs/media/json/extended/module.f.mjs +153 -0
- package/fjs/media/json/extended/proof.f.d.mts +42 -0
- package/fjs/media/json/extended/proof.f.mjs +127 -0
- package/fjs/media/json/extended/types.d.ts +23 -0
- package/fjs/media/json/module.f.d.mts +8 -2
- package/fjs/media/json/module.f.mjs +43 -41
- package/fjs/media/json/number/module.f.d.mts +59 -0
- package/fjs/media/json/number/module.f.mjs +136 -0
- package/fjs/media/json/number/proof.f.d.mts +24 -0
- package/fjs/media/json/number/proof.f.mjs +86 -0
- package/fjs/media/json/number/types.d.ts +28 -0
- package/fjs/media/json/parser/module.f.d.mts +25 -13
- package/fjs/media/json/parser/module.f.mjs +114 -70
- package/fjs/media/json/parser/proof.f.d.mts +5 -0
- package/fjs/media/json/parser/proof.f.mjs +31 -1
- package/fjs/media/json/parser/types.d.ts +33 -14
- package/fjs/media/json/rtti/module.f.d.mts +1 -1
- package/fjs/media/json/rtti/module.f.mjs +1 -1
- package/fjs/media/json/rtti/proof.f.mjs +9 -9
- package/fjs/media/json/schema/module.f.mjs +3 -13
- package/fjs/media/json/schema/proof.f.d.mts +0 -1
- package/fjs/media/json/schema/proof.f.mjs +1 -2
- package/fjs/media/json/serializer/module.f.d.mts +32 -1
- package/fjs/media/json/serializer/module.f.mjs +64 -2
- package/fjs/media/json/tokenizer/module.f.mjs +7 -3
- package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
- package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
- package/fjs/media/json/types.d.ts +36 -10
- package/fjs/media/lock/module.f.d.mts +100 -0
- package/fjs/media/lock/module.f.mjs +125 -0
- package/fjs/media/lock/proof.f.d.mts +33 -0
- package/fjs/media/lock/proof.f.mjs +196 -0
- package/fjs/media/lock/types.d.ts +15 -0
- package/fjs/media/module.f.d.mts +6 -5
- package/fjs/media/module.f.mjs +8 -7
- package/fjs/media/note/module.f.d.mts +121 -0
- package/fjs/media/note/module.f.mjs +131 -0
- package/fjs/media/note/proof.f.d.mts +29 -0
- package/fjs/media/note/proof.f.mjs +150 -0
- package/fjs/media/note/types.d.ts +10 -0
- package/fjs/media/proof.f.d.mts +4 -1
- package/fjs/media/proof.f.mjs +40 -21
- package/fjs/media/revision/module.f.d.mts +78 -7
- package/fjs/media/revision/module.f.mjs +119 -12
- package/fjs/media/revision/proof.f.d.mts +10 -0
- package/fjs/media/revision/proof.f.mjs +88 -0
- package/fjs/media/revision/types.d.ts +34 -5
- package/fjs/media/type/module.f.d.mts +33 -15
- package/fjs/media/type/module.f.mjs +35 -29
- package/fjs/media/type/proof.f.d.mts +2 -1
- package/fjs/media/type/proof.f.mjs +30 -10
- package/fjs/module.f.mjs +29 -8
- package/fjs/nanvm/proof.f.mjs +3 -3
- package/fjs/nanvm/rust/module.f.mjs +1 -1
- package/fjs/nanvm/update/module.f.d.mts +4 -4
- package/fjs/nanvm/update/module.f.mjs +8 -9
- package/fjs/nanvm/update/proof.f.mjs +4 -3
- package/fjs/proof.f.d.mts +3 -3
- package/fjs/proof.f.mjs +36 -10
- package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
- package/fjs/protocol/json_rpc/module.f.mjs +3 -3
- package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
- package/fjs/protocol/mcp/module.f.d.mts +10 -13
- package/fjs/protocol/mcp/module.f.mjs +74 -58
- package/fjs/protocol/mcp/proof.f.d.mts +13 -4
- package/fjs/protocol/mcp/proof.f.mjs +193 -61
- package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
- package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
- package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
- package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
- package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
- package/fjs/protocol/mcp/types.d.ts +17 -6
- package/fjs/sul/id/module.f.d.mts +0 -1
- package/fjs/sul/id/module.f.mjs +2 -3
- package/fjs/sul/level/hash/module.f.mjs +2 -1
- package/fjs/sul/level/hash/proof.f.mjs +2 -2
- package/fjs/sul/module.f.mjs +18 -13
- package/fjs/text/code_point/module.f.d.mts +8 -0
- package/fjs/text/code_point/module.f.mjs +8 -1
- package/fjs/text/code_point/proof.f.d.mts +1 -0
- package/fjs/text/code_point/proof.f.mjs +11 -0
- package/fjs/text/sgr/module.f.d.mts +4 -6
- package/fjs/text/sgr/module.f.mjs +6 -7
- package/fjs/text/utf16/module.f.mjs +7 -2
- package/fjs/text/utf16/proof.f.mjs +3 -3
- package/fjs/text/utf8/module.f.mjs +4 -2
- package/fjs/types/array/module.f.mjs +14 -2
- package/fjs/types/bit_vec/module.f.d.mts +0 -2
- package/fjs/types/bit_vec/module.f.mjs +46 -32
- package/fjs/types/bit_vec/proof.f.mjs +2 -2
- package/fjs/types/btree/remove/module.f.mjs +1 -1
- package/fjs/types/btree/set/module.f.mjs +9 -11
- package/fjs/types/btree/set/proof.f.mjs +12 -0
- package/fjs/types/byte_set/module.f.d.mts +11 -4
- package/fjs/types/byte_set/module.f.mjs +14 -6
- package/fjs/types/byte_set/proof.f.mjs +7 -7
- package/fjs/types/function/compare/module.f.mjs +10 -3
- package/fjs/types/list/module.f.d.mts +1 -1
- package/fjs/types/list/module.f.mjs +1 -1
- package/fjs/types/nullable/module.f.d.mts +18 -4
- package/fjs/types/nullable/module.f.mjs +21 -4
- package/fjs/types/nullable/proof.f.d.mts +4 -0
- package/fjs/types/nullable/proof.f.mjs +15 -0
- package/fjs/types/object/module.f.d.mts +12 -2
- package/fjs/types/object/module.f.mjs +11 -1
- package/fjs/types/patricia_trie/module.f.mjs +26 -11
- package/fjs/types/result/module.f.d.mts +3 -3
- package/fjs/types/result/module.f.mjs +3 -3
- package/fjs/types/rtti/common/module.f.d.mts +25 -28
- package/fjs/types/rtti/common/module.f.mjs +36 -32
- package/fjs/types/rtti/common/proof.f.mjs +5 -5
- package/fjs/types/rtti/data/module.f.d.mts +14 -0
- package/fjs/types/rtti/data/module.f.mjs +25 -2
- package/fjs/types/rtti/data/proof.f.d.mts +1 -0
- package/fjs/types/rtti/data/proof.f.mjs +25 -2
- package/fjs/types/rtti/parse/module.f.d.mts +24 -14
- package/fjs/types/rtti/parse/module.f.mjs +37 -28
- package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
- package/fjs/types/rtti/parse/proof.f.mjs +33 -14
- package/fjs/types/rtti/proof.f.mjs +3 -1
- package/fjs/types/rtti/ts/module.f.mjs +13 -17
- package/fjs/types/rtti/ts/types.d.ts +14 -1
- package/fjs/types/rtti/validate/module.f.d.mts +81 -19
- package/fjs/types/rtti/validate/module.f.mjs +107 -61
- package/fjs/types/rtti/validate/proof.f.d.mts +13 -10
- package/fjs/types/rtti/validate/proof.f.mjs +186 -197
- package/fjs/types/sorted_set/module.f.d.mts +18 -0
- package/fjs/types/sorted_set/module.f.mjs +22 -0
- package/fjs/types/sorted_set/proof.f.d.mts +1 -0
- package/fjs/types/sorted_set/proof.f.mjs +16 -1
- package/fjs/types/uint8array/module.f.d.mts +1 -1
- package/fjs/types/uint8array/module.f.mjs +1 -1
- package/fjs/website/module.f.d.mts +3 -3
- package/fjs/website/module.f.mjs +4 -7
- package/fjs/website/proof.f.mjs +2 -1
- package/package.json +2 -2
- package/fjs/dev/package_json/module.f.d.mts +0 -39
- package/fjs/dev/package_json/module.f.mjs +0 -40
- package/fjs/dev/package_json/proof.f.d.mts +0 -6
- package/fjs/dev/package_json/proof.f.mjs +0 -32
- package/fjs/effects/eff/module.f.d.mts +0 -20
- package/fjs/effects/eff/module.f.mjs +0 -70
- package/fjs/effects/eff/proof.f.d.mts +0 -15
- package/fjs/effects/eff/proof.f.mjs +0 -69
- package/fjs/effects/eff/types.d.ts +0 -71
- package/fjs/types/rtti/validate/types.d.ts +0 -6
|
@@ -67,7 +67,7 @@ const escapeTable = /** @type {const} */ ({
|
|
|
67
67
|
|
|
68
68
|
/** @type {(code: number) => string} */
|
|
69
69
|
const escapeCharCode = code =>
|
|
70
|
-
escapeTable[
|
|
70
|
+
escapeTable[code] ?? fromCharCode(code)
|
|
71
71
|
|
|
72
72
|
const escape = compose(stringToList)(map(escapeCharCode))
|
|
73
73
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended JSON: the ordinary JSON data model with `bigint` added to the
|
|
3
|
+
* primitive leaves, so bare integer syntax survives a parse exactly.
|
|
4
|
+
*
|
|
5
|
+
* It is a runtime representation, not a syntax. `stringify` emits ordinary,
|
|
6
|
+
* valid JSON text — there is no `123n` literal, tagged object, or
|
|
7
|
+
* quoted-integer convention — and ordinary JavaScript consumers of that text
|
|
8
|
+
* are free to read it back into whatever numeric representation they use,
|
|
9
|
+
* `JSON.parse`'s `number` included. What the extended codec adds is that
|
|
10
|
+
* *this* codec reads it back into the same runtime value it wrote.
|
|
11
|
+
*
|
|
12
|
+
* ### Parse
|
|
13
|
+
*
|
|
14
|
+
* The split is lexical, not mathematical: **a token containing `.` or `e` /
|
|
15
|
+
* `E` is a `number`, even when its value is an integer.**
|
|
16
|
+
*
|
|
17
|
+
* ```text
|
|
18
|
+
* 123 -> 123n -123 -> -123n 0 -> 0n
|
|
19
|
+
* -0 -> -0 1.5 -> 1.5 1.0 -> 1
|
|
20
|
+
* 1e3 -> 1000 1E3 -> 1000
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `-0` is the exception to bare integer syntax: `bigint` has no negative zero,
|
|
24
|
+
* so keeping it as a `number` is what preserves the sign the JSON text spells.
|
|
25
|
+
*
|
|
26
|
+
* The extended domain is exact where it claims to be, so it rejects rather
|
|
27
|
+
* than rounds: a token whose value is outside the finite `number` range
|
|
28
|
+
* (`1e400`) is a parse `error`, not `Infinity`. Ordinary rounding within that
|
|
29
|
+
* range is inherent to `number` and is not an error — `1e-400` is `0`, and
|
|
30
|
+
* `0.1` is the nearest double, as everywhere else.
|
|
31
|
+
*
|
|
32
|
+
* ### Serialize
|
|
33
|
+
*
|
|
34
|
+
* Spelling follows the runtime type of the leaf, so parsing the output returns
|
|
35
|
+
* the value that was written:
|
|
36
|
+
*
|
|
37
|
+
* ```text
|
|
38
|
+
* 0n -> 0 -> 0n 3n -> 3 -> 3n
|
|
39
|
+
* 0 -> 0.0 -> 0 3 -> 3.0 -> 3
|
|
40
|
+
* -0 -> -0 -> -0
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* A `bigint` is always its full base-10 digits, **never** exponent notation,
|
|
44
|
+
* however large: exponent syntax belongs to the `number` side and would parse
|
|
45
|
+
* back as one. A whole-valued `number` other than `-0` gets a non-integer
|
|
46
|
+
* spelling (`3.0`) for the same reason. This `.0` rule is the extended codec's
|
|
47
|
+
* own; the standard codec in [`../module.f.mjs`](../module.f.mjs) has its own
|
|
48
|
+
* numeric spelling and does not route through here.
|
|
49
|
+
*
|
|
50
|
+
* `NaN`, `Infinity` and `-Infinity` cannot arrive from JSON text but can be
|
|
51
|
+
* supplied programmatically, and JSON has no syntax for them. They serialize
|
|
52
|
+
* as `null`, matching what `JSON.stringify` does with them.
|
|
53
|
+
*
|
|
54
|
+
* @module
|
|
55
|
+
*
|
|
56
|
+
* @import { Result } from '../../../types/result/types.ts'
|
|
57
|
+
* @import { List } from '../../../types/list/types.ts'
|
|
58
|
+
* @import { NumberPolicy } from '../parser/types.ts'
|
|
59
|
+
* @import { Primitive, Unknown, _MapEntries } from './types.ts'
|
|
60
|
+
*/
|
|
61
|
+
import type { Result } from '../../../types/result/types.ts';
|
|
62
|
+
import type { List } from '../../../types/list/types.ts';
|
|
63
|
+
import type { Unknown, _MapEntries } from './types.ts';
|
|
64
|
+
/**
|
|
65
|
+
* Parses `text` as extended JSON, reporting failure as a `Result` rather than
|
|
66
|
+
* throwing.
|
|
67
|
+
*
|
|
68
|
+
* @type {(text: string) => Result<Unknown, string>}
|
|
69
|
+
*/
|
|
70
|
+
export declare const parse: (text: string) => Result<Unknown, string>;
|
|
71
|
+
/**
|
|
72
|
+
* Serializes an extended JSON value as a list of string chunks.
|
|
73
|
+
*
|
|
74
|
+
* @type {(mapEntries: _MapEntries) => (value: Unknown) => List<string>}
|
|
75
|
+
*/
|
|
76
|
+
export declare const serialize: (mapEntries: _MapEntries) => (value: Unknown) => List<string>;
|
|
77
|
+
/**
|
|
78
|
+
* Serializes an extended JSON value as ordinary JSON text.
|
|
79
|
+
*
|
|
80
|
+
* @type {(mapEntries: _MapEntries) => (value: Unknown) => string}
|
|
81
|
+
*/
|
|
82
|
+
export declare const stringify: (mapEntries: _MapEntries) => (value: Unknown) => string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended JSON: the ordinary JSON data model with `bigint` added to the
|
|
3
|
+
* primitive leaves, so bare integer syntax survives a parse exactly.
|
|
4
|
+
*
|
|
5
|
+
* It is a runtime representation, not a syntax. `stringify` emits ordinary,
|
|
6
|
+
* valid JSON text — there is no `123n` literal, tagged object, or
|
|
7
|
+
* quoted-integer convention — and ordinary JavaScript consumers of that text
|
|
8
|
+
* are free to read it back into whatever numeric representation they use,
|
|
9
|
+
* `JSON.parse`'s `number` included. What the extended codec adds is that
|
|
10
|
+
* *this* codec reads it back into the same runtime value it wrote.
|
|
11
|
+
*
|
|
12
|
+
* ### Parse
|
|
13
|
+
*
|
|
14
|
+
* The split is lexical, not mathematical: **a token containing `.` or `e` /
|
|
15
|
+
* `E` is a `number`, even when its value is an integer.**
|
|
16
|
+
*
|
|
17
|
+
* ```text
|
|
18
|
+
* 123 -> 123n -123 -> -123n 0 -> 0n
|
|
19
|
+
* -0 -> -0 1.5 -> 1.5 1.0 -> 1
|
|
20
|
+
* 1e3 -> 1000 1E3 -> 1000
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* `-0` is the exception to bare integer syntax: `bigint` has no negative zero,
|
|
24
|
+
* so keeping it as a `number` is what preserves the sign the JSON text spells.
|
|
25
|
+
*
|
|
26
|
+
* The extended domain is exact where it claims to be, so it rejects rather
|
|
27
|
+
* than rounds: a token whose value is outside the finite `number` range
|
|
28
|
+
* (`1e400`) is a parse `error`, not `Infinity`. Ordinary rounding within that
|
|
29
|
+
* range is inherent to `number` and is not an error — `1e-400` is `0`, and
|
|
30
|
+
* `0.1` is the nearest double, as everywhere else.
|
|
31
|
+
*
|
|
32
|
+
* ### Serialize
|
|
33
|
+
*
|
|
34
|
+
* Spelling follows the runtime type of the leaf, so parsing the output returns
|
|
35
|
+
* the value that was written:
|
|
36
|
+
*
|
|
37
|
+
* ```text
|
|
38
|
+
* 0n -> 0 -> 0n 3n -> 3 -> 3n
|
|
39
|
+
* 0 -> 0.0 -> 0 3 -> 3.0 -> 3
|
|
40
|
+
* -0 -> -0 -> -0
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* A `bigint` is always its full base-10 digits, **never** exponent notation,
|
|
44
|
+
* however large: exponent syntax belongs to the `number` side and would parse
|
|
45
|
+
* back as one. A whole-valued `number` other than `-0` gets a non-integer
|
|
46
|
+
* spelling (`3.0`) for the same reason. This `.0` rule is the extended codec's
|
|
47
|
+
* own; the standard codec in [`../module.f.mjs`](../module.f.mjs) has its own
|
|
48
|
+
* numeric spelling and does not route through here.
|
|
49
|
+
*
|
|
50
|
+
* `NaN`, `Infinity` and `-Infinity` cannot arrive from JSON text but can be
|
|
51
|
+
* supplied programmatically, and JSON has no syntax for them. They serialize
|
|
52
|
+
* as `null`, matching what `JSON.stringify` does with them.
|
|
53
|
+
*
|
|
54
|
+
* @module
|
|
55
|
+
*
|
|
56
|
+
* @import { Result } from '../../../types/result/types.ts'
|
|
57
|
+
* @import { List } from '../../../types/list/types.ts'
|
|
58
|
+
* @import { NumberPolicy } from '../parser/types.ts'
|
|
59
|
+
* @import { Primitive, Unknown, _MapEntries } from './types.ts'
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import { concat } from '../../../types/string/module.f.mjs'
|
|
63
|
+
import { compose } from '../../../types/function/module.f.mjs'
|
|
64
|
+
import { error, ok } from '../../../types/result/module.f.mjs'
|
|
65
|
+
import { stringToList } from '../../../text/utf16/module.f.mjs'
|
|
66
|
+
import { parse as parseTokens } from '../parser/module.f.mjs'
|
|
67
|
+
import { tokenize } from '../tokenizer/module.f.mjs'
|
|
68
|
+
import { isBareInteger, numberLexeme } from '../number/module.f.mjs'
|
|
69
|
+
import { treeSerialize, stringSerialize, nullSerialize, boolSerialize } from '../serializer/module.f.mjs'
|
|
70
|
+
|
|
71
|
+
const { isFinite } = Number
|
|
72
|
+
|
|
73
|
+
const { is } = Object
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The extended numeric policy, applied to the token's exact lexeme.
|
|
77
|
+
*
|
|
78
|
+
* Bare integer syntax is materialized as `bigint` straight from that lexeme —
|
|
79
|
+
* never through `number`, which would round anything above
|
|
80
|
+
* `Number.MAX_SAFE_INTEGER`. Everything else is `number`, and is rejected if
|
|
81
|
+
* the finite `number` range cannot hold it.
|
|
82
|
+
*
|
|
83
|
+
* @type {NumberPolicy<number | bigint>}
|
|
84
|
+
*/
|
|
85
|
+
const numberPolicy = ({ value }) => {
|
|
86
|
+
if (isBareInteger(numberLexeme(value))) {
|
|
87
|
+
return ok(value === '-0' ? -0 : BigInt(value))
|
|
88
|
+
}
|
|
89
|
+
const n = Number(value)
|
|
90
|
+
return isFinite(n) ? ok(n) : error(`number is out of the finite range: ${value}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Parses `text` as extended JSON, reporting failure as a `Result` rather than
|
|
95
|
+
* throwing.
|
|
96
|
+
*
|
|
97
|
+
* @type {(text: string) => Result<Unknown, string>}
|
|
98
|
+
*/
|
|
99
|
+
export const parse = text => parseTokens(numberPolicy)(tokenize(stringToList(text)))
|
|
100
|
+
|
|
101
|
+
const negativeZeroSerialize = ['-0']
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Spells a `number` so that reparsing it returns a `number` — never a
|
|
105
|
+
* `bigint`.
|
|
106
|
+
*
|
|
107
|
+
* A whole-valued `number` already spells itself with `.` or `e` often enough
|
|
108
|
+
* (`1e+21`, `1.5`); when it does not, `.0` is appended, which is the same
|
|
109
|
+
* value in JSON and the other lexical branch on the way back. `-0` is written
|
|
110
|
+
* out as the exact token `-0`, the one bare integer the parser keeps as a
|
|
111
|
+
* `number`.
|
|
112
|
+
*
|
|
113
|
+
* @type {(value: number) => List<string>}
|
|
114
|
+
*/
|
|
115
|
+
const numberSerialize = value => {
|
|
116
|
+
if (is(value, -0)) { return negativeZeroSerialize }
|
|
117
|
+
if (!isFinite(value)) { return nullSerialize }
|
|
118
|
+
const text = `${value}`
|
|
119
|
+
return [isBareInteger(numberLexeme(text)) ? `${text}.0` : text]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Spells a `bigint` as its full base-10 digits. `BigInt`'s own decimal form is
|
|
124
|
+
* exactly that: no `n` suffix, and no exponent notation at any magnitude.
|
|
125
|
+
*
|
|
126
|
+
* @type {(value: bigint) => List<string>}
|
|
127
|
+
*/
|
|
128
|
+
const bigintSerialize = value => [`${value}`]
|
|
129
|
+
|
|
130
|
+
/** @type {(value: Primitive) => List<string>} */
|
|
131
|
+
const primitiveSerialize = value => {
|
|
132
|
+
switch (typeof value) {
|
|
133
|
+
case 'boolean': { return boolSerialize(value) }
|
|
134
|
+
case 'bigint': { return bigintSerialize(value) }
|
|
135
|
+
case 'number': { return numberSerialize(value) }
|
|
136
|
+
case 'string': { return stringSerialize(value) }
|
|
137
|
+
default: { return nullSerialize }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Serializes an extended JSON value as a list of string chunks.
|
|
143
|
+
*
|
|
144
|
+
* @type {(mapEntries: _MapEntries) => (value: Unknown) => List<string>}
|
|
145
|
+
*/
|
|
146
|
+
export const serialize = treeSerialize(primitiveSerialize)
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Serializes an extended JSON value as ordinary JSON text.
|
|
150
|
+
*
|
|
151
|
+
* @type {(mapEntries: _MapEntries) => (value: Unknown) => string}
|
|
152
|
+
*/
|
|
153
|
+
export const stringify = sort => compose(serialize(sort))(concat)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Unknown } from './types.ts'
|
|
3
|
+
*/
|
|
4
|
+
export declare const proof: {
|
|
5
|
+
bigint: {
|
|
6
|
+
positive: () => void;
|
|
7
|
+
negative: () => void;
|
|
8
|
+
zero: () => void;
|
|
9
|
+
beyondSafeInteger: () => void;
|
|
10
|
+
oversized: () => void;
|
|
11
|
+
};
|
|
12
|
+
number: {
|
|
13
|
+
negativeZero: () => void;
|
|
14
|
+
fraction: () => void;
|
|
15
|
+
wholeFraction: () => void;
|
|
16
|
+
exponent: () => void;
|
|
17
|
+
capitalExponent: () => void;
|
|
18
|
+
negativeExponent: () => void;
|
|
19
|
+
underflow: () => void;
|
|
20
|
+
exponentSpelling: () => void;
|
|
21
|
+
};
|
|
22
|
+
overflow: {
|
|
23
|
+
exponent: () => void;
|
|
24
|
+
negativeExponent: () => void;
|
|
25
|
+
unboundedExponent: () => void;
|
|
26
|
+
malformed: () => void;
|
|
27
|
+
};
|
|
28
|
+
containers: {
|
|
29
|
+
array: () => void;
|
|
30
|
+
object: () => void;
|
|
31
|
+
undefinedProperty: () => void;
|
|
32
|
+
};
|
|
33
|
+
nonFinite: {
|
|
34
|
+
nan: () => void;
|
|
35
|
+
infinity: () => void;
|
|
36
|
+
negativeInfinity: () => void;
|
|
37
|
+
};
|
|
38
|
+
serialize: {
|
|
39
|
+
oneToOne: () => void;
|
|
40
|
+
largeBigintHasNoExponent: () => void;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Unknown } from './types.ts'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parse, stringify as extendedStringify } from './module.f.mjs'
|
|
6
|
+
import { sort } from '../../../types/object/module.f.mjs'
|
|
7
|
+
import { unwrap } from '../../../types/result/module.f.mjs'
|
|
8
|
+
import { assert, assertEq, assertStructurallySame } from '../../../asserts/module.f.mjs'
|
|
9
|
+
|
|
10
|
+
const stringify = extendedStringify(sort)
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The runtime type a token parses to, and the text that value serializes back
|
|
14
|
+
* to — the two halves of the extended codec's round trip.
|
|
15
|
+
*
|
|
16
|
+
* @type {(text: string) => readonly [string, string]}
|
|
17
|
+
*/
|
|
18
|
+
const parsed = text => {
|
|
19
|
+
const value = unwrap(parse(text))
|
|
20
|
+
return [typeof value, stringify(value)]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @type {(text: string) => Unknown} */
|
|
24
|
+
const parseValue = text => unwrap(parse(text))
|
|
25
|
+
|
|
26
|
+
/** @type {(text: string) => string} */
|
|
27
|
+
const parseError = text => {
|
|
28
|
+
const [tag, message] = parse(text)
|
|
29
|
+
assertEq(tag, 'error')
|
|
30
|
+
return `${message}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Long enough that `Number` of it is `Infinity` and `10 ** exponent` is not a
|
|
34
|
+
// computation anyone can afford.
|
|
35
|
+
const hugeExp = '99999999999999999999'
|
|
36
|
+
|
|
37
|
+
export const proof = {
|
|
38
|
+
// bare integer syntax is `bigint`, whatever the magnitude
|
|
39
|
+
bigint: {
|
|
40
|
+
positive: () => assertStructurallySame(parsed('123'), ['bigint', '123']),
|
|
41
|
+
negative: () => assertStructurallySame(parsed('-123'), ['bigint', '-123']),
|
|
42
|
+
zero: () => assertStructurallySame(parsed('0'), ['bigint', '0']),
|
|
43
|
+
// beyond `Number.MAX_SAFE_INTEGER`, so a materializer that went
|
|
44
|
+
// through `number` first would round here
|
|
45
|
+
beyondSafeInteger: () => {
|
|
46
|
+
assertEq(parseValue('12345678901234567890123'), 12345678901234567890123n)
|
|
47
|
+
assertEq(parseValue('-12345678901234567890123'), -12345678901234567890123n)
|
|
48
|
+
},
|
|
49
|
+
// an integer of a size no `number` can even approximate
|
|
50
|
+
oversized: () => {
|
|
51
|
+
const digits = `1${'0'.repeat(2000)}`
|
|
52
|
+
assertStructurallySame(parsed(digits), ['bigint', digits])
|
|
53
|
+
assertEq(parseValue(digits), 10n ** 2000n)
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
// `.`/`e`/`E` means `number`, even when the value is a whole number
|
|
57
|
+
number: {
|
|
58
|
+
negativeZero: () => {
|
|
59
|
+
assertStructurallySame(parsed('-0'), ['number', '-0'])
|
|
60
|
+
// the sign is the whole point: `bigint` has no negative zero
|
|
61
|
+
assert(Object.is(parseValue('-0'), -0))
|
|
62
|
+
},
|
|
63
|
+
fraction: () => assertStructurallySame(parsed('1.5'), ['number', '1.5']),
|
|
64
|
+
wholeFraction: () => assertStructurallySame(parsed('1.0'), ['number', '1.0']),
|
|
65
|
+
exponent: () => assertStructurallySame(parsed('1e3'), ['number', '1000.0']),
|
|
66
|
+
capitalExponent: () => assertStructurallySame(parsed('1E3'), ['number', '1000.0']),
|
|
67
|
+
negativeExponent: () => assertStructurallySame(parsed('1e-3'), ['number', '0.001']),
|
|
68
|
+
// ordinary `number` rounding is not an error: an exponent below the
|
|
69
|
+
// range is zero, as it is everywhere else in JavaScript
|
|
70
|
+
underflow: () => assertStructurallySame(parsed('1e-400'), ['number', '0.0']),
|
|
71
|
+
// a `number` big enough to spell itself with an exponent keeps that
|
|
72
|
+
// spelling — appending `.0` to it would not even be valid JSON
|
|
73
|
+
exponentSpelling: () => assertStructurallySame(parsed('1e21'), ['number', '1e+21']),
|
|
74
|
+
},
|
|
75
|
+
// a valid token the extended domain cannot represent is an ordinary
|
|
76
|
+
// parse error, never `Infinity` and never a throw
|
|
77
|
+
overflow: {
|
|
78
|
+
exponent: () => assertEq(
|
|
79
|
+
parseError('1e400'),
|
|
80
|
+
'number is out of the finite range: 1e400'),
|
|
81
|
+
negativeExponent: () => assertEq(
|
|
82
|
+
parseError('-1e400'),
|
|
83
|
+
'number is out of the finite range: -1e400'),
|
|
84
|
+
// exponent text far beyond `number` precision: the tokenizer keeps
|
|
85
|
+
// every digit, and the policy rejects the token rather than losing them
|
|
86
|
+
unboundedExponent: () => assertEq(
|
|
87
|
+
parseError(`1e${hugeExp}`),
|
|
88
|
+
`number is out of the finite range: 1e${hugeExp}`),
|
|
89
|
+
// malformed input is still an ordinary error
|
|
90
|
+
malformed: () => assertEq(parseError('{'), 'unexpected end'),
|
|
91
|
+
},
|
|
92
|
+
containers: {
|
|
93
|
+
array: () => assertEq(
|
|
94
|
+
stringify(parseValue('[1,2.0,-0,"x",true,false,null]')),
|
|
95
|
+
'[1,2.0,-0,"x",true,false,null]'),
|
|
96
|
+
object: () => assertEq(
|
|
97
|
+
stringify(parseValue('{"b":[1e2],"a":{"c":-7}}')),
|
|
98
|
+
'{"a":{"c":-7},"b":[100.0]}'),
|
|
99
|
+
// a missing property is not a leaf
|
|
100
|
+
undefinedProperty: () => assertEq(stringify({ a: 1n, b: undefined }), '{"a":1}'),
|
|
101
|
+
},
|
|
102
|
+
// programmatic values: JSON has no syntax for these, so the serializer
|
|
103
|
+
// spells them `null` — the same choice `JSON.stringify` makes
|
|
104
|
+
nonFinite: {
|
|
105
|
+
nan: () => assertEq(stringify(NaN), 'null'),
|
|
106
|
+
infinity: () => assertEq(stringify(Infinity), 'null'),
|
|
107
|
+
negativeInfinity: () => assertEq(stringify(-Infinity), 'null'),
|
|
108
|
+
},
|
|
109
|
+
// the spellings that keep `number` and `bigint` apart across a round trip
|
|
110
|
+
serialize: {
|
|
111
|
+
oneToOne: () => {
|
|
112
|
+
assertEq(stringify(0n), '0')
|
|
113
|
+
assertEq(stringify(0), '0.0')
|
|
114
|
+
assertEq(stringify(-0), '-0')
|
|
115
|
+
assertEq(stringify(3n), '3')
|
|
116
|
+
assertEq(stringify(3), '3.0')
|
|
117
|
+
assertEq(stringify(1.5), '1.5')
|
|
118
|
+
},
|
|
119
|
+
// a bigint is never shortened to exponent notation, however large:
|
|
120
|
+
// that syntax would parse back as a `number`
|
|
121
|
+
largeBigintHasNoExponent: () => {
|
|
122
|
+
const text = stringify(10n ** 30n)
|
|
123
|
+
assertEq(text, `1${'0'.repeat(30)}`)
|
|
124
|
+
assertStructurallySame(parsed(text), ['bigint', text])
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript counterparts of the extended JSON data model: ordinary JSON's
|
|
3
|
+
* containers with `bigint` added to the primitive leaf set.
|
|
4
|
+
*
|
|
5
|
+
* This is a runtime representation, not a new syntax: an extended value's
|
|
6
|
+
* serialized form is ordinary valid JSON text, with no `123n` literal, tagged
|
|
7
|
+
* object, or quoted-integer convention.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { Primitive as JsonPrimitive, Tree, TreeObject, TreeArray, TreeMapEntries } from '../types.ts';
|
|
12
|
+
/**
|
|
13
|
+
* `null | boolean | string | number | bigint`.
|
|
14
|
+
*
|
|
15
|
+
* `bigint` carries JSON's bare integer syntax exactly, whatever its
|
|
16
|
+
* magnitude; `number` carries decimal and exponent syntax, and the one bare
|
|
17
|
+
* integer that `bigint` cannot represent — negative zero.
|
|
18
|
+
*/
|
|
19
|
+
export type Primitive = JsonPrimitive | bigint;
|
|
20
|
+
export type Unknown = Tree<Primitive>;
|
|
21
|
+
export type Object = TreeObject<Primitive>;
|
|
22
|
+
export type Array = TreeArray<Primitive>;
|
|
23
|
+
export type _MapEntries = TreeMapEntries<Primitive>;
|
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
* [`./types.ts`](./types.ts), and the rtti schemas they are pinned against in
|
|
10
10
|
* [`./rtti/module.f.mjs`](./rtti/module.f.mjs).
|
|
11
11
|
*
|
|
12
|
+
* This is the standard, bigint-free codec: numbers are JavaScript `number`s.
|
|
13
|
+
* [`./extended/module.f.mjs`](./extended/module.f.mjs) is the sibling codec
|
|
14
|
+
* that keeps JSON's bare integer syntax as `bigint`. Both are numeric policies
|
|
15
|
+
* over the same tokenizer and the same structural parser — see
|
|
16
|
+
* [`./README.md`](./README.md).
|
|
17
|
+
*
|
|
12
18
|
* @module
|
|
13
19
|
*
|
|
14
|
-
* @import { StringMap } from '../../types/object/types.ts'
|
|
15
20
|
* @import { Result } from '../../types/result/types.ts'
|
|
16
|
-
* @import { _MapEntries,
|
|
21
|
+
* @import { _MapEntries, Primitive, Unknown, } from './types.ts'
|
|
22
|
+
* @import { NumberPolicy } from './parser/types.ts'
|
|
17
23
|
* @import { List } from '../../types/list/types.ts'
|
|
18
24
|
*/
|
|
19
25
|
import type { Result } from '../../types/result/types.ts';
|
|
@@ -9,22 +9,29 @@
|
|
|
9
9
|
* [`./types.ts`](./types.ts), and the rtti schemas they are pinned against in
|
|
10
10
|
* [`./rtti/module.f.mjs`](./rtti/module.f.mjs).
|
|
11
11
|
*
|
|
12
|
+
* This is the standard, bigint-free codec: numbers are JavaScript `number`s.
|
|
13
|
+
* [`./extended/module.f.mjs`](./extended/module.f.mjs) is the sibling codec
|
|
14
|
+
* that keeps JSON's bare integer syntax as `bigint`. Both are numeric policies
|
|
15
|
+
* over the same tokenizer and the same structural parser — see
|
|
16
|
+
* [`./README.md`](./README.md).
|
|
17
|
+
*
|
|
12
18
|
* @module
|
|
13
19
|
*
|
|
14
|
-
* @import { StringMap } from '../../types/object/types.ts'
|
|
15
20
|
* @import { Result } from '../../types/result/types.ts'
|
|
16
|
-
* @import { _MapEntries,
|
|
21
|
+
* @import { _MapEntries, Primitive, Unknown, } from './types.ts'
|
|
22
|
+
* @import { NumberPolicy } from './parser/types.ts'
|
|
17
23
|
* @import { List } from '../../types/list/types.ts'
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
|
-
import { next
|
|
26
|
+
import { next } from '../../types/list/module.f.mjs'
|
|
21
27
|
import { concat } from '../../types/string/module.f.mjs'
|
|
22
28
|
import { stringToList } from '../../text/utf16/module.f.mjs'
|
|
23
29
|
import { parse as parseTokens } from './parser/module.f.mjs'
|
|
24
30
|
import { tokenize } from './tokenizer/module.f.mjs'
|
|
25
|
-
import { at
|
|
26
|
-
import { compose
|
|
27
|
-
import {
|
|
31
|
+
import { at } from '../../types/object/module.f.mjs'
|
|
32
|
+
import { compose } from '../../types/function/module.f.mjs'
|
|
33
|
+
import { ok } from '../../types/result/module.f.mjs'
|
|
34
|
+
import { treeSerialize, stringSerialize, numberSerialize, nullSerialize, boolSerialize } from './serializer/module.f.mjs'
|
|
28
35
|
|
|
29
36
|
// ── JSON utilities ────────────────────────────────────────────────────────────
|
|
30
37
|
|
|
@@ -43,42 +50,23 @@ export const setProperty = value => {
|
|
|
43
50
|
return f
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
/**
|
|
54
|
+
* The standard codec's leaf spelling. The containers around it are
|
|
55
|
+
* `treeSerialize`'s, shared with every other JSON codec.
|
|
56
|
+
*
|
|
57
|
+
* @type {(value: Primitive) => List<string>}
|
|
58
|
+
*/
|
|
59
|
+
const primitiveSerialize = value => {
|
|
60
|
+
switch (typeof value) {
|
|
61
|
+
case 'boolean': { return boolSerialize(value) }
|
|
62
|
+
case 'number': { return numberSerialize(value) }
|
|
63
|
+
case 'string': { return stringSerialize(value) }
|
|
64
|
+
default: { return nullSerialize }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
50
67
|
|
|
51
68
|
/** @type {(mapEntries: _MapEntries) => (value: Unknown) => List<string>} */
|
|
52
|
-
export const serialize =
|
|
53
|
-
/** @type {(kv: readonly[string, Unknown]) => List<string>} */
|
|
54
|
-
const propertySerialize = ([k, v]) => flat([
|
|
55
|
-
stringSerialize(k),
|
|
56
|
-
colon,
|
|
57
|
-
f(v)
|
|
58
|
-
])
|
|
59
|
-
const mapPropertySerialize = map(propertySerialize)
|
|
60
|
-
/** @type {(object: Object) => List<string>} */
|
|
61
|
-
const objectSerialize = fn(df)
|
|
62
|
-
.map(sort)
|
|
63
|
-
.map(mapPropertySerialize)
|
|
64
|
-
.map(objectWrap)
|
|
65
|
-
.result
|
|
66
|
-
/** @type {(value: Unknown) => List<string>} */
|
|
67
|
-
const f = value => {
|
|
68
|
-
switch (typeof value) {
|
|
69
|
-
case 'boolean': { return boolSerialize(value) }
|
|
70
|
-
case 'number': { return numberSerialize(value) }
|
|
71
|
-
case 'string': { return stringSerialize(value) }
|
|
72
|
-
default: {
|
|
73
|
-
if (value === null) { return nullSerialize }
|
|
74
|
-
if (value instanceof Array) { return arraySerialize(value) }
|
|
75
|
-
return objectSerialize(value)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const arraySerialize = compose(map(f))(arrayWrap)
|
|
80
|
-
return f
|
|
81
|
-
}
|
|
69
|
+
export const serialize = treeSerialize(primitiveSerialize)
|
|
82
70
|
|
|
83
71
|
/**
|
|
84
72
|
* The standard `JSON.stringify` rules determined by
|
|
@@ -89,6 +77,20 @@ export const serialize = sort => {
|
|
|
89
77
|
*/
|
|
90
78
|
export const stringify = sort => compose(serialize(sort))(concat)
|
|
91
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The standard codec's numeric policy: every JSON number token becomes a
|
|
82
|
+
* JavaScript `number`, read from the token's own lexeme.
|
|
83
|
+
*
|
|
84
|
+
* It is total — no valid JSON number is rejected — so a magnitude outside the
|
|
85
|
+
* finite `number` range materializes the way JavaScript itself reads that
|
|
86
|
+
* text (`1e400` is `Infinity`, `1e-400` is `0`). The bigint-free domain has
|
|
87
|
+
* nothing more exact to offer; the extended codec keeps such distinctions,
|
|
88
|
+
* from the same token, without this one having to.
|
|
89
|
+
*
|
|
90
|
+
* @type {NumberPolicy<number>}
|
|
91
|
+
*/
|
|
92
|
+
const numberPolicy = token => ok(parseFloat(token.value))
|
|
93
|
+
|
|
92
94
|
/**
|
|
93
95
|
* Parses `text` as JSON with this module's own pure tokenizer and parser,
|
|
94
96
|
* reporting failure as a `Result` rather than throwing: malformed input is
|
|
@@ -101,4 +103,4 @@ export const stringify = sort => compose(serialize(sort))(concat)
|
|
|
101
103
|
*
|
|
102
104
|
* @type {(text: string) => Result<Unknown, string>}
|
|
103
105
|
*/
|
|
104
|
-
export const parse = text => parseTokens(tokenize(stringToList(text)))
|
|
106
|
+
export const parse = text => parseTokens(numberPolicy)(tokenize(stringToList(text)))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded lexical analysis of a JSON number token.
|
|
3
|
+
*
|
|
4
|
+
* `NumberToken.value` is the canonical lossless numeric source, and these
|
|
5
|
+
* helpers read it **without narrowing it first**: every operation here costs
|
|
6
|
+
* at most the length of the token, so a valid JSON number is classified
|
|
7
|
+
* correctly whatever its magnitude. In particular no helper builds a
|
|
8
|
+
* coefficient bigint, converts an arbitrary exponent to `number`, or evaluates
|
|
9
|
+
* a power such as `10 ** exponent`.
|
|
10
|
+
*
|
|
11
|
+
* That matters for schema-directed consumers, which have to decide questions
|
|
12
|
+
* such as "is this token an integer?" *before* a materializer rounds the value
|
|
13
|
+
* into `number`: `1.00000000000000001` and `1` are the same `number` but
|
|
14
|
+
* different tokens.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*
|
|
18
|
+
* @import { Sign } from '../../../types/function/compare/types.ts'
|
|
19
|
+
* @import { NumberLexeme } from './types.ts'
|
|
20
|
+
*/
|
|
21
|
+
import type { NumberLexeme } from './types.ts';
|
|
22
|
+
/**
|
|
23
|
+
* Splits a JSON number token into its lexical parts.
|
|
24
|
+
*
|
|
25
|
+
* The token is grammar-validated by the tokenizer —
|
|
26
|
+
* `-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?` — so this is a split, not a
|
|
27
|
+
* validation.
|
|
28
|
+
*
|
|
29
|
+
* @type {(value: string) => NumberLexeme}
|
|
30
|
+
*/
|
|
31
|
+
export declare const numberLexeme: (value: string) => NumberLexeme;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the lexeme is bare integer syntax: no `.` and no `e` / `E`.
|
|
34
|
+
*
|
|
35
|
+
* The distinction is lexical, not mathematical — `1e3` is not bare integer
|
|
36
|
+
* syntax even though its value is the integer `1000`.
|
|
37
|
+
*
|
|
38
|
+
* @type {(lexeme: NumberLexeme) => boolean}
|
|
39
|
+
*/
|
|
40
|
+
export declare const isBareInteger: (lexeme: NumberLexeme) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the lexeme spells a mathematical integer.
|
|
43
|
+
*
|
|
44
|
+
* This is the exact question, not the lexical one: `1e3` and `1.00` are
|
|
45
|
+
* integral while `1.5` is not. It stays bounded by the token's length.
|
|
46
|
+
*
|
|
47
|
+
* The value is `coefficient × 10 ** (exponent - fractionDigits)`, so it is an
|
|
48
|
+
* integer exactly when the exponent shifts the decimal point no further right
|
|
49
|
+
* than the coefficient's own trailing zeros allow. Both sides of that
|
|
50
|
+
* comparison — the fraction-digit count and the trailing-zero count — are
|
|
51
|
+
* bounded by the token, so the exponent only ever has to be compared against a
|
|
52
|
+
* small number.
|
|
53
|
+
*
|
|
54
|
+
* A zero coefficient is the simple case: zero is integral for any exponent, so
|
|
55
|
+
* the exponent is not examined at all.
|
|
56
|
+
*
|
|
57
|
+
* @type {(lexeme: NumberLexeme) => boolean}
|
|
58
|
+
*/
|
|
59
|
+
export declare const isIntegral: (lexeme: NumberLexeme) => boolean;
|