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
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
|
|
22
|
+
import { cmp } from '../../../types/function/compare/module.f.mjs'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Splits a JSON number token into its lexical parts.
|
|
26
|
+
*
|
|
27
|
+
* The token is grammar-validated by the tokenizer —
|
|
28
|
+
* `-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?` — so this is a split, not a
|
|
29
|
+
* validation.
|
|
30
|
+
*
|
|
31
|
+
* @type {(value: string) => NumberLexeme}
|
|
32
|
+
*/
|
|
33
|
+
export const numberLexeme = value => {
|
|
34
|
+
const sign = value.startsWith('-') ? '-' : ''
|
|
35
|
+
const unsigned = value.slice(sign.length)
|
|
36
|
+
// at most one of 'e'/'E' can occur, so whichever `indexOf` finds is it
|
|
37
|
+
const lowerE = unsigned.indexOf('e')
|
|
38
|
+
const eIndex = lowerE < 0 ? unsigned.indexOf('E') : lowerE
|
|
39
|
+
const coefficient = eIndex < 0 ? unsigned : unsigned.slice(0, eIndex)
|
|
40
|
+
const expText = eIndex < 0 ? '' : unsigned.slice(eIndex + 1)
|
|
41
|
+
const dotIndex = coefficient.indexOf('.')
|
|
42
|
+
const int = dotIndex < 0 ? coefficient : coefficient.slice(0, dotIndex)
|
|
43
|
+
const frac = dotIndex < 0 ? '' : coefficient.slice(dotIndex + 1)
|
|
44
|
+
const expSign = expText.startsWith('-')
|
|
45
|
+
? '-'
|
|
46
|
+
: expText.startsWith('+') ? '+' : ''
|
|
47
|
+
return { sign, int, frac, expSign, exp: expText.slice(expSign.length) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the lexeme is bare integer syntax: no `.` and no `e` / `E`.
|
|
52
|
+
*
|
|
53
|
+
* The distinction is lexical, not mathematical — `1e3` is not bare integer
|
|
54
|
+
* syntax even though its value is the integer `1000`.
|
|
55
|
+
*
|
|
56
|
+
* @type {(lexeme: NumberLexeme) => boolean}
|
|
57
|
+
*/
|
|
58
|
+
export const isBareInteger = ({ frac, exp }) => frac === '' && exp === ''
|
|
59
|
+
|
|
60
|
+
/** @type {(digits: string) => string} */
|
|
61
|
+
const stripLeadingZeros = digits => {
|
|
62
|
+
let i = 0
|
|
63
|
+
while (i < digits.length && digits[i] === '0') { i += 1 }
|
|
64
|
+
return digits.slice(i)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @type {(digits: string) => number} */
|
|
68
|
+
const trailingZeroCount = digits => {
|
|
69
|
+
let n = 0
|
|
70
|
+
while (n < digits.length && digits[digits.length - 1 - n] === '0') { n += 1 }
|
|
71
|
+
return n
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Compares arbitrarily long non-negative digit text against an ordinary
|
|
76
|
+
* non-negative integer, in the length of that text.
|
|
77
|
+
*
|
|
78
|
+
* Leading zeros are dropped, so equal-length digit strings compare
|
|
79
|
+
* numerically as strings and unequal lengths are decided by length alone.
|
|
80
|
+
* `bound` therefore never has to be turned into digit text long enough to
|
|
81
|
+
* match `digits`, and `digits` never has to become a number.
|
|
82
|
+
*
|
|
83
|
+
* @type {(digits: string) => (bound: number) => Sign}
|
|
84
|
+
*/
|
|
85
|
+
const compareDigits = digits => bound => {
|
|
86
|
+
const a = stripLeadingZeros(digits)
|
|
87
|
+
const b = stripLeadingZeros(`${bound}`)
|
|
88
|
+
const byLength = cmp(a.length)(b.length)
|
|
89
|
+
return byLength === 0 ? cmp(a)(b) : byLength
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Whether the lexeme's exponent is at least `bound`, an ordinary integer
|
|
94
|
+
* small enough to have come from the token's own length.
|
|
95
|
+
*
|
|
96
|
+
* The exponent itself is never converted: its sign decides which side of the
|
|
97
|
+
* comparison can already be answered, and only its digit text is compared —
|
|
98
|
+
* so `1e-99999999999999999999` is settled by looking at the sign and a length,
|
|
99
|
+
* not by representing that exponent.
|
|
100
|
+
*
|
|
101
|
+
* @type {(lexeme: NumberLexeme) => (bound: number) => boolean}
|
|
102
|
+
*/
|
|
103
|
+
const exponentAtLeast = ({ expSign, exp }) => bound =>
|
|
104
|
+
expSign === '-'
|
|
105
|
+
// a negative exponent reaches `bound` only if `bound` is not positive
|
|
106
|
+
// and the exponent's magnitude stays within `-bound`
|
|
107
|
+
? bound <= 0 && compareDigits(exp)(-bound) <= 0
|
|
108
|
+
// a non-negative exponent (`''` — an absent exponent — included, since
|
|
109
|
+
// its digit text is then empty and compares as zero) always reaches a
|
|
110
|
+
// non-positive `bound`
|
|
111
|
+
: bound <= 0 || compareDigits(exp)(bound) >= 0
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether the lexeme spells a mathematical integer.
|
|
115
|
+
*
|
|
116
|
+
* This is the exact question, not the lexical one: `1e3` and `1.00` are
|
|
117
|
+
* integral while `1.5` is not. It stays bounded by the token's length.
|
|
118
|
+
*
|
|
119
|
+
* The value is `coefficient × 10 ** (exponent - fractionDigits)`, so it is an
|
|
120
|
+
* integer exactly when the exponent shifts the decimal point no further right
|
|
121
|
+
* than the coefficient's own trailing zeros allow. Both sides of that
|
|
122
|
+
* comparison — the fraction-digit count and the trailing-zero count — are
|
|
123
|
+
* bounded by the token, so the exponent only ever has to be compared against a
|
|
124
|
+
* small number.
|
|
125
|
+
*
|
|
126
|
+
* A zero coefficient is the simple case: zero is integral for any exponent, so
|
|
127
|
+
* the exponent is not examined at all.
|
|
128
|
+
*
|
|
129
|
+
* @type {(lexeme: NumberLexeme) => boolean}
|
|
130
|
+
*/
|
|
131
|
+
export const isIntegral = lexeme => {
|
|
132
|
+
const { int, frac } = lexeme
|
|
133
|
+
const coefficient = `${int}${frac}`
|
|
134
|
+
const zeros = trailingZeroCount(coefficient)
|
|
135
|
+
return zeros === coefficient.length || exponentAtLeast(lexeme)(frac.length - zeros)
|
|
136
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
numberLexeme: {
|
|
3
|
+
int: () => void;
|
|
4
|
+
negative: () => void;
|
|
5
|
+
fraction: () => void;
|
|
6
|
+
exponent: () => void;
|
|
7
|
+
capitalExponent: () => void;
|
|
8
|
+
plusExponent: () => void;
|
|
9
|
+
minusExponent: () => void;
|
|
10
|
+
unbounded: () => void;
|
|
11
|
+
};
|
|
12
|
+
isBareInteger: {
|
|
13
|
+
int: () => void;
|
|
14
|
+
zero: () => void;
|
|
15
|
+
fraction: () => void;
|
|
16
|
+
exponent: () => void;
|
|
17
|
+
};
|
|
18
|
+
isIntegral: {
|
|
19
|
+
zero: (() => void)[];
|
|
20
|
+
plain: (() => void)[];
|
|
21
|
+
exponent: (() => void)[];
|
|
22
|
+
unbounded: (() => void)[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isBareInteger, isIntegral, numberLexeme } from './module.f.mjs'
|
|
2
|
+
import { assert, assertStructurallySame } from '../../../asserts/module.f.mjs'
|
|
3
|
+
|
|
4
|
+
/** @type {(value: string) => boolean} */
|
|
5
|
+
const integral = value => isIntegral(numberLexeme(value))
|
|
6
|
+
|
|
7
|
+
/** @type {(value: string) => boolean} */
|
|
8
|
+
const bareInteger = value => isBareInteger(numberLexeme(value))
|
|
9
|
+
|
|
10
|
+
// An exponent long enough that representing it as a `number` loses digits and
|
|
11
|
+
// `10 ** exponent` is not a computation anyone can afford. Every check below
|
|
12
|
+
// that uses it must be decided by the token's shape alone.
|
|
13
|
+
const hugeExp = '99999999999999999999'
|
|
14
|
+
|
|
15
|
+
export const proof = {
|
|
16
|
+
numberLexeme: {
|
|
17
|
+
int: () => assertStructurallySame(
|
|
18
|
+
numberLexeme('123'),
|
|
19
|
+
{ sign: '', int: '123', frac: '', expSign: '', exp: '' }),
|
|
20
|
+
negative: () => assertStructurallySame(
|
|
21
|
+
numberLexeme('-123'),
|
|
22
|
+
{ sign: '-', int: '123', frac: '', expSign: '', exp: '' }),
|
|
23
|
+
fraction: () => assertStructurallySame(
|
|
24
|
+
numberLexeme('-0.125'),
|
|
25
|
+
{ sign: '-', int: '0', frac: '125', expSign: '', exp: '' }),
|
|
26
|
+
exponent: () => assertStructurallySame(
|
|
27
|
+
numberLexeme('1e10'),
|
|
28
|
+
{ sign: '', int: '1', frac: '', expSign: '', exp: '10' }),
|
|
29
|
+
capitalExponent: () => assertStructurallySame(
|
|
30
|
+
numberLexeme('1.5E10'),
|
|
31
|
+
{ sign: '', int: '1', frac: '5', expSign: '', exp: '10' }),
|
|
32
|
+
plusExponent: () => assertStructurallySame(
|
|
33
|
+
numberLexeme('1e+10'),
|
|
34
|
+
{ sign: '', int: '1', frac: '', expSign: '+', exp: '10' }),
|
|
35
|
+
minusExponent: () => assertStructurallySame(
|
|
36
|
+
numberLexeme('1e-10'),
|
|
37
|
+
{ sign: '', int: '1', frac: '', expSign: '-', exp: '10' }),
|
|
38
|
+
// nothing is narrowed, so an unrepresentable exponent is just text
|
|
39
|
+
unbounded: () => assertStructurallySame(
|
|
40
|
+
numberLexeme(`-1.5e-${hugeExp}`),
|
|
41
|
+
{ sign: '-', int: '1', frac: '5', expSign: '-', exp: hugeExp }),
|
|
42
|
+
},
|
|
43
|
+
// bare integer syntax: no `.` and no `e`/`E`, whatever the value is
|
|
44
|
+
isBareInteger: {
|
|
45
|
+
int: () => assert(bareInteger('-1230')),
|
|
46
|
+
zero: () => assert(bareInteger('-0')),
|
|
47
|
+
fraction: () => assert(!bareInteger('1.0')),
|
|
48
|
+
exponent: () => assert(!bareInteger('1e3')),
|
|
49
|
+
},
|
|
50
|
+
isIntegral: {
|
|
51
|
+
// a zero coefficient is integral at any exponent, and is decided
|
|
52
|
+
// without looking at the exponent at all
|
|
53
|
+
zero: [
|
|
54
|
+
() => assert(integral('0')),
|
|
55
|
+
() => assert(integral('-0.000')),
|
|
56
|
+
() => assert(integral(`0.0e-${hugeExp}`)),
|
|
57
|
+
],
|
|
58
|
+
plain: [
|
|
59
|
+
() => assert(integral('123')),
|
|
60
|
+
() => assert(integral('1.00')),
|
|
61
|
+
() => assert(!integral('1.5')),
|
|
62
|
+
() => assert(!integral('1.50')),
|
|
63
|
+
],
|
|
64
|
+
// the exponent moves the point; only the coefficient's own trailing
|
|
65
|
+
// zeros can absorb a move to the right
|
|
66
|
+
exponent: [
|
|
67
|
+
() => assert(integral('1e3')),
|
|
68
|
+
() => assert(integral('1.5e1')),
|
|
69
|
+
() => assert(integral('1.5e2')),
|
|
70
|
+
() => assert(integral('150e-1')),
|
|
71
|
+
() => assert(!integral('1.5e-1')),
|
|
72
|
+
() => assert(!integral('15e-2')),
|
|
73
|
+
() => assert(!integral('1.55e1')),
|
|
74
|
+
() => assert(!integral('1.0000000000001e5')),
|
|
75
|
+
// leading zeros in the exponent are not extra magnitude
|
|
76
|
+
() => assert(integral('1.5e001')),
|
|
77
|
+
],
|
|
78
|
+
// decided from lengths and a sign, never by representing the exponent
|
|
79
|
+
unbounded: [
|
|
80
|
+
() => assert(integral(`1e${hugeExp}`)),
|
|
81
|
+
() => assert(integral(`1.5e${hugeExp}`)),
|
|
82
|
+
() => assert(!integral(`1e-${hugeExp}`)),
|
|
83
|
+
() => assert(!integral(`1.5e-${hugeExp}`)),
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the lexical view of a JSON number token.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The parts of a JSON number lexeme, each kept as text.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here is narrowed to a runtime numeric type, so a lexeme describes
|
|
10
|
+
* every syntactically valid JSON number — including coefficients larger than
|
|
11
|
+
* the runtime can build as a `bigint` and exponents beyond `number` precision.
|
|
12
|
+
*
|
|
13
|
+
* The empty string means "absent" for both optional parts, which is
|
|
14
|
+
* unambiguous: JSON requires at least one digit after `.` and at least one
|
|
15
|
+
* exponent digit, so a present part is never empty.
|
|
16
|
+
*/
|
|
17
|
+
export type NumberLexeme = {
|
|
18
|
+
/** `'-'` for a negative number, `''` otherwise. */
|
|
19
|
+
readonly sign: '' | '-';
|
|
20
|
+
/** The integer digits; always at least one digit. */
|
|
21
|
+
readonly int: string;
|
|
22
|
+
/** The fraction digits, or `''` when the lexeme has no `.`. */
|
|
23
|
+
readonly frac: string;
|
|
24
|
+
/** The exponent sign as written, or `''` when it is omitted. */
|
|
25
|
+
readonly expSign: '' | '+' | '-';
|
|
26
|
+
/** The exponent digits, or `''` when the lexeme has no `e` / `E`. */
|
|
27
|
+
readonly exp: string;
|
|
28
|
+
};
|
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSON parser
|
|
2
|
+
* The shared structural JSON parser: one tokenizer, one container state
|
|
3
|
+
* machine, and a numeric policy per codec.
|
|
4
|
+
*
|
|
5
|
+
* The state machine builds objects and arrays; a number token is handed to the
|
|
6
|
+
* `NumberPolicy` the caller supplies, together with its exact lexeme. Numeric
|
|
7
|
+
* syntax therefore stays lossless all the way to the policy, and each codec
|
|
8
|
+
* chooses its own runtime numeric domain from the same parse:
|
|
9
|
+
*
|
|
10
|
+
* ```text
|
|
11
|
+
* JSON text -> tokenizer -> parse(policy) -+-> json.Unknown (number)
|
|
12
|
+
* +-> extended.Unknown (number | bigint)
|
|
13
|
+
* +-> another policy's domain
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* No codec has to materialize another codec's domain first: standard JSON
|
|
17
|
+
* parsing never needs an intermediate `bigint`, and extended parsing never
|
|
18
|
+
* needs an intermediate rounded `number`.
|
|
3
19
|
*
|
|
4
20
|
* @module
|
|
5
21
|
*
|
|
6
|
-
* @import { Unknown } from '../types.ts'
|
|
7
22
|
* @import { Result } from '../../../types/result/types.ts'
|
|
8
23
|
* @import { List } from '../../../types/list/types.ts'
|
|
9
24
|
* @import { Fold } from '../../../types/function/operator/types.ts'
|
|
10
25
|
* @import { JsonToken } from '../tokenizer/types.ts'
|
|
11
|
-
* @import { _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack, _ValueToken } from './types.ts'
|
|
26
|
+
* @import { NumberPolicy, ParseUnknown, _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack, _ValueToken } from './types.ts'
|
|
12
27
|
*/
|
|
13
|
-
import type { Unknown } from '../types.ts';
|
|
14
28
|
import type { Result } from '../../../types/result/types.ts';
|
|
15
29
|
import type { List } from '../../../types/list/types.ts';
|
|
16
30
|
import type { JsonToken } from '../tokenizer/types.ts';
|
|
31
|
+
import type { NumberPolicy, ParseUnknown } from './types.ts';
|
|
17
32
|
/**
|
|
18
|
-
* Parses a list of JSON tokens into
|
|
33
|
+
* Parses a list of JSON tokens into the value domain `policy` materializes
|
|
34
|
+
* numbers into.
|
|
19
35
|
*
|
|
20
36
|
* Returns `ok` with the parsed value on success, or `error` with a message
|
|
21
|
-
* when the token sequence is invalid or incomplete
|
|
37
|
+
* when the token sequence is invalid or incomplete — or when `policy` rejects
|
|
38
|
+
* a number token that its domain cannot represent.
|
|
22
39
|
*
|
|
23
|
-
* @type {(tokenList: List<JsonToken>) => Result<
|
|
40
|
+
* @type {<P>(policy: NumberPolicy<P>) => (tokenList: List<JsonToken>) => Result<ParseUnknown<P>, string>}
|
|
24
41
|
*/
|
|
25
|
-
export declare const parse: (tokenList: List<JsonToken>) => Result<
|
|
26
|
-
export declare const proof: {
|
|
27
|
-
pushKey: {
|
|
28
|
-
nonObjectTop: () => void;
|
|
29
|
-
};
|
|
30
|
-
};
|
|
42
|
+
export declare const parse: <P>(policy: NumberPolicy<P>) => (tokenList: List<JsonToken>) => Result<ParseUnknown<P>, string>;
|
|
@@ -1,48 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSON parser
|
|
2
|
+
* The shared structural JSON parser: one tokenizer, one container state
|
|
3
|
+
* machine, and a numeric policy per codec.
|
|
4
|
+
*
|
|
5
|
+
* The state machine builds objects and arrays; a number token is handed to the
|
|
6
|
+
* `NumberPolicy` the caller supplies, together with its exact lexeme. Numeric
|
|
7
|
+
* syntax therefore stays lossless all the way to the policy, and each codec
|
|
8
|
+
* chooses its own runtime numeric domain from the same parse:
|
|
9
|
+
*
|
|
10
|
+
* ```text
|
|
11
|
+
* JSON text -> tokenizer -> parse(policy) -+-> json.Unknown (number)
|
|
12
|
+
* +-> extended.Unknown (number | bigint)
|
|
13
|
+
* +-> another policy's domain
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* No codec has to materialize another codec's domain first: standard JSON
|
|
17
|
+
* parsing never needs an intermediate `bigint`, and extended parsing never
|
|
18
|
+
* needs an intermediate rounded `number`.
|
|
3
19
|
*
|
|
4
20
|
* @module
|
|
5
21
|
*
|
|
6
|
-
* @import { Unknown } from '../types.ts'
|
|
7
22
|
* @import { Result } from '../../../types/result/types.ts'
|
|
8
23
|
* @import { List } from '../../../types/list/types.ts'
|
|
9
24
|
* @import { Fold } from '../../../types/function/operator/types.ts'
|
|
10
25
|
* @import { JsonToken } from '../tokenizer/types.ts'
|
|
11
|
-
* @import { _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack, _ValueToken } from './types.ts'
|
|
26
|
+
* @import { NumberPolicy, ParseUnknown, _JsonObject, _JsonArray, _StateParse, _JsonState, _JsonStack, _ValueToken } from './types.ts'
|
|
12
27
|
*/
|
|
13
28
|
|
|
29
|
+
import { assert } from '../../../asserts/module.f.mjs'
|
|
14
30
|
import { error, ok } from '../../../types/result/module.f.mjs'
|
|
15
31
|
import { fold, next, toArray, concat } from '../../../types/list/module.f.mjs'
|
|
16
32
|
import { setReplace } from '../../../types/ordered_map/module.f.mjs'
|
|
17
33
|
import { fromMap } from '../../../types/object/module.f.mjs'
|
|
18
|
-
import { assertEq } from '../../../asserts/module.f.mjs'
|
|
19
34
|
|
|
20
|
-
/**
|
|
35
|
+
/**
|
|
36
|
+
* Every syntax error the parser can report is the same one: a token that
|
|
37
|
+
* cannot follow the state it arrived in. It carries no position or metadata,
|
|
38
|
+
* so one shared value serves all of them — and no numeric policy either, so
|
|
39
|
+
* the same value serves every instantiation of the parser.
|
|
40
|
+
*
|
|
41
|
+
* @type {_JsonState<never>}
|
|
42
|
+
*/
|
|
43
|
+
const unexpectedToken = { status: 'error', message: 'unexpected token' }
|
|
44
|
+
|
|
45
|
+
/** @type {<P>(obj: _JsonObject<P>) => (key: string) => _JsonObject<P>} */
|
|
21
46
|
const addKeyToObject =
|
|
22
47
|
obj => key => ({ kind: 'object', values: obj.values, key: key })
|
|
23
48
|
|
|
24
|
-
/** @type {(obj: _JsonObject) => (value:
|
|
49
|
+
/** @type {<P>(obj: _JsonObject<P>) => (value: ParseUnknown<P>) => _JsonObject<P>} */
|
|
25
50
|
const addValueToObject =
|
|
26
51
|
obj => value => ({ kind: 'object', values: setReplace(obj.key)(value)(obj.values), key: '' })
|
|
27
52
|
|
|
28
|
-
/** @type {(array: _JsonArray) => (value:
|
|
53
|
+
/** @type {<P>(array: _JsonArray<P>) => (value: ParseUnknown<P>) => _JsonArray<P>} */
|
|
29
54
|
const addToArray =
|
|
30
55
|
array => value => ({ kind: 'array', values: concat(array.values)([value]) })
|
|
31
56
|
|
|
32
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* `pushKey` only ever runs while parsing the object `startObject` opened
|
|
59
|
+
* (status `'{'`/`'{,'`), so `state.top` is always that object here — the same
|
|
60
|
+
* construction guarantee `endArray` relies on below.
|
|
61
|
+
*
|
|
62
|
+
* @template P
|
|
63
|
+
* @param {_StateParse<P>} state
|
|
64
|
+
* @returns {(key: string) => _JsonState<P>}
|
|
65
|
+
*/
|
|
33
66
|
const pushKey = state => value => {
|
|
34
|
-
|
|
35
|
-
|
|
67
|
+
const { top } = state
|
|
68
|
+
assert(top !== null && top.kind === 'object', top)
|
|
69
|
+
return {
|
|
70
|
+
status: '{k',
|
|
71
|
+
top: addKeyToObject(top)(value),
|
|
72
|
+
stack: state.stack,
|
|
73
|
+
}
|
|
36
74
|
}
|
|
37
75
|
|
|
38
|
-
/** @type {(state: _StateParse) => (value:
|
|
76
|
+
/** @type {<P>(state: _StateParse<P>) => (value: ParseUnknown<P>) => _JsonState<P>} */
|
|
39
77
|
const pushValue = state => value => {
|
|
40
78
|
if (state.top === null) { return { status: 'result', value: value } }
|
|
41
79
|
if (state.top.kind === 'array') { return { status: '[v', top: addToArray(state.top)(value), stack: state.stack } }
|
|
42
80
|
return { status: '{v', top: addValueToObject(state.top)(value), stack: state.stack }
|
|
43
81
|
}
|
|
44
82
|
|
|
45
|
-
/** @type {(state: _StateParse) => _JsonState} */
|
|
83
|
+
/** @type {<P>(state: _StateParse<P>) => _JsonState<P>} */
|
|
46
84
|
const startArray = state => {
|
|
47
85
|
const newStack = state.top === null ? null : { first: state.top, tail: state.stack }
|
|
48
86
|
return { status: '[', top: { kind: 'array', values: null }, stack: newStack }
|
|
@@ -54,7 +92,7 @@ const startArray = state => {
|
|
|
54
92
|
* always as a literal cons, and a lazy pop would leave one unforced thunk per
|
|
55
93
|
* closed container — a chain that overflows the stack when it is finally forced.
|
|
56
94
|
*
|
|
57
|
-
* @type {(stack: _JsonStack) => _StateParse}
|
|
95
|
+
* @type {<P>(stack: _JsonStack<P>) => _StateParse<P>}
|
|
58
96
|
*/
|
|
59
97
|
const popStack = stack => {
|
|
60
98
|
const ne = next(stack)
|
|
@@ -67,15 +105,19 @@ const popStack = stack => {
|
|
|
67
105
|
* `endArray` only ever runs while parsing the array `startArray` opened
|
|
68
106
|
* (status `'['`/`'[v'`), so `state.top` is always that array here.
|
|
69
107
|
*
|
|
70
|
-
* @
|
|
108
|
+
* @template P
|
|
109
|
+
* @param {_StateParse<P>} state
|
|
110
|
+
* @returns {_JsonState<P>}
|
|
71
111
|
*/
|
|
72
112
|
const endArray = state => {
|
|
73
|
-
const
|
|
113
|
+
const { top } = state
|
|
114
|
+
assert(top !== null && top.kind === 'array', top)
|
|
115
|
+
const array = toArray(top.values)
|
|
74
116
|
const newState = popStack(state.stack)
|
|
75
117
|
return pushValue(newState)(array)
|
|
76
118
|
}
|
|
77
119
|
|
|
78
|
-
/** @type {(state: _StateParse) => _JsonState} */
|
|
120
|
+
/** @type {<P>(state: _StateParse<P>) => _JsonState<P>} */
|
|
79
121
|
const startObject = state => {
|
|
80
122
|
const newStack = state.top === null ? null : { first: state.top, tail: state.stack }
|
|
81
123
|
return { status: '{', top: { kind: 'object', values: null, key: '' }, stack: newStack }
|
|
@@ -85,10 +127,14 @@ const startObject = state => {
|
|
|
85
127
|
* `endObject` only ever runs while parsing the object `startObject` opened
|
|
86
128
|
* (status `'{'`/`'{v'`), so `state.top` is always that object here.
|
|
87
129
|
*
|
|
88
|
-
* @
|
|
130
|
+
* @template P
|
|
131
|
+
* @param {_StateParse<P>} state
|
|
132
|
+
* @returns {_JsonState<P>}
|
|
89
133
|
*/
|
|
90
134
|
const endObject = state => {
|
|
91
|
-
const
|
|
135
|
+
const { top } = state
|
|
136
|
+
assert(top !== null && top.kind === 'object', top)
|
|
137
|
+
const obj = fromMap(top.values)
|
|
92
138
|
const newState = popStack(state.stack)
|
|
93
139
|
return pushValue(newState)(obj)
|
|
94
140
|
}
|
|
@@ -96,19 +142,27 @@ const endObject = state => {
|
|
|
96
142
|
/**
|
|
97
143
|
* Only ever called on a token `isValueToken` has already confirmed carries a
|
|
98
144
|
* value, so the switch covers every `_ValueToken` case with no fallback arm.
|
|
145
|
+
* A number is the one leaf the parser does not know how to build: `policy`
|
|
146
|
+
* owns that decision and may reject the token.
|
|
99
147
|
*
|
|
100
|
-
* @type {(token: _ValueToken) =>
|
|
148
|
+
* @type {<P>(policy: NumberPolicy<P>) => (token: _ValueToken) => Result<ParseUnknown<P>, string>}
|
|
101
149
|
*/
|
|
102
|
-
const tokenToValue = token => {
|
|
150
|
+
const tokenToValue = policy => token => {
|
|
103
151
|
switch (token.kind) {
|
|
104
|
-
case 'null': return null
|
|
105
|
-
case 'false': return false
|
|
106
|
-
case 'true': return true
|
|
107
|
-
case 'number': return
|
|
108
|
-
case 'string': return token.value
|
|
152
|
+
case 'null': return ok(null)
|
|
153
|
+
case 'false': return ok(false)
|
|
154
|
+
case 'true': return ok(true)
|
|
155
|
+
case 'number': return policy(token)
|
|
156
|
+
case 'string': return ok(token.value)
|
|
109
157
|
}
|
|
110
158
|
}
|
|
111
159
|
|
|
160
|
+
/** @type {<P>(policy: NumberPolicy<P>) => (state: _StateParse<P>) => (token: _ValueToken) => _JsonState<P>} */
|
|
161
|
+
const pushLeaf = policy => state => token => {
|
|
162
|
+
const [kind, value] = tokenToValue(policy)(token)
|
|
163
|
+
return kind === 'error' ? { status: 'error', message: value } : pushValue(state)(value)
|
|
164
|
+
}
|
|
165
|
+
|
|
112
166
|
/**
|
|
113
167
|
* @param {JsonToken} token
|
|
114
168
|
* @returns {token is _ValueToken}
|
|
@@ -124,95 +178,99 @@ const isValueToken = token => {
|
|
|
124
178
|
}
|
|
125
179
|
}
|
|
126
180
|
|
|
127
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
128
|
-
const parseValueOp = token => state => {
|
|
181
|
+
/** @type {<P>(policy: NumberPolicy<P>) => (token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
182
|
+
const parseValueOp = policy => token => state => {
|
|
129
183
|
switch (token.kind) {
|
|
130
184
|
// A value is required here (top level, after `[`+`,`, or after `:`),
|
|
131
185
|
// so `]` is never valid — strict JSON has no trailing commas.
|
|
132
186
|
case ']':
|
|
133
|
-
return
|
|
187
|
+
return unexpectedToken
|
|
134
188
|
case '[': return startArray(state)
|
|
135
189
|
case '{': return startObject(state)
|
|
136
190
|
default:
|
|
137
|
-
if (isValueToken(token)) { return
|
|
138
|
-
return
|
|
191
|
+
if (isValueToken(token)) { return pushLeaf(policy)(state)(token) }
|
|
192
|
+
return unexpectedToken
|
|
139
193
|
}
|
|
140
194
|
}
|
|
141
195
|
|
|
142
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
143
|
-
const parseArrayStartOp = token => state => {
|
|
144
|
-
if (isValueToken(token)) { return
|
|
196
|
+
/** @type {<P>(policy: NumberPolicy<P>) => (token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
197
|
+
const parseArrayStartOp = policy => token => state => {
|
|
198
|
+
if (isValueToken(token)) { return pushLeaf(policy)(state)(token) }
|
|
145
199
|
if (token.kind === '[') { return startArray(state) }
|
|
146
200
|
if (token.kind === ']') { return endArray(state) }
|
|
147
201
|
if (token.kind === '{') { return startObject(state) }
|
|
148
|
-
return
|
|
202
|
+
return unexpectedToken
|
|
149
203
|
}
|
|
150
204
|
|
|
151
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
205
|
+
/** @type {<P>(token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
152
206
|
const parseArrayValueOp = token => state => {
|
|
153
207
|
if (token.kind === ']') { return endArray(state) }
|
|
154
208
|
if (token.kind === ',') { return { status: '[,', top: state.top, stack: state.stack } }
|
|
155
|
-
return
|
|
209
|
+
return unexpectedToken
|
|
156
210
|
}
|
|
157
211
|
|
|
158
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
212
|
+
/** @type {<P>(token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
159
213
|
const parseObjectStartOp = token => state => {
|
|
160
214
|
if (token.kind === 'string') { return pushKey(state)(token.value) }
|
|
161
215
|
if (token.kind === '}') { return endObject(state) }
|
|
162
|
-
return
|
|
216
|
+
return unexpectedToken
|
|
163
217
|
}
|
|
164
218
|
|
|
165
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
219
|
+
/** @type {<P>(token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
166
220
|
const parseObjectKeyOp = token => state => {
|
|
167
221
|
if (token.kind === ':') { return { status: '{:', top: state.top, stack: state.stack } }
|
|
168
|
-
return
|
|
222
|
+
return unexpectedToken
|
|
169
223
|
}
|
|
170
224
|
|
|
171
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
225
|
+
/** @type {<P>(token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
172
226
|
const parseObjectNextOp = token => state => {
|
|
173
227
|
if (token.kind === '}') { return endObject(state) }
|
|
174
228
|
if (token.kind === ',') { return { status: '{,', top: state.top, stack: state.stack } }
|
|
175
|
-
return
|
|
229
|
+
return unexpectedToken
|
|
176
230
|
}
|
|
177
231
|
|
|
178
|
-
/** @type {(token: JsonToken) => (state: _StateParse) => _JsonState} */
|
|
232
|
+
/** @type {<P>(token: JsonToken) => (state: _StateParse<P>) => _JsonState<P>} */
|
|
179
233
|
const parseObjectCommaOp = token => state => {
|
|
180
234
|
// After a `,` a member (string key) is required — `}` here would be a
|
|
181
235
|
// trailing comma, which strict JSON rejects.
|
|
182
236
|
if (token.kind === 'string') { return pushKey(state)(token.value) }
|
|
183
|
-
return
|
|
237
|
+
return unexpectedToken
|
|
184
238
|
}
|
|
185
239
|
|
|
186
|
-
/** @type {Fold<JsonToken, _JsonState
|
|
187
|
-
const foldOp = token => state => {
|
|
240
|
+
/** @type {<P>(policy: NumberPolicy<P>) => Fold<JsonToken, _JsonState<P>>} */
|
|
241
|
+
const foldOp = policy => token => state => {
|
|
188
242
|
if (token.kind === 'eof')
|
|
189
243
|
return state
|
|
190
244
|
|
|
191
245
|
switch (state.status) {
|
|
192
|
-
case 'result': return
|
|
246
|
+
case 'result': return unexpectedToken
|
|
193
247
|
case 'error': return { status: 'error', message: state.message }
|
|
194
|
-
case '': return parseValueOp(token)(state)
|
|
195
|
-
case '[': return parseArrayStartOp(token)(state)
|
|
248
|
+
case '': return parseValueOp(policy)(token)(state)
|
|
249
|
+
case '[': return parseArrayStartOp(policy)(token)(state)
|
|
196
250
|
case '[v': return parseArrayValueOp(token)(state)
|
|
197
|
-
case '[,': return parseValueOp(token)(state)
|
|
251
|
+
case '[,': return parseValueOp(policy)(token)(state)
|
|
198
252
|
case '{': return parseObjectStartOp(token)(state)
|
|
199
253
|
case '{k': return parseObjectKeyOp(token)(state)
|
|
200
|
-
case '{:': return parseValueOp(token)(state)
|
|
254
|
+
case '{:': return parseValueOp(policy)(token)(state)
|
|
201
255
|
case '{v': return parseObjectNextOp(token)(state)
|
|
202
256
|
case '{,': return parseObjectCommaOp(token)(state)
|
|
203
257
|
}
|
|
204
258
|
}
|
|
205
259
|
|
|
206
260
|
/**
|
|
207
|
-
* Parses a list of JSON tokens into
|
|
261
|
+
* Parses a list of JSON tokens into the value domain `policy` materializes
|
|
262
|
+
* numbers into.
|
|
208
263
|
*
|
|
209
264
|
* Returns `ok` with the parsed value on success, or `error` with a message
|
|
210
|
-
* when the token sequence is invalid or incomplete
|
|
265
|
+
* when the token sequence is invalid or incomplete — or when `policy` rejects
|
|
266
|
+
* a number token that its domain cannot represent.
|
|
211
267
|
*
|
|
212
|
-
* @type {(tokenList: List<JsonToken>) => Result<
|
|
268
|
+
* @type {<P>(policy: NumberPolicy<P>) => (tokenList: List<JsonToken>) => Result<ParseUnknown<P>, string>}
|
|
213
269
|
*/
|
|
214
|
-
export const parse = tokenList => {
|
|
215
|
-
|
|
270
|
+
export const parse = policy => tokenList => {
|
|
271
|
+
/** @type {_JsonState<never>} */
|
|
272
|
+
const init = { status: '', top: null, stack: null }
|
|
273
|
+
const state = fold(foldOp(policy))(init)(tokenList)
|
|
216
274
|
switch (state.status) {
|
|
217
275
|
case 'result': return ok(state.value)
|
|
218
276
|
case 'error': return error(state.message)
|
|
@@ -220,17 +278,3 @@ export const parse = tokenList => {
|
|
|
220
278
|
}
|
|
221
279
|
}
|
|
222
280
|
|
|
223
|
-
export const proof = {
|
|
224
|
-
pushKey: {
|
|
225
|
-
// `pushKey` is only ever invoked while `state.top` is an object (the
|
|
226
|
-
// state machine's `'{'`/`'{,'` statuses guarantee it), so its
|
|
227
|
-
// non-object guard is a defensive branch unreachable through `parse`.
|
|
228
|
-
// Call it directly to cover that branch.
|
|
229
|
-
nonObjectTop: () => {
|
|
230
|
-
/** @type {_StateParse} */
|
|
231
|
-
const state = { status: '[', top: { kind: 'array', values: null }, stack: null }
|
|
232
|
-
const result = pushKey(state)('key')
|
|
233
|
-
assertEq(result.status, 'error')
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
}
|