functionalscript 0.45.0 → 0.46.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.
Files changed (296) hide show
  1. package/README.md +5 -3
  2. package/fjs/asn.1/module.f.mjs +8 -1
  3. package/fjs/asn.1/proof.f.d.mts +10 -0
  4. package/fjs/asn.1/proof.f.mjs +16 -0
  5. package/fjs/basen/base128/module.f.mjs +22 -5
  6. package/fjs/bnf/data/module.f.d.mts +32 -6
  7. package/fjs/bnf/data/module.f.mjs +112 -7
  8. package/fjs/bnf/data/proof.f.d.mts +2 -0
  9. package/fjs/bnf/data/proof.f.mjs +74 -3
  10. package/fjs/bnf/data/types.d.ts +20 -2
  11. package/fjs/bnf/descent/module.f.d.mts +28 -14
  12. package/fjs/bnf/descent/module.f.mjs +122 -77
  13. package/fjs/bnf/descent/proof.f.d.mts +2 -0
  14. package/fjs/bnf/descent/proof.f.mjs +117 -31
  15. package/fjs/bnf/descent/types.d.ts +12 -14
  16. package/fjs/bnf/ll1/module.f.d.mts +64 -22
  17. package/fjs/bnf/ll1/module.f.mjs +214 -154
  18. package/fjs/bnf/ll1/proof.f.d.mts +15 -2
  19. package/fjs/bnf/ll1/proof.f.mjs +323 -149
  20. package/fjs/bnf/ll1/types.d.ts +16 -24
  21. package/fjs/bnf/matcher/module.f.d.mts +66 -0
  22. package/fjs/bnf/matcher/module.f.mjs +81 -0
  23. package/fjs/bnf/matcher/proof.f.d.mts +10 -0
  24. package/fjs/bnf/matcher/proof.f.mjs +79 -0
  25. package/fjs/bnf/matcher/types.d.ts +54 -0
  26. package/fjs/bnf/testlib.f.d.mts +31 -0
  27. package/fjs/bnf/testlib.f.mjs +80 -0
  28. package/fjs/cas/cli/module.f.d.mts +1 -1
  29. package/fjs/cas/cli/module.f.mjs +14 -20
  30. package/fjs/cas/cli/proof.f.d.mts +1 -3
  31. package/fjs/cas/cli/proof.f.mjs +44 -33
  32. package/fjs/cas/evo/module.f.d.mts +64 -18
  33. package/fjs/cas/evo/module.f.mjs +148 -70
  34. package/fjs/cas/evo/proof.f.d.mts +10 -1
  35. package/fjs/cas/evo/proof.f.mjs +305 -223
  36. package/fjs/cas/evo/types.d.ts +45 -9
  37. package/fjs/cas/module.f.d.mts +18 -32
  38. package/fjs/cas/module.f.mjs +129 -128
  39. package/fjs/cas/proof.f.d.mts +11 -8
  40. package/fjs/cas/proof.f.mjs +259 -147
  41. package/fjs/cas/types.d.ts +24 -10
  42. package/fjs/ci/config/module.f.d.mts +2 -2
  43. package/fjs/ci/config/module.f.mjs +2 -2
  44. package/fjs/ci/module.f.d.mts +5 -5
  45. package/fjs/ci/module.f.mjs +8 -7
  46. package/fjs/ci/nix/module.f.d.mts +7 -5
  47. package/fjs/ci/nix/module.f.mjs +13 -12
  48. package/fjs/ci/nix/proof.f.mjs +2 -2
  49. package/fjs/ci/proof.f.mjs +9 -5
  50. package/fjs/cli/module.f.d.mts +4 -6
  51. package/fjs/cli/module.f.mjs +4 -8
  52. package/fjs/cli/proof.f.mjs +17 -16
  53. package/fjs/cli/types.d.ts +2 -3
  54. package/fjs/common/monoid/types.d.ts +1 -1
  55. package/fjs/crypto/hmac/module.f.mjs +2 -2
  56. package/fjs/crypto/sha2/module.f.mjs +3 -1
  57. package/fjs/crypto/sha2/proof.f.d.mts +1 -0
  58. package/fjs/crypto/sha2/proof.f.mjs +24 -0
  59. package/fjs/crypto/sha2/types.d.ts +11 -0
  60. package/fjs/crypto/sign/module.f.mjs +2 -2
  61. package/fjs/dev/module.f.d.mts +13 -4
  62. package/fjs/dev/module.f.mjs +56 -27
  63. package/fjs/dev/update/module.f.d.mts +9 -4
  64. package/fjs/dev/update/module.f.mjs +14 -10
  65. package/fjs/dev/update/proof.f.d.mts +1 -3
  66. package/fjs/dev/update/proof.f.mjs +10 -5
  67. package/fjs/djs/module.f.d.mts +13 -5
  68. package/fjs/djs/module.f.mjs +31 -16
  69. package/fjs/djs/parser/module.f.d.mts +13 -3
  70. package/fjs/djs/parser/module.f.mjs +117 -16
  71. package/fjs/djs/parser/proof.f.d.mts +5 -1
  72. package/fjs/djs/parser/proof.f.mjs +274 -12
  73. package/fjs/djs/parser/types.d.ts +7 -1
  74. package/fjs/djs/proof.f.d.mts +18 -2
  75. package/fjs/djs/proof.f.mjs +187 -12
  76. package/fjs/djs/serializer/module.f.d.mts +25 -8
  77. package/fjs/djs/serializer/module.f.mjs +61 -15
  78. package/fjs/djs/serializer/proof.f.d.mts +5 -0
  79. package/fjs/djs/serializer/proof.f.mjs +23 -1
  80. package/fjs/djs/tokenizer/module.f.d.mts +17 -5
  81. package/fjs/djs/tokenizer/module.f.mjs +99 -60
  82. package/fjs/djs/tokenizer/proof.f.d.mts +1 -1
  83. package/fjs/djs/tokenizer/proof.f.mjs +74 -67
  84. package/fjs/djs/transpiler/module.f.d.mts +12 -7
  85. package/fjs/djs/transpiler/module.f.mjs +82 -56
  86. package/fjs/djs/transpiler/types.d.ts +7 -3
  87. package/fjs/djs/types.d.ts +7 -1
  88. package/fjs/effects/list/module.f.d.mts +14 -11
  89. package/fjs/effects/list/module.f.mjs +13 -11
  90. package/fjs/effects/list/types.d.ts +27 -7
  91. package/fjs/effects/memory/module.f.d.mts +2 -1
  92. package/fjs/effects/memory/module.f.mjs +3 -4
  93. package/fjs/effects/memory/proof.f.mjs +13 -7
  94. package/fjs/effects/memory/types.d.ts +4 -3
  95. package/fjs/effects/mock/module.f.d.mts +20 -4
  96. package/fjs/effects/mock/module.f.mjs +37 -5
  97. package/fjs/effects/mock/types.d.ts +10 -1
  98. package/fjs/effects/module.d.mts +4 -2
  99. package/fjs/effects/module.f.d.mts +466 -280
  100. package/fjs/effects/module.f.mjs +537 -299
  101. package/fjs/effects/module.mjs +2 -1
  102. package/fjs/effects/node/memory/module.d.mts +4 -2
  103. package/fjs/effects/node/memory/module.mjs +6 -3
  104. package/fjs/effects/node/memory/proof.mjs +10 -4
  105. package/fjs/effects/node/module.d.mts +4 -3
  106. package/fjs/effects/node/module.f.d.mts +168 -33
  107. package/fjs/effects/node/module.f.mjs +256 -52
  108. package/fjs/effects/node/module.mjs +57 -34
  109. package/fjs/effects/node/proof.f.d.mts +28 -2
  110. package/fjs/effects/node/proof.f.mjs +161 -42
  111. package/fjs/effects/node/types.d.ts +106 -18
  112. package/fjs/effects/node/virtual/module.f.d.mts +18 -4
  113. package/fjs/effects/node/virtual/module.f.mjs +110 -68
  114. package/fjs/effects/node/virtual/proof.f.d.mts +28 -2
  115. package/fjs/effects/node/virtual/proof.f.mjs +190 -9
  116. package/fjs/effects/proof.f.d.mts +69 -37
  117. package/fjs/effects/proof.f.mjs +410 -130
  118. package/fjs/effects/types.d.ts +161 -33
  119. package/fjs/emergent_testing/module.f.d.mts +19 -12
  120. package/fjs/emergent_testing/module.f.mjs +93 -33
  121. package/fjs/emergent_testing/proof.f.d.mts +21 -7
  122. package/fjs/emergent_testing/proof.f.mjs +166 -32
  123. package/fjs/emergent_testing/types.d.ts +22 -4
  124. package/fjs/fsm/module.f.d.mts +14 -4
  125. package/fjs/fsm/module.f.mjs +54 -37
  126. package/fjs/fsm/proof.f.d.mts +2 -0
  127. package/fjs/fsm/proof.f.mjs +83 -114
  128. package/fjs/js/keywords/module.f.d.mts +52 -0
  129. package/fjs/js/keywords/module.f.mjs +72 -0
  130. package/fjs/js/keywords/proof.f.d.mts +3 -0
  131. package/fjs/js/keywords/proof.f.mjs +13 -0
  132. package/fjs/js/tokenizer/module.f.d.mts +26 -6
  133. package/fjs/js/tokenizer/module.f.mjs +145 -159
  134. package/fjs/js/tokenizer/proof.f.d.mts +1 -0
  135. package/fjs/js/tokenizer/proof.f.mjs +54 -24
  136. package/fjs/js/tokenizer/types.d.ts +33 -24
  137. package/fjs/mcp/cas/module.f.d.mts +1 -6
  138. package/fjs/mcp/cas/module.f.mjs +55 -51
  139. package/fjs/mcp/cas/proof.f.d.mts +15 -0
  140. package/fjs/mcp/cas/proof.f.mjs +174 -0
  141. package/fjs/mcp/evo/module.f.d.mts +19 -10
  142. package/fjs/mcp/evo/module.f.mjs +48 -27
  143. package/fjs/mcp/evo/proof.f.d.mts +6 -1
  144. package/fjs/mcp/evo/proof.f.mjs +115 -31
  145. package/fjs/mcp/module.f.d.mts +4 -4
  146. package/fjs/mcp/module.f.mjs +6 -6
  147. package/fjs/mcp/proof.f.d.mts +5 -3
  148. package/fjs/mcp/proof.f.mjs +112 -46
  149. package/fjs/media/html/module.f.mjs +1 -1
  150. package/fjs/media/json/extended/module.f.d.mts +82 -0
  151. package/fjs/media/json/extended/module.f.mjs +153 -0
  152. package/fjs/media/json/extended/proof.f.d.mts +42 -0
  153. package/fjs/media/json/extended/proof.f.mjs +127 -0
  154. package/fjs/media/json/extended/types.d.ts +23 -0
  155. package/fjs/media/json/module.f.d.mts +8 -2
  156. package/fjs/media/json/module.f.mjs +43 -41
  157. package/fjs/media/json/number/module.f.d.mts +59 -0
  158. package/fjs/media/json/number/module.f.mjs +136 -0
  159. package/fjs/media/json/number/proof.f.d.mts +24 -0
  160. package/fjs/media/json/number/proof.f.mjs +86 -0
  161. package/fjs/media/json/number/types.d.ts +28 -0
  162. package/fjs/media/json/parser/module.f.d.mts +25 -13
  163. package/fjs/media/json/parser/module.f.mjs +114 -70
  164. package/fjs/media/json/parser/proof.f.d.mts +5 -0
  165. package/fjs/media/json/parser/proof.f.mjs +31 -1
  166. package/fjs/media/json/parser/types.d.ts +33 -14
  167. package/fjs/media/json/rtti/module.f.d.mts +1 -1
  168. package/fjs/media/json/rtti/module.f.mjs +1 -1
  169. package/fjs/media/json/rtti/proof.f.mjs +9 -9
  170. package/fjs/media/json/schema/module.f.mjs +3 -13
  171. package/fjs/media/json/schema/proof.f.d.mts +0 -1
  172. package/fjs/media/json/schema/proof.f.mjs +1 -2
  173. package/fjs/media/json/serializer/module.f.d.mts +32 -1
  174. package/fjs/media/json/serializer/module.f.mjs +64 -2
  175. package/fjs/media/json/tokenizer/module.f.mjs +7 -3
  176. package/fjs/media/json/tokenizer/proof.f.d.mts +6 -0
  177. package/fjs/media/json/tokenizer/proof.f.mjs +62 -21
  178. package/fjs/media/json/types.d.ts +36 -10
  179. package/fjs/media/lock/module.f.d.mts +100 -0
  180. package/fjs/media/lock/module.f.mjs +125 -0
  181. package/fjs/media/lock/proof.f.d.mts +33 -0
  182. package/fjs/media/lock/proof.f.mjs +196 -0
  183. package/fjs/media/lock/types.d.ts +15 -0
  184. package/fjs/media/module.f.d.mts +6 -5
  185. package/fjs/media/module.f.mjs +8 -7
  186. package/fjs/media/note/module.f.d.mts +121 -0
  187. package/fjs/media/note/module.f.mjs +131 -0
  188. package/fjs/media/note/proof.f.d.mts +29 -0
  189. package/fjs/media/note/proof.f.mjs +150 -0
  190. package/fjs/media/note/types.d.ts +10 -0
  191. package/fjs/media/proof.f.d.mts +4 -1
  192. package/fjs/media/proof.f.mjs +40 -21
  193. package/fjs/media/revision/module.f.d.mts +78 -7
  194. package/fjs/media/revision/module.f.mjs +119 -12
  195. package/fjs/media/revision/proof.f.d.mts +10 -0
  196. package/fjs/media/revision/proof.f.mjs +88 -0
  197. package/fjs/media/revision/types.d.ts +34 -5
  198. package/fjs/media/type/module.f.d.mts +33 -15
  199. package/fjs/media/type/module.f.mjs +35 -29
  200. package/fjs/media/type/proof.f.d.mts +2 -1
  201. package/fjs/media/type/proof.f.mjs +30 -10
  202. package/fjs/module.f.mjs +29 -8
  203. package/fjs/nanvm/proof.f.mjs +3 -3
  204. package/fjs/nanvm/rust/module.f.mjs +1 -1
  205. package/fjs/nanvm/update/module.f.d.mts +4 -4
  206. package/fjs/nanvm/update/module.f.mjs +8 -9
  207. package/fjs/nanvm/update/proof.f.mjs +4 -3
  208. package/fjs/proof.f.d.mts +3 -3
  209. package/fjs/proof.f.mjs +36 -10
  210. package/fjs/protocol/json_rpc/module.f.d.mts +2 -2
  211. package/fjs/protocol/json_rpc/module.f.mjs +3 -3
  212. package/fjs/protocol/json_rpc/proof.f.mjs +4 -4
  213. package/fjs/protocol/mcp/module.f.d.mts +10 -13
  214. package/fjs/protocol/mcp/module.f.mjs +74 -58
  215. package/fjs/protocol/mcp/proof.f.d.mts +13 -4
  216. package/fjs/protocol/mcp/proof.f.mjs +193 -61
  217. package/fjs/protocol/mcp/stdio/module.f.d.mts +15 -7
  218. package/fjs/protocol/mcp/stdio/module.f.mjs +38 -25
  219. package/fjs/protocol/mcp/stdio/proof.f.d.mts +4 -2
  220. package/fjs/protocol/mcp/stdio/proof.f.mjs +45 -12
  221. package/fjs/protocol/mcp/stdio/types.d.ts +1 -1
  222. package/fjs/protocol/mcp/types.d.ts +17 -6
  223. package/fjs/sul/id/module.f.d.mts +0 -1
  224. package/fjs/sul/id/module.f.mjs +2 -3
  225. package/fjs/sul/level/hash/module.f.mjs +2 -1
  226. package/fjs/sul/level/hash/proof.f.mjs +2 -2
  227. package/fjs/sul/module.f.mjs +18 -13
  228. package/fjs/text/code_point/module.f.d.mts +8 -0
  229. package/fjs/text/code_point/module.f.mjs +8 -1
  230. package/fjs/text/code_point/proof.f.d.mts +1 -0
  231. package/fjs/text/code_point/proof.f.mjs +11 -0
  232. package/fjs/text/sgr/module.f.d.mts +4 -6
  233. package/fjs/text/sgr/module.f.mjs +6 -7
  234. package/fjs/text/utf16/module.f.mjs +7 -2
  235. package/fjs/text/utf16/proof.f.mjs +3 -3
  236. package/fjs/text/utf8/module.f.mjs +4 -2
  237. package/fjs/types/array/module.f.mjs +14 -2
  238. package/fjs/types/bit_vec/module.f.d.mts +0 -2
  239. package/fjs/types/bit_vec/module.f.mjs +46 -32
  240. package/fjs/types/bit_vec/proof.f.mjs +2 -2
  241. package/fjs/types/btree/remove/module.f.mjs +1 -1
  242. package/fjs/types/btree/set/module.f.mjs +9 -11
  243. package/fjs/types/btree/set/proof.f.mjs +12 -0
  244. package/fjs/types/byte_set/module.f.d.mts +11 -4
  245. package/fjs/types/byte_set/module.f.mjs +14 -6
  246. package/fjs/types/byte_set/proof.f.mjs +7 -7
  247. package/fjs/types/function/compare/module.f.mjs +10 -3
  248. package/fjs/types/list/module.f.d.mts +1 -1
  249. package/fjs/types/list/module.f.mjs +1 -1
  250. package/fjs/types/nullable/module.f.d.mts +18 -4
  251. package/fjs/types/nullable/module.f.mjs +21 -4
  252. package/fjs/types/nullable/proof.f.d.mts +4 -0
  253. package/fjs/types/nullable/proof.f.mjs +15 -0
  254. package/fjs/types/object/module.f.d.mts +12 -2
  255. package/fjs/types/object/module.f.mjs +11 -1
  256. package/fjs/types/patricia_trie/module.f.mjs +26 -11
  257. package/fjs/types/result/module.f.d.mts +3 -3
  258. package/fjs/types/result/module.f.mjs +3 -3
  259. package/fjs/types/rtti/common/module.f.d.mts +25 -28
  260. package/fjs/types/rtti/common/module.f.mjs +36 -32
  261. package/fjs/types/rtti/common/proof.f.mjs +5 -5
  262. package/fjs/types/rtti/data/module.f.d.mts +14 -0
  263. package/fjs/types/rtti/data/module.f.mjs +25 -2
  264. package/fjs/types/rtti/data/proof.f.d.mts +1 -0
  265. package/fjs/types/rtti/data/proof.f.mjs +25 -2
  266. package/fjs/types/rtti/parse/module.f.d.mts +24 -14
  267. package/fjs/types/rtti/parse/module.f.mjs +37 -28
  268. package/fjs/types/rtti/parse/proof.f.d.mts +3 -2
  269. package/fjs/types/rtti/parse/proof.f.mjs +33 -14
  270. package/fjs/types/rtti/proof.f.mjs +3 -1
  271. package/fjs/types/rtti/ts/module.f.mjs +13 -17
  272. package/fjs/types/rtti/ts/types.d.ts +14 -1
  273. package/fjs/types/sorted_set/module.f.d.mts +18 -0
  274. package/fjs/types/sorted_set/module.f.mjs +22 -0
  275. package/fjs/types/sorted_set/proof.f.d.mts +1 -0
  276. package/fjs/types/sorted_set/proof.f.mjs +16 -1
  277. package/fjs/types/uint8array/module.f.d.mts +1 -1
  278. package/fjs/types/uint8array/module.f.mjs +1 -1
  279. package/fjs/website/module.f.d.mts +3 -3
  280. package/fjs/website/module.f.mjs +4 -7
  281. package/fjs/website/proof.f.mjs +2 -1
  282. package/package.json +2 -2
  283. package/fjs/dev/package_json/module.f.d.mts +0 -39
  284. package/fjs/dev/package_json/module.f.mjs +0 -40
  285. package/fjs/dev/package_json/proof.f.d.mts +0 -6
  286. package/fjs/dev/package_json/proof.f.mjs +0 -32
  287. package/fjs/effects/eff/module.f.d.mts +0 -20
  288. package/fjs/effects/eff/module.f.mjs +0 -70
  289. package/fjs/effects/eff/proof.f.d.mts +0 -15
  290. package/fjs/effects/eff/proof.f.mjs +0 -69
  291. package/fjs/effects/eff/types.d.ts +0 -71
  292. package/fjs/types/rtti/validate/module.f.d.mts +0 -41
  293. package/fjs/types/rtti/validate/module.f.mjs +0 -171
  294. package/fjs/types/rtti/validate/proof.f.d.mts +0 -125
  295. package/fjs/types/rtti/validate/proof.f.mjs +0 -437
  296. 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 that consumes tokenizer output into JSON values.
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 a JSON-compatible value.
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<Unknown, string>}
40
+ * @type {<P>(policy: NumberPolicy<P>) => (tokenList: List<JsonToken>) => Result<ParseUnknown<P>, string>}
24
41
  */
25
- export declare const parse: (tokenList: List<JsonToken>) => Result<Unknown, string>;
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 that consumes tokenizer output into JSON values.
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
- /** @type {(obj: _JsonObject) => (key: string) => _JsonObject} */
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: Unknown) => _JsonObject} */
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: Unknown) => _JsonArray} */
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
- /** @type {(state: _StateParse) => (key: string) => _JsonState} */
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
- if (state.top?.kind === 'object') { return { status: '{k', top: addKeyToObject(state.top)(value), stack: state.stack } }
35
- return { status: 'error', message: 'error' }
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: Unknown) => _JsonState} */
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
- * @type {(state: _StateParse) => _JsonState}
108
+ * @template P
109
+ * @param {_StateParse<P>} state
110
+ * @returns {_JsonState<P>}
71
111
  */
72
112
  const endArray = state => {
73
- const array = toArray(/** @type {_JsonArray} */ (state.top).values)
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
- * @type {(state: _StateParse) => _JsonState}
130
+ * @template P
131
+ * @param {_StateParse<P>} state
132
+ * @returns {_JsonState<P>}
89
133
  */
90
134
  const endObject = state => {
91
- const obj = fromMap(/** @type {_JsonObject} */ (state.top).values)
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) => Unknown}
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 parseFloat(token.value)
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 { status: 'error', message: 'unexpected token' }
187
+ return unexpectedToken
134
188
  case '[': return startArray(state)
135
189
  case '{': return startObject(state)
136
190
  default:
137
- if (isValueToken(token)) { return pushValue(state)(tokenToValue(token)) }
138
- return { status: 'error', message: 'unexpected token' }
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 pushValue(state)(tokenToValue(token)) }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 { status: 'error', message: 'unexpected token' }
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 a JSON-compatible value.
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<Unknown, string>}
268
+ * @type {<P>(policy: NumberPolicy<P>) => (tokenList: List<JsonToken>) => Result<ParseUnknown<P>, string>}
213
269
  */
214
- export const parse = tokenList => {
215
- const state = fold(foldOp)({ status: '', top: null, stack: null })(tokenList)
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
- }